//
// Programmer:    Craig Stuart Sapp <craig@ccrma.stanford.edu>
// Creation Date: Tue Mar  1 18:07:39 PST 2011
// Last Modified: Tue Mar  1 18:07:44 PST 2011
//
// Filename:      ...sig/examples/all/hum2cmme.cpp 
// Web Address:   http://sig.sapp.org/examples/museinfo/humdrum/hum2cmme.cpp
// Syntax:        C++; museinfo
//
// Description:   Converts Humdrum files into MEI data 
//                (monophonic spines only allowed at the moment).
//

#include <math.h>

#ifndef OLDCPP
   #include <iostream>
   #include <sstream>
   #define SSTREAM stringstream
   #define CSTRING str().c_str()
   using namespace std;
#else
   #include <iostream.h>
   #ifdef VISUAL
      #include <strstrea.h>
   #else
      #include <strstream.h>
   #endif
   #define SSTREAM strstream
   #define CSTRING str()
#endif

#include "string.h"

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


#define BAR_NONE     (0)
#define BAR_SINGLE   (1)
#define BAR_END      (2)
#define BAR_RPTSTART (3)
#define BAR_RPTEND   (4)
#define BAR_RPTBOTH  (5)
#define BAR_INVIS    (6)
#define BAR_DBL      (7)

#define XML_SCOPE    "xml:"

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

// function declarations:
void      checkOptions(Options& opts, int argc, char** argv);
void      example(void);
void      usage(const char* command);

int       validateHumdurmFile(HumdrumFile& infile);
void      convertHumdrumToCmme(int indent, SSTREAM& out, HumdrumFile& infile);
void      createGeneralData(int indent, SSTREAM& out, HumdrumFile& infile);
void      createVoiceData(int indent, SSTREAM& out, HumdrumFile& infile);
void      createVoiceDataBody(int indent, SSTREAM& out, HumdrumFile& infile);
void      createVoiceDataBodyMdiv(int indent, SSTREAM& out, 
                               HumdrumFile& infile);
void      createVoiceDataBodyMdivScore(int indent, SSTREAM& out, 
                                HumdrumFile& infile);
void      Indent(SSTREAM& out, int indent, 
                                const char* string = NULL);
void      printTitle(int indent, SSTREAM& out, HumdrumFile& infile);
void      printComposer(int indent, SSTREAM& out, HumdrumFile& infile);
void      printEditor(int indent, SSTREAM& out, HumdrumFile& infile);
void      printAsCdata(SSTREAM& out, const char* string);
void      printMdivScoreScoredef(int indent, SSTREAM& out, 
		                HumdrumFile& infile);
void      printInitialKeySignature(SSTREAM& out, HumdrumFile& infile);
void      printInitialMeterSignature(SSTREAM& out, HumdrumFile& infile);
int       getMidiTicksPerQuarterNote(HumdrumFile& infile);
void      printScoredefStaffgrp(int indent, SSTREAM& out, HumdrumFile& infile);
void      getStaffCount(Array<int>& primarytracks, HumdrumFile& infile);
void      printStaffgrpStaffdef(int indent, SSTREAM& out, HumdrumFile& infile, 
                                Array<int>& tracks, int index);
void      printInitialClef(SSTREAM& out, HumdrumFile& infile, int ptrack);
void      printBibliographicRecords(int indent, SSTREAM& out, 
                                HumdrumFile& infile);
void      getSectionInfo(Array<int>& sections, HumdrumFile& infile);
void      printScoreSection(int indent, SSTREAM& out, HumdrumFile& infile, 
                                Array<int>& sections, int sindex);
void      printSingleSection(int indent, SSTREAM& out, HumdrumFile& infile,
                                int startline, int stopline);
void      printMeasure(int indent, SSTREAM& out, HumdrumFile& infile, 
                                Array<int>& measures, int mindex, 
                                int startsection, int stopsection,
				Array<int>& ptrack);
void      getMeasureInfoForSection(Array<int>& measures, HumdrumFile& infile, 
                                int startline, int stopline);
void      generateMeasureInfo(Array<MeasureInfo>& minfo, HumdrumFile& infile);
void      printBarlineStyle(SSTREAM& out, int stylecode);
void      calculateLayerInformation(Array<MeasureInfo>& minfo, 
                                HumdrumFile& infile, Array<int>& ptrack);
void      printMeasureStaff(int indent, SSTREAM& out, HumdrumFile& infile, 
                                Array<MeasureInfo>& minfo, int mindex, 
                                int startsection, int stopsection, 
                                Array<int>& ptrack, int staffindex);
void      printStaffLayer(int indent, SSTREAM& out, HumdrumFile& infile, 
                                Array<MeasureInfo>& minfo, int mindex, 
                                int startsection, int stopsection, 
                                Array<int>& ptrack, int staffindex, 
                                int layerindex);
int       getLayerCountInMeasure(Array<MeasureInfo>& minfo, int mindex, 
                                int startsection, int stopsection, 
                                int staffindex);
int       getLayerFieldIndex(int primary, int lindex, int line, 
                                HumdrumFile& infile);
void      addElementToList(Array<Array<int> >& layeritems, int lindex, 
                                int line, int spine);
void      getLayerItems(Array<Array<int> >& layeritems, 
                                HumdrumFile& infile, 
                                int staffindex, Array<int>& ptrack, 
                                int layerindex, Array<MeasureInfo>& minfo, 
                                int mindex, int start, int stop);
void      printLayerItems(int indent, SSTREAM& out, 
		                Array<Array<int> >& layeritems,
                                HumdrumFile& infile, Array<MeasureInfo>& minfo,
			       	int mindex, int start, int stop, 
                                int layerindex);
int       getStartOfData(HumdrumFile& infile);
int       getBeamAdjustment(const char* token);
void      printNote(int indent, SSTREAM& out, const char* string,
                                int i, int j, int k, int layerindex);
void      printRest(int indent, SSTREAM& out, const char* string,
                                int i, int j, int k, int layerindex);
void      printKernData(int indent, SSTREAM& out, HumdrumFile& infile, 
                                int row, int col, int layerindex);
void      printTempExpandRules(int indent, SSTREAM& out, HumdrumFile& infile);
void      printDtdAlterations(ostream& out);
void      initializeGlobalVariables(HumdrumFile& infile);
void      printProfiledescLangusage(int indent, SSTREAM& out, 
                                HumdrumFile& infile, Array<const char*> langs);
void      printMeiKeySignatureAttributes(SSTREAM& out, const char* keysig);
void      printMeiKeyAttributes(SSTREAM& out, const char* key);
void      printMeiMeterSymbolAttributes(SSTREAM& out, const char* metstring);
void      printMeiMeterSignatureAttributes(SSTREAM& out, const char* metersig);
void      checkForTimeAndOrKeyChange(int indent, SSTREAM& out, 
                                 HumdrumFile& infile, Array<int>& measures, 
                                 int mindex, Array<int>& ptrack, 
                                 Array<MeasureInfo>& minfo);

// User interface variables:
Options options;
int         debugQ    = 0;             // used with --debug option
int         verboseQ  = 0;             // used with -v option


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

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

   // initial processing of the command-line options
   checkOptions(options, argc, argv);

   if (options.getArgCount() < 1) {
      infile.read(cin);
   } else {
      infile.read(options.getArg(1));
   }
   infile.analyzeRhythm("4");

   int validation = validateHumdurmFile(infile);
   if (validation == 0) {
      cerr << "This humdrum file cannot be processed." << endl;
      cerr << "Only files with monophonic **kern spines are allowed." << endl;
      exit(1);
   }

   int indent = 0;
   SSTREAM cmmedata;

   convertHumdrumToCmme(indent, cmmedata, infile);
   Indent(cmmedata, indent);

   cmmedata << ends;
   cout    << cmmedata.CSTRING;

   return 0;
}


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



//////////////////////////////
//
// getStartOfData -- the index of the first line with data on it in the file.
//

int getStartOfData(HumdrumFile& infile) {
   int i;
   for (i=0; i<infile.getNumLines(); i++) {
      if (infile[i].isData()) {
         return i;
      }
   }
   return i;
}



//////////////////////////////
//
// validateHumdrumFile --  Currently only allowing single-layer
//   staves.
//

int validateHumdurmFile(HumdrumFile& infile) {
   int i, j;
   for (i=0; i<infile.getNumLines(); i++) {
      for (j=0; j<infile[i].getFieldCount(); j++) {
         if (infile[i].getSpineInfo(j)[0] == '(') {
            return 0;
         }
      }
   }

   return 1;
}



//////////////////////////////
//
// convertHumdrumToCmme --
//

void convertHumdrumToCmme(int indent, SSTREAM& out, HumdrumFile& infile) {
   Indent(out, indent);
   out << "<Piece xsi:schemaLocation=\"http://www.cmme.org cmme.xsd\">\n";

   createGeneralData(indent+1, out, infile);
   createVoiceData(indent+1, out, infile);

   Indent(out, indent);
   out << "</Piece>\n";
}



//////////////////////////////
//
// createGeneralData -- Fill in the <Title>, <Composer> and <Editor>
//       fields
//

void createGeneralData(int indent, SSTREAM& out, HumdrumFile& infile) {
   Indent(out, indent);
   out << "<GeneralData>\n";

   printTitle(indent+1, out, infile);
   printComposer(indent+1, out, infile);
   printEditor(indent+1, out, infile);

   printBibliographicRecords(indent+1, out, infile);

   Indent(out, indent);
   out << "</GeneralData>\n";
}



//////////////////////////////
//
// printBibliographicRecords --
//
// Version 1.9.1b convert stored raw Humdrum reference records like this:
// <!-- Humdrum Bibliographic Records:
//   <bib key="COM" meaning="Composer's name" value="Bach, Johann Sebastian"/>
// End Humdrum Bibliographic Records -->
//
//  For the MEI 2010-05 converter, a more eleagant method is used using
//  XML processing instructions for a theoretical Humdrum processor:
//  <?Humdrum type="bib" key="COM" value="Bach, Johann Sebastian" 
//      meaning="Composer's name"?>
//

void printBibliographicRecords(int indent, SSTREAM& out, 
      HumdrumFile& infile) {

   char buffer[1024] = {0};
   Array<char> keymeaning(1);
   keymeaning[0] = '\0';
   // int found = 0;
   int i;
   for (i=0; i<infile.getNumLines(); i++) {
      if (!infile[i].isBibliographic()) {
         continue;
      }
      // Used in MEI 1.9.1b conversion:
      // if (found == 0) {
      //    found = 1;
      //    Indent(out, indent);
      //    out << "<!-- Humdrum Bibliographic Records:\n";
      // }
      Indent(out, indent);
      out << "<?Humdrum type=\"bib\"";
      out << " key=\"";
      printAsCdata(out, infile[i].getBibKey(buffer, 1024));
      out << "\"";

      out << " value=\"";
      printAsCdata(out, infile[i].getBibValue(buffer, 1024));
      out << "\"";

      HumdrumRecord::getBibliographicMeaning(keymeaning, 
            infile[i].getBibKey(buffer, 1024));
      if (keymeaning.getSize() > 1) {
         out << " meaning=\"";
         printAsCdata(out, keymeaning.getBase());
         out << "\"";
         //Indent(out, indent+1);
      }

      out << "?>\n";
   }

   // Used in MEI 1.9.1b conversion:
   //if (found) {
   //   Indent(out, indent);
   //   out << "End Humdrum Bibliographic Records -->\n";
   //}
}



//////////////////////////////
//
// printTitle -- Fill in the <Title> section of <GeneralData>
// 

void printTitle(int indent, SSTREAM& out, HumdrumFile& infile) {
   Indent(out, indent);
   out << "<Title>";
   PerlRegularExpression pre;
   int i;
   for (i=0; i<infile.getNumLines(); i++) {
      if (!infile[i].isBibliographic()) {
         continue;
      }
      if (pre.search(infile[i][0], "^!!!OTL:\\s*(.*)\\s*$", "")) {
         printAsCdata(out, pre.getSubmatch(1));
      }
   }
   out << "</Title>\n";
}



//////////////////////////////
//
// printComposer -- Fill in the <Composer> section of <GeneralData>
//    Perhaps switch around the last name and firstname...
// 

void printComposer(int indent, SSTREAM& out, HumdrumFile& infile) {
   Indent(out, indent);
   out << "<Composer>";
   PerlRegularExpression pre;
   int i;
   for (i=0; i<infile.getNumLines(); i++) {
      if (!infile[i].isBibliographic()) {
         continue;
      }
      if (pre.search(infile[i][0], "^!!!COM:\\s*(.*)\\s*$", "")) {
         printAsCdata(out, pre.getSubmatch(1));
      }
   }
   out << "</Composer>\n";
}



//////////////////////////////
//
// printEditor -- Fill in the <Editor> section of <GeneralData>
// 

void printEditor(int indent, SSTREAM& out, HumdrumFile& infile) {
   Indent(out, indent);
   out << "<Editor>";
   PerlRegularExpression pre;
   int i;
   for (i=0; i<infile.getNumLines(); i++) {
      if (!infile[i].isBibliographic()) {
         continue;
      }
      if (pre.search(infile[i][0], "^!!!EED:\\s*(.*)\\s*$", "")) {
         printAsCdata(out, pre.getSubmatch(1));
      }
   }
   out << "</Editor>\n";
}



//////////////////////////////
//
// createVoiceData -- Fill in the <music> section of <mei>
//      <mei/music>
//
// <music> 	(facsimile*, ((front?, (body | group)?, back?)))
//
//

void createVoiceData(int indent, SSTREAM& out, HumdrumFile& infile) {
   Indent(out, indent);
   out << "<VoiceData>\n";

   createVoiceDataBody(indent+1, out, infile);

   Indent(out, indent);
   out << "</VoiceData>\n";
}



//////////////////////////////
//
// createVoiceDataBody --
//      <mei/music/body>
//
// <body> 	mdiv+
//

void createVoiceDataBody(int indent, SSTREAM& out, HumdrumFile& infile) {
   Indent(out, indent);
   out << "<body>\n";

   createVoiceDataBodyMdiv(indent+1, out, infile);

   Indent(out, indent);
   out << "</body>\n";
}



//////////////////////////////
//
// createVoiceDataBodyMdiv --
//      <mei/music/body/mdiv>
//
// <mdiv> 	((score?, parts?) | mdiv*)
//

void createVoiceDataBodyMdiv(int indent, SSTREAM& out, HumdrumFile& infile) {
   Indent(out, indent);
   out << "<mdiv>\n";

   generateMeasureInfo(MINFO, infile);
   createVoiceDataBodyMdivScore(indent+1, out, infile);

   Indent(out, indent);
   out << "</mdiv>\n";
}



//////////////////////////////
//
// generateMeterInfo --
//

void generateMeterInfo(Array& measuredur, HumdrumFile& infile) {
   Array<double>& md = measuredur;
   md.setSize(infile.getNumLines());
   md.allowGrowth(0);
   md.setAll(0.0);
   int i, j;
   PerlRegularExpression pre;
   for (i=0; i<infile.getNumLines(); i++) {
      if (i > 0) {
         md[i] = md[i-1];
      }
      if (!infile[i].isInterpretation()) {
         continue;
      }
      for (j=0; j<infile[i].getFieldCount(); j++) {
         if (!pre.search(infile[i][j], "^\\*M(\\d+)/(\\d+)$")) {
            continue;
         }
         md[i] = Convert::kernTimeSignatureTop(infile[i][j]) * 
                 Convert::kernTimeSignatureBottomToDuration(infile[i][j]);
         break;
      }
   }

   for (i=md.getSize()-2; i>=0; i--) {
      if (md[i] == 0.0) {
         md[i] = md[i+1];
      }
   }
}



//////////////////////////////
//
// generateMeasureInfo --
//    int  right;             // visual style of right-side barline of meas.
//    int  left;              // visual style of left-side barline of meas.
//    int  num;               // measure number
//    int  nextbar;           // index of next barline
//    int  lastbar;           // index of last barline
//    char letter;            // sub measure enumeration
//    char complete;          // c = complete, i = underfull, o = overfull
//    char pickupbefore;      // true if pickup measure preceeds this 
//    char ispickup;          // true if this measure is a pickup measure
//    char valid;             // true if index represents a real barline
//    Array<char> layers;     // number of layers for each staff on line
//

void generateMeasureInfo(Array& minfo, HumdrumFile& infile) {
   int i;

   Array<double> measuredur;
   generateMeterInfo(measuredur, infile);

   minfo.setSize(infile.getNumLines());
   minfo.allowGrowth(0);
   PerlRegularExpression pre;
   for (i=0; i<infile.getNumLines(); i++) {
      if (!infile[i].isMeasure()) {
         continue;
      }

      // line in file descibes an actual barline
      minfo[i].valid = 1;

      // store the measure number if specified:
      if (pre.search(infile[i][0], "(\\d+)")) {
         minfo[i].num = strtol(pre.getSubmatch(1), NULL, 10);
      }

      // store the style of the left side of the measure
      if (pre.search(infile[i][0], "==")) {
         minfo[i].left = BAR_END;
      }
      if (pre.search(infile[i][0], "^=+(\\d+)?([^=\\d]+)$")) {
         if (strcmp(pre.getSubmatch(2), ":|!|:") == 0) {
            minfo[i].left = BAR_RPTBOTH;
         } else if (strcmp(pre.getSubmatch(), ":||:") == 0) {
            minfo[i].left = BAR_RPTBOTH;
         } else if (strcmp(pre.getSubmatch(), ":!!:") == 0) {
            minfo[i].left = BAR_RPTBOTH;
         } else if (strcmp(pre.getSubmatch(), ":|!") == 0) {
            minfo[i].left = BAR_RPTEND;
         } else if (strcmp(pre.getSubmatch(), "!|:") == 0) {
            minfo[i].left = BAR_RPTSTART;
         } else if (strcmp(pre.getSubmatch(), "||") == 0) {
            minfo[i].left = BAR_DBL;
         } else if (strcmp(pre.getSubmatch(), ":|") == 0) {
            minfo[i].left = BAR_RPTEND;
         } else if (strcmp(pre.getSubmatch(), ":!") == 0) {
            minfo[i].left = BAR_RPTEND;
         } else if (strcmp(pre.getSubmatch(), "|:") == 0) {
            minfo[i].left = BAR_RPTSTART;
         } else if (strcmp(pre.getSubmatch(), "!:") == 0) {
            minfo[i].left = BAR_RPTSTART;
         } else if (strcmp(pre.getSubmatch(), "|") == 0) {
            minfo[i].left = BAR_SINGLE;
         } else if (strcmp(pre.getSubmatch(), "-") == 0) {
            minfo[i].left = BAR_INVIS;
         } else {
         cout << "<!-- UNKNOWN BARLINE STYLE: " << pre.getSubmatch() 
              << " -->\n" << endl;
         }
      } else {
         minfo[i].left = BAR_SINGLE;
      }

      // store the complete marker: 
      //    c = matches time signature
      //    i = incomplete, underfull
      //    o = overfull
      if (fabs(infile[i].getBeat()) == measuredur[i]) {
         minfo[i].complete = 'c';
      } else if (fabs(infile[i].getBeat()) > measuredur[i]) {
         minfo[i].complete = 'o';
      } else if (fabs(infile[i].getBeat()) < measuredur[i]) {
         minfo[i].complete = 'i';
      }

      // store measure number of a pickup beat:
      if (infile[i].getBeat() < 0) {
         minfo[i].pickupbefore = 1;
      }
   }


   // store the right-side barline style
   int tempnext = -1;
   for (i=minfo.getSize()-1; i>=0; i--) {
      minfo[i].nextbar = tempnext;
      if (minfo[i].valid) {
         if (tempnext >= 0) {
            minfo[i].right = minfo[tempnext].left;
         }
         tempnext = i;
      }
   }
	     
   int templast = -1;
   for (i=0; i<minfo.getSize(); i++) {
      minfo[i].lastbar = templast;
      if (minfo[i].valid) {
         templast = i;
      } 
   }
	     
   // finalize pickup measure information
   for (i=0; i<minfo.getSize(); i++) {
      if (minfo[i].valid) {
         continue;
      }
      if (minfo[i].nextbar >= 0) {
         if (minfo[minfo[i].nextbar].pickupbefore) {
            minfo[i].ispickup = 1;
            minfo[i].left     = BAR_INVIS;
            minfo[i].right    = minfo[minfo[i].nextbar].left;
	    minfo[i].complete = 'i';
	    if (minfo[minfo[i].nextbar].num == 1) {
               minfo[i].num = 0;
            }
         }
      }
   }

   // check for intermediate barlines between two
   // barlines with consecutive numbers
   for (i=0; i<minfo.getSize(); i++) {
      if (minfo[i].valid == 0) {  
         continue;
      }
      if (minfo[i].num >= 0) {
         continue;
      }
      if (minfo[i].nextbar < 0) {
         continue;
      }
      if (minfo[i].lastbar < 0) {
         continue;
      }
      if (minfo[minfo[i].lastbar].num == minfo[minfo[i].nextbar].num - 1) {
         // also should check if the first two bars duration sum
         // to the prevailing meter.
         minfo[i].num = minfo[minfo[i].lastbar].num;
         minfo[i].letter = 'b';
         minfo[minfo[i].lastbar].letter = 'a';
      }
   }

   calculateLayerInformation(minfo, infile, PTRACK);


   if (debugQ) {
      cout << "<!-- MEASURE INFORMATION\n";

      for (i=0; i<minfo.getSize(); i++) {
         cout << "\n\n===  " << i << "  =================================\n";
         cout << minfo[i];
      }
      cout << "-->\n";
   }


}



//////////////////////////////
//
// calculateLayerInformation --  Calculate the number of layers
//    for each staff to be processed for each line of the Humdrum file.
//

void calculateLayerInformation(Array<MeasureInfo>& minfo, 
      HumdrumFile& infile, Array<int>& ptrack) {

   Array<int> reverselookup;
   reverselookup.setSize(infile.getMaxTracks()+10);
   reverselookup.setAll(-1);
   Array<int>& rl = reverselookup;

   int i, j;
   for (i=0; i<ptrack.getSize(); i++) {
      rl[ptrack[i]] = i;
   }

   int tval;
   for (i=0; i<infile.getNumLines(); i++) {
      minfo[i].layers.setSize(ptrack.getSize());
      minfo[i].layers.setAll(0);
      minfo[i].layers.allowGrowth(0);
      if (!(infile[i].isData() || infile[i].isMeasure() ||
            infile[i].isInterpretation() || infile[i].isLocalComment())) {
         continue;
      }
      for (j=0; j<infile[i].getFieldCount(); j++) {
         tval = rl[infile[i].getPrimaryTrack(j)];
         if (tval >= 0) {
            minfo[i].layers[tval]++;
         }
      }
   }
}



//////////////////////////////
//
// createVoiceDataBodyMdivScore --
//      <mei/music/body/mdiv/score>
//
//	<score>		((app | div | pb | sb | scoredef | staffdef |
//                      staffgrp | annot | curve | line | symbol
//                      | anchoredtext | choice | handshift | gap |
//                      subst | add | corr | damage | del | orig | reg |
//                      restore | sic | supplied | unclear)*, ((section
//                      | ending), (app | div | pb | sb | scoredef |
//                      staffdef | staffgrp | annot | curve | line |
//                      symbol | anchoredtext | choice | handshift |
//                      gap | subst | add | corr | damage | del | orig |
//                      reg | restore | sic | supplied | unclear)*)*)
//
//	Full score view of the mdiv. Since the measure element is
//	optional, a score may consist entirely of pagebreaks, each of
//	which points to a page image. Div elements are allowed preceding
//	and following sections of music data in order to accommodate
//	blocks of explanatory text.
//

void createVoiceDataBodyMdivScore(int indent, SSTREAM& out, 
      HumdrumFile& infile) {
   Indent(out, indent);
   out << "<score>\n";

   // <scoredef>
   printMdivScoreScoredef(indent+1, out, infile);

   // sections
   Array<int> sections;
   getSectionInfo(sections, infile);

   if (sections.getSize() == 1) {
      printSingleSection(indent+1, out, infile, sections[0], 
            infile.getNumLines()-1);
   } else {
      int i;
      for (i=0; i<sections.getSize(); i++) {
         printScoreSection(indent+1, out, infile, sections, i);
      }
   }

   Indent(out, indent);
   out << "</score>\n";
}



//////////////////////////////
//
// printSingleSection -- print a section of music
//

void printSingleSection(int indent, SSTREAM& out, HumdrumFile& infile,
   int startline, int stopline) {

   Array<int> measures;
   getMeasureInfoForSection(measures, infile, startline, stopline);

   int i;
   for (i=0; i<measures.getSize(); i++) {
      printMeasure(indent, out, infile, measures, i, startline, 
            stopline, PTRACK);
   }
}



//////////////////////////////
//
// getMeasuresInfoForSection --
//

void getMeasureInfoForSection(Array<int>& measures, HumdrumFile& infile, 
      int startline, int stopline) {

   measures.setSize(10000);
   measures.setSize(0);
   measures.setGrowth(10000);

   int i;
   int barinsection = 0;
   for (i=startline; i<=stopline; i++) {
      if (infile[i].isData()) {
         break;
      }
      if (infile[i].isMeasure()) {
         barinsection = 1;
         break;
      }
   }

   int backdata = 0;
   if (barinsection == 0) {
      for (i=startline-1; i>=0; i--) {
         if (infile[i].isData()) {
            backdata = 1;
         }
         if (infile[i].isMeasure()) {
            barinsection = i - startline;
            break;
         }
      }
   }

   if (barinsection > 0) {
      measures.append(barinsection);
   } else {
      barinsection = startline + barinsection;
      measures.append(barinsection);
   }

   int start = barinsection + 1;
   if (start < startline) {
      start = startline;
   }
   for (i=start; i<=stopline; i++) {
      if (infile[i].isMeasure()) {
         measures.append(i);
      }
   }

   // if there is no data after last measure in section, then don't
   // process it:
   int enddata = 0;
   for (i=measures[measures.getSize()-1]; i<stopline; i++) {
      if (infile[i].isData()) {
         enddata = 1;
         break;
      }
   }
   if (enddata == 0) {
      measures.setSize(measures.getSize()-1);
   }

}



//////////////////////////////
//
// checkForTimeAndOrKeyChange -- print any changes in the meter or key
//    signatures when it does not come at the start of the data.  Look
//    in the neighborhood of non-data lines to find parallel changes
//    in the key signature, time signature, and meter symbol (e.g.
//    cut time).  This function should not be called inside a
//    <measure> element, but after one is finished and before another
//    is started.
//

void checkForTimeAndOrKeyChange(int indent, SSTREAM& out, HumdrumFile& infile, 
      Array<int>& measures, int mindex, Array<int>& ptrack, 
      Array<MeasureInfo>& minfo) {

   if (minfo[measures[mindex]].valid == 0) {
      // measure does not start data section (only allows at start of music)
      // so don't try to print anything.  Initial signatures are handled
      // elsewhere.
      return;
   }

   int keysigline    = -1;
   int meterline     = -1;
   int metline       = -1;
   int keysigspine   = -1;
   int meterspine    = -1;
   int metspine      = -1;
   // not printing key information for now.

   PerlRegularExpression pre;

   int i, j;
   for (i=measures[mindex]; i<minfo[measures[mindex]].nextbar; i++) {
      if (infile[i].isData()) {
         break;
      }
      if (!infile[i].isInterpretation()) {
         continue;
      }
      for (j=0; j<infile[i].getFieldCount(); j++) {
         if (infile[i].isExInterp(j, "**kern")) {
            if (pre.search(infile[i][j], "^\\*M\\d+/\\d+$")) {
               meterline  = i;
               meterspine = j;
            } else if (pre.search(infile[i][j], "^\\*met\\(.*\\)$")) {
               metline  = i;
               metspine = j;
            } else if (pre.search(infile[i][j], "^\\*k\\[.*\\]$")) {
               keysigline  = i;
               keysigspine = j;
            }
         }
      }
   }

   if ((keysigline == -1) && (meterline == -1) && (metline == -1)) {
      // nothing to do
      return;
   }

   Indent(out, indent);
   out << "<scoredef";

   if (keysigline >= 0) {
      printMeiKeySignatureAttributes(out, infile[keysigline][keysigspine]);
   }
   if (meterline >= 0) {
      printMeiMeterSignatureAttributes(out, infile[meterline][meterspine]);
   }
   if (metline >= 0) {
      printMeiMeterSymbolAttributes(out, infile[metline][metspine]);
   }

   out << "/>\n";
}



//////////////////////////////
//
// printMeasure --
//    <measure>
//

void printMeasure(int indent, SSTREAM& out, HumdrumFile& infile, 
      Array<int>& measures, int mindex, int startsection, int stopsection,
      Array<int>& ptrack) {

   checkForTimeAndOrKeyChange(indent, out, infile, measures, mindex, 
      ptrack, MINFO);
		    

   Indent(out, indent);
   out << "<measure";

   // measure/@n:
   char letter = MINFO[measures[mindex]].letter;

   if (MINFO[measures[mindex]].num >= 0) {
      out << " n=\"";
      out << MINFO[measures[mindex]].num;
      if (letter != 0) {
         if (letter > 'a') {
            out << letter;
         }
      }
      out << "\"";
   }

   // measure/@id:
   out << " " << xmlscope << "id=\"";
   out << "m";
   // mark an "x" if the line in the file being used as a barline
   // does not contain a barline.
   if (MINFO[measures[mindex]].valid == 0) {
      out << "x";
   }
   out << "_" << IDMARKER << "_" << measures[mindex];
   out << "\"";


   if (MINFO[measures[mindex]].left != BAR_NONE) {
      if (verboseQ || (MINFO[measures[mindex]].lastbar == -1)) {
         if (verboseQ || (MINFO[measures[mindex]].left != BAR_SINGLE)) {
            out << " left=\"";
            printBarlineStyle(out, MINFO[measures[mindex]].left);
            out << "\"";
         }
      }
   }
   if (MINFO[measures[mindex]].right != BAR_NONE) {
      if (verboseQ || (MINFO[measures[mindex]].right != BAR_SINGLE)) {
         out << " right=\"";
         printBarlineStyle(out, MINFO[measures[mindex]].right);
         out << "\"";
      }
   }

   // print mesure/@complete if specified:
   switch (MINFO[measures[mindex]].complete) {
      case 'c':  if (verboseQ) { out << " complete=\"c\""; } break;
      case 'i':  out << " complete=\"i\""; break;
      case 'o':  out << " complete=\"o\""; break;
   }

   if (MINFO[measures[mindex]].num >= 0) {
      if ((letter == 'a') && (MINFO[measures[mindex]].nextbar >= 0)) {
         out << " join=\"" << "m_" << IDMARKER << "_" 
             << MINFO[measures[mindex]].nextbar << "\"";
      } else if ((letter == 'b') && (MINFO[measures[mindex]].lastbar >= 0)) {
         out << " join=\"" << "m_" << IDMARKER << "_" 
             << MINFO[measures[mindex]].lastbar << "\"";
      }
   }
	     
   // All barlines are assumed to be MEI "control" barlines.
   // It is possible to have "non-control" barlines in Humdrum files,
   // but the HumdrumFile class currently does not permit them.
   if (verboseQ) {
      out << " control=\"true\"";
   }
   
   out << ">\n";

   int i;
   for (i=0; i<ptrack.getSize(); i++) {
      printMeasureStaff(indent+1, out, infile, MINFO, measures[mindex], 
         startsection, stopsection, ptrack, i);
   }

   Indent(out, indent);
   out << "</measure>\n";
}



//////////////////////////////
//
// printMeasureStaff --  Print a particular staff for a particular measure.
//     <staff>
//

void printMeasureStaff(int indent, SSTREAM& out, HumdrumFile& infile, 
      Array<MeasureInfo>& minfo, int mindex, int startsection,
         int stopsection, Array<int>& ptrack, int staffindex) {
   Indent(out, indent);
   out << "<staff";
   out << " n=\"";
   out << staffindex + 1;
   out << "\"";
   out << ">\n";


   int layercount = getLayerCountInMeasure(minfo, mindex, startsection, 
         stopsection, staffindex);

   int i;
   for (i=0; i<layercount; i++) {
      printStaffLayer(indent+1, out, infile, minfo, mindex, startsection,
            stopsection, ptrack, staffindex, i);
   }

   Indent(out, indent);
   out << "</staff>\n";
}



//////////////////////////////
//
// getLayerCountInMeasure -- return the maximum number of layers in the
//

int getLayerCountInMeasure(Array<MeasureInfo>& minfo, int mindex, 
      int startsection, int stopsection, int staffindex) {

   int maxcount = 0;
   int i;

   int start;
   if (minfo[mindex].valid) {
      start = mindex + 1;
   } else {
      start = mindex;
   }
   for (i=start; i<stopsection; i++) {
      if (minfo[i].valid) {
         break;
      }
      if (minfo[i].layers[staffindex] > maxcount) {
         maxcount = minfo[i].layers[staffindex];
      }
   }

   return maxcount;
}



//////////////////////////////
//
// printStaffLayer --
//

void printStaffLayer(int indent, SSTREAM& out, HumdrumFile& infile, 
      Array<MeasureInfo>& minfo, int mindex, int startsection, int stopsection,
      Array<int>& ptrack, int staffindex, int layerindex) {

   Indent(out, indent);
   out << "<layer";
   out << " n=\"";
   out << layerindex + 1;
   out << "\"";
   out << ">\n";

   int start = mindex;
   if (mindex < startsection) {
      start = startsection;
   }
   int stop = minfo[mindex].nextbar;
   if ((stop < 0) || (stop > stopsection)) {
      stop = stopsection;
   }
   if (start < DATASTART) {
      start = DATASTART;
   }
   if (stop < start) {
      out << "<!-- GOT TO FUNNY PLACE IN CONVERTER -->" << endl;
      int tempval = stop;
      stop = start;
      start = tempval;
   }
	     

   Array<Array<int> > layeritems;
   getLayerItems(layeritems, infile, staffindex, ptrack, layerindex, minfo, 
         mindex, start, stop);

   printLayerItems(indent+1, out, layeritems, infile, minfo, mindex, 
         start, stop, layerindex);

   Indent(out, indent);
   out << "</layer>\n";
}



//////////////////////////////
//
// printLayerItems --
//

void printLayerItems(int indent, SSTREAM& out, Array<Array<int> >& layeritems,
      HumdrumFile& infile, Array<MeasureInfo>& minfo, int mindex, int start, 
      int stop, int layerindex) {

   int bindent = 0;

   int oldbeamstate = 0;
   int newbeamstate = 0;
   int oldgracebeamstate = 0;
   int newgracebeamstate = 0;

   int graceQ = 0;               // true if a grace note rhythm
   int lastgraceQ = 0;
 
   PerlRegularExpression pre;

   int li;
   int i, j;
   for (li=0; li<layeritems.getSize(); li++) {
      i = layeritems[li][0];
      j = layeritems[li][1];
      if (debugQ) {
         Indent(out, indent);
         out << "<!-- (" << i << "," << j << ")\t=\t" << infile[i][j] 
             << "\t-->\n";
      }

      switch (infile[i].getType()) {
         case E_humrec_data:
            // deal with <beam> containers
            lastgraceQ = graceQ;
            graceQ = pre.search(infile[i][j], "Q", "i");
            if ((graceQ == 0) && (lastgraceQ != 0) 
                  && (newgracebeamstate != 0)) {
               // turn of a hanging gracenote beam
               Indent(out, indent + --bindent);
               out << "</beam>\n";
            }

            if (graceQ) {
               // grace note or rest
               oldgracebeamstate = newgracebeamstate;
               newgracebeamstate += getBeamAdjustment(infile[i][j]);
               if (newgracebeamstate < 0) {
                  newgracebeamstate = 0;
               }
               if ((newbeamstate > 0) && (oldbeamstate == 0)) {
                  Indent(out, indent + bindent++);
                  out << "<beam>\n";
               }
               printKernData(indent + bindent, out, infile, i, j, layerindex);
               if ((newbeamstate == 0) && (oldbeamstate > 0)) {
                  Indent(out, indent + --bindent);
                  out << "<beam>\n";
               }

            } else {
               // regular note or rest
               oldbeamstate = newbeamstate;
               newbeamstate += getBeamAdjustment(infile[i][j]);
               if (newbeamstate < 0) {
                  newbeamstate = 0;
               }
               if ((newbeamstate > 0) && (oldbeamstate == 0)) {
                  Indent(out, indent + bindent++);
                  out << "<beam>\n";
               }
               printKernData(indent + bindent, out, infile, i, j, layerindex);
               if ((newbeamstate == 0) && (oldbeamstate > 0)) {
                  Indent(out, indent + --bindent);
                  out << "</beam>\n";
               }
            }

            break;
         case E_humrec_interpretation:
            if (strstr(infile[i][j], "*clef") != NULL) {
               out << "<!-- CLEF -->\n";
            } else if (strstr(infile[i][j], "*k[") != NULL) {
               // out << "<!-- Key Signature -->\n";
            } else if (pre.search(infile[i][j], "^\\*M\\d+/\\d+")) {
               out << "<!-- Time Signature -->\n";
            }
            break;
         default:
            Indent(out, indent);
            out << "<!-- UNKNOWN DATA TYPE -->\n";
      }


   }

   if (newbeamstate != 0) {
      Indent(out, indent);
      out << "<!-- Error in beaming: not cross-staff beams allowed -->\n";
      Indent(out, --indent);
      out << "</beam>\n";
   }
   if (newgracebeamstate != 0) {
      Indent(out, indent);
      out << "<!-- Error in beaming: not cross-staff grace-note beams allowed -->\n";
      Indent(out, --indent);
      out << "</beam>\n";
   }
}



//////////////////////////////
//
// printKernData -- print a note, a chord, or a rest
//

void printKernData(int indent, SSTREAM& out, HumdrumFile& infile, int row,
      int col, int layerindex) {

   int k;
   int subcount;
   char buffer[128] = {0};
   if (strchr(infile[row][col], 'r') != NULL) {
      printRest(indent, out, infile[row][col], row, col, -1, layerindex);
   } else if (strchr(infile[row][col], ' ') != NULL) {
      Indent(out, indent++);
      out << "<chord>\n";
      subcount = infile[row].getTokenCount(col);
      for (k=0; k<subcount; k++) {
         infile[row].getToken(buffer, col, k);
         printNote(indent, out, buffer, row, col, k, layerindex);
      }
      Indent(out, --indent);
      out << "<chord>\n";
   } else {
      printNote(indent, out, infile[row][col], row, col, -1, layerindex);
   }
}



//////////////////////////////
//
// printRest -- convert a Humdrum **kern rest in to an MEI rest.
//

void printRest(int indent, SSTREAM& out, const char* string, int i, int j, 
      int k, int layerindex) {
   if (string == NULL) {
      return;
   }

   if (debugQ) {
      Indent(out, indent);
      out << "<!-- REST: " << string << " " << xmlscope << "id=\"n_" 
          << IDMARKER << "_";
      out << i << "_" << j;
      if (k >= 0) {
         out << "_" << k;
      }
      out << "\"" << " -->\n";
   }

   Indent(out, indent);
   out << "<rest";

   // print id marker for rest
   out << " " << xmlscope << "id=\"n_" << IDMARKER << "_" << i << "_" << j;
   if (k >= 0) {
      out << "_" << k;
   }
   out << "\"";
   

   // print rhythm information /////////////////////////////////////////

   PerlRegularExpression pre;

   if (pre.search(string, "Q", "i")) {
      // deal with grace note identification here
   }

   if (pre.search(string, "(\\d+)")) {
      int durval = strtol(pre.getSubmatch(1), NULL, 10);
      if (strcmp(pre.getSubmatch(), "0") == 0) {
         out << " dur=\"breve\"";
      } else if (strcmp(pre.getSubmatch(), "00") == 0) {
         out << " dur=\"long\"";
      } else if (durval > 0) {
         //int visdur = int(pow(2.0, 
         //      (int(log((double)durval)/log(2.0))+0.00000001)));
         int visdur = int(pow(2.0, 
             int(log((double)durval)/log(2.0)+0.001) ));
         out << " dur=\"" << visdur << "\"";
      }
   }
	     
   if (pre.search(string, "(\\.)")) {
      int dotcount = strlen(pre.getSubmatch(1));
      if ((dotcount >= 0) && (dotcount <= 4)) {
         // allowed range for dots in MEI 1.9b
         out << " dots=\"" << dotcount << "\"";
      } 
   }


   out << "/>\n";
}



//////////////////////////////
//
// printNote -- convert a Humdrum **kern note in to an MEI note.
//

void printNote(int indent, SSTREAM& out, const char* string, int i, int j, 
      int k, int layerindex) {
   if (string == NULL) {
      return;
   }

   if (strcmp(string, ".") == 0) {
      return;
   }

   if (debugQ) {
      Indent(out, indent);
      out << "<!-- NOTE: " << string << " " << xmlscope << "id=\"n_" 
          << IDMARKER << "_";
      out << i << "_" << j;
      if (k >= 0) {
         out << "_" << k;
      }
      out << "\"" << " -->\n";
   }

   Indent(out, indent++);
   out << "<note";

   // print id marker for note
   out << " " << xmlscope << "id=\"n_" << IDMARKER << "_" << i << "_" << j;
   if (k >= 0) {
      out << "_" << k;
   }
   out << "\"";

   PerlRegularExpression pre;
   char pname = 'c';
   if (pre.search(string, "([A-G])", "i")) {
      pname = tolower(pre.getSubmatch(1)[0]);
   } else {
      // kern data can contains rhythms with no pitches, but
      // disallow this case for now
      Indent(out, indent);
      out << "<!-- Error: cannot find diatonic pitch class in: " 
          << string << " -->\n";
      return;
   }

   // print pname (diatonic pitch class) attribute
   out << " pname=\"" << pname << "\"";

   // print accidental attribute:
   if (pre.search(string, "([#-n]+)")) {
      if (strcmp(pre.getSubmatch(1), "##") == 0) {
         out << " accid=\"ss\"";
      } else if (strcmp(pre.getSubmatch(), "#") == 0) {
         out << " accid=\"s\"";
      } else if (strcmp(pre.getSubmatch(), "--") == 0) {
         out << " accid=\"ff\"";
      } else if (strcmp(pre.getSubmatch(), "-") == 0) {
         out << " accid=\"f\"";
      } else if (strcmp(pre.getSubmatch(), "n") == 0) {
         out << " accid=\"n\"";
      }
   }

   // print octave attribute:
   int octave = Convert::kernToBase40(string) / 40;
   if (octave >= 0) {
      out << " oct=\"" << octave << "\"";
   }

   // print rhythm information /////////////////////////////////////////

   if (pre.search(string, "Q", "i")) {
      // deal with grace note identification here
   }

   if (pre.search(string, "(\\d+)")) {
      int durval = strtol(pre.getSubmatch(1), NULL, 10);
      if (strcmp(pre.getSubmatch(), "0") == 0) {
         out << " dur=\"breve\"";
      } else if (strcmp(pre.getSubmatch(), "00") == 0) {
         out << " dur=\"long\"";
      } else if (durval > 0) {
         int visdur = int(pow(2.0, 
             int(log((double)durval)/log(2.0)+0.001) ));
         out << " dur=\"" << visdur << "\"";
      }
   }
	     
   if (pre.search(string, "(\\.)")) {
      int dotcount = strlen(pre.getSubmatch(1));
      if ((dotcount >= 0) && (dotcount <= 4)) {
         // allowed range for dots in MEI 1.9b
         out << " dots=\"" << dotcount << "\"";
      } 
   }

   ////////////////////////////////////////////////////////////////////
   
   // print tie information
   if (strchr(string, '[') != NULL) {
      out << " tie=\"i\"";
   } else if (strchr(string, '_') != NULL) {
      out << " tie=\"m\"";
   } else if (strchr(string, ']') != NULL) {
      out << " tie=\"t\"";
   }

   // print fermata attribute:
   if (strchr(string, ';') != NULL) {
      if (layerindex == 0) {
         out << " fermata=\"above\"";
      } else {
         out << " fermata=\"below\"";
      }
   }

   int articulationQ = 0;

   // print articulation marks here, such as staccato


   if (articulationQ == 0) {
      out << "/>\n";
      indent--;
   } else {
      Indent(out, --indent);
      out << "</note>\n";
   }
}



//////////////////////////////
//
// getBeamAdjustment -- counts the difference in L and J beam start/end 
//    markers.
//

int getBeamAdjustment(const char* token) {
   int output = 0;
   if (token == NULL) {
      return output;
   }
   int i = 0;
   while (token[i] != '\0') {
      switch (token[i]) {
         case 'L': output++; break;
         case 'J': output--; break;
      }
      i++;
   }
   return output;
}



//////////////////////////////
//
// getLayerItems --
//

void getLayerItems(Array<Array<int> >& layeritems, HumdrumFile& infile, 
      int staffindex, Array<int>& ptrack, int layerindex, 
      Array<MeasureInfo>& minfo, int mindex, int start, int stop) {

   layeritems.setSize(1000);
   layeritems.setGrowth(1000);
   layeritems.allowGrowth(1);

   int lindex = 0;
   PerlRegularExpression pre;

   int i, j;
   int lcounter;
   for (i=start; i<=stop; i++) {
      lcounter = 0;

      // global comments stored only in first staff, first layer
      if (infile[i].isGlobalComment() && (layerindex == 0)
            && (staffindex == 0)) {
         addElementToList(layeritems, lindex++, i, 0);
         continue;
      }

      if (infile[i].isBibliographic()) {
         // bibliographic records should already have been processed.
         continue;
      }

      // don't search current line for layer information if it has
      // already been determined that there is no layer information
      // on that line (excluding null tokens).
      if (layerindex < minfo[i].layers[staffindex]-1) {
         continue;
      }
      
      // tandem interpretations only stored on first layer of staff
      if (infile[i].isInterpretation() && (layerindex == 0)) {
         j = getLayerFieldIndex(ptrack[staffindex], layerindex, i, infile);
         if (j < 0) {
            continue;
         }
         if (pre.search(infile[i][j], "^\\*clef")) {
            // store a clef change
            addElementToList(layeritems, lindex++, i, j);
         } else if (pre.search(infile[i][j], "^\\*k\\[.*\\]")) {
            // store key change
            addElementToList(layeritems, lindex++, i, j);
         }
         continue;
      }
  
      if (infile[i].isLocalComment()) {
         j = getLayerFieldIndex(ptrack[staffindex], layerindex, i, infile);
         if (j < 0) {
            continue;
         }
         if (!pre.search(infile[i][j], "^!\\s*$")) {
            // store a non-empty local comment for the layer
            addElementToList(layeritems, lindex++, i, j);
         }
      }

      if (infile[i].isData()) {
         j = getLayerFieldIndex(ptrack[staffindex], layerindex, i, infile);
         if (j < 0) {
            continue;
         }
         if (strcmp(infile[i][j], ".") == 0) {
            continue;
         } else {
            addElementToList(layeritems, lindex++, i, j);
         }
      }
   }

   layeritems.setSize(lindex);
   layeritems.allowGrowth(0);
}



//////////////////////////////
//
// getLayerFieldIndex -- return the spine index on the given line
//     in the Humdrum file which matches the primary track and layer index.
//     Returns -1 if the requested track/layer was not found.
//

int getLayerFieldIndex(int primary, int lindex, int line, HumdrumFile& infile) {
   int layercounter = 0;
   int j;
   for (j=0; j<infile[line].getFieldCount(); j++) {
      if (infile[line].getPrimaryTrack(j) == primary) {
         if (layercounter == lindex) {
            return j;
         }
         layercounter++;
      }
   }

   return -1;
}



//////////////////////////////
//
// addElemetntToList --
//

void addElementToList(Array<Array<int> >& layeritems, int lindex, 
      int line, int spine) {
   layeritems[lindex].setSize(2);
   layeritems[lindex].allowGrowth(0);
   layeritems[lindex][0] = line;
   layeritems[lindex][1] = spine;
}



//////////////////////////////
//
// printBarlineStyle --
// BAR_NONE     
// BAR_SINGLE   single
// BAR_END      end
// BAR_RPTSTART rptstart
// BAR_RPTEND   rptend
// BAR_RPTBOTH  rptboth
// BAR_INVIS    invis
// BAR_DBL      dbl
//

void printBarlineStyle(SSTREAM& out, int stylecode) {
   switch (stylecode) {
      case BAR_SINGLE:   out << "single"    ;break;
      case BAR_END:      out << "end"       ;break;
      case BAR_RPTSTART: out << "rptstart"  ;break;
      case BAR_RPTEND:   out << "rptend"    ;break;
      case BAR_RPTBOTH:  out << "rptboth"   ;break;
      case BAR_INVIS:    out << "invis"     ;break;
      case BAR_DBL:      out << "dbl"       ;break;
   }
}



//////////////////////////////
//
// printScoreSection --
//
// <section>
//   section/@id = name of section
//

void printScoreSection(int indent, SSTREAM& out, HumdrumFile& infile, 
   Array<int>& sections, int sindex) {

   Indent(out, indent);
   out << "<section";

   // check for a name for the section:
   PerlRegularExpression pre;
   if (pre.search(infile[sections[sindex]][0], "^\\*>\\s*([^\\[\\]]+)\\s*$")) {
      out << " " << xmlscope << "id=\"";
      printAsCdata(out, pre.getSubmatch(1));
      out << "\"";
   }

   out << ">\n";

   int startline = sections[sindex];
   int stopline = -1;
   if (sindex >= sections.getSize()-1) {
      stopline = infile.getNumLines()-1;
   } else {
      stopline = sections[sindex+1]-1;
   }
   printSingleSection(indent+1, out, infile, startline, stopline);

   Indent(out, indent);
   out << "</section>\n";
}



//////////////////////////////
//
// getSectionInfo -- identify labeled segments in the music.
//     If no labeled segements, then there is a single segment
//     for all of the music (and that single segment should not
//     be placed inside of a <segment> marker.
//

void getSectionInfo(Array& sections, HumdrumFile& infile) {
   int i;
   int dataline = -1;
   PerlRegularExpression pre;

   sections.setSize(100);
   sections.setSize(0);
   sections.setGrowth(100);

   for (i=0; i<infile.getNumLines(); i++) {
      if (infile[i].isData()) {
         dataline = i;
         if (sections.getSize() == 0) {
            sections.append(dataline);
         }
         continue;
      }
      if (!infile[i].isInterpretation()) {
         continue;
      }
      if (pre.search(infile[i][0], "^\\*>[^\\[\\]]+$")) {
         sections.append(i);
      }
   }
}



//////////////////////////////
//
// printMdivScoreScoredef --
//      <mei/music/body/div/score/scoredef>
//
// <scoredef> 	(timeline*, chordtable?, symboltable?, keysig?, pghead1?, 
//               pghead2?, pgfoot1?, pgfoot2?, (staffgrp? | staffdef?))
//
// Container for score meta-information.
// 
// Score-level encoding strategies for:
// 
//    a) alternating meter sig, e.g. 2/4 3/4 in alternating measures (Read,
//       p. 164-165) and combined meters (Read, p.166-168): explicitly
//       encode meters, make them invisible, display both meter sigs at the
//       start of the section
//    b) compound meter sig, e.g. 2+3+2/4 (Read, p. 168-170): set
//       meter.count=2+3+2
//    c) polymeters, e.g. different simultaneous meters (Read, p. 170-173):
//       1. where barlines coincide, use beaming to elucidate the polymeter
//       2. where barlines sometimes coincide, break into measures
//       according to a common unit of time, draw barlines where visually
//       required
//       3. where barlines never coincide, encode as parts only
//    d) mixed meter sig, e.g. 2/4 + 3/16 in the same measure (Read, p.
//       173-174): encode in common time base, e.g. 11/16, make meter
//       invisible, display both meter sigs at the start of the measure
//    e) fractional meter sig, e.g. 3.5/4 (Read, p. 175-177):
//       set meter.count=3.5
//       The beat count may be displayed as a fraction or as its decimal
//       equivalent.
// 
// 
//  Attribute	        Type	                    Value
//  beam.group    	CDATA 	                    #IMPLIED
//  beam.rests    	true|false 	            #IMPLIED
//  clef.line    	CDATA 	                    #IMPLIED
//  clef.shape    	G|GG|F|C|perc|TAB           #IMPLIED
//  clef.trans    	8va|8vb|15va 	            #IMPLIED
//  dur.default    	long|breve|1|2|4|8|16|32|64|128|256|512|1024|2048
//                      |maxima|longa|brevis|semibrevis|minima|semiminima|
//                      fusa|semifusa 	            #IMPLIED
//  id    	        ID 	                    #IMPLIED
//  key.accid    	s|f|ss|ff|n 	            #IMPLIED
//  key.mode    	major|minor|dorian|phrygian|lydian|
//                      mixolydian|aeolian|locrian  #IMPLIED
//  key.pname    	a|b|c|d|e|f|g 	            #IMPLIED
//  key.sig.mixed    	CDATA 	                    #IMPLIED
//  key.sig    	        7f|6f|5f|4f|3f|2f|1f|0|1s|2s|3s|4s|5s|6s|7s|mixed   
//                                                  #IMPLIED
//  meter.count    	CDATA 	                    #IMPLIED
//  meter.sym    	common|cut                  #IMPLIED
//  meter.unit    	CDATA 	                    #IMPLIED
//  modusmaior    	[2-3] 	                    #IMPLIED
//  modusminor    	[2-3] 	                    #IMPLIED
//  n    	        NMTOKEN                     #IMPLIED
//  num    	        CDATA 	                    #IMPLIED
//  numbase            	CDATA 	                    #IMPLIED
//  octave.default    	[0-9] 	                    #IMPLIED
//  prolatio    	[2-3] 	                    #IMPLIED
//  proport.num    	CDATA 	                    #IMPLIED
//  proport.numbase    	CDATA 	                    #IMPLIED
//  source            	IDREFS 	                    #IMPLIED
//  tempus    	        [2-3] 	                    #IMPLIED
//  trans.diat    	CDATA 	                    #IMPLIED
//  trans.semi    	CDATA 	                    #IMPLIED
//  

void printMdivScoreScoredef(int indent, SSTREAM& out, HumdrumFile& infile) {
   Indent(out, indent);
   out << "<scoredef";

   // @key.sig
   printInitialKeySignature(out, infile);

   // @meter.count
   // @meter.unit
   // @meter.sym
   printInitialMeterSignature(out, infile);

   // @midi.div
   MIDITPQ = getMidiTicksPerQuarterNote(infile);
   if (verboseQ) {
      out << " midi.div=\"" << MIDITPQ << "\"";
   }
   
   out << ">\n";

   printScoredefStaffgrp(indent+1, out, infile);

   printTempExpandRules(indent+1, out, infile);

   Indent(out, indent);
   out << "</scoredef>\n";
}



///////////////////////////////
//
// printTempExpandRules -- print section expansion rules
//

void printTempExpandRules(int indent, SSTREAM& out, HumdrumFile& infile) {
   int i;
   PerlRegularExpression pre;
   Array<char> buffer;
   int len;

   for (i=0; i<infile.getNumLines(); i++) {
      if (!infile[i].isInterpretation()) {
         continue;
      }
      if (pre.search(infile[i][0], "^\\*>([^\\[]*)\\[([^\\]]+)\\]$")) {
         Indent(out, indent);
         out << "<!-- " << "<secexpan";
         if (strlen(pre.getSubmatch(1)) == 0) {
            out << " repeat=\"true\"";
            out << " label=\"default\"";
         } else if (strcmp(pre.getSubmatch(), "norep") == 0) {
            out << " repeat=\"false\"";
            out << " label=\"norep\"";
         } else {
            out << " label=\"";
            printAsCdata(out, pre.getSubmatch());
            out << "\"";
         }
	 len = strlen(pre.getSubmatch(2));
	 buffer.setSize(len+1);
	 strcpy(buffer.getBase(), pre.getSubmatch());
         pre.sar(buffer, "\\s*,\\s*", " ", "g");
         if (buffer.getSize() > 1) {
            out << " ids=\"";
            printAsCdata(out, buffer.getBase());
            out << "\"";
         }

         out << "/> -->\n";
      }
   }
}



//////////////////////////////
//
// printScoredefStaffgrp -- list of staves found in the music.
//   staffgrp/@barthru    = true if a barline goes through all staves
//   staffgrp/@symbol     = brace
//   staffgrp/@lable.full = string to place to left of first staff on
//       first system.
//

void printScoredefStaffgrp(int indent, SSTREAM& out, HumdrumFile& infile) {

   if (PTRACK.getSize() < 1) {
      return;
   }

   Indent(out, indent);
   out << "<staffgrp";
   out << ">\n";

   int i;
   for (i=0; i<PTRACK.getSize(); i++) {
      printStaffgrpStaffdef(indent+1, out, infile, PTRACK, i);
   }

   Indent(out, indent);
   out << "</staffgrp>\n";
}



//////////////////////////////
//
// getStaffCount -- count the number of **kern spines on
//     the first instance of exclusive interpretations.
// 

void getStaffCount(Array& primarytracks, HumdrumFile& infile) {
   primarytracks.setSize(100);
   primarytracks.setSize(0);
   primarytracks.setGrowth(100);
   int i, j;
   int track;
   for (i=0; i<infile.getNumLines(); i++) {
      if (infile[i].isInterpretation()) {
         for (j=0; j<infile[i].getFieldCount(); j++) {
            if (strcmp(infile[i][j], "**kern") == 0) {
               track = infile[i].getPrimaryTrack(j);
               primarytracks.append(track);
            }
         }
      }
   }

   // reverse the order of the staves to enumerate from higher instruments
   // to lower instruments.
   Array<int>& pt = primarytracks;
   int tempval;
   for (i=0; i<primarytracks.getSize()/2; i++) {
      tempval = primarytracks[i];
      primarytracks[i] = primarytracks[pt.getSize()-1-i];
      primarytracks[pt.getSize()-1-i] = tempval;
   }
}



//////////////////////////////
//
// printStaffgrpStaffdef --
//

void printStaffgrpStaffdef(int indent, SSTREAM& out, HumdrumFile& infile, 
      Array<int>& tracks, int index) {
   Indent(out, indent);
   out << "<staffdef";

   out << " n=\"" << index+1 << "\"";

   printInitialClef(out, infile, tracks[index]);

   out << "/>\n";
}



//////////////////////////////
//
// printIntialClef -- print the initial clef of the
//     specified primary track spine.
//  clef.line = what staffline the cleff is on
//  clef.shape = G, GG, F, C, perc, TAB
//  clef.trans = 8va, 8vb, 15va
// 
// Types of **kern *clef's:
// G	G clef (as used, for example in the treble staff)
// F	F clef (as used, for example in the bass staff)
// C	C clef (as used, for example in the alto staff)
// X	percussion clef
// -	explicit indication that clef is absent
// 1	first (lowest) line is designated by the pitch of the clef
// 2	second (from bottom) line is designated by the pitch of the clef
// 3	third (from bottom) line is designated by the pitch of the clef
// 4	fourth (from bottom) line is designated by the pitch of the clef
// 5	fifth (highest) line is designated by the pitch of the clef
// v =  8va bassa (played one octave higher)
// ^ =  8va treble (played one octave lower)
// ^^ double octave treble (played two octaves higher)
// vv double octave bass (played two octaves lower)
//

void printInitialClef(SSTREAM& out, HumdrumFile& infile, int ptrack) {
   int i, j;
   const char* clef = NULL;
   PerlRegularExpression pre;

   for (i=0; i<infile.getNumLines(); i++) {
      if (infile[i].isData()) {
         break;
      }
      if (infile[i].isMeasure()) {
         break;
      }
      if (infile[i].isInterpretation()) {
         for (j=0; j<infile[i].getFieldCount(); j++) {
            if (infile[i].getPrimaryTrack(j) == ptrack) {
               if (pre.search(infile[i][j], "^\\*clef(.*)")) {
		  clef = infile[i][j];
                  break;
               }
            }
         }
      }
      if (clef != NULL) {
         break;
      }
   }

   if (clef == NULL) {
      return;
   }
	     
   char buffer[128] = {0};
   strncpy(buffer, pre.getSubmatch(1), 32);


   pre.search(buffer, "([CFGX-])");
   if (strcmp(pre.getSubmatch(1), "-") == 0) {
      // explicitly no clef present.
      return;
   } else if (strcmp(pre.getSubmatch(), "C") == 0) {
      out << " clef.shape=\"C\"";
   } else if (strcmp(pre.getSubmatch(), "F") == 0) {
      out << " clef.shape=\"F\"";
   } else if (strcmp(pre.getSubmatch(), "G") == 0) {
      out << " clef.shape=\"G\"";
   } else if (strcmp(pre.getSubmatch(), "G") == 0) {
      out << " clef.shape=\"perc\"";
   }
   
   if (pre.search(buffer, "(\\d)")) {
      out << " clef.line=\"";
      out << pre.getSubmatch(1);
      out << "\"";
   }

   if (strstr(buffer, "vv") != NULL) {
      // transpose two octaves lower
   } else if (strchr(buffer, 'v') != NULL) {
      // transpose one octave lower
      out << " clef.trans=\"8vb\"";
   } else if (strstr(buffer, "^^") != NULL) {
      // transpose two octaves higher
   } else if (strchr(buffer, '^') != NULL) {
      // transpose one octave higher
      out << " clef.trans=\"8va\"";
   }
   
}



//////////////////////////////
//
// getMidiTicksPerQuarterNote --
//

int getMidiTicksPerQuarterNote(HumdrumFile& infile) {
   int returnval = infile.getMinTimeBase();
   if (returnval % 4 != 0) {
      returnval = returnval * 4;
   }

   if (returnval % 4 != 0) {
      cerr << "Error: timebase is not divisible by 4: " << returnval << endl;
      exit(1);
   }
	
   return returnval/4;
}



//////////////////////////////
//
// printInitialKeySignature --
//
// score/scoredef/@key.sig, @key.mode
//
// @key.sig:  1f = 1 flat, 0 = no sharp/flat, 2s = two sharps
// @key.mode: major|minor|dorian|phrygian|lydian|mixolydian|aeolian|locrian
//            #IMPLIED
// @key.pname a|b|c|d|e|f|g
// @key.accid s|f|ss|ff|n
//

void printInitialKeySignature(SSTREAM& out, HumdrumFile& infile) {
   int i, j;
   const char* keysig = NULL;
   const char* key    = NULL;

   PerlRegularExpression pre;

   for (i=0; i<infile.getNumLines(); i++) {
      if (infile[i].isData()) {
         break;
      }
      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], "^\\*k\\[.*\\]$")) {
            keysig = infile[i][j];
         }
         if (pre.search(infile[i][j], "^\\*[A-G][#-]?:", "i")) {
            key = infile[i][j];
         }
         break;
      }
   }
   
   printMeiKeySignatureAttributes(out, keysig);
   printMeiKeyAttributes(out, key);
}



//////////////////////////////
//
// printMeiKeyAttributes -- prints the key of the music
//   (such as whether the music is in C major, or D minor, etc.
//   This is not the key signature which indicates which notes
//   in the music are to be played with sharps or flats.
//   These attributes are stored in <scoredef>.
//   Attributes which describe the musical key are:
//      @pname = diatonic pitch name
//      @accid = accidental to apply to pitch name
//      @mode  = mode of key: major, minor, other mode
//

void printMeiKeyAttributes(SSTREAM& out, const char* key) {

   if (key != NULL) {
      PerlRegularExpression pre;
      pre.search(key, "^\\*([A-G])([#-]?):(.*)", "i");
      char pname = pre.getSubmatch(1)[0];
      
      out << " key.pname=\"" << (char)tolower(pname) << "\"";

      char accid = pre.getSubmatch(2)[0];
      switch (accid) {
         case '#':  accid = 's';  break;
         case '-':  accid = 'f';  break;
         default:   accid = 'n';
      }
      out << " key.accid=\"" << accid << "\"";

      char mode[32] = {0};
      if (strlen(pre.getSubmatch(3)) == 3) {
         if (strcmp(pre.getSubmatch(), "mix") == 0) {
            strcpy(mode, "mixolydian");
         } else if (strcmp(pre.getSubmatch(), "dor") == 0) {
            strcpy(mode, "dorian");
         } else if (strcmp(pre.getSubmatch(), "phr") == 0) {
            strcpy(mode, "phrygian");
         } else if (strcmp(pre.getSubmatch(), "lyd") == 0) {
            strcpy(mode, "lydian");
         } else if (strcmp(pre.getSubmatch(), "aeo") == 0) {
            strcpy(mode, "aeolian");
         } else if (strcmp(pre.getSubmatch(), "loc") == 0) {
            strcpy(mode, "locrian");
         } else if (isupper(pname)) {
            strcpy(mode, "major");
         } else {
            strcpy(mode, "minor");
         }
      } else {
         if (isupper(pname)) {
            strcpy(mode, "major");
         } else {
            strcpy(mode, "minor");
         }
      }
      out << " key.mode=\"" << mode << "\"";
   }
}



//////////////////////////////
//
// printMeiKeySignatureAttributes -- convert **kern signature into
//   and MEI key signature.  Assumes that the order of the pitches 
//   in *k[] are standard and consecutive.
//

void printMeiKeySignatureAttributes(SSTREAM& out, const char* keysig) {
   if (keysig != NULL) {
      int count = 0;
      int len = strlen(keysig);
      int i;
      for (i=0; i<len; i++) {
         if (keysig[i] == '#') {
            count++;
         } else if (keysig[i] == '-') {
            count--;
         }
      }
      out << " key.sig=\"" << abs(count);
      if (count > 0) {
         out << "s";
      } else if (count < 0) {
         out << "f";
      }
      out << "\"";
   }
}



//////////////////////////////
//
// printInitialMeterSignature --
//    @meter.count == numerator of key signature
//    @meter.unit  == demoninator of key signature
//    @meter.sym   == cut time or common time symbol
//

void printInitialMeterSignature(SSTREAM& out, HumdrumFile& infile) {
   int i, j;
   const char* metersig   = NULL;
   const char* metstring  = NULL;

   PerlRegularExpression pre;

   for (i=0; i<infile.getNumLines(); i++) {
      if (infile[i].isData()) {
         break;
      }
      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], "^\\*M(\\d+)/(\\d+)$")) {
            metersig = infile[i][j];
         }
         if (pre.search(infile[i][j], "^\\*met\\(.+\\)$", "i")) {
            metstring = infile[i][j];
         }
         break;
      }
   }

   printMeiMeterSignatureAttributes(out, metersig);
   printMeiMeterSymbolAttributes(out, metstring);
}



//////////////////////////////
//
// printMeiMeterSignatureAttributes -- time signature such as 4/4;
//

void printMeiMeterSignatureAttributes(SSTREAM& out, const char* metersig) {
   if (metersig != NULL) {
      PerlRegularExpression pre;
      pre.search(metersig, "^\\*M(\\d+)/(\\d+)$");
      out << " meter.count=\"";
      out << pre.getSubmatch(1);
      out << "\"";

      out << " meter.unit=\"";
      out << pre.getSubmatch(2);
      out << "\"";
   }
}



//////////////////////////////
//
// printMeiMeterSymbolAttributes -- such as "C" for common time.
//

void printMeiMeterSymbolAttributes(SSTREAM& out, const char* metstring) {
   if (metstring != NULL) {
      PerlRegularExpression pre;
      if (pre.search(metstring, "\\*met\\(C\\)", "i")) {
         out << " meter.sym=\"common\"";
      } else if (pre.search(metstring, "\\*met\\(C|\\)", "i")) {
         out << " meter.sym=\"cut\"";
      } 
   }
}



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

void checkOptions(Options& opts, int argc, char** argv) {
   opts.define("v|verbose=b",   "Verbose output of data");
   opts.define("I|indent=s:\t", "Indenting string");
   opts.define("X|noxmlscope=s:\t","Do not prepend xml: scope to XML attributes");

   opts.define("d|debug=b");                    // used for debuging info
   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, August 2009" << endl;
      exit(0);
   } else if (opts.getBoolean("version")) {
      cout << argv[0] << ", version: 7 August 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);
   }

   debugQ   = opts.getBoolean("debug");
   verboseQ = opts.getBoolean("verbose");
   INDENT   = opts.getString("indent");

   // tab character in option is being eaten (probably by the 
   // option parser, so if the INDENT string is empty, force it
   // to be a tab character.
   if (strlen(INDENT) == 0) {
      INDENT = "\t";
   }
   if (opts.getBoolean("noxmlscope")) {
      xmlscope = "";
   } 
}



//////////////////////////////
//
// Indent --
//
// default value: string = NULL
//

void Indent(SSTREAM& out, int indent, const char* string) {
   int i;
   int limit = 100;
   const char* istring = string;
   if (istring == NULL) {
      istring = INDENT;
   }
	    
   if (indent > limit) {
      return;
   }
   for (i=0; i<indent; i++) {
      out << istring;
   }
}



//////////////////////////////
//
// printAsCdata -- print text data with special escaping for XML data.
//

void printAsCdata(SSTREAM& out, const char* string) {
   int i = 0;
   while (string[i] != '\0') {
      switch(string[i]) {
         case '>':   out << ">";     break;
         case '<':   out << "<";     break;
         case '\"':  out << """;   break;
         // case '\'':  out << "'";   break;
         default:    out << string[i];
      }
      i++;
   }
}



//////////////////////////////
//
// example -- example function calls to the program.
//

void example(void) {


}



//////////////////////////////
//
// usage -- command-line usage description and brief summary
//

void usage(const char* command) {

}

/*
<Piece xsi:schemaLocation="http://www.cmme.org cmme.xsd">
   <GeneralData>
      <Title>
      <Composer>
      <Editor>
   <VoiceData>
      <NumVoices>
      <Voice>
         <Name>
         <EventList>
            <Clef>
            <Mensuration>
            <OriginalText>
            <Note>
               <Type>
               <Length>
               <LetterName>
               <OctaveNum>
               <ModernText>
                  <Syllable>
                  <WordEnd>
            <Dot>
               <StaffLoc>
            <Rest>
               <Type>
               <Length>
               <BottomStaffLine>
               <NumSpaces>
            <Custos>
            <LineEnd>
            <MiscItem>
               <Barline>


*/
 


// md5sum: d26c3f0616d08f3b00d920ae33fe611c hum2cmme.cpp [20110304]