//
// Programmer:    Craig Stuart Sapp <craig@ccrma.stanford.edu>
// Creation Date: Wed Jun  2 23:30:21 PDT 2010
// Last Modified: Fri Dec 24 16:48:57 PST 2010 continued implementation
// Last Modified: Sat Feb  5 14:57:09 PST 2011 added text for lyrics
// Last Modified: Mon Feb 28 06:30:23 PST 2011 added --footer
// Last Modified: Mon Mar  7 19:19:44 PST 2011 added LO:N:smx/Y;bmx/Y
// Last Modified: Fri Mar 25 11:37:46 PDT 2011 macro expansion adjustments
// Last Modified: Fri Apr  1 07:54:48 PDT 2011 hanging tie endings 
// Last Modified: Wed Apr  6 19:54:37 PDT 2011 added --vz
// Last Modified: Thu Apr 14 21:44:11 PDT 2011 added *rscale processing
// Last Modified: Thu Aug 18 14:43:25 PDT 2011 added segno sign on barlines
// Last Modified: Wed Aug 31 16:59:11 PDT 2011 added \+ infront of # text
// Last Modified: Sat Sep 24 19:21:17 PDT 2011 added -x option
// Last Modified: Fri May 25 11:02:26 PDT 2012 added "P C0:x1" print suggestions
// Last Modified: Mon Jul 16 16:21:25 PDT 2012 added unterminated tie handling
// Last Modified: Mon Aug 20 14:18:50 PDT 2012 added -textvadd
// Last Modified: Tue Aug 21 09:36:43 PDT 2012 added line extensions
// Filename:      ...sig/examples/all/hum2muse.cpp 
// Web Address:   http://sig.sapp.org/examples/museinfo/humdrum/hum2muse.cpp
// Syntax:        C++; museinfo
//
// Description:   Converts Humdrum files into MuseData files.
//

#include <math.h>
#include <time.h>    /* for current time/date */

#ifndef OLDCPP
   #include <iostream>
   #include <fstream>
   #include <sstream>
   #define SSTREAM stringstream
   #define CSTRING str().c_str()
   using namespace std;
#else
   #include <iostream.h>
   #include <fstream.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"
#include "MuseData.h"
#include "MuseDataSet.h"
#include "CheckSum.h"
#include "SigString.h"


//////////////////////////////////////////////////////////////////////////
//
// Layout codes
//

#define LO_WEDGE_START "HP"
#define LO_WEDGE_END   "HPZ"


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

class Coord {
   public:
      Coord(void) { i = j = -1; }
      int i;
      int j;
};


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

class LayoutParameters {
   public:
      LayoutParameters           (void) { clear(); }
      void         clear         (void);
      void         parseLayout   (HumdrumFile& infile, Array<Coord>& layout);
      int          getParameter  (int codeindex, const char* searchkey);
      void         addEntry      (LayoutParameters& param, int index);
      int          getSize       (void) {return code.getSize(); }
      int          getCodeSize   (int index) {return key[index].getSize();}
      Array<char>& getCode       (int index)      { return code[index];   }
      Array<Array<char> >& getKeys (int index)    { return key[index];    }
      Array<Array<char> >& getValues (int index)  { return value[index];  }
      Array<char>& getKey        (int i1, int i2) { return key[i1][i2];   }
      Array<char>& getValue      (int i1, int i2) { return value[i1][i2]; }
      int          appendCode    (const char* codeName);
      int          addKeyValue   (int index, const char* keystr, 
                                  const char* valuestr);
      int          hasKeyName    (int codeindex, const char* kname);
      int          getKeyValueInt(int codeindex, const char* kname);
      ostream&     print         (ostream& out, int index=-1);

   protected:
      void prepareCode(const char* xcode, const char* params);

   private:
      Array<Array<char> >          code;
      Array<Array<Array<char> > >  key;
      Array<Array<Array<char> > >  value;
};


ostream& operator<<(ostream& out, LayoutParameters& lp) {
   return lp.print(out);
}

ostream& LayoutParameters::print(ostream& out, int index) {
   int i,j;

   if (index >= 0) {
      out << "@LO:" << getCode(index) << ":";
      for (j=0; j<key[index].getSize(); j++) {
         out << key[index][j];
         if (strlen(value[index][j].getBase()) > 0) {
            out << "=" << value[index][j];
         }
         if (j < getCodeSize(index)-1) {
            out << ":";
         }
      }
      out << "\n";
      return out;
   }

   for (i=0; i<getSize(); i++) {
      out << "@LO:" << getCode(i) << ":";
      for (j=0; j<key[i].getSize(); j++) {
         out << key[i][j];
         if (strlen(value[i][j].getBase()) > 0) {
            out << "=" << value[i][j];
         }
         if (j < getCodeSize(i)-1) {
            out << ":";
         }
      }
      out << "\n";
   }

   return out;
}



int LayoutParameters::getKeyValueInt(int codeindex, const char* kname) {
   int i;
   for (i=0; i<key[codeindex].getSize(); i++) {
      if (strcmp(kname, key[codeindex][i].getBase()) == 0) {
         return atoi(value[codeindex][i].getBase());
      }
   }
   return 0;
}



int LayoutParameters::hasKeyName(int codeindex, const char* kname) {
   int i;
   for (i=0; i<key[codeindex].getSize(); i++) {
      if (strcmp(kname, key[codeindex][i].getBase()) == 0) {
         return i+1;
      }
   }
   return 0;
}


void LayoutParameters::clear(void) {
   code.setSize(0);
   key.setSize(0);
   value.setSize(0);
}


int LayoutParameters::appendCode(const char* codeName) {
   code.increase(1);
   key.increase(1);
   value.increase(1);
   code.last().setSize(strlen(codeName)+1);
   strcpy(code.last().getBase(), codeName);
   key.last().setSize(0);
   value.last().setSize(0);
   return code.getSize() - 1;
}

int LayoutParameters::addKeyValue(int index, const char* keystr, 
      const char* valuestr) {

   key[index].increase(1);
   key[index].last().setSize(strlen(keystr)+1);
   strcpy(key[index].last().getBase(), keystr);

   value[index].increase(1);
   value[index].last().setSize(strlen(valuestr)+1);
   strcpy(value[index].last().getBase(), valuestr);

   return key.last().getSize() - 1;
}


void LayoutParameters::parseLayout(HumdrumFile& infile, Array<Coord>& layout) {
   PerlRegularExpression pre;
   int i;
   int ii;
   int jj;
   clear();
   char codename[128] = {0};
   for (i=0; i<layout.getSize(); i++) {
      ii = layout[i].i;
      jj = layout[i].j;
      if ((ii < 0) || (jj < 0)) {
         continue;
      }
      if (!pre.search(infile[ii][jj], "^!+LO:([^:]+):?(.*)", "")) {
         continue;
      }
      strcpy(codename, pre.getSubmatch(1));
      prepareCode(codename, pre.getSubmatch(2));
   }
}


void LayoutParameters::addEntry(LayoutParameters& param, int index) { 
   code.increase(1);
   code.last().setSize(strlen(param.getCode(index).getBase())+1);
   strcpy(code.last().getBase(), param.getCode(index).getBase());

   int i;
   int keycount = param.getCodeSize(index);

   key.increase(1);
   value.increase(1);

   key.last().setSize(keycount);
   value.last().setSize(keycount);

   for (i=0; i<keycount; i++) {
      key.last()[i].setSize(strlen(param.getKey(index, i).getBase())+1);
      strcpy(key.last()[i].getBase(), param.getKey(index, i).getBase());

      value.last()[i].setSize(strlen(param.getValue(index, i).getBase())+1);
      strcpy(value.last()[i].getBase(), param.getValue(index, i).getBase());
   }
}


int LayoutParameters::getParameter(int codeindex, const char* searchkey) {
   int output = -1;
   int j;
   for (j=0; j<key[codeindex].getSize(); j++) {
      if (strcmp(key[codeindex][j].getBase(), searchkey) == 0) {
         output = j;
         break;
      }
   }
   return output;
}

void LayoutParameters::prepareCode(const char* xcode, const char* params) {
   code.increase(1);
   code.last().setSize(strlen(xcode)+1);
   strcpy(code.last().getBase(), xcode);

   PerlRegularExpression pre;
   Array<Array<char> > tokens;
   pre.getTokens(tokens, ":", params);

   key.increase(1);
   value.increase(1);
   key.last().setSize(tokens.getSize());
   value.last().setSize(tokens.getSize());

   PerlRegularExpression pre2;

   int i;
   for (i=0; i<tokens.getSize(); i++) {
      if (pre2.search(tokens[i], "^([^=]+)=?(.*)", "")) {
         key.last()[i].setSize(strlen(pre2.getSubmatch(1))+1);
         strcpy(key.last()[i].getBase(), pre2.getSubmatch());
         value.last()[i].setSize(strlen(pre2.getSubmatch(2))+1);
         strcpy(value.last()[i].getBase(), pre2.getSubmatch());
      } else {
         key.last()[i].setSize(1);
         key.last()[i][0] = '\0';
         value.last()[i].setSize(1);
         value.last()[i][0] = '\0';
      }
   }
}


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

// function declarations:
void  checkOptions(Options& opts, int argc, char** argv);
void  example(void);
void  usage(const char* command);
void  convertData(Array<MuseData*>& outfiles, 
                                HumdrumFile& infile);
void  getKernTracks(Array<int>& tracks, HumdrumFile& infile);
void  convertTrackToMuseData(MuseData& musedata, int track, 
                                HumdrumFile& infile, Array<int>& tpq, 
                                int& tickpos, int counter, int total, 
                                int printFirstMeasureQ);
void  getMeasureInfo(Array<int>& measures, HumdrumFile& infile);
void  getMeasureData(MuseData& tempdata, int track, 
                                HumdrumFile& infile, int startline, 
                                int stopline, int tpq, int& tickpos,
                                int& measuresuppress, int& founddataQ);
int   getMaxVoices(HumdrumFile& infile, int startline, 
                                int stopline, int track);
void  addMeasureEntry(MuseData& tempdata, HumdrumFile& infile, 
                                int line, int spine);
void  processVoice(MuseData& tempdata, HumdrumFile& infile, 
                                int startline, int stopline, int track, 
                                int voice, int tpq, int& tickpos,
                                int& measuresuppress, int& founddataQ,
                                int starttick, int stoptick);
int   getGlobalTicksPerQuarter(HumdrumFile& infile);
int   getTick(int tpq, HumdrumFile& infile, int line);
int   addNoteToEntry(MuseData& tempdata, HumdrumFile& infile, 
                                int row, int col, int tpq, int voice, 
                                int opentie);
int   getTickDur(int tpq, HumdrumFile& infile, int row, 
                                int col);
void  addBackup(MuseData& tempdata, int backticks, int tpq);
void  checkColor(Array<char>& colorchar, MuseRecord& arecord, 
                                const char* token, Array<char>& colorout);
void  setColorCharacters(HumdrumFile& infile, Array<char>& colorchar,
                                Array<char>& colorout);
void  setNoteheadShape(MuseRecord& arecord, const char* token);
void  insertHeaderRecords(HumdrumFile& infile, MuseData& tempdata, 
                                int track, int counter, int total);
void  addCopyrightLine(HumdrumFile& infile, MuseData& tempdata);
void  addControlNumber(HumdrumFile& infile, MuseData& tempdata);
void  addTimeStamp(MuseData& tempdata);
void  addDateAndEncoder(HumdrumFile& infile, MuseData& tempdata);
void  addWorkNumberInfo(HumdrumFile& infile, MuseData& tempdata);
void  addSourceRecord(HumdrumFile& infile, MuseData& tempdata);
void  addWorkTitle(HumdrumFile& infile, MuseData& tempdata);
void  addMovementTitle(HumdrumFile& infile, MuseData& tempdata);
void  addPartName(HumdrumFile& infile, int track, 
                                MuseData& tempdata);
void  insertDollarRecord(HumdrumFile& infile, int line, 
                                MuseData& musedata, int track, int counter, 
                                int total, int tpq);
int   appendKeySignature(char* buffer, HumdrumFile& infile, int line, 
                                int track);
int   appendClef(char* buffer, HumdrumFile& infile, int line, 
                                int track);
int   isBarlineBeforeData(HumdrumFile& infile, int startindex);
char  getColorCategory(const char* color);
int   appendTimeSignature(char* buffer, HumdrumFile& infile, int line, 
                                int track, int tpq);
void  printMuse2PsOptions(HumdrumFile& infile);
void  getPartNames(HumdrumFile& infile, 
                                Array<Array<char> >& PartNames);
void  addChordLevelArtic(MuseData& tempdata, MuseRecord&  arecord, 
                                MuseRecord& printsuggestion, 
                                HumdrumFile& infile, int row, int col, 
                                int voice);
void  addNoteLevelArtic(MuseRecord&  arecord, HumdrumFile& infile, 
                                int row, int col, int tnum);
void  setupMusicaFictaVariables(HumdrumFile& infile);
void  getBeamState(Array<Array<Array<char> > >& beams, 
                                Array<Array<Array<Coord> > >& layout, 
                                Array<Array<Coord> >& glayout,
                                Array<Array<Coord> >& clefs,
                                HumdrumFile& infile);
void  countBeamStuff(const char* token, int& start, int& stop, 
                                int& flagr, int& flagl);
void  getMeasureDuration(HumdrumFile& infile, 
                                Array<RationalNumber>& rns);
int   isPowerOfTwo(RationalNumber& rn);
void  getTupletState(Array<int>& hasTuplet, 
                                Array<Array<char> >& TupletState, 
                                Array<Array<int> >& TupletTopNum, 
                                Array<Array<int> >& TupletBotNum, 
                                HumdrumFile& infile);
void  primeFactorization(Array<int>& factors, int input);
char  getBase36(int value);
int   getTupletTop(HumdrumFile& infile, int row, int col);
void  storeLayoutInformation(HumdrumFile& infile, int line, 
                                Array<Array<Array<Coord> > >& laystate, 
                                Array<Array<Array<Coord> > >& layout,
                                int initializeQ);
void  addLayoutInfo(HumdrumFile& infile, int line, 
                                Array<Array<Array<Coord> > > &laystate);
void  handleLayoutInfoChord(MuseData& tempdata, HumdrumFile& infile, 
                                int row, int col, LayoutParameters& lp, 
                                int voice);
void  insertSlurUpPrintSug(MuseData& data, MuseRecord& prec);
void  insertSlurDownPrintSug(MuseData& data, MuseRecord& prec);
void  insertStaccatoSuggestion(MuseData& indata, MuseRecord& prec, 
                                int direction);
void  getPitches(Array<int>& pitches, HumdrumFile& infile, 
                                int row, int col);
int   numbersort(const void* A, const void* B);
int   numberRsort(const void* A, const void* B);
void  getChordMapping(Array<int>& chordmapping, HumdrumFile& infile, 
                                int row, int col);
void  getDynamicsAssignment(HumdrumFile& infile, 
                                Array<int>& DynamicsAssignment, int& dynamicsQ);
void  addDynamics(HumdrumFile& infile, int row, int col, 
                                MuseData& indata, MuseRecord& prec, 
                                Array<int>& DynamicsAssignment,
                                LayoutParameters& lp);
void  addMovementDesignation(char* buffer, HumdrumFile& infile, int line);
void  convertKernLODYtoMusePS(char* buffer, Array<Array<char> >& keys,
                                Array<Array<char> >& values);
void  processDynamicsLayout(int loc, MuseRecord& prec, 
                                LayoutParameters& lp);
int   rnsort(const void* A, const void* B);
void  getRhythms(Array<RationalNumber>& rns, HumdrumFile& infile,
                                int startindex, int stopindex);
void  prepareLocalTickChanges(HumdrumFile& infile, Array<int>& ticks);
int   LCM(Array<int>& rhythms);
int   GCD(int a, int b);
int   getTPQByRhythmSet(Array<RationalNumber>& rhys);
void  adjustClefState(HumdrumFile& infile, int line, 
                                Array<Coord>& clefstate);
void  storeClefInformation(HumdrumFile& infile, int line, 
                                Array<Coord>& clefstate, 
                                Array<Array<Coord> >& clefs);
void  insertArpeggio(MuseData& tempdata, HumdrumFile& infile, 
                                int row, int col);
int   getScoreStaffVerticalPos(int note, int line, int row, 
                                Array<Array<Coord> >& clefs, 
                                HumdrumFile& infile);
void  processMeasureLayout(MuseData& tempdata, HumdrumFile& infile, 
                                int line, int col, 
                                LayoutParameters& lp,
                                LayoutParameters& glp);
void  storeGlobalLayoutInfo(HumdrumFile& infile, int line, 
                                Array<Coord>& glaystate, 
                                Array<Array<Coord> >& glayout);
void  addGlobalLayoutInfo(HumdrumFile& infile, int line, 
                                Array<Coord>& glaystate);
void  handleMeasureLayoutParam(MuseData& tempdata, HumdrumFile& infile,
                                LayoutParameters& lp, int index);
void  addHairpinStops(MuseData& tempdata, HumdrumFile& infile, 
                                int row, int col);
void  addHairpinStarts(MuseData& tempdata, HumdrumFile& infile, 
                                int row, int col);
void  addDecrescendoStop(MuseData& tempdata, LayoutParameters& lp);
void  addCrescendoStop(MuseData& tempdata, LayoutParameters& lp);
void  addCrescendoStart(MuseData& tempdata, LayoutParameters& lp);
void  addDecrescendoStart(MuseData& tempdata, LayoutParameters& lp);
void  getXxYy(Array<int>& vals, Array<int>& states, 
                                Array<Array<char> >& keys,
                                Array<Array<char> >& values);
void  addPositionInfo(MuseData& tempdata, int column, 
                                LayoutParameters lp, const char* code);
void  addCrescText(MuseData& tempdata, HumdrumFile& infile, 
                                int row, int col, int dcol, 
                                LayoutParameters& lp, const char* text);
void  addPositionParameters(MuseData& tempdata, int column, 
                                Array<Array<char> >& keys, 
                                Array<Array<char> >& values);
void  addTextLayout(MuseData& tempdata, HumdrumFile& infile, 
                                int row, int col, LayoutParameters& lp, 
                                const char* code);
void  addText(MuseData& tempdata, Array<Array<char> >& keys, 
                                Array<Array<char> >& values);
int   addDashing(MuseData& tempdata, int column, int track, 
                                LayoutParameters& lp);
void  addUnDash(MuseData& tempdata, HumdrumFile& infile, 
                                int row, int col, int dcol, 
                                LayoutParameters& lpd);
void  setupTextAssignments(HumdrumFile& infile, int& textQ, 
                                Array<Array<int> >& TextAssignment,
                                const char* textspines);
void  track2column(Array<int>& trackcol, HumdrumFile& infile, 
                                int row);
void  addLyrics(MuseRecord& arecord, HumdrumFile& infile, 
                                int row, int col, 
                                Array<Array<int> >& TextAssignment);
void  convertHumdrumTextToMuseData(Array<char> & text);
void  convertHtmlTextToMuseData(Array<char> & text);
void  getWorkAndMovement(Array<char>& work, Array<char>& movment, 
                                HumdrumFile& infile);
void  printWithMd5sum(MuseData& datafile);
void  appendReferenceRecords(MuseData& musedata, HumdrumFile& infile);
RationalNumber getDuration(const char* input, const char* def);
RationalNumber getDurationNoDots(const char* input, const char* def);
void  processLyricsSpines(Array<Array<int> >& spineinfo, 
                                HumdrumFile& infile, const char* data);
void  verifyMuseDataFile(const char* filename);
void  verifyMuseDataFile(istream& input);
void  verifyMuseDataFiles(Options& options);
int   verifyPart(MuseData& part);
void  updateMuseDataFileTimeStamps(Options& options);
void  removeOldTimeStamps(MuseData& part);
int   getTimeStampLine(MuseRecord& arecord, MuseData& part);
void  getNewLine(Array<char>& newline, MuseData& part);
void  updateFileTimeStamps(const char* filename,  Options& options);
void  updateFileTimeStamps(istream& input, Options& options);
void  doUpdateWork(MuseDataSet& mds);
void  updatePart(MuseData& part);
void  getCheckSum(Array<char>& checksum, MuseData& part, 
                                Array<char>& newline);
void  appendToEndOfPart(MuseData& md, MuseRecord& arecord);
void  addHumdrumVeritas(MuseData& musedata, HumdrumFile& infile);
void  setGracenoteVisualRhythm(MuseRecord& arecord, const char* token);
void  convertKernLONtoMusePS(char* buffer, Array<Array<char> >& keys,
                                Array<Array<char> >& values, const char* token);
void  printFooter(MuseData& musedata, HumdrumFile& infile, 
                                const char* footertext);
void  cleanFooterField(Array<char>& footerline, HumdrumFile& infile);
void  substituteReferenceRecord(Array<char>& string, const char* refstring, 
                                const char* extension, HumdrumFile& infile);
void  getReferenceValue(Array<char>& value, Array<char>& refkey, 
                                HumdrumFile& infile);
void  hideNotesAfterLong(HumdrumFile& infile, int row, int col);
void  analyzeForwardTieConditions(Array<Array<Array<char> > >& tiestate, 
                                HumdrumFile& infile);
void  flipNameComma(Array<char>& value);
void  getRscaleState(HumdrumFile& infile, 
                                Array<Array<RationalNumber> >& rscale);
void  filterOptions(Array<char>& options, const char* filter);
void  addRepeatLines(MuseData& tempdata, HumdrumFile& infile,
                                LayoutParameters& lp, int index);
int   isTopStaffOnSystem(HumdrumFile& infile, int row, int col);
int   isTopStaffOnSystem(int track);
int   isBottomStaffOnSystem(HumdrumFile& infile, int row, int col);
void  processGlobalComment(HumdrumFile& infile, int line, 
                                MuseData& tempdata, int track);
void  printRehearsalMark(MuseData& tempdata, LayoutParameters& lp, 
                                int index);
int   printUnterminatedTies(MuseData& tempdata, HumdrumFile& infile, 
                                int line, int track);
void  printPrehangTie(MuseRecord& arecord, const char* buffer, 
                                HumdrumFile& infile, int row, int col, 
                                int voice);
void  addTextVertcialSpace(Array<char>& ostring, HumdrumFile& infile);
int   needsWordExtension(HumdrumFile& infile, int row, int notecol, 
                                int versecol, Array<char>& verse);


Array<Array<Array<char> > > TieConditionsForward;

// User interface variables:
Options options;
int    debugQ       = 0;          // used with --debug option
int    roundQ       = 0;          // used with --round option
int    slurQ        = 1;          // used with --no-slurs option
int    dynamicsQ    = 1;          // used with --no-dynamics option
int    sfzQ         = 1;          // used with --no-dynamics option
int    textQ        = 1;          // used with --no-text option
const char* TextSpines = "";      // used with --text option
int    metQ         = 1;          // used with --no-met option
int    verselimit   = 5;          // used with --vl option
int    mensuralQ    = 0;          // used with --mensural option
int    abbreviationQ = 0;         // used with --abbreviation option
int    mensural2Q   = 0;          // used with --mensural2 option
int    referenceQ   = 1;          // used with -R option
int    noinvisibleQ = 0;          // used with --no-invisible option
int    beamQ        = 1;          // used with --no-beams option
int    tieQ         = 1;          // used with --no-ties option
int    excludeQ     = 0;          // used with -x option
const char* excludeString = "";   // used with -x option
int    tupletQ      = 1;          // used with -no-tuplets option
int    vzQ          = 0;          // used with --vz option
int    hangtieQ     = 1;          // used with --no-hang-tie option
int    composerQ    = 1;          // used with -C option
int    titleQ       = 1;          // used with -T option
int    checksumQ    = 0;          // used with --no-checksum option
int    textvaddQ    = 0;          // used with --textvadd option
int    sepbracketQ  = 0;          // used with --sepbracket option
int    extensionQ   = 1;          // used with --no-extentions option
int    ignoreTickError = 0;       // used with --dd option
int    footerQ    = 0;            // used with --footer option
const char* workNumber = "";      // used with --wk option
const char* movementNumber = "";  // used with --mv option
const char* footertext = "";      // used with --footer option
const char* defaultDur  = "4";    // used with --dd option
const char* LyricSpines = "";     // used with --ls option
const char* Encoder = "";         // used with --encoder option
int encoderQ = 0;                 // used with --encoder option
int copyrightQ = 0;               // used with --copyright option
const char* Copyright = "";       // used with --copyright option
Array<char> Colorchar;            // charcter in **kern data which causes color
Array<char> Colorout;             // converted charcter in col 14 of MuseData
Array<Array<char> > PartNames;
int hasFictaQ = 0;                // used with !!!RDF**kern: i=musica ficta
char FictaChar = 'i';
int hasLongQ = 0;                 // used with !!!RDF**kern: l=long note
char LongChar = 'l';
Array<Array<Array<char> > > BeamState;
Array<int> hasTuplet;
Array<Array<char> > TupletState;
Array<Array<int> > TupletTopNum;
Array<Array<int> > TupletBotNum;
Array<RationalNumber> MeasureDur;  // used to decide centered whole rest
Array<Array<Array<Coord> > > LayoutInfo;
Array<Array<Coord> > GlobalLayoutInfo;
int LastTPQPrinted;                // used for tpqs that change
const char* muse2psoptionstring = "";  // used with --mo option
Array<char> NEWLINE;               // used to control the newline style

Array<Array<int> > TextAssignment;  // used to keep track of lyics by staff

Array<int> DynamicsAssignment;    
Array<Array<Coord> > ClefState;     // which clef is being used for every note
Array<int> DashState;               // used for turning off dashed lines
Array<Array<RationalNumber> > RscaleState;  // used for *rscale processing
Array<int> KernTracks;


// muse2ps =k options: Display alternative options
unsigned int kflag = 0;
#define k_active   (1)
#define k_sfz      (1 << 1)   // on = display as sfz
#define k_subedit  (1 << 2)   // on = editorial data looks like regular data
#define k_noedit   (1 << 3)   // on = hide editorla data from print
#define k_roman    (1 << 4)   // on = editorial marks in times-roman font
#define k_ligature (1 << 5)   // on = text ligatures
#define k_fbass    (1 << 6)   // on = figured bass above staff
#define k_lrbar    (1 << 7)   // on =:|!|:, off =:!!:
#define k_augdot   (1 << 8)   // on = don't share aug. dots between voices
#define k_newkey   (1 << 9)   // on = allow same key sig to repeat on staff
#define k_chord    (1 << 10)  // on = mixed rhythm chords
#define k_hidekey  (1 << 11)  // on = hide key signatures
#define k_clef     (1 << 12)  // on = clef changes are normal size not cue size
  

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

int main(int argc, char** argv) {
   NEWLINE.setSize(3);
   NEWLINE[0] = (char)0x0d;
   NEWLINE[1] = (char)0x0a;
   NEWLINE[2] = '\0';

   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");
   setColorCharacters(infile, Colorchar, Colorout);
   DashState.setSize(infile.getMaxTracks()+1);
   DashState.setAll(0);
   getKernTracks(KernTracks, infile);

   setupTextAssignments(infile, textQ, TextAssignment, TextSpines);
   setupMusicaFictaVariables(infile);
   getBeamState(BeamState, LayoutInfo, GlobalLayoutInfo, ClefState, infile);
   getTupletState(hasTuplet, TupletState, TupletTopNum, TupletBotNum, infile);
   getMeasureDuration(infile, MeasureDur); 
   Array<MuseData*> outfiles;
   outfiles.setSize(0);
   getRscaleState(infile, RscaleState);

   if (dynamicsQ) {
      getDynamicsAssignment(infile, DynamicsAssignment, dynamicsQ);
      // dynamicsQ may become false if no dynamics found
   }

   getPartNames(infile, PartNames);
   analyzeForwardTieConditions(TieConditionsForward, infile);
   convertData(outfiles, infile);
   printMuse2PsOptions(infile);     // must come after convertData()
   // eventual the footer should go here, but it is not currently
   // echoed with =M option.
   //if (footerQ) {
   //   printFooter(infile, footertext);
   //}

   int i;
   for (i=0; i<outfiles.getSize(); i++) {
      outfiles[i]->cleanLineEndings();
      printWithMd5sum(*(outfiles[i]));
      // cout << *(outfiles[i]);
      delete outfiles[i];
      outfiles[i] = NULL;
      cout << "/eof" << NEWLINE << flush; 
   }
   // end of all data file marker (two slashes on a line by themselves):
   cout << "//" << NEWLINE << flush; 
   outfiles.setSize(0);

   return 0;
}


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


//////////////////////////////
//
// getRscaleState --  Does not work on sub-tracks.  Only powers of two will
//     give predictable results at the moment.
//

void getRscaleState(HumdrumFile& infile, 
    Array<Array<RationalNumber> >& rscale) {

   int tracks = infile.getMaxTracks();
   rscale.setSize(infile.getNumLines());
   int i, j;
   Array<RationalNumber> current;
   current.setSize(tracks+1);
   current.setAll(1);
   
   PerlRegularExpression pre;
   RationalNumber rnum;
   int track;

   for (i=0; i<infile.getNumLines(); i++) {
      if (infile[i].isInterpretation()) {
         for (j=0; j<infile[i].getFieldCount(); j++) {
            if (pre.search(infile[i][j], "^\\*rscale\\s*:\\s*(\\d+)/(\\d+)", 
                  "")) {
               rnum = atoi(pre.getSubmatch(1));
               rnum /= atoi(pre.getSubmatch(2));
               track = infile[i].getPrimaryTrack(j);
               current[track] = rnum;
            } else if (pre.search(infile[i][j], 
                  "^\\*rscale\\s*:\\s*(\\d+)",  "")) {
               rnum = atoi(pre.getSubmatch(1));
               track = infile[i].getPrimaryTrack(j);
               current[track] = rnum;
            }
         }
      }
      if (!infile[i].isData()) {
         continue;
      }
      rscale[i].setSize(infile[i].getFieldCount());
      for (j=0; j<infile[i].getFieldCount(); j++) {
         track = infile[i].getPrimaryTrack(j);
         rscale[i][j] = current[track];
      }
   }
}



//////////////////////////////
//
// analyzeForwardTieConditions -- check to see if a tie has a matching 
//    start/end.  If the tie is not ended at the end of the music add codes 
//    to the output data to draw a hanging tie.
//
// Here is a complicated example with two layers on the staff, with one
// of the voices containing a chord:
//
// measure 3
// C3    24      1 w     d
// F3    24-     1 w     d        -
// G3    24-     1 w     d        -
// *               X       F3
// *               X       G3
// back  48
// G3    24      1 w     u
// A3    24-     1 w     u        -
// *               X       A3
// measure         &
//
// Place a * record after the note or all notes in the chord which have a
// hanging tie.  In column 17, place an 'X'. In column 25, place the pitch
// of the tie.  On the measure where the tie will end, place a "&" character
// in column 17.
//

void analyzeForwardTieConditions(Array<Array<Array<char> > >& tiestate, 
      HumdrumFile& infile) {

   tiestate.setSize(infile.getNumLines());

   Array<Array<char> > currentState;
   currentState.setSize(infile.getMaxTracks()+1);
   int i, j, k;

   for (i=0; i<currentState.getSize(); i++) {
      currentState[i].setSize(1000);
      currentState[i].setAll(0);
      currentState[i].allowGrowth(0);
   }

   int tcount;
   int track;
   int b40;
   char buffer[128] = {0};
   for (i=0; i<infile.getNumLines(); i++) {
      if (infile[i].isMeasure()) {
         // copy current state into tiestate structure
         tiestate[i] = currentState;
      }
      if (!infile[i].isData()) {
         continue;
      }
      if (debugQ) {
         cout << "Processing line " << i + 1 << endl;
      }

      for (j=0; j<infile[i].getFieldCount(); j++) {
         if (!infile[i].isExInterp(j, "**kern")) {
            continue;
         }
         if (strcmp(infile[i][j], ".") == 0) {
            continue;
         }
         if (strchr(infile[i][j], 'r') != NULL) {
            continue;
         }
         tcount = infile[i].getTokenCount(j);
         track = infile[i].getPrimaryTrack(j);
         for (k=0; k<tcount; k++) {
            infile[i].getToken(buffer, j, k);
            b40 = Convert::kernToBase40(buffer);
            if (strchr(buffer, '[') != NULL) {
               currentState[track][b40]++;
            } 
            if (strchr(buffer, ']') != NULL) {
               if (currentState[track][b40]) {
                  // this if statement is needed so that this method
                  // works when keeping track of opening ties.
                  currentState[track][b40]--;
               }
               // special cases needed for JRP use
               else if (currentState[track][b40+1]) {
                  currentState[track][b40+1]--;
               } else if (currentState[track][b40-1]) {
                  currentState[track][b40-1]--;
               } 
            }
         }
      }
   }

   if (debugQ) {
      for (i=0; i<infile.getNumLines(); i++) {
         if (!infile[i].isMeasure()) {
            continue;
         }
         cout << "FTIESTATE " << i + 1 << ": ";
         for (j=0; j<tiestate[i].getSize(); j++) {
            for (k=0; k<tiestate[i][j].getSize(); k++) {
               if (tiestate[i][j][k] != 0) {
                  cout << j << ":" 
                       << Convert::base40ToKern(buffer, k) 
                       << "=" << (int)tiestate[i][j][k]
                       << " ";
               }
            }
         }
         cout << endl;
      }
   }

}



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

void printFooter(MuseData& musedata, HumdrumFile& infile, const char* footertext) {
   if (strlen(footertext) < 1) {
      return;
   }
   PerlRegularExpression pre;
   MuseRecord arecord;

   Array<char> separator;
   separator.setSize(2);
   separator[0] = footertext[0];
   separator[1] = '\0';

   pre.sar(separator, "\\$", "\\$", "");   // escape $ character
   pre.sar(separator, "\\^", "\\^", "");   // escape ^ character
   pre.sar(separator, "\\*", "\\*", "");   // escape * character

   Array<Array<char> > tokens;
   pre.getTokensWithEmpties(tokens, separator.getBase(), footertext+1);

   int i;
   for (i=0; i<tokens.getSize(); i++) {
      cleanFooterField(tokens[i], infile);
   }

   arecord.clear();
   arecord.appendString("@START:\tFOOTER");
   musedata.append(arecord); arecord.clear();

   arecord.appendString("@FOOT1: \t");
   if (tokens.getSize() >= 3) { arecord.appendString(tokens[2].getBase()); }
   musedata.append(arecord); arecord.clear();

   arecord.appendString("@FOOT2: \t");
   if (tokens.getSize() >= 2) { arecord.appendString(tokens[1].getBase()); }
   musedata.append(arecord); arecord.clear();

   arecord.appendString("@FOOT3: \t");
   if (tokens.getSize() >= 1) { arecord.appendString(tokens[0].getBase()); }
   musedata.append(arecord); arecord.clear();

   arecord.appendString("@FOOT1R:\t");
   if (tokens.getSize() >= 6) { arecord.appendString(tokens[5].getBase()); }
   musedata.append(arecord); arecord.clear();

   arecord.appendString("@FOOT2R:\t");
   if (tokens.getSize() >= 5) { arecord.appendString(tokens[4].getBase()); }
   musedata.append(arecord); arecord.clear();

   arecord.appendString("@FOOT3R:\t");
   if (tokens.getSize() >= 4) { arecord.appendString(tokens[3].getBase()); }
   musedata.append(arecord); arecord.clear();

   // extra control parameters, should start with @:
   if (tokens.getSize() >= 7) { 
      arecord.appendString(tokens[6].getBase()); 
      musedata.append(arecord); arecord.clear();
   }

   arecord.appendString("@END:\tFOOTER");
   musedata.append(arecord);
}



//////////////////////////////
//
// cleanFooterField --
//

void cleanFooterField(Array& footerline, HumdrumFile& infile) {
   PerlRegularExpression pre;
   pre.sar(footerline, "^\\s+", "", "");  // remove leading spaces
   pre.sar(footerline, "\\s+$", "", "");  // remove trailing spaces
   Array<char> buf1;
 
   while(pre.search(footerline, "(@\\{[^}]+\\})(\\{[^}]*\\})?", "")) {
      buf1.setSize(strlen(pre.getSubmatch(1))+1);
      strcpy(buf1.getBase(), pre.getSubmatch(1));
      substituteReferenceRecord(footerline, buf1.getBase(), 
            pre.getSubmatch(2), infile);
   }
}



//////////////////////////////
//
// substituteReferenceRecord --
//

void substituteReferenceRecord(Array<char>& string, const char* refstring, 
      const char* extension, HumdrumFile& infile) {

   Array<char> refkey;
   refkey.setSize(strlen(refstring)+1);
   strcpy(refkey.getBase(), refstring);
   PerlRegularExpression pre;
   pre.sar(refkey, "^@\\{", "", "");
   pre.sar(refkey, "\\}$", "", "");
   Array<char> value;
   value.setSize(1);
   value[0] = '\0';
   getReferenceValue(value, refkey, infile);
   pre.sar(value, "^\\s+", "", "");
   pre.sar(value, "\\s+$", "", "");

   Array<char> newref;
   newref.setSize(strlen(refstring)+1);
   strcpy(newref.getBase(), refstring);

   Array<char> tref;
   tref.setSize(strlen(refstring)+1);
   strcpy(tref.getBase(), refstring);
   PerlRegularExpression pre2;

   if(pre.search(value, "^(20\\d\\d)/0?(\\d+?)\\/0?(\\d+?)\\/?$", "")) {
      char buffer[1024] = {0};
      strcpy(buffer, pre.getSubmatch(3));
      int month = atoi(pre.getSubmatch(2));
      switch(month) {
         case  1:  strcat(buffer, " Jan"); break;
         case  2:  strcat(buffer, " Feb"); break;
         case  3:  strcat(buffer, " Mar"); break;
         case  4:  strcat(buffer, " Apr"); break;
         case  5:  strcat(buffer, " May"); break;
         case  6:  strcat(buffer, " Jun"); break;
         case  7:  strcat(buffer, " Jul"); break;
         case  8:  strcat(buffer, " Aug"); break;
         case  9:  strcat(buffer, " Sep"); break;
         case 10:  strcat(buffer, " Oct"); break;
         case 11:  strcat(buffer, " Nov"); break;
         case 12:  strcat(buffer, " Dec"); break;
      }
      strcat(buffer, " ");
      strcat(buffer, pre.getSubmatch(1));
      pre.sar(value, "^.*$", buffer, "");
   }

   if(pre.search(value, "^\\s*$", "")) {
      // reference record is empty
      // remove any conditional text after @{} operator:
      pre.sar(newref, "$", "\\{[^}]*\\}", "");
      char empty = '\0';
      newref.append(empty);
      pre.sar(string, newref.getBase(), value.getBase(), "");
      // get rid of marker since it does not exist in bib records.
      pre.sar(string, refstring, "", "");
      // get rid of extra text marker as well
      pre.sar(string, extension, "", "");
   } else {
      // found reference record 
      // also insert conditional text
      pre.sar(tref, "$", "\\{([^}]+)\\}", "");
      if (pre.search(string, tref.getBase(), "")) {
         pre2.sar(value, "$", pre.getSubmatch(1), "");
         pre2.sar(newref, "$", "\\{[^}]*\\}", "");
      }
      pre.sar(string, newref.getBase(), value.getBase(), "");
   }
}



//////////////////////////////
//
// getReferenceValue -- Flip COM & COA around if there is a comma
//      in the name.
//

void getReferenceValue(Array<char>& value, Array<char>& refkey, 
      HumdrumFile& infile) {
   int i;
   char buffer[1024] = {0};
   value.setSize(1);
   value = '\0';

   for (i=0; i<infile.getNumLines(); i++) {
      if (!infile[i].isBibliographic()) {
         continue;
      }
      if (strcmp(infile[i].getBibKey(buffer), refkey.getBase()) != 0) {
         continue; 
      }
      infile[i].getBibValue(buffer);
      value.setSize(strlen(buffer)+1);
      strcpy(value.getBase(), buffer);
      if (strncmp(refkey.getBase(), "COM", 3) == 0) {
         flipNameComma(value);
      } else if (strncmp(refkey.getBase(), "COA", 3) == 0) {
         flipNameComma(value);
      }
      return;
   }

   //  record was not found, search without any @ qualifiers:
  
   Array<char> refkey2 = refkey;  
   PerlRegularExpression pre;
   pre.sar(refkey2, "@.*", "");

   for (i=0; i<infile.getNumLines(); i++) {
      if (!infile[i].isBibliographic()) {
         continue;
      }
      if (!pre.search(infile[i].getBibKey(buffer), refkey2.getBase(), "")) {
         continue; 
      }
      infile[i].getBibValue(buffer);
      value.setSize(strlen(buffer)+1);
      strcpy(value.getBase(), buffer);
      if (strncmp(refkey.getBase(), "COM", 3) == 0) {
         flipNameComma(value);
      } else if (strncmp(refkey.getBase(), "COA", 3) == 0) {
         flipNameComma(value);
      }
      return;
   }

}



//////////////////////////////
//
// flipNameComma -- reverse last and first name
//

void flipNameComma(Array<char>& value) {
   PerlRegularExpression pre;
   char buffer[1024] = {0};
   if (pre.search(value, "^([^,]+)\\s*,\\s*([^,]+)$", "")) {
      strcpy(buffer, pre.getSubmatch(2));
      strcat(buffer, " ");
      strcat(buffer, pre.getSubmatch(1));
      value.setSize(strlen(buffer)+1);
      strcpy(value.getBase(), buffer);
   }
}



//////////////////////////////
//
// appendReferenceRecords --
// 

void appendReferenceRecords(MuseData& musedata, HumdrumFile& infile) {
   int i;
   /*  Always print reference records if being printed: if no VTS, then    *  generate one.
    *
    
   int hasbib = 0;
   for (i=0; i<infile.getNumLines(); i++) {
      if (infile[i].isBibliographic()) {
         hasbib = 1;
         break;
      }
   }

   if (hasbib == 0) {
      return;
   }
   */

   MuseRecord arecord;

   arecord.clear();
   arecord.insertString(1, "@@START: HUMDRUMREFERENCE");
   musedata.append(arecord);

   RationalNumber lastabs(-1, 1);
   RationalNumber testabs(0, 1);

   arecord.clear();
   arecord.insertString(1, "@@TOP");
   int startfound = 0;
   int endfound = 0;
   musedata.append(arecord);
   for (i=0; i<infile.getNumLines(); i++) {
      if (!infile[i].isBibliographic()) {
         if (infile[i].isInterpretation()) {
            if(strncmp(infile[i][0], "**", 2) == 0) {
               startfound++;
            }
            if(strcmp(infile[i][0], "*-") == 0) {
               endfound = 1;
               arecord.clear();
               arecord.insertString(1, "@@BOTTOM");
               musedata.append(arecord);
            }
         }
         continue;
      }

      if (!endfound && startfound) {
         testabs = infile[i].getAbsBeatR(); 
         if (testabs != lastabs) {
            arecord.clear();
            arecord.append("sr", "@@POSITION:", &testabs);
            musedata.append(arecord);

            lastabs = testabs;
         }
      }
      
      arecord.clear();
      arecord.append("ss", "@", infile[i][0]);
      musedata.append(arecord);
    
   }

   addHumdrumVeritas(musedata, infile);

   arecord.clear();
   arecord.insertString(1, "@@END: HUMDRUMREFERENCE");
   musedata.append(arecord);
}



//////////////////////////////
//
// addHudrumVeritas --
//

void addHumdrumVeritas(MuseData& musedata, HumdrumFile& infile) {
   int hasVts = 0;
   int hasVtsData = 0;
   int i;
   for (i=0; i<infile.getNumLines(); i++) {
      if (!infile[i].isBibliographic()) { 
         continue;
      }
      if (strncmp(infile[i][0], "!!!VTS:", strlen("!!!VTS:")) == 0) {
         hasVts = 1;
      } else if (strncmp(infile[i][0], "!!!VTS-data:", 
            strlen("!!!VTS-data:")) == 0) {
         hasVtsData = 1;
      }
   }

   if (hasVts && hasVtsData) {
      return;
   }

   MuseRecord arecord;
   arecord.insertString(1, "@@EXTRA");
   musedata.append(arecord);
   if (!hasVts) {
      Array<char> vts;
      infile.makeVts(vts);
      arecord.clear();
      arecord.append("ss", "@", vts.getBase());
      musedata.append(arecord);
   }
   if (!hasVtsData) {
      Array<char> vts;
      infile.makeVtsData(vts);
      arecord.clear();
      arecord.append("ss", "@", vts.getBase());
      musedata.append(arecord);
   }
}



//////////////////////////////
//
// printWithMd5sum -- add an MD5sum for each file on the third header
//     record line.
//

void printWithMd5sum(MuseData& datafile) {
   SSTREAM tempstream;
   int i;
   for (i=0; i<datafile.getLineCount(); i++) {
      tempstream << datafile[i] << NEWLINE;
   }
   // tempstream << datafile;
   tempstream << ends;
   Array<char> data;
   data.setSize(strlen(tempstream.CSTRING)+1);
   strcpy(data.getBase(), tempstream.CSTRING);
   PerlRegularExpression pre;

   if (checksumQ) {
      Array<char> md5sum;
      CheckSum::getMD5Sum(md5sum, data);
      // 0d0a added to indicate the the md5sum was calculated with DOS newlines.
      char newlinestring[1024] = {0};
      char buffer[32] = {0};
      for (i=0; i<NEWLINE.getSize(); i++) {
         if ((i==NEWLINE.getSize()-1) && (NEWLINE[i] == '\0')) { 
            break;
         }
         sprintf(buffer, "%02x", (int)NEWLINE[i]);
         strcat(newlinestring, buffer);
      }

      pre.sar(md5sum, "^", "[md5sum:XXXXXXXXXX:", "");
      pre.sar(md5sum, "XXXXXXXXXX", newlinestring, "");
      pre.sar(md5sum, "$", "]", "");
      pre.sar(data, "\\[\\]", md5sum.getBase(), "");
   } else {
     // do nothing, just leave empty bracket
   }

   cout << data;

}



//////////////////////////////
//
// setupTextAssignments --  Currently attach **text spines to the 
//   first **kern spine found on their left.  The order of the spines
//   indiate which verse the **text belongs to, left most spine is the
//   first verse for a **kern spine.
//

void setupTextAssignments(HumdrumFile& infile, int& textQ, 
      Array<Array<int> >& TextAssignment, const char* textspines) {
   int i, j, track;
   TextAssignment.setSize(infile.getMaxTracks()+1);
   for (i=0; i<TextAssignment.getSize(); i++) {
      TextAssignment[i].setSize(0);
   }
   if (textQ == 0) {
      // user request to not print text
      return;
   }
   int foundtext = 0;

   SigString exinterp;
   PerlRegularExpression pre;


   // print **text spines if --ls is not used and textspines is empty.
   if (strlen(textspines) == 0) {
      textspines = "**text";
   }

   if (strlen(LyricSpines) > 0) {
      textQ = 1;
      Array<Array<int> > lyricspines;
      processLyricsSpines(lyricspines, infile, LyricSpines);
      Array<int> kerntracks;
      getKernTracks(kerntracks, infile);

      for (i=0; i<kerntracks.getSize(); i++) {
         for (j=0; j<lyricspines[kerntracks[i]].getSize(); j++) {
            TextAssignment[kerntracks[i]].append(lyricspines[kerntracks[i]][j]);
            foundtext++;
         }
      }

   } else {

      int lastkern = 0;
      for (i=0; i<infile.getNumLines(); i++) {
         if (!infile[i].isInterpretation()) {
            continue;
         }
         for (j=0; j<infile[i].getFieldCount(); j++) {
            if(infile[i].isExInterp(j, "**kern")) {
               lastkern = infile[i].getPrimaryTrack(j);
               // continue;
            }
            exinterp = "\\b";
            exinterp += (infile[i].getExInterp(j)+2);
            exinterp += "\\b";
            if(pre.search(textspines, exinterp.getBase(), "")) {
               track = infile[i].getPrimaryTrack(j);
               foundtext = 1;
               TextAssignment[lastkern].append(track);
            }
         }
         break;
      }
   }
   
   if (foundtext && textQ) {
      textQ = 1;
   } else {
      textQ = 0;
   }
}



//////////////////////////////
//
// getDynamicsAssignment -- map **dynam spines to the **kern spines
//     with which they should be printed.  Currently a simple mapping
//     model.  If a **dynam spine occurs immediately after a **kern spine
//     then it will be assigned to that **kern spine.  Only one **dynam
//     spine to a **kern spine.  In the future *staff tandem interpretations
//     should be processed for more complicated **dynam assignments.
//     Also think later about sub-spine assingment of **dynam/**kern data.//

void getDynamicsAssignment(HumdrumFile& infile, Array<int>& DynamicsAssignment,       int& dynamicsQ) {
   DynamicsAssignment.setSize(infile.getMaxTracks()+1);
   DynamicsAssignment.setAll(0);
   Array<int>& da = DynamicsAssignment;
   dynamicsQ = 0;
   int i, j;
   int lastkerntrack = -1;
   int track;
   for (i=0; i<infile.getNumLines(); i++) {
      if (!infile[i].isInterpretation()) {
         continue;
      }
      for (j=0; j<infile[i].getFieldCount(); j++) {
         track = infile[i].getPrimaryTrack(j);
         if (infile[i].isExInterp(j, "**kern")) {
            lastkerntrack = track;
         } else if (infile[i].isExInterp(j, "**dynam")) {
            if((lastkerntrack > 0) && (da[lastkerntrack] <= 0)) {
               da[lastkerntrack] = track;
               dynamicsQ = 1;
               if(debugQ) {
                  cout << "ASSIGNING DYNAM TRACK " << track
                       << " TO KERN TRACK " << lastkerntrack << NEWLINE;
               }
            }
         }
      }
      break;
   }
}



//////////////////////////////
//
// getMeasureDuration -- for each line, report the duration of the
// current measure.  The algorithm only considers one meter for all
// parts, and would have to be done by part if the meter is different in
// different parts...
//

void getMeasureDuration(HumdrumFile& infile, Array<RationalNumber>& rns) {
   PerlRegularExpression pre;
   int i, j;
   int top;
   int bot;
   int zbot;
   RationalNumber current(0,1);
   rns.setSize(infile.getNumLines());
   for (i=0; i<infile.getNumLines(); i++) {
      if (!infile[i].isInterpretation()) {
         rns[i] = current;
         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+)%(\\d+)", "")) {
            top = atoi(pre.getSubmatch(1));
            bot = atoi(pre.getSubmatch(2));
            zbot = atoi(pre.getSubmatch(3));
            current = top * 4;
            current /= bot;
            current *= zbot;
            break;
         } else if (pre.search(infile[i][j], "^\\*M(\\d+)/(\\d+)", "")) {
            top = atoi(pre.getSubmatch(1));
            bot = atoi(pre.getSubmatch(2));
            current = top * 4;
            current /= bot;
            break;
         }
      }
      rns[i] = current;
   }
}



//////////////////////////////
//
// setupMusicaFictaVariables --
//
// !!!RDF**kern: i=musica ficta
//

void setupMusicaFictaVariables(HumdrumFile& infile) {
   int i;
   PerlRegularExpression pre;
   for (i=infile.getNumLines()-1; i>=0; i--) {
      if (!infile[i].isBibliographic()) {
         continue;
      }       
      if (pre.search(infile[i][0], 
            "^!!!RDF\\*\\*kern\\s*:\\s*([^\\s=])\\s*=.*musica ficta", "i")) {
         // "musica ficta"
         hasFictaQ = 1;
         FictaChar = pre.getSubmatch(1)[0];
      } else if (pre.search(infile[i][0], 
            "^!!!RDF\\*\\*kern\\s*:\\s*([^\\s=])\\s*=.*editorial", "i")) {
         // "editorial accidental"
         hasFictaQ = 1;
         FictaChar = pre.getSubmatch(1)[0];
      }
      if (pre.search(infile[i][0], 
            "^!!!RDF\\*\\*kern\\s*:\\s*([^\\s=])\\s*=.*long", "i")) {
         hasLongQ = 1;
         LongChar = pre.getSubmatch(1)[0];
      }
   }
}



//////////////////////////////
//
// getPartNames --
//

void getPartNames(HumdrumFile& infile, Array<Array<char> >& PartNames) {
   int i, j;
   PartNames.setSize(infile.getMaxTracks()+1);  //  0 = unused
   for (i=0; i<PartNames.getSize(); i++) {
      PartNames[i].setSize(1);
      PartNames[i][0]= '\0';
   }

   Array<int> ignore;
   ignore.setSize(infile.getMaxTracks()+1);
   ignore.setAll(0);

   PerlRegularExpression pre;
   int track;
   for (i=0; i<infile.getNumLines(); i++) {
      if (infile[i].isData()) {
         // stop looking when the first data line is found
         break;
      }
      if (!infile[i].isInterpretation()) {
         continue;
      }
      for (j=0; j<infile[i].getFieldCount(); j++) {
         if (strcmp(infile[i][j], "*^") == 0) {
            // don't search for names after spine splits (there might
            // be two names, and one of them will be ignored).
            ignore[infile[i].getPrimaryTrack(j)] = 1;
         }
         if (ignore[infile[i].getPrimaryTrack(j)]) {
            continue;
         }

         if (!abbreviationQ) {
            if(pre.search(infile[i][j], "^\\*I\"\\s*(.*)\\s*$", "")) {
               track = infile[i].getPrimaryTrack(j);
               PartNames[track].setSize(strlen(pre.getSubmatch(1))+1);
               strcpy(PartNames[track].getBase(), pre.getSubmatch());
            }
         } else {
            if(pre.search(infile[i][j], "^\\*I\'\\s*(.*)\\s*$", "")) {
               track = infile[i].getPrimaryTrack(j);
               PartNames[track].setSize(strlen(pre.getSubmatch(1))+1);
               strcpy(PartNames[track].getBase(), pre.getSubmatch());
            }
         }

      }
   }
   
   // if no part name, set to "part name" (for debugging purposes):
   //for (i=1; i<=infile.getMaxTracks(); i++) {
   //   if (strcmp(PartNames[i].getBase(), "") == 0) {
   //      PartNames[i].setSize(strlen("part name")+1);
   //      strcpy(PartNames[i].getBase(), "part name");
   //   }
   // }

}



//////////////////////////////
//
// filterOptions -- removed specified options from an embedded option string.
//     can only handle single-character options.  Will need to be adjusted 
//     when/if double-letter parameters are allowed.
//

void filterOptions(Array<char>& options, const char* filter) {
   int olength = strlen(options.getBase());
   Array<Array<char> > singles;
   singles.setSize(100);
   singles.setSize(0);
   char empty = '\0';
   int i, j, k;
   int startindex = 0;
   for (i=0; i<options.getSize(); i++) {
      if (options[i] == ':') {
         i++;
         while ((i<options.getSize()) && isspace(options[i])) {
            i++;
         }
         startindex = i;
         break;
      }
   }
 
   // splits the options string into separate components.
   for (i=startindex; i<olength; i++) {
      singles.setSize(singles.getSize()+1);
      singles.last().setSize(256);
      singles.last().setSize(0);
      singles.last().append(options[i++]);
      if (i >= olength - 1) {
         break;
      }
      if ((i < olength - 1) && (options[i] == '^')) {
         // the next character is a '^' so start reading until
         // the next '^' enclosing a string value is found.
         // First go to '^' character and store it
         singles.last().append(options[i++]);
         while (i < olength) {
            singles.last().append(options[i]);
            i++;
            if(options[i] == '^') {
               singles.last().append(options[i]);
               break;
            }
         }
      } else if (isdigit(options[i+1])) {
         // read one or more integers, separated by commas
         while ((i < olength) && (isdigit(options[i]) || (options[i] == ','))) {
            singles.last().append(options[i++]);
         }
         if (i <= olength) {
            i--;
         }
      }
   }

   //for (i=0; i<singles.getSize(); i++) {
   //   singles[i].append(empty);
   //}

   Array<char> temp;
   temp.setSize(options.getSize() + 32);
   temp.setSize(0);


   for (i=0; i<startindex; i++) {
      temp.append(options[i]);
   }
  
   int flength = strlen(filter);
   int ignore = 0;
   
   for (i=0; i<singles.getSize(); i++) {
      ignore = 0;
      for (j=0; j<flength; j++) {
         if (filter[j] == singles[i][0]) {
            ignore = 1;
            break;
         }
      }
      if (ignore) {
         continue;
      }

      // the option should not be filtered, so store in temporary output
      for (k=0; k<singles[i].getSize(); k++) {
         if (singles[i][k] == '\0') {
            continue;
         }
         temp.append(singles[i][k]);
      }
   }
   temp.append(empty);
   options.setSize(temp.getSize()+1);
   strcpy(options.getBase(), temp.getBase());

}



//////////////////////////////
//
// printMuse2PsOptions -- print any lines in the Humdrum file
// which have the form !!!muse2ps:, !!muse2ps:, !!!muse2psv1:, !!muse2psv1:
// 
// Always converts v\d+ to v1. May need to change in future.
//

void printMuse2PsOptions(HumdrumFile& infile) {
   int i;
   PerlRegularExpression pre;
   PerlRegularExpression pre2;
   int titleline = -1;
   int composerline = -1;
   int dateline = -1;
   int hastitle = 0;
   int hascomposer = 0;

   SSTREAM globaldefaults;
   SSTREAM localdefaults;
   Array<char> tempdata;
   Array<char> temp2;

   for (i=0; i<infile.getNumLines(); i++) {
      if (!(infile[i].isGlobalComment() || infile[i].isBibliographic())) {
         continue;
      }
      
      tempdata.setSize(strlen(infile[i][0])+1);
      strcpy(tempdata.getBase(), infile[i][0]);
      if (excludeQ && (strstr(infile[i][0], "muse2ps") != NULL)) {
         filterOptions(tempdata, excludeString);
      }
      cleanFooterField(tempdata, infile);
      if (pre.search(tempdata, 
            "^!!!?\\s*muse2ps(?:v\\d+)?\\s*:\\s*['\"]*=?(.*)\\s*['\"]*\\s*$", 
            "i")) {
         if (pre2.search(pre.getSubmatch(1), "^=", "")) {
           // a global option
           temp2.setSize(strlen(pre.getSubmatch())+1);
           strcpy(temp2.getBase(), pre.getSubmatch());
           convertHtmlTextToMuseData(temp2);
           globaldefaults << "@muse2psv1=" << temp2 << NEWLINE;
         } else if (pre2.search(pre.getSubmatch(1), "=", "")) {
           // a local option
           temp2.setSize(strlen(pre.getSubmatch())+1);
           strcpy(temp2.getBase(), pre.getSubmatch());
           convertHtmlTextToMuseData(temp2);
           localdefaults << "@muse2psv1=" << temp2 << NEWLINE;
         } else {
           // a global option
           temp2.setSize(strlen(pre.getSubmatch())+1);
           strcpy(temp2.getBase(), pre.getSubmatch());
           convertHtmlTextToMuseData(temp2);
           globaldefaults << "@muse2psv1==" << temp2 << NEWLINE;
         }
         if (pre2.search(tempdata, "T^")) {
            // already a title so don't try to add one
            hastitle = 1;
         }
         if (pre2.search(tempdata, "C^")) {
            // already a title so don't try to add one
            hascomposer = 1;
         }
      } else if ((titleline < 0) && pre.search(tempdata, 
            "^!!!OTL[^:]*:\\s*.+", "")) {
         titleline = i;
      } else if ((composerline < 0) && pre.search(tempdata, 
            "^!!!COM[^:]*:\\s*.+", "")) {
         composerline = i;
      } else if ((dateline < 0) && pre.search(tempdata, 
            "^!!!CDT[^:]*:\\s*.+", "")) {
         dateline = i;
      }
   }


   // Print title if not found in muse2ps options, but found in file.
   // Think about !!!title: and !!title: markers (from Themefinder system).
   if (titleQ && (!hastitle) && (titleline >= 0)) {
      if (pre.search(infile[titleline][0], "^!!!OTL[^:]*:\\s*(.+)\\s*$", "")) {
         Array<char> title;
         title.setSize(strlen(pre.getSubmatch(1))+1);
         strcpy(title.getBase(), pre.getSubmatch());
         // deal with HTML accented characters converted into MuseData 
         // equivalents here...
         
         // title cannot contain ^ character
         pre2.sar(title, "\\^", "", "g");
         convertHtmlTextToMuseData(title);

         cout << "@muse2psv1==T^" << title << "^" << NEWLINE;
      }
   }


   // Print composer if not found in muse2ps options, but found in file.
   if (composerQ && (!hascomposer) && (composerline >= 0)) {
      if (pre.search(infile[composerline][0], 
            "^!!!COM[^:]*:\\s*(.*)\\s*$", "")) {
         Array<char> composer;
         composer.setSize(strlen(pre.getSubmatch(1))+1);
         strcpy(composer.getBase(), pre.getSubmatch());

         // reverse order of names if there is a comma in the name
         if (pre.search(composer, "^\\s*(.*?)\\s*,\\s*(.*)", "")) {
            char buffer[1024] = {0};
            strcpy(buffer, pre.getSubmatch(2));
            strcat(buffer, " ");
            strcat(buffer, pre.getSubmatch(1));
            composer.setSize(strlen(buffer)+1);
            strcpy(composer.getBase(), buffer);
         }

         // abbreviate the names of famous composers
         pre2.sar(composer, "Johann Sebastian Bach", "J.S. Bach", "");
         pre2.sar(composer, "Wolfgang Amadeus Mozart", "W.A. Mozart", "");
         pre2.sar(composer, "Ludwig van Beethoven", "L. van Beethoven", "");
         convertHtmlTextToMuseData(composer);

         // deal with HTML accented characters converted into MuseData 
         // equivalents here...
         
         // composer cannot contain ^ character
         pre2.sar(composer, "\\^", "", "g");

         cout << "@muse2psv1==C^" << composer << "^" << NEWLINE;
      }
   }

   Array<int> kerntracks;
   getKernTracks(kerntracks, infile);

   Array<char> systemspine;
   systemspine.setSize(1024);
   systemspine.setSize(1);
   systemspine[0] = '\0';
   if (kerntracks.getSize() == 1) {
      //do nothing;
   } else if (sepbracketQ) {
      pre.sar(systemspine, "$", "s^[", "");
      for (i=0; i<kerntracks.getSize(); i++) {
         pre.sar(systemspine, "$", "(.)", "");
      }
      pre.sar(systemspine, "$", "]^", "");
      cout << "@muse2psv1==" << systemspine << NEWLINE;
   } else {
      // hack to have curly braces on piano solo part:
      // This is now a default behavior of muse2ps.
      //if (kerntracks.getSize() == 2) {
      //   cout << "@muse2psv1==s^{(..)}^" << NEWLINE;
      //}
      if (kerntracks.getSize() == 4) {
         // brackets on SATB parts:
         cout << "@muse2psv1==s^[(....)]^" << NEWLINE;
      } else if (kerntracks.getSize() == 3) {
         cout << "@muse2psv1==s^[(...)]^" << NEWLINE;
      } else if (kerntracks.getSize() > 4) {
         cout << "@muse2psv1==s^[(";
         for (int ii=0; ii<kerntracks.getSize(); ii++) {
            cout << ".";
         }
         cout << ")]^" << NEWLINE;
      }
   }

   Array<char> ostring;
   ostring.setSize(1);
   ostring[0] = '\0';
   if (strlen(muse2psoptionstring) > 0) {
      ostring.setSize(strlen(muse2psoptionstring) + 1);
      strcpy(ostring.getBase(), muse2psoptionstring);
   }
   if (kflag > 0) {
      char buffer[32] = {0};
      if (kflag < 0xff) {
         sprintf(buffer, "k^0x%02x^", kflag);
      } else if (kflag < 0xffff) {
         sprintf(buffer, "k^0x%04x^", kflag);
      } else if (kflag < 0xffffff) {
         sprintf(buffer, "k^0x%06x^", kflag);
      } else {
         sprintf(buffer, "k^0x%08x^", kflag);
      }
      pre.sar(ostring, "$", buffer, "");
   }
   if (mensuralQ) {
      pre.sar(ostring, "$", "W1", "");  // thin barlines
   }


   if (strlen(ostring.getBase()) > 0) {
      if (!pre.search(ostring, "^=", "")) {
         // add muse2ps option marker at start of option string.
         pre.sar(ostring, "^", "=", "");
      }
      if (!pre.search(ostring, "^=[^=]*=", "")) {
         // mark as a default type of option.
         pre.sar(ostring, "^", "=", "");
      }
      cout << "@muse2psv1" << ostring << NEWLINE;
   }
   
   // print global default options
   globaldefaults << ends;
   Array<char> globals(strlen(globaldefaults.CSTRING)+1);
   strcpy(globals.getBase(), globaldefaults.CSTRING);
   if ((globals.getSize() > 1) && textvaddQ) {
      addTextVertcialSpace(globals, infile);
   }
   // cout << globaldefaults.CSTRING;
   cout << globals;

   // print local default options
   localdefaults << ends;
   cout << localdefaults.CSTRING;

}



//////////////////////////////
//
// setColorCharacters -- If the Humdrum file has a record in the form:
//    !!!RDF**kern:\s*([^\s])\s*=\s*match
//    Then $1 is the marker in **kern data to indicate a match result
//    from a search red is the default color.  If there is a color code
//    in the RDF marker, the the most dominant color will be used
//    to mark the note in MuseData.
//
//    This note will be colored red in muse2ps, indicated
//    by placing "R" in column 14 of the note record, "G" for green,
//    and "B" for blue.
//

void setColorCharacters(HumdrumFile& infile, Array<char>& colorchar,
      Array<char>& colorout) {
   PerlRegularExpression pre;
   colorchar.setSize(0);
   colorout.setSize(0);
   char value;
   int i;
   for (i=0; i<infile.getNumLines(); i++) {
      if (!infile[i].isBibliographic()) {
         continue;
      }
      // !!!RDF**kern: N= mark color="#ff0000", root
      if (pre.search(infile[i].getLine(), 
            "^!!!RDF\\*\\*kern:\\s*([^\\s])\\s*=\\s*match", "i") ||
          pre.search(infile[i].getLine(), 
            "^!!!RDF\\*\\*kern:\\s*([^\\s])\\s*=\\s*mark", "i")
         ) {
         value = pre.getSubmatch(1)[0];
         colorchar.append(value);
         if (pre.search(infile[i].getLine(), 
               "color\\s*=\\s*\"?#([a-f0-9]{6})\"?", "i")) {
            value = getColorCategory(pre.getSubmatch(1));
            colorout.append(value);
         } else {
            value = 'R';
            colorout.append(value);
         }
      }
   }

   if (debugQ) {
      if (colorchar.getSize() > 0) {
         cout << "COLOR MARKERS:\n";
         for (i=0; i<colorchar.getSize(); i++) {
            cout << "\t\"" << colorchar[i] << "\" ==> ";
            cout << "\t\"" << colorout[i] << "\"";
            cout << NEWLINE;
         }
      }
   }
}



//////////////////////////////
//
// getColorCategory --
//

char getColorCategory(const char* color) {
   int len = strlen(color);
   if (len != 6) {
      return ' ';
   }

   char buffer[4] = {0};
   int num[3];

   buffer[0] = color[0];
   buffer[1] = color[1];
   num[0] = strtol(buffer, NULL, 16);

   buffer[0] = color[2];
   buffer[1] = color[3];
   num[1] = strtol(buffer, NULL, 16);

   buffer[0] = color[4];
   buffer[1] = color[5];
   num[2] = strtol(buffer, NULL, 16);

   if ((num[0] == num[1]) && (num[1] == num[2])) {
      // grayscale, so print black
      return ' ';
   }

   int max = 0;
   if (num[1] > num[0]) {
      max = 1;
   }
   if (num[2] > num[max]) {
      max = 2;
   }

   switch (max) {
      case 0:  return 'R';   // red channel is most dominant
      case 1:  return 'G';   // green channel is most dominant
      case 2:  return 'B';   // blue channel is most dominant
   }

   return ' ';
}



//////////////////////////////
//
// isBarlineBeforeData -- returns true if there is a barline found
//    before a dataline.
//

int isBarlineBeforeData(HumdrumFile& infile, int startindex) {
   int i;
   for (i=startindex; i<infile.getNumLines(); i++) {
      if (infile[i].isData()) {
         return 0;
      }
      if (infile[i].isMeasure()) {
         return 1;
      }
   }
   return 0;
}



//////////////////////////////
//
// convertData -- Extract all spines of **kern data into separate 
//     musedata files which are serially appended to the program output.
//

void convertData(Array<MuseData*>& outfiles, HumdrumFile& infile) {
   Array<int> kerntracks;
   getKernTracks(kerntracks, infile);
   int i;
   int tickpos; // tick location within score.
   outfiles.setSize(kerntracks.getSize());
   int printfirstmeasureQ = isBarlineBeforeData(infile, 0);
   int reversei;

   Array<int> ticksperquarter;
   prepareLocalTickChanges(infile, ticksperquarter);

   MuseRecord endrecord;
   for (i=0; i<kerntracks.getSize(); i++) {
      reversei = kerntracks.getSize() - i - 1;
      if (debugQ) {
         cout << "Extracting data for track: " << kerntracks[i]+1 << NEWLINE;
      }
      outfiles[reversei] = new MuseData;
      tickpos = 0;
      convertTrackToMuseData(*(outfiles[reversei]), 
            kerntracks[i], infile, ticksperquarter, tickpos, i+1, 
            kerntracks.getSize(), printfirstmeasureQ);

      // add an "/END" record to indicate the end of the file (there can 
      // be free-form comments after this
      if (referenceQ) {
         appendReferenceRecords(*outfiles[reversei], infile);
      }
      if (footerQ) {
         printFooter(*outfiles[reversei], infile, footertext);
      }
      // place abouve appendReferenceRecord when the =M preserves post-END comments
      endrecord.clear();
      endrecord.insertString(1, "/END");
      outfiles[reversei]->append(endrecord);
   }
}



//////////////////////////////
//
// prepareLocalTickChanges -- check to make sure that ticks never
//    exceed 999 for rhythmic durations.  If they do, then try to change
//    the Q: record (ticks per quarter) based on measures, If the global
//    tick setting will not work.  If measure-based doesn't work, then
//    generalized this function to allow Q: to change within measures
//    (but this is difficult to generalize).
//
//    If this algorithm fails, next thing to try is to encode tpq
//    for parts separately.  If that generalization fails, then
//    adjust tpq within measures.  If that generalization fails, then
//    too bad.
//

void prepareLocalTickChanges(HumdrumFile& infile, Array<int>& ticks) {
   int ticksperquarter = getGlobalTicksPerQuarter(infile);

   // maybe make measurelines an input parameter since it is also
   // calculated elsewhere.  But it is calculated in an inconvenient
   // location, so doing it here.
   Array<int> measurelines;
   getMeasureInfo(measurelines, infile);

   // handle case where there are no measure lines in the data:
   if (measurelines.getSize() == 0) {
      measurelines.setSize(2);
      measurelines[0] = 0;
      measurelines[1] = infile.getNumLines()-1;
   }

   Array<RationalNumber> rhys;
   getRhythms(rhys, infile, 0, -1);
   if (rhys.getSize() == 0) {
      // something funny: no durations in file, so don't try to do anything
      ticks.setSize(measurelines.getSize());
      ticks.setAll(ticksperquarter);
      return;
   }
   RationalNumber maxtick;
   // the first rhythm is the largest.
   maxtick = rhys[0].getInversion() * ticksperquarter;

   if (maxtick < 1000) {
      // there will never be a tick overflow in the converted data.
      ticks.setSize(measurelines.getSize());
      ticks.setAll(ticksperquarter);
      return;
   }

   // there will be an overflow in the converted data.
   // try to adjust the ticks by each measure to localize
   // tuplets or extreme rhythms which are causing the problem.

   Array<int> mtpq;
   mtpq.setSize(measurelines.getSize());
   mtpq.setAll(0);
   Array<RationalNumber> mticks;
   mticks.setSize(measurelines.getSize());
   mticks.setAll(0);
   int i;
   for (i=0; i<measurelines.getSize()-1; i++) {
      getRhythms(rhys, infile, measurelines[i], measurelines[i+1]);
      if (rhys.getSize() > 0) {
         mtpq[i] = getTPQByRhythmSet(rhys);
         mticks[i] = rhys[0].getInversion() * mtpq[i];
      } else {
         mticks[i] = 0;
         if (i > 0) {
            mtpq[i] = mtpq[i-1];
         }
      }

   }

   if (mtpq.getSize() == 0) {
      // no barlines, too difficult to deal with.
      ticks.setSize(measurelines.getSize());
      ticks.setAll(ticksperquarter);
      return;
   }
   if (mtpq[mtpq.getSize()-2] == 0) {
      // no non-zero mtpq values, strange, so just give up.
      ticks.setSize(measurelines.getSize());
      ticks.setAll(ticksperquarter);
      return;
   }

   // group measures by their ability to not exceed durations of 999 ticks
   // for the maximum rhythmic duration in the range.
   Array<int> newtpq;
   newtpq.setSize(measurelines.getSize());
   newtpq.setAll(0);

   int curtpq = mtpq[0];
   int lcm;
   RationalNumber maxdur = mticks[0];  // keep track of the maximum rhy value
   maxdur /= mtpq[0];                 
   RationalNumber tmax;
   RationalNumber tdur;                // test new maximum rhy duration
   Array<int> twonum(2);

   for (i=1; i<mtpq.getSize(); i++) {
      twonum[0] = curtpq;
      twonum[1] = mtpq[i];
      lcm = LCM(twonum);

      // get the non-tick version of the new possible maximum.
      tdur = mticks[i];
      tdur /= mtpq[i];
      if (tdur > maxdur) {
         tmax = tdur;
      } else {
         tmax = maxdur;
      }
      // check to see if the test maximum does not exceed the new
      // test ticks per quarter setting:
      if (tmax * lcm < 1000) {
         // the new measure can be added to the previous grouping
         // so store the new values (and erase previous setting.
         maxdur = tmax;
         curtpq = lcm;
         newtpq[i] = lcm;
         if (i < measurelines.getSize()-2) {
            newtpq[i-1] = 0;
         }
         continue;
      }

      // the new tpq would be too large to accomodate the largest
      // duration in the range. So restart the calculation on the
      // current measure.

      maxdur = tdur;
      curtpq = mtpq[i];
      newtpq[i] = curtpq;
   }


   // travel backwards in newtpq, setting any values that are 0 to the 
   // next higher index value which is non-zero.
   for (i=newtpq.getSize()-2; i>=0; i--) {
      if (newtpq[i] == 0) {
         newtpq[i] = newtpq[i+1];
      }
   }

   

   if (debugQ) {
      RationalNumber rn;
      for (i=0; i<newtpq.getSize()-1; i++) {
         cout << "START:" << measurelines[i] << "\tSTOP:" << measurelines[i+1] 
              << "\ttpq = " << mtpq[i]  << "(" << newtpq[i] << ")";
         cout << "\tmaxticks = " << mticks[i];
         rn = mticks[i];
         rn /= mtpq[i];
         rn *= newtpq[i];
         cout << "(" << rn << ")";
         //cout << "\trhys: ";
         //int j;
         //for (j=0; j<rhys.getSize(); j++) {
         //   cout << rhys[j] * 4 << " ";
         //}
         cout << NEWLINE;
      }
   }

   ticks.setSize(measurelines.getSize());
   for (i=0; i<ticks.getSize(); i++) {
      ticks[i] = newtpq[i];
   }

}



//////////////////////////////
//
// getTPQByRhythmSet --
//

int getTPQByRhythmSet(Array<RationalNumber>& rhys) {
   Array<int> intrhy;
   intrhy.setSize(rhys.getSize());
   int i;
   for (i=0; i<rhys.getSize(); i++) {
      intrhy[i] = rhys[i].getNumerator();
   }
   return LCM(intrhy);
}



//////////////////////////////
//
// getGlobalTicksPerQuarter -- Returns the number of ticks per quarter
//     which would be the result of all rhythms found in the file.
//     If the ticks per quarter will generate a note duration longer than 
//     999 ticks, then a local tick system needs to be done.
//

int getGlobalTicksPerQuarter(HumdrumFile& infile) {
   RationalNumber rn(1,1);
   rn = infile.getMinTimeBaseR();
   rn /= 4;

   if (rn.getDenominator() != 1) {
      // tuplets at a higher level than a quarter note most likely,
      // so put the 4 back in place.
      rn *= rn.getDenominator();
   }

   // hacky case where the minrhy is 0 and there is a defaultDur
   if (rn == 0) {
      rn = getDuration("", defaultDur);
      rn.invert();
      Array<int> rhythms(2);
      rhythms[0] = rn.getNumerator();
      rhythms[1] = 4;
      int returnval = LCM(rhythms);
      ignoreTickError = 1;
      return returnval;
   }

   if (debugQ) {
      cout << "Ticks per quarter: " << rn << NEWLINE;
   }
   
   // should be simplified automatically.
   return rn.getNumerator() * rn.getDenominator();
}



//////////////////////////////
//
// convertTrackToMuseData -- Convert a particular track of **kern data into
//     a MuseData file.  Successive measures are processed one at a time.
//

void convertTrackToMuseData(MuseData& musedata, int track, 
      HumdrumFile& infile, Array<int>& tpq, int& tickpos, int counter, 
      int total, int printFirstMeasureQ) {
   Array<int> measures;

   // should probably move to calling function, but keeping here for now
   // in case the more generalized solution of finding the barlines in each
   // individual track should be done instead of the global barlines.
   // The HumdrumFile class likes barlines to be global...
   getMeasureInfo(measures, infile);
   int i;
   MuseData tempdata;
   insertHeaderRecords(infile, musedata, track, counter, total);
   LastTPQPrinted = tpq[0];
   insertDollarRecord(infile, 0, musedata, track, counter, total, tpq[0]);
   
   if (hasTuplet[track]) {
      // add a style which places no tuplet slur on beamed notes
      MuseRecord arecord;
      arecord.insertString(1, "P C0:t2");
      musedata.append(arecord);
   }

   int founddataQ = 0;  // needed to suppress first $ record from being
                       // reprinted (the first one was needed above
                       // with the Q: field added).

   for (i=0; i<measures.getSize()-1; i++) {
      tempdata.clear();
      if (debugQ) {
         cout << "Extracting data for measure starting at line: " 
              << measures[i] << NEWLINE;
      }
      getMeasureData(tempdata, track, infile, measures[i], measures[i+1], 
            tpq[i], tickpos, printFirstMeasureQ, founddataQ);
      musedata.append(tempdata);
   }

}



//////////////////////////////
//
//
// insertDollarRecord -- Musical Attributes Record
//
// Example:
//    $  K:0   Q:4   T:1/1   C1:4   C2:22
// Column 1: $
// Column 2: level number (optional)
// Column 3: footnote column
// Columns 4-80 attributes
//
// K: key
// Q: divisions per quarter note (required in first $ record in part)
// T: time signature, such as T:4/4
// C: clef (for all staves in part)
// C1: clef for staff 1 in part
// C2: clef for staff 2 in part
// X: transposing part
// S: number of staves in part
// I: number of instruments in part
// D: directive (last field on data line)
//

void insertDollarRecord(HumdrumFile& infile, int line, MuseData& musedata, 
      int track, int counter, int total, int tpq) {

   MuseRecord arecord;
   arecord.insertString(1, "$  ");
   char buffer[1024] = {0};
   char tempbuf[16] = {0};

   if (line != 0) {
      // don't know why this is being done, maybe remove, or always
      // place extra space?
      strcat(buffer, " ");
   }
   
   // key signature
   if (appendKeySignature(buffer, infile, line, track)) {
      strcat(buffer, "   ");
   }
   // ticks
   if (tpq > 0) {
      sprintf(tempbuf, "Q:%d   ", tpq);
      strcat(buffer, tempbuf);
   }

   // time signature
   if (appendTimeSignature(buffer, infile, line, track, tpq)) {
         strcat(buffer, "   ");
   }

   // clefs
   if (appendClef(buffer, infile, line, track)) {
      strcat(buffer, "   ");
   } else {
      // a clef must be present in the first musical attributes line
      // for a part, so force one here.  Make it a treble clef...
      if (tpq > 0) {
         // only print if Q: recrod is being printed.  Not quite
         // right, but rare to print Q: record twice in part, so good
         // enought for now.
         strcat(buffer, "C:4   ");
      }
   }

   // add a movement designation if this is the first
   // one (will have to fix so that movement designations which
   // occur later can be also printed...
   // if (tpq > 0) {
      addMovementDesignation(buffer, infile, line);
   // } 

   // [20111016] If the $ record contains no content, then do not add it
   // into the output data.
   
   arecord.insertString(4, buffer);
   PerlRegularExpression pre;
   if (pre.search(arecord.getLine(), "^.\\s*$")) {
      return;
   } else {
      musedata.append(arecord);
   }
}



//////////////////////////////
//
// addMovementDesignation --
//

void addMovementDesignation(char* buffer, HumdrumFile& infile, int line) {
   int omdline = -1;
   int i;
   PerlRegularExpression pre;

   for (i=line; i<infile.getNumLines(); i++) {
      if (infile[i].isData()) {
         break;
      }
      if (infile[i].isMeasure()) {
         // [20111016] don't check across barlines becuase this
         // will add a double designation in the graphical score.
         break;
      }
      if (!infile[i].isBibliographic()) {
         continue;
      }
      if (pre.search(infile[i][0], "^!!!OMD[^:]*:\\s*(.*)\\s*$", "")) {
         omdline = i;
         break;
      }
   }


   if (omdline < 0) {
      for (i=line-1; i>=0; i--) {
         if (infile[i].isData()) {
            break;
         }
         if (!infile[i].isBibliographic()) {
            continue;
         }
         if (pre.search(infile[i][0], "^!!!OMD[^:]*:\\s*(.*)\\s*$", "")) {
            omdline = i;
            break;
         }
      }
   }

   if (omdline >= 0) {
      strcat(buffer, "D:");
      Array<char> movementdesignation(strlen(pre.getSubmatch(1))+1);
      strcpy(movementdesignation.getBase(), pre.getSubmatch());
      convertHtmlTextToMuseData(movementdesignation);
      strcat(buffer, movementdesignation.getBase());
      // probably don't need this, but just in case something is added on line:
      strcat(buffer, "   ");
   }

}



//////////////////////////////
//
// appendClef --  Append a clef marker to a Music Attribute
// record if there is a key signature intepretation somewhere between the 
// current line and the first data line found after that line -- in the 
// specified primary track.
//

int appendClef(char* buffer, HumdrumFile& infile, int line, int track) {
   int i, j;

   int row = -1;  // row where key signature was found;
   int col = -1;  // col where key signature was found;

   for (i=line; 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].getPrimaryTrack(j) != track) {
            continue;
         }
         if (strncmp("*clef", infile[i][j], 5) == 0) {
            row = i;
            col = j;
         }
      }
   }

   if ((row < 0) || (col < 0)) {
      return 0;
   }

   if (strcmp(infile[row][col], "*clefG1") == 0) {  // French violin clef
      strcat(buffer, "C:5");
   } else if (strcmp(infile[row][col], "*clefG2") == 0) {  // treble clef
      strcat(buffer, "C:4");
   } else if (strcmp(infile[row][col], "*clefG3") == 0) {
      strcat(buffer, "C:3");
   } else if (strcmp(infile[row][col], "*clefG4") == 0) {
      strcat(buffer, "C:2");
   } else if (strcmp(infile[row][col], "*clefG5") == 0) {
      strcat(buffer, "C:1");
   } else if (strcmp(infile[row][col], "*clefC1") == 0) { // soprano
      strcat(buffer, "C:15");
   } else if (strcmp(infile[row][col], "*clefC2") == 0) { // mezzo-soprano
      strcat(buffer, "C:14");
   } else if (strcmp(infile[row][col], "*clefC3") == 0) { // alto
      strcat(buffer, "C:13");
   } else if (strcmp(infile[row][col], "*clefC4") == 0) { // tenor
      strcat(buffer, "C:12");
   } else if (strcmp(infile[row][col], "*clefC5") == 0) { // baritone
      strcat(buffer, "C:11");
   } else if (strcmp(infile[row][col], "*clefF1") == 0) {
      strcat(buffer, "C:25");
   } else if (strcmp(infile[row][col], "*clefF2") == 0) {
      strcat(buffer, "C:24");
   } else if (strcmp(infile[row][col], "*clefF3") == 0) {
      strcat(buffer, "C:23");
   } else if (strcmp(infile[row][col], "*clefF4") == 0) { // bass clef
      strcat(buffer, "C:22");
   } else if (strcmp(infile[row][col], "*clefF5") == 0) {
      strcat(buffer, "C:21");
   } else if (strcmp(infile[row][col], "*clefGv1") == 0) {
      strcat(buffer, "C:35");
   } else if (strcmp(infile[row][col], "*clefGv2") == 0) { // vocal tenor clef
      strcat(buffer, "C:34");
   } else if (strcmp(infile[row][col], "*clefGv3") == 0) {
      strcat(buffer, "C:33");
   } else if (strcmp(infile[row][col], "*clefGv4") == 0) {
      strcat(buffer, "C:32");
   } else if (strcmp(infile[row][col], "*clefGv5") == 0) {
      strcat(buffer, "C:31");
   } else {
      strcat(buffer, "C:4"); // use default of treble clef
      // although auto-detecting the range of the data in the spine 
      // might be better (so that bass clef might be selected automatically).
   }

   return 1;
}



//////////////////////////////
//
// appendTimeSignature --  Append a time signature marker to a Music Attribute
// record if there is a time signature intepretation somewhere between the 
// current line and the first data line found after that line -- in the 
// specified primary track.  If there is a *met() code, then use that instead
// of the *M attribute.
//

int appendTimeSignature(char* buffer, HumdrumFile& infile, int line, 
      int track, int tpq) {
   int metrow = -1;
   int metcol = -1;
   int timerow = -1;
   int timecol = -1;

   PerlRegularExpression pre;
   int i, j;
   for (i=line; i<infile.getNumLines(); i++) {
      if (infile[i].isData()) {
         break;
      }
      if (!infile[i].isInterpretation()) {
         continue;
      }
      for (j=0; j<infile[i].getFieldCount(); j++) {
         if (track != infile[i].getPrimaryTrack(j)) {
            continue;
         }
         if (pre.search(infile[i][j], "^\\*M\\d+/\\d+", "")) {
            timerow = i;
            timecol = j;
         } else if (pre.search(infile[i][j], "^\\*met\\([^)]*\\)", "")) {
            if(metQ) {
               metrow = i;
               metcol = j;
            }
         }
         // only look at first layer of track on a line.
         break;
      }
   }

   if (metrow >= 0) {
      // a met code has been found, so use that to print a time signature
      if (strcmp(infile[metrow][metcol], "*met(O)") == 0) {
         strcat(buffer, "T:11/0");   return 1;
      } else if (strcmp(infile[metrow][metcol], "*met(c)") == 0) {
         strcat(buffer, "T:1/1");   return 1;
      } else if (strcmp(infile[metrow][metcol], "*met(com)") == 0) {
         strcat(buffer, "T:1/1");   return 1;
      } else if (strcmp(infile[metrow][metcol], "*met(common)") == 0) {
         strcat(buffer, "T:1/1");   return 1;
      } else if (strcmp(infile[metrow][metcol], "*met(c|)") == 0) {
         strcat(buffer, "T:0/0");   return 1;
      } else if (strcmp(infile[metrow][metcol], "*met(cut)") == 0) {
         strcat(buffer, "T:0/0");   return 1;
      } else if (strcmp(infile[metrow][metcol], "*met(O:)") == 0) {
         // preferred alternate for *met(O..)
         strcat(buffer, "T:12/0");   return 1;
      } else if (strcmp(infile[metrow][metcol], "*met(O..)") == 0) {
         strcat(buffer, "T:12/0");   return 1;
      } else if (strcmp(infile[metrow][metcol], "*met(O.)") == 0) {
         strcat(buffer, "T:21/0");   return 1;
      } else if (strcmp(infile[metrow][metcol], "*met(O:.)") == 0) {
         strcat(buffer, "T:22/0");   return 1;
      } else if (strcmp(infile[metrow][metcol], "*met(O...)") == 0) {
         strcat(buffer, "T:22/0");   return 1;
      } else if (strcmp(infile[metrow][metcol], "*met(O;)") == 0) {
         // preferred alternate for *met(O:.) and *met(O...)
         strcat(buffer, "T:22/0");   return 1;
      } else if (strcmp(infile[metrow][metcol], "*met(C)") == 0) {
         strcat(buffer, "T:31/0");   return 1;
      } else if (strcmp(infile[metrow][metcol], "*met(C.)") == 0) {
         strcat(buffer, "T:41/0");   return 1;
      } else if (strcmp(infile[metrow][metcol], "*met(Cr)") == 0) {
         strcat(buffer, "T:51/0");   return 1;
      } else if (strcmp(infile[metrow][metcol], "*met(C|)") == 0) {
         strcat(buffer, "T:61/0");   return 1;
      } else if (strcmp(infile[metrow][metcol], "*met(C2)") == 0) {
         strcat(buffer, "T:71/0");   return 1;
      } else if (strcmp(infile[metrow][metcol], "*met(O2)") == 0) {
         strcat(buffer, "T:81/0");   return 1;
      } else if (strcmp(infile[metrow][metcol], "*met(O|)") == 0) {
         strcat(buffer, "T:91/0");   return 1;
      } else if (strcmp(infile[metrow][metcol], "*met(C|3)") == 0) {
         strcat(buffer, "T:101/0");   return 1;
      } else if (strcmp(infile[metrow][metcol], "*met(3)") == 0) {
         strcat(buffer, "T:102/0");   return 1;
      } else if (strcmp(infile[metrow][metcol], "*met(O/3)") == 0) {
         // eventually O/3 should generate a new mensural sign in
         // musedata.  O/3 has the same function as 3.
         strcat(buffer, "T:102/0");   return 1;
      } else if (strcmp(infile[metrow][metcol], "*met(3/2)") == 0) {
         strcat(buffer, "T:103/0");   return 1;
      } else if (strcmp(infile[metrow][metcol], "*met(C|2)") == 0) {
         strcat(buffer, "T:111/0");   return 1;
      } else if (strcmp(infile[metrow][metcol], "*met(2)") == 0) {
         strcat(buffer, "T:112/0");   return 1;
      } else if (strcmp(infile[metrow][metcol], "*met(Oo)") == 0) {
         strcat(buffer, "T:121/0");   return 1;
      } else if (strcmp(infile[metrow][metcol], "*met()") == 0) {
         // hide the time signature
         // strcat(buffer, "");   return 1;
         return 0;
      } else {
         // an unknown metric symbol, so try your luck with time signature
         // data below.
      }
      // mensurations which need to be added to MuseData:
      // *met(O|3/2)
   }

   if (timerow >= 0) {
      if (!pre.search(infile[timerow][timecol], "^\\*M(\\d+)/(\\d+)")) {
         // something funny happened...
         return 0;
      }
      int timetop = atoi(pre.getSubmatch(1));
      int timebot = atoi(pre.getSubmatch(2));
      if ((timerow > 0) && (timecol >= 0) && pre.search(infile[timerow][timecol], "^\\*M3/3%2", "")) {
         // can map to 2/1 or 3/1 depending on context.  Only used
         // with mensurations, and usually best to display in 3/1
         // which serves as an shorthand for triplet markers.
         timetop = 3;
         timebot = 1;
      }

      if ((timebot == 0) && (timetop < 9)) {
         // Currently reserving 9/0 for a hidden time signature
         // meters larger than 10/0 are used for mensural signatures
         //
         // For time signatures smaller than 9/0, note that
         // MuseData cannot handle a breve as the beat, so adjust
         // by shifting the bottom number from 0 to 1 and double the
         // top number in the time signature.
         timebot = 1;
         timetop *= 2;
      }
      SSTREAM temps;
      temps << "T:";
      temps << timetop << "/" << timebot;
      temps << ends;
      strcat(buffer, temps.CSTRING);
   } else {
      // no time signature found in interpretation region.
      // currently print the time signature 9/0 which functions
      // as an invisible time signature, although this will change
      // later when the muse2ps program no longer requires a time
      // signature at the start of the music.
      if (tpq >= 0) {
         // no longer needed since muse2ps can now handle an empty time sig.
         // strcat(buffer, "T:9/0");
      }
      // don't print hidden time signature if no Q record.
      // return 0;
   }

   // *met(.*) or *M\d+/\d+ found in interpretation region.
   return 1;
}




//////////////////////////////
//
// appendKeySignature --  Append a key signature marker to a Music Attribute
// record if there is a key signature intepretation somewhere between the 
// current line and the first data line found after that line -- in the 
// specified primary track.
//

int appendKeySignature(char* buffer, HumdrumFile& infile, int line, int track) {
   int i, j;
   PerlRegularExpression pre;

   int row = -1;  // row where key signature was found;
   int col = -1;  // col where key signature was found;

   for (i=line; 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].getPrimaryTrack(j) != track) {
            continue;
         }
         if (pre.search(infile[i][j], "^\\*k\\[.*\\]", "")) {
            row = i;
            col = j;
         }
      }
   }

   if ((row < 0) || (col < 0)) {
      return 0;
   }

   int keynumber = Convert::kernKeyToNumber(infile[row][col]);
   char keybuf[16] = {0};
   sprintf(keybuf, "K:%d", keynumber);
   strcat(buffer, keybuf);
   return 1;
}



//////////////////////////////
//
// insertHeaderRecords --
//

void insertHeaderRecords(HumdrumFile& infile, MuseData& tempdata, 
     int track, int counter, int total) {

   MuseRecord arecord;

   // Record 1: 		Copyright notice
   addCopyrightLine(infile, tempdata);
 
   // Record 2: 		Control number
   addControlNumber(infile, tempdata);

   // Record 3: 		Timestamp
   addTimeStamp(tempdata);
   
   // Record 4: 		<date> <name of encoder>
   addDateAndEncoder(infile, tempdata);

   // Record 5: 		WKn:<work number> MVn:<movement number>
   addWorkNumberInfo(infile, tempdata);
   
   // Record 6: 		<source>
   addSourceRecord(infile, tempdata);
   
   // Record 7: 		<work title>
   addWorkTitle(infile, tempdata);
   
   // Record 8: 		<movement title>
   addMovementTitle(infile, tempdata);
   
   // Record 9: 		<name of part>
   addPartName(infile, track, tempdata);
   
   // Record 10:                miscellaneous designations 
   // such as          [mode], [movement type] and [voice]
   arecord.clear();
   arecord.insertString(1, "Header Record 10");
   tempdata.append(arecord);
   
   // Record 11: 		group memberships: <name1> <name2> . . .
   arecord.clear();
   arecord.insertString(1, "Group memberships: score");
   tempdata.append(arecord);

   // Record 12: 		<name1>: part <x> of <number in group>
   SSTREAM partnum;
   partnum << "score: part " << (total - counter)+1 << " of " << total;
   partnum << ends;
   arecord.clear();
   arecord.insertString(1, partnum.CSTRING);
   tempdata.append(arecord);
}



//////////////////////////////
//
// addCopyrightLine -- add fixed header record 1
//


void addCopyrightLine(HumdrumFile& infile, MuseData& tempdata) {
   MuseRecord arecord;
   arecord.insertString(1, "Header Record 1: optional copyright notice");

   char buffer [1024] = {0};
   int i;
   for (i=0; i<infile.getNumLines(); i++) {
      if (!infile[i].isBibliographic()) {
         continue;
      }
      if (strncmp("YEC", infile[i].getBibKey(buffer, 1000), 3) == 0) {
         arecord.clear();
         arecord.insertString(1, infile[i].getBibValue(buffer, 80));
         break;
      }
   }

   if (copyrightQ) {
      arecord.clear();
      arecord.insertString(1, Copyright);
   }

   tempdata.append(arecord);
}



//////////////////////////////
//
// addControlNumber -- add fixed header record 2
//

void addControlNumber(HumdrumFile& infile, MuseData& tempdata) {
   MuseRecord arecord;
   arecord.insertString(1, "Header Record 2: optional file identification");
   tempdata.append(arecord);
}



//////////////////////////////
//
// addTimeStamp -- add fixed header record 3
//

void addTimeStamp(MuseData& tempdata) {
   MuseRecord arecord;
   // arecord.insertString(1, "Header Record 3: optional timestamp/checksum");
   struct tm *current;
   time_t now;
   time(&now);
   current = localtime(&now);
   int year    = current->tm_year + 1900;
   int month   = current->tm_mon + 1;
   int day     = current->tm_mday;
   const char* ptr = "JAN";
   switch (month) {
      case  1:  ptr = "JAN"; break;
      case  2:  ptr = "FEB"; break;
      case  3:  ptr = "MAR"; break;
      case  4:  ptr = "APR"; break;
      case  5:  ptr = "MAY"; break;
      case  6:  ptr = "JUN"; break;
      case  7:  ptr = "JUL"; break;
      case  8:  ptr = "AUG"; break;
      case  9:  ptr = "SEP"; break;
      case 10:  ptr = "OCT"; break;
      case 11:  ptr = "NOV"; break;
      case 12:  ptr = "DEC"; break;
   }
   const char* dptr = "";
   if (day < 10) {
      dptr = "0";
   }
   arecord.append("ssssisis", "TIMESTAMP: ", ptr, "/", dptr, day, 
         "/", year, " []");
   tempdata.append(arecord);
}



//////////////////////////////
//
// addDateAndEncoder -- add fixed header record 4
//

void addDateAndEncoder(HumdrumFile& infile, MuseData& tempdata) {
   MuseRecord arecord;
   int encline = -1;
   int eedline = -1;
   int endline = -1;
   int eevline = -1;
   int i;
   for (i=0; i<infile.getNumLines(); i++) {
      if (!infile[i].isBibliographic()){ 
         continue;
      }
      if (strncmp(infile[i][0], "!!!END:", 7) == 0) {  // encoding date
         endline = i;
      }
      if (strncmp(infile[i][0], "!!!ENC:", 7) == 0) { // encoder's name
         encline = i;
      }
      if (strncmp(infile[i][0], "!!!EED:", 7) == 0) { // edition editor
         eedline = i;
      }
      if (strncmp(infile[i][0], "!!!EEV:", 7) == 0) { // edition date
         eevline = i;
      }
   }

   int year  = -1;
   int month = -1;
   int day   = -1;

   PerlRegularExpression pre;
   if (endline >= 0) {

      if (pre.search(infile[endline][0], "(\\d{4})/0*(\\d+)/0*(\\d+)", "")) {
         year  = atoi(pre.getSubmatch(1));
         month = atoi(pre.getSubmatch(2)); 
         day   = atoi(pre.getSubmatch(3));
      } else if (pre.search(infile[endline][0], "(\\d{4})/0*(\\d+)", "")) {
         year  = atoi(pre.getSubmatch(1));
         month = atoi(pre.getSubmatch(2)); 
         day   = 0;
      } else if (pre.search(infile[endline][0], "(\\d{4})", "")) {
         year  = atoi(pre.getSubmatch(1));
         month = 0;
         day   = 0;
      }

   } else if (eevline >= 0) {

      if (pre.search(infile[eevline][0], "(\\d{4})/0*(\\d+)/0*(\\d+)", "")) {
         year  = atoi(pre.getSubmatch(1));
         month = atoi(pre.getSubmatch(2)); 
         day   = atoi(pre.getSubmatch(3));
      } else if (pre.search(infile[eevline][0], "(\\d{4})/0*(\\d+)", "")) {
         year  = atoi(pre.getSubmatch(1));
         month = atoi(pre.getSubmatch(2)); 
         day   = 0;
      } else if (pre.search(infile[eevline][0], "(\\d{4})", "")) {
         year  = atoi(pre.getSubmatch(1));
         month = 0;
         day   = 0;
      }

   }

   char encoder[1024] = {0};

   if (year < 0) {
      // Use today's date:
      struct tm *current;
      time_t now;
      time(&now);
      current = localtime(&now);
      year = current->tm_year + 1900;
      month = current->tm_mon + 1;
      day = current->tm_mday;
   }

   char datebuf[1024] = {0};
   char daybuf[16] = {0};
   if (day < 10) {
      sprintf(daybuf, "0%d", day);
   } else {
      sprintf(daybuf, "%d", day);
   }
   char monthbuf[16] = {0};
   if (month < 10) {
      sprintf(monthbuf, "0%d", month);
   } else {
      sprintf(monthbuf, "%d", month);
   }
   sprintf(datebuf, "%s/%s/%d ", monthbuf, daybuf, year);

   if (encline >= 0) {
      if (pre.search(infile[encline][0], "^!!!ENC:\\s*(.*)\\s*$", "")) {
         strcpy(encoder, pre.getSubmatch(1));
      }
   }  else if (eedline >= 0) {
      if (pre.search(infile[eedline][0], "^!!!EED:\\s*(.*)\\s*$", "")) {
         strcpy(encoder, pre.getSubmatch(1));
      }
   }
   if (encoderQ) {
      strcpy(encoder, Encoder);
   } else if (strlen(encoder) == 0) {
      strcpy(encoder, "hum2muse");
   }

   arecord.insertString(1, datebuf);
   arecord.appendString(encoder);

   tempdata.append(arecord);
}



//////////////////////////////
//
// addWorkNumberInfo -- add fixed header record 5.  This line must start
//    with "WK#" in order for muse2ps to process the file.
//

void addWorkNumberInfo(HumdrumFile& infile, MuseData& tempdata) {
   MuseRecord arecord;
   Array<char> work;
   Array<char> movement;
   getWorkAndMovement(work, movement, infile);
   arecord.append("ssss", "WK#:", work.getBase(), " MV#:", movement.getBase());
   tempdata.append(arecord);
}



//////////////////////////////
//
// getWorkAndMovement --
//

void getWorkAndMovement(Array<char>& work, Array<char>& movement, 
      HumdrumFile& infile) {

   movement.setSize(2);
   work.setSize(2);
   strcpy(movement.getBase(), "1");
   strcpy(work.getBase(),     "1");

   if (workNumber[0] != '\0') {
      work.setSize(strlen(workNumber)+1);
      strcpy(work.getBase(), workNumber);
   }
   if (movementNumber[0] != '\0') {
      movement.setSize(strlen(movementNumber)+1);
      strcpy(movement.getBase(), movementNumber);
   }

   int omvline = -1;
   int sctline = -1;
   int opsline = -1;
   int onmline = -1;

   int i;
   for (i=0; i<infile.getNumLines(); i++) {
      if (!infile[i].isBibliographic()) {
         continue;
      }
      if (strncmp(infile[i][0], "!!!OMV", 6) == 0) {
         omvline = i;
      } else if (strncmp(infile[i][0], "!!!SCT", 6) == 0) {
         sctline = i;
      } else if (strncmp(infile[i][0], "!!!OPS", 6) == 0) {
         opsline = i;
      } else if (strncmp(infile[i][0], "!!!ONM", 6) == 0) {
         onmline = i;
      }
   }

   PerlRegularExpression pre;
   PerlRegularExpression pre2;

   if (omvline >= 0) {
      pre.search(infile[omvline][0], "^!!!OMV[^:]*:\\s*(.*)\\s*$", "");
      movement.setSize(strlen(pre.getSubmatch(1)) +1);
      strcpy(movement.getBase(), pre.getSubmatch());
      pre.sar(movement, "^[^\\d]+", "", "");
      pre.sar(movement, "\\s*\\.\\s*", "", "");
   }
   if (strlen(movement.getBase()) == 0) {
      movement.setSize(2);
      strcpy(movement.getBase(), "1");
   }

   // if there is a BWV in SCT, then use that as the work # and any
   // slash after the first number after BWV as the movment number.
   if ((sctline >= 0) && pre.search(infile[sctline][0], 
         "^!!!SCT[^:]*:\\s*(.+)\\s*$", "")) {

      if (pre2.search(pre.getSubmatch(1), "BWV\\s+(\\d+)/(\\d+)", "i")) {
         work.setSize(strlen(pre2.getSubmatch(1)) + 1);
         strcpy(work.getBase(), pre2.getSubmatch());
         movement.setSize(strlen(pre2.getSubmatch(2)) + 1);
         strcpy(movement.getBase(), pre2.getSubmatch());
         return;
      }

      if (pre2.search(pre.getSubmatch(1), "BWV\\s*(\\d+[^\\s]*)", "i")) {
         work.setSize(strlen(pre2.getSubmatch(1)) + 1);
         strcpy(work.getBase(), pre2.getSubmatch());
         return;
      }

   }

   // if there is an opus number, then use that as the work number
   // handle onm line later...
   if ((opsline >= 0) && pre2.search(infile[opsline][0], 
         "^!!!OPS[^:]*:\\s*(\\d[^\\s]*)", "")) {
      work.setSize(strlen(pre2.getSubmatch(1)) + 1);
      strcpy(work.getBase(), pre2.getSubmatch());
      return;
   }

}



//////////////////////////////
//
// addSourceRecord -- add fixed header record 6, which is the 
// original source for this particular digital encoding.
//

void addSourceRecord(HumdrumFile& infile, MuseData& tempdata) {
   MuseRecord arecord;

   PerlRegularExpression pre;
   int smsline = -1;
   int i;
   for (i=0; i<infile.getNumLines(); i++) {
      if (!infile[i].isBibliographic()) {
         continue;
      }
      if (pre.search(infile[i][0], "^!!!SMS[^:]*:\\s(.*)\\s*$", "")) {
         smsline = i;
         break;
      }
      
   }
   if (smsline < 0) {
      arecord.insertString(1, "Header Record 6: source");
   } else {
      Array<char> sms;
      sms.setSize(strlen(pre.getSubmatch(1)) + 1);
      strcpy(sms.getBase(), pre.getSubmatch());
      convertHtmlTextToMuseData(sms);
      arecord.insertString(1, sms.getBase());
   }

   tempdata.append(arecord);
}



//////////////////////////////
//
// addWorkTitle -- add fixed header record 7 which is the title of the work
// (of which this data may be a particular movment).
//

void addWorkTitle(HumdrumFile& infile, MuseData& tempdata) {
   MuseRecord arecord;

   PerlRegularExpression pre;
   int otlline = -1;
   int i;
   for (i=0; i<infile.getNumLines(); i++) {
      if (!infile[i].isBibliographic()) {
         continue;
      }
      if (pre.search(infile[i][0], "^!!!OTL[^:]*:\\s*(.+)\\s*$", "")) {
         otlline = i;
         break;
      }
      
   }
   if (otlline < 0) {
      arecord.insertString(1, "Header Record 7: work title");
   } else {
      Array<char> strang(strlen(pre.getSubmatch(1))+1);
      strcpy(strang.getBase(), pre.getSubmatch(1));
      convertHtmlTextToMuseData(strang);
      arecord.insertString(1, strang.getBase());
   }

   tempdata.append(arecord);
}



//////////////////////////////
//
// addMovementTitle -- add fixed header record 8, which is the movement
// name.  Change to OMV: OMD, instead of OMD.
//

void addMovementTitle(HumdrumFile& infile, MuseData& tempdata) {
   MuseRecord arecord;

   PerlRegularExpression pre;
   int omdline = -1;
   int i;
   for (i=0; i<infile.getNumLines(); i++) {
      if (!infile[i].isBibliographic()) {
         continue;
      }
      if (pre.search(infile[i][0], "^!!!OMD[^:]*:\\s(.*)\\s*$", "")) {
         omdline = i;
         break;
      }
      
   }
   if (omdline < 0) {
      arecord.insertString(1, "Header Record 8: movement title");
   } else {
      Array<char> strang(strlen(pre.getSubmatch(1))+1);
      strcpy(strang.getBase(), pre.getSubmatch(1));
      convertHtmlTextToMuseData(strang);
      arecord.insertString(1, strang.getBase());
   }

   tempdata.append(arecord);
}



//////////////////////////////
//
// addPartName -- add fixed header record 9 which is the instrumental
// name of the part.  This name can be set explicitly with *I" tandem
// interpretations in the data.  Otherwise, the part name will be
// (eventually) extracted automatically from the *I instrument 
// code names.
//

void addPartName(HumdrumFile& infile, int track, MuseData& tempdata) {
   MuseRecord arecord;
   if (strcmp(PartNames[track].getBase(), "") != 0) {
      arecord.insertString(1, PartNames[track].getBase());
   } else {
      arecord.insertString(1, "                                  ");
   }
   tempdata.append(arecord);
}



//////////////////////////////
//
// getMeasureData --  Get MuseData for a particular measure in a particular
//     part (track).
//

void getMeasureData(MuseData& tempdata, int track, HumdrumFile& infile, 
      int startline, int stopline, int tpq, int& tickpos, 
      int& measuresuppress, int& founddataQ) {
   int maxvoice = getMaxVoices(infile, startline, stopline, track);
   if (debugQ) {
      cout << "For line range " << startline+1 << " to " << stopline+1
           << " in track " << track << " maxvoices = " << maxvoice << NEWLINE;
   }

   if (maxvoice == 0) {
      // measure has no data
      maxvoice = 1;
   }

   int starttick;
   int stoptick;
   starttick = getTick(tpq, infile, startline);
   stoptick  = getTick(tpq, infile, stopline);

   int voice;
   for (voice=0; voice<maxvoice; voice++) {
      processVoice(tempdata, infile, startline, stopline, track, voice+1, 
            tpq, tickpos, measuresuppress, founddataQ, starttick, stoptick);
   }
}



//////////////////////////////
//
// processVoice --
//

void processVoice(MuseData& tempdata, HumdrumFile& infile, int startline, 
      int stopline, int track, int voice, int tpq, int& tickpos, 
      int& measuresuppress, int& founddataQ, int starttick, int stoptick) {

   int tpqtest = -1;
   if (LastTPQPrinted != tpq) {
      tpqtest = tpq;
      LastTPQPrinted = tpq;
      // hack for variable tpq:
      tickpos = -1;    
   }

   PerlRegularExpression pre;

   // int starttick = getTick(tpq, infile, startline);
   // int stoptick  = getTick(tpq, infile, stopline);
   if (debugQ) {
      cout << "STARTTICK = " << starttick 
           << "\tSTOPTICK = " << stoptick << NEWLINE;
   }

   if (tickpos < 0) {
      // hack for variable tpq
      tickpos = starttick;
   }
   int backQ = 0;

   // emit a back command to go back to the start of the measure
   // if this is not the first voice to process...
   if (voice > 1) {
      addBackup(tempdata, stoptick - starttick, tpq);
      tickpos -= stoptick - starttick;
      backQ = 1;
   }

   if ((!ignoreTickError) && (tickpos != starttick)) {
      cerr << "Error: tick mismatch: " << tickpos << " " << starttick << NEWLINE;
      cerr << "on line " << startline + 1 << NEWLINE;
      exit(1);
   }

   int i, j;
   int startingmeasure = 1;
   for (i=startline; i>0; i--) {
      if (infile[i].isData()) {
         startingmeasure = 0;
         break;
      }
   }
   int firstitem = 1;

   int dollarprint = 0;

   int curvoice = 0;
   for (i=startline; i<stopline; i++) {
      if (debugQ) {
         cout << "INPUT LINE: " << infile[i] << endl;
      }
      curvoice = 0;
      for (j=0; j<infile[i].getFieldCount(); j++) {
         if (debugQ) {
            cout << "J = " << j << endl;
         }
        
         if (infile[i].isGlobalComment()) {
            processGlobalComment(infile, i, tempdata, track);
         }

         if (track == infile[i].getPrimaryTrack(j)) {
            curvoice++;
         } else {
            if ((!backQ) && (curvoice == 0) && (!dollarprint) &&
               infile[i].isBibliographic()) {
               if (strncmp(infile[i][0], "!!!OMD", 6) == 0) {
                  dollarprint = 1;
                  insertDollarRecord(infile, i, tempdata, track, 0, 0, tpqtest);
                  tpqtest = -1;
               }
            }

            continue;
         }
         if (curvoice != voice) {
            continue;
         }

         // print the barline for this segment of data
         if ((curvoice == 1) && infile[i].isMeasure()) {
            if(!measuresuppress) {
               addMeasureEntry(tempdata, infile, i, j);
            } else {
               // turn off the suppression (of the first measure)
               measuresuppress = !measuresuppress;
            }
            continue;
         }

         if (founddataQ) {
            if ((curvoice == 1) && (!dollarprint) && 
                  infile[i].isInterpretation()) {
               if (pre.search(infile[i][j], "^\\*M\\d+/\\d+")) {
                  dollarprint = 1;
                  insertDollarRecord(infile, i, tempdata, track, 0, 0, tpqtest);
                  tpqtest = -1;
               }
               if (pre.search(infile[i][j], "^\\*MM\\d+")) {
                  dollarprint = 1;
                  insertDollarRecord(infile, i, tempdata, track, 0, 0, tpqtest);
                  tpqtest = -1;
               }
               if (pre.search(infile[i][j], "^\\*clef")) {
                  dollarprint = 1;
                  insertDollarRecord(infile, i, tempdata, track, 0, 0, tpqtest);
                  tpqtest = -1;
               }
               if (pre.search(infile[i][j], "^\\*k\\[.*\\]")) {
                  dollarprint = 1;
                  insertDollarRecord(infile, i, tempdata, track, 0, 0, tpqtest);
                  tpqtest = -1;
               }
            }
         }

         if (strcmp(infile[i][j], "*tacet") == 0) {
            MuseRecord suppressvoice;
            suppressvoice.setLine("P C0:x1");
            tempdata.append(suppressvoice);
         } else if (strcmp(infile[i][j], "*xtacet") == 0) {
            MuseRecord unsuppressvoice;
            unsuppressvoice.setLine("P C0:x0");
            tempdata.append(unsuppressvoice);
         }

         if (!infile[i].isData()) {
            continue;
         }
         founddataQ = 1;
         dollarprint = 0;
      
         // if tpqtest is not -1, then a dollar record with the updated
         // ticks per quarter must be printed now.
         if (tpqtest > 0) {
            MuseRecord dchange;
            char buffer[128] = {0};
            sprintf(buffer, "$ Q:%d", tpqtest);
            dchange.insertString(1, buffer);
            tempdata.append(dchange);
            tpqtest = -1;
         }
 
         int currtick  = getTick(tpq, infile, i);
         if (currtick > tickpos) {
            // a voice which does not start at the beginning of the measure.
            MuseRecord forward;
            forward.setPitch("irst");   // also "irest" can be used
            forward.setTicks(currtick-tickpos);
            tempdata.append(forward);
            tickpos += (currtick - tickpos);
         }

         // print a note in the voice.  Keeping track of the
         // time that the next note is expected, and emit a
         // forward marker if needed.
         if (strcmp(infile[i][j], ".") != 0) {
            tickpos += addNoteToEntry(tempdata, infile, i, j, tpq, 
                             voice, firstitem & startingmeasure & hangtieQ);
            firstitem = 0;
         }
      }

   }

   if (debugQ) { cout << "GOT HERE AAA" << endl; }
   // emit a forward to get to end of measure in current voice
   if (tickpos < stoptick) {
      if (debugQ) { cout << "GOT HERE BBB" << endl; }
      // cout << "FORWARD " << stoptick - tickpos << NEWLINE;
      MuseRecord forward;
      forward.setPitch("irst");   // also "irest" can be used
      forward.setTicks(stoptick-tickpos);
      tempdata.append(forward);
      tickpos += stoptick - tickpos;
   } else if ((!ignoreTickError) && (tickpos > stoptick)) {
      cerr << "Error: duration of music is too large on line " 
           << startline+1 << NEWLINE;
      cerr << "Tickpos = " << tickpos << "\tStoptick = " << stoptick << NEWLINE;
      cerr << "The first number should be smaller or "
           << "equal to the second number "<< NEWLINE;
      exit(1);
   }

   if (debugQ) { cout << "GOT HERE CCC" << endl; }
}



//////////////////////////////
//
// processGlobalComments --
//

void processGlobalComment(HumdrumFile& infile, int line, MuseData& tempdata,
      int track) {
   PerlRegularExpression pre;
   if (strncmp(infile[line][0], "!!LO:", 5) != 0) {
      return;
   }
   LayoutParameters lp;
   Array<Coord> coords(1);
   coords[0].i = line;
   coords[0].j = 0;
   lp.parseLayout(infile, coords);

   int i;
   for (i=0; i<lp.getSize(); i++) {
      if (strcmp("REH", lp.getCode(i).getBase()) == 0) {
         // rehearsal mark
         if (isTopStaffOnSystem(track)) {
            printRehearsalMark(tempdata, lp, i);
         }
      }
   }

}


//////////////////////////////
//
// printRehearsalMark --
//
// col 1: *
// col 2-5: blank
// col 6-8: time offset (blank)
// col 9-12: blank
// col 13-15: footnote/editorial level (blank)
// col 16: blank
// col 17-18: "R " or " R" for rehearsal mark
// col 19: "+" for above the staff, blank for below the staff
// col 20: blank
// col 21-23: numberic parameter (blank)
// col 24: staff (blank for 1)
// col 25...: text to put in rehearsal mark
//
// Rehearsal mark is automatically put in a box (no control at the moment).
//
//

void printRehearsalMark(MuseData& tempdata, LayoutParameters& lp, int index) {

   int jdex = lp.hasKeyName(index, "t");
   if (jdex <= 0) {
      jdex = lp.hasKeyName(index, "text");
   }
   if (jdex <= 0) {
      // no text to display for rehearsal mark
      return;
   }
   jdex = jdex - 1;

   MuseRecord arecord;
   arecord.insertString(25, lp.getValue(index, jdex).getBase());
   arecord.getColumn(1)  = '*';
   arecord.getColumn(17) = 'R';
   arecord.getColumn(19) = '+';

   tempdata.append(arecord);
}




//////////////////////////////
//
// addBackup -- insert a backup command.  If the tick size is greater than
//     999, it will have to be split into multiple entries.
//

void addBackup(MuseData& tempdata, int backticks, int tpq) {
   if (backticks <= 0) {
      cerr << "Error with ticks in addBackup: " << backticks << NEWLINE;
   }
   MuseRecord arecord;
   if (backticks < 1000) {
      arecord.setBack(backticks);
      tempdata.append(arecord);
      return;
   }

   int increment = tpq * 4;
   if (increment >= 1000) {
      increment = 999;
   }
   while (backticks > 0) {
      arecord.clear();
      if (backticks <= increment) {
         arecord.setBack(backticks);
         tempdata.append(arecord);
         break;
      }
      backticks -= increment;
      arecord.setBack(increment);
      tempdata.append(arecord);
   }
}



//////////////////////////////
//
// getTick -- return the tick position of the given line of music in
//     the Humdrum score.
//

int getTick(int tpq, HumdrumFile& infile, int line) {
   RationalNumber tique = infile[line].getAbsBeatR();
   tique *= tpq;
   if (tique.getDenominator() != 1) {
      cerr << "Error at line " << line+1 << " in file.\n";
      cerr << "Tick position is not an integer: " << tique << NEWLINE;
      exit(1);
   }

   return tique.getNumerator();
}



//////////////////////////////
//
// getDuration -- return the duration of the note/chord/rest.  If the
//    note is not a grace note (indicated by a q or Q in the record),
//    and the note does not have a duration, then use the default duration
//    which can be set by the --dd option (the default duration if none
//    exists is a quarter note).  This is used to display music with no
//    durations (primarily monophonic, but also polyphonic as long as 
//    all parts have the same rhythmn).
//

RationalNumber getDuration(const char* input, const char* def) {
   Array<char> strang;
   strang.setSize(strlen(input)+1);
   strcpy(strang.getBase(), input);
   PerlRegularExpression pre;
   pre.sar(strang, "\\s.*", "", "");
   if (pre.search(strang, "\\d", "")) {
      // has some sort of numeric value so don't add default
      return Convert::kernToDurationR(strang.getBase());
   }
   if (pre.search(strang, "q", "i")) {
      // is a grace note (0 duration, but let kernToDurationR decide)
      return Convert::kernToDurationR(strang.getBase());
   }
   pre.sar(strang, "$", def);
   return Convert::kernToDurationR(strang.getBase());
}



//////////////////////////////
//
// getDurationNoDots -- 
//

RationalNumber getDurationNoDots(const char* input, const char* def) {
   Array<char> strang;
   strang.setSize(strlen(input)+1);
   strcpy(strang.getBase(), input);
   PerlRegularExpression pre;
   pre.sar(strang, "\\s.*", "", "");


   
   if (pre.search(strang, "(\\d+)%(\\d+)", "")) {
      // handle a rational value in the rhythm
      RationalNumber rn;
      rn = atoi(pre.getSubmatch(2));
      rn /= atoi(pre.getSubmatch(1));
      rn *= 4;  // convert from whole-note to quarter-note units.
      return rn;
   }


   if (pre.search(strang, "\\d", "")) {
      // has some sort of numeric value so don't add default
      return Convert::kernToDurationNoDotsR(strang.getBase());
   }

   if (pre.search(strang, "q", "i")) {
      // is a grace note (0 duration, but let kernToDurationR decide)
      return Convert::kernToDurationNoDotsR(strang.getBase());
   }

   pre.sar(strang, "$", def);
   return Convert::kernToDurationNoDotsR(strang.getBase());

}



/////////////////////////////
//
// addNoteToEntry --  Add a note or a chord to the data. 
//

int addNoteToEntry(MuseData& tempdata, HumdrumFile& infile, int row, int col,
      int tpq, int voice, int opentie) {
   int& i = row;
   int& j = col;
   MuseRecord arecord;
   MuseRecord psuggestion;
   if (roundQ) {
      arecord.setRoundedBreve();
   }
   RationalNumber rn = getDuration(infile[row][col], defaultDur);
   int  tokencount   = infile[row].getTokenCount(col);
   char buffer[128]  = {0};
   int tickdur = getTickDur(tpq, infile, row, col);
   int k, kk;
   int hidetieQ = 0;
   int hidetie  = 0;

   LayoutParameters lp;
   lp.parseLayout(infile, LayoutInfo[row][col]);

   const char* visual_display = "";
   char visbuffer[128] = {0};

   // if RscaleState[i][j] is not one, then apply a new visual display
   if (RscaleState[i][j] != 1) {
      RationalNumber visrn;
      visrn = rn * RscaleState[i][j];
      Convert::durationRToKernRhythm(visbuffer, visrn);
      visual_display = visbuffer;
   }

   int m;
   for (m=0; m<lp.getSize(); m++) {
      if (strcmp(lp.getCode(m).getBase(), "N") != 0) {
         continue;
      }
      int ind = lp.getParameter(m, "vis");
      if (ind >= 0) {
         visual_display = lp.getValue(m,ind).getBase();
      }
      if (strcmp(visual_display, "dot") == 0) {
         // vis=dot case handled in print suggestion do not needed here
         // vis=dot means replace the noteheat/stem with a dot.
         visual_display = "";
      }

      // only use the first visual_display setting found, and ignore any other
      break;
   }

   if (mensural2Q) {
      // if the note is tied, and the second note is 1/2 the duration
      // of the first note, then make the tie invisible, and make the
      // second note a dot, only considering the first note listed
      // in a chord.
      char tbuffer[32] = {0};
      infile[row].getToken(tbuffer, col, 0);
      if (strchr(tbuffer, '[') != NULL) {
         // start of tied note: see if the duration of the note is 2/3 of the
         // tied duration.  If so, then hide the tie.  A dot will replace
         // the ending note of tie, so keep the same visual appearance.
         RationalNumber dur;
         RationalNumber tdur;
         dur = Convert::kernToDurationR(tbuffer);
         tdur = infile.getTiedDuration(row, col, 0);
         if (dur * 3 / 2 == tdur) {
            hidetieQ = 1;
            hidetie  = 1;
         }
      } else if (strchr(tbuffer, ']') != NULL) {
         // end of tied note: see if the duration of the note is 1/3 of the
         // tied duration.  If so, then convert the note to a dot.
         RationalNumber dur;
         RationalNumber tdur;
         dur = Convert::kernToDurationR(tbuffer);
         tdur = infile.getTotalTiedDurationR(row, col, 0);
         if (dur * 3  == tdur) {
            visual_display = "";  // turn off any existing visual instruction
            // add faked LO:N:vis=dot layout directive
            int cindex = lp.appendCode("N");
            lp.addKeyValue(cindex, "vis", "dot");
         }
      }
   }
   
   if (voice == 1) {
      addHairpinStarts(tempdata, infile, row, col);
   }

   // check for global layout text codes add above/below the system.
   LayoutParameters glp;
   glp.parseLayout(infile, GlobalLayoutInfo[row]);

   int ii;
   LayoutParameters tempparam;

   if ((glp.getSize() > 0) && isTopStaffOnSystem(infile, row, col)) {
      // Only display text if placed above the staff
      for (ii=0; ii<glp.getSize(); ii++) {
         if (strcmp(glp.getCode(ii).getBase(), "TX") != 0) {
            continue;
         }
         if (glp.hasKeyName(ii, "Z")) {
            tempparam.clear();
            tempparam.addEntry(glp, ii);
            addTextLayout(tempdata, infile, row, col, tempparam, "TX");
         }
      }
   } else if ((glp.getSize() > 0) && isBottomStaffOnSystem(infile, row, col)) {
      // Only display text if placed below the staff
      for (ii=0; ii<glp.getSize(); ii++) {
         if (strcmp(glp.getCode(ii).getBase(), "TX") != 0) {
            continue;
         }
         if (!glp.hasKeyName(ii, "Z")) {
            tempparam.clear();
            tempparam.addEntry(glp, ii);
            addTextLayout(tempdata, infile, row, col, tempparam, "TX");
         }
      }
   }

   addTextLayout(tempdata, infile, row, col, lp, "TX");

   Array<int> chordmapping;
   chordmapping.setSize(tokencount);
   if (tokencount > 1) {
      getChordMapping(chordmapping, infile, row, col);
   } else {
      chordmapping.setAll(0);
   }

   for (kk=0; kk<tokencount; kk++) {
      k = chordmapping[kk];
      infile[row].getToken(buffer, col, k);
      arecord.clear();
      if ((voice > 0) && (voice < 10)) {
         // set the track number
         arecord.getColumn(15) = '0' + voice;
      }
      checkColor(Colorchar, arecord, buffer, Colorout);
      if (tickdur > 0) {
         arecord.setTicks(tickdur);
      } else {
         if (kk > 0) {
            arecord.setTypeGraceChordNote();
         } else {
            arecord.setTypeGraceNote();
         }
      }
      if (strchr(buffer, 'r') != NULL) {
         if ((strchr(buffer, ';') != NULL) && (strstr(buffer, ";y") == NULL)) {
            if(voice != 2) {
               arecord.addAdditionalNotation('F');
            } else{
               // put fermata underneath if rest is in the second voice
               arecord.addAdditionalNotation('E');
            }
         }
         if ((!noinvisibleQ) && strstr(buffer, "ry") != NULL) {
            // the rest should not be printed
            // also don't provide a shape for the rest.
            arecord.setPitch("irst");   // also "irest" can be used
         } else {
            if((rn == MeasureDur[row]) && (RscaleState[i][j] == 1)) {
               // the duration of the rest matches the duration of the measure,
               // so make the rest a centered whole rest.  If the duration
               // is greated than 4 quarter notes, maybe don't center?
               // To make a centered whole note shaped rests, put a space
               // in column 17:
               arecord.getColumn(17) = ' ';
               // sometimes have problems with blank rhythmic value
               // on rests [20120124].  So adding an explicit note
               // shape in certain conditions:
               if(rn * RscaleState[i][j] == 4) {
                  arecord.getColumn(17) = 'w';
               } else if (rn * RscaleState[i][j] == 8) {
                  arecord.getColumn(17) = 'B';
               }
            } else {
               if (strlen(visual_display) > 0) {
                  setNoteheadShape(arecord, visual_display);
               } else {
                  setNoteheadShape(arecord, buffer);
               }
            }
            arecord.setPitch("rest");
         }

         // added 20110815 so that column 20 tuplet info is filled in for 
         // rests:
         addNoteLevelArtic(arecord, infile, row, col, k);

         // added 20110815 so that rests can start/end tuplet brackets
         addChordLevelArtic(tempdata, arecord, psuggestion, 
               infile, row, col, voice);

         tempdata.append(arecord);
         if (kk == 0) {
            // also handles dynamics which are likely to have layout
            // information associated with them, and it is easier
            // to keep track of them in this function:
            handleLayoutInfoChord(tempdata, infile, row, col, lp, voice);
         }

         continue;
      }
      if (strlen(visual_display) > 0) {
         setNoteheadShape(arecord, visual_display);
      } else {
         setNoteheadShape(arecord, buffer);
      }

      if (hasLongQ) {
         // Longa notehead shape
         if (strchr(infile[row][col], LongChar) != NULL) {
            if(strlen(visual_display) == 0) {
               arecord.setNoteheadLong();
            }
            // current usage of the longa will not desire an
            // augmentation dot.  If it is ever needed, then 
            // there should be an option added to suppress
            // the dot in other cases.  Ideally, the addition of
            // an augmentation dot for longs should be an option
            // and the default behavior left as is.
            arecord.getColumn(18) = ' ';
            hideNotesAfterLong(infile, row, col);
         }
      }

      if (strchr(buffer, '/') != NULL) {   // stem up
         arecord.setStemUp();    
      }
      if (strchr(buffer, '\\') != NULL) {   // stem down
         arecord.setStemDown();    
      }

      if (!(hasLongQ && (strchr(infile[row][col], LongChar) != NULL))) {
         if (strchr(buffer, '[') != NULL) {
            // tie start
            if((strstr(buffer, "yy") != NULL) || (strstr(buffer, "[y") != NULL)) {
               arecord.setTie(1);
            } else {
               if(hidetieQ) {
                  arecord.setTie(hidetie);
               } else {
                  arecord.setTie(!tieQ);
               }
            }
         }
      }
      if (strchr(buffer, '_') != NULL) {
         // tie continuation
         if ((strstr(buffer, "yy") != NULL) || (strstr(buffer, "_y") != NULL)) {
            arecord.setTie(1);
         } else {
            if(hidetieQ) { 
               arecord.setTie(hidetie);
            } else {
               arecord.setTie(!tieQ);
            }
         }
      }
      if ((opentie) && (strchr(buffer, ']') != NULL)) {
         // this closing tie has no opening, so show a tie going
         // off to the left which is not tied to anything. 
         printPrehangTie(arecord, buffer, infile, row, col, voice);
      }

      if (kk == 0) {
         if (beamQ) {
            arecord.setBeamInfo(BeamState[i][j]);
         }
      }

      if (kk == 0) {
         // handle artculations
         addChordLevelArtic(tempdata, arecord, psuggestion, 
               infile, row, col, voice);
      }

      addNoteLevelArtic(arecord, infile, row, col, k);
      int graceQ = 0;
      if (strchr(buffer, 'q') != NULL) {
         graceQ = 1;
      }
      arecord.setPitch(Convert::kernToBase40(buffer), kk, graceQ);
      if ((strstr(buffer, "-y") == 0) || (strstr(buffer, "#y") == 0) ||
          (strstr(buffer, "ny") == 0)) {
         // hidden accidental
         arecord.getColumn(19) = ' ';
      }
      if (kk == 0) {
         addLyrics(arecord, infile, row, col, TextAssignment);
      }
      tempdata.append(arecord);
      if (!psuggestion.isEmpty()) {
         tempdata.append(psuggestion);
      }
      if (kk == 0) {
         // also handles dynamics which are likely to have layout
         // information associated with them, and it is easier
         // to keep track of them in this function:
         handleLayoutInfoChord(tempdata, infile, row, col, lp, voice);
      }
   }

   if (voice == 1) {
      addHairpinStops(tempdata, infile, row, col);
   }
   
   return tickdur;
}



//////////////////////////////
//
// printPrehaningTie -- add a hanging tie before a note.
//    K = slur dipping down
//    J = slur dipping up
//
//    If single voice on staff, then use stem direction to determine which
//    of J,K to use.  If there is no stem direction, then determine the clef
//    and then the staff position of the note (but input into this program
//    required a stem direction).  
//
//    If there are two voices, then the first voice will be stem up (use J), 
//    and the second will be down (use K)
//
//
//

void printPrehangTie(MuseRecord& arecord, const char* buffer, 
      HumdrumFile& infile, int row, int col, int voice) {

   if (!tieQ) {
      return;
   }

   int voicecount = 0;
   int track = infile[row].getPrimaryTrack(col);
   int ptrack;
   int j;
   for (j=0; j<infile[row].getFieldCount(); j++) {
      ptrack = infile[row].getPrimaryTrack(j);
      if (ptrack == track) {
         voicecount++;
      }
   }

   int stemdir = 0;
   if (strchr(buffer, '/') != NULL) {
      stemdir = 1; // stem up
   } else if (strchr(buffer, '\\') != NULL) {
      stemdir = -1; // stem down
   }

   if (voicecount > 1) {
      if (voice == 1) {
         // there are two voices, and this note is in the first voice, so make
         // the tie bend upwards
         arecord.addAdditionalNotation('J');
         return;
      } else if (voice == 2) {
         arecord.addAdditionalNotation('K');
         return;
      } else {
         // place the tie in the opposite direction of the beam
         if (stemdir > 0) {
            arecord.addAdditionalNotation('J');
            return;
         } else {
            arecord.addAdditionalNotation('K');
            return;
         }
      }
   }

   if (stemdir > 0) {
      arecord.addAdditionalNotation('K');
   } else {
      arecord.addAdditionalNotation('J');
   }

}



//////////////////////////////
//
// isTopStaffOnSystem -- return true if the primary track of the given
//    cell is the top part on the system.  Will not match to secondary tracks
//    other than the first one.
//

int isTopStaffOnSystem(int track) {
   if (track != KernTracks.last()) {
      return 0;
   }
   return 1; 
}


int isTopStaffOnSystem(HumdrumFile& infile, int row, int col) {
   if (KernTracks.getSize() == 0) {
      return 0;
   }

   // the last entry in KernTracks global variable is the top part
   int track = infile[row].getPrimaryTrack(col);

   // only return true if there is no "b" character in the track string
   if (strchr(infile[row].getSpineInfo(col), 'b') != NULL) {
      return 0;
   }

   return isTopStaffOnSystem(track);
}



//////////////////////////////
//
// isBottomStaffOnSystem -- return true if the primary track of the given
//    cell is the bottom part on the system.  Will not match to secondary 
//    tracks other than the first one.
//

int isBottomStaffOnSystem(HumdrumFile& infile, int row, int col) {
   if (KernTracks.getSize() == 0) {
      return 0;
   }

   // the first entry in KernTracks global variable is the top part
   int track = infile[row].getPrimaryTrack(col);
   if (track != KernTracks[0]) {
      return 0;
   }
   // only return true if there is no "b" character in the track string
   if (strchr(infile[row].getSpineInfo(col), 'b') != NULL) {
      return 0;
   }
   return 1; 
}



//////////////////////////////
//
// hidNotesAfterLong -- hides all notes tied after a long note.
//    also hides the barlines of any barlines which are found while removing.
//    currently presumes that each track does not have subspines.
//

void hideNotesAfterLong(HumdrumFile& infile, int row, int col) {
   if (strchr(infile[row][col], '[') == NULL) {
      // no ties on the long, so nothing to hide later.
      return;
   }
   int track = infile[row].getPrimaryTrack(col);
   RationalNumber endtime = infile[row].getAbsBeatR();
   endtime += infile.getTiedDurationR(row,col);
 
   int j;
   int i = row+1;
   char buffer[1024] = {0}; 
   while ((i<infile.getNumLines()) && (endtime >= infile[i].getAbsBeatR())) {
      if (infile[i].isMeasure()) {
         // hide measure
         for (j=0; j<infile[i].getFieldCount(); j++) {
            if(track != infile[i].getPrimaryTrack(j)) {
               continue;
            }
            strcpy(buffer, infile[i][j]);
            strcat(buffer, "yy");
            infile[i].changeField(j, buffer);
         }
      }
      if (!infile[i].isData()) {
         i++;
         continue; 
      }
      for (j=0; j<infile[i].getFieldCount(); j++) {
         if (track != infile[i].getPrimaryTrack(j)) {
            continue;
         }

         if (strcmp(infile[i][j], ".") == 0) {
            continue;
         }

         if ((strchr(infile[i][j], ']') != NULL) && 
             (strchr(infile[i][j], '_') != NULL)) {
            // no tie on note for some reason
            continue;
         }
         strcpy(buffer, infile[i][j]);
         strcat(buffer, "yy");
         infile[i].changeField(j, buffer);
         if (strchr(infile[i][j], ']') != NULL) {
            // this is the end of the tied group, so stop
            // making things invisible.
            return;
         }
         break;
      }

      i++;
   }


}



//////////////////////////////
//
// addLyrics --
//

void addLyrics(MuseRecord& arecord, HumdrumFile& infile, int row, int col, 
      Array<Array<int> >& TextAssignment) {
   int track = infile[row].getPrimaryTrack(col);
   int versecount = TextAssignment[track].getSize();
   if (versecount == 0) {
      // no text to print so return
      return;
   }

   // Muse2ps cannot handle more than 6 verses
   // but limiting to 5 since MuseData cannot handle more than 80 columns
   if (versecount > verselimit) {
      versecount = verselimit;
   }

   Array<int> trackcol;
   track2column(trackcol, infile, row);

   Array<Array<char> > verses;
   verses.setSize(versecount);
   int i;
   int texttrack;
   const char* ptr;
   PerlRegularExpression pre;
   char buffer[1024] = {0};
   int textcol;
   for (i=0; i<versecount; i++) {
      verses[i].setSize(1);
      verses[i][0] = '\0';
      textcol = trackcol[TextAssignment[track][i]];
      ptr = infile[row][textcol];
      if (strcmp(ptr, ".") == 0) {
         // don't print null records, but keep track of verse
         ptr = "";
      }
 
      // skip verse if blank.  This will need to be fixed later
      // to avoid verse ordering to get mixed up.
      if ((i > 0) && pre.search(ptr, "^\\s*$")) {
         // ggg
         continue;
      }
      if (i > 0) {
         strcat(buffer, "|");
      }
      int extensionneeded = 0;
      verses[i].setSize(strlen(ptr)+1);
      strcpy(verses[i].getBase(), ptr);
      texttrack = TextAssignment[track][i];
      PerlRegularExpression pre3;
      if (infile[row].isExInterp(trackcol[texttrack], "**text")) {
         convertHumdrumTextToMuseData(verses[i]);
         convertHtmlTextToMuseData(verses[i]);

         // if the verse syllable starts with a digit, then it will not be
         // printed by default in MuseData muse2ps program.  Adding
         // the string "\+" without quote in front of the number will
         // allow the number to be printed.
         if (isdigit(verses[i].getBase()[0]) && (strcmp(ptr, "") != 0)) {
            strcat(buffer, "\\+");
         }

         extensionneeded = 0;
         if (extensionQ && needsWordExtension(infile, row, col, textcol, 
               verses[i])) {
            extensionneeded = 1;
         }

         // removing & at end of line.  This means do not extend a line after
         // the syllable.
         if (strcmp(verses[i].getBase(), "&") == 0) {
            pre3.sar(verses[i], "^.*$", "\\+");
         } else {
            pre3.sar(verses[i], "&\\s*$", "");
         }

         strcat(buffer, verses[i].getBase());

         if (extensionneeded) {
            strcat(buffer, "_");
         }
      } else {
         // treat non **text spines as lyrics:
         // (Don't apply convertHumdrumTextToMuseData)
         convertHtmlTextToMuseData(verses[i]);
         // cannot print backslashes. (don't know why, \\ doesn't work)

         pre3.sar(verses[i], "\\\\", "", "g");
         // cannot print pipes (because it is a verse separator).
         pre3.sar(verses[i], "\\|", "", "g"); 
         // [change 20111222]
         pre3.sar(verses[i], "^\\s*$", "\\+", "");
         pre3.sar(verses[i], "^\\.$", "\\+", "");

         // if the verse starts with a digit, then it will not be
         // printed by default in MuseData muse2ps program.  Adding
         // the string "\+" without quote in front of the number will
         // allow the number to be printed.
         if (isdigit(verses[i].getBase()[0]) && (strcmp(ptr, "") != 0)) {
            strcat(buffer, "\\+");
         }

         extensionneeded = 0;
         if (extensionQ && needsWordExtension(infile, row, col, textcol, 
               verses[i])) {
            extensionneeded = 1;
         }

         // removing & at end of line.  This means do not extend a line after
         // the syllable.
         if (strcmp(verses[i].getBase(), "&") == 0) {
            pre3.sar(verses[i], "^.*$", "\\+");
         } else {
            pre3.sar(verses[i], "&\\s*$", "");
         }

         strcat(buffer, verses[i].getBase());

         if (extensionneeded) {
            strcat(buffer, "_");
         }
      }
   }

   // convert spaces to \+
   Array<char> newbuffer;
   newbuffer.setSize(strlen(buffer)+1);
   strcpy(newbuffer.getBase(), buffer);

   if (!pre.search(newbuffer, "^\\s*$")) {
      pre.sar(newbuffer, "\\s", "\\+", "g");
   }

   if (strlen(buffer) > 0) {
      arecord.insertString(44, newbuffer.getBase());
   }

}



//////////////////////////////
//
// needsWordExtension --
//

int needsWordExtension(HumdrumFile& infile, int row, int notecol, 
         int versecol, Array<char>& verse) {
   PerlRegularExpression pre;

   if (pre.search(verse, "-\\s*$")) {
      // doesn't need a word extender if not end of word...
      return 0;
   }

   if (pre.search(verse, "&\\s*$")) {
      // doesn't need a word extender. Using "&" at the end of the syllable
      // means do not put a line extender.  This is an impromptu
      // method which may change.
      return 0;
   }

   if (!pre.search(verse, "[a-z]", "i")) {
      // don't print a line extender if there are no letters in the syllable.
      return 0;
   }

   if (strcmp(verse.getBase(), "\\+") == 0) {
      // don't print if only a space character.
      return 0;
   } 

   int i, j;
   int ntrack = infile[row].getPrimaryTrack(notecol);
   int vtrack = infile[row].getPrimaryTrack(versecol);

   int newnote  = 0;
   int newverse = 0;
   int track;

   for (i=row+1; i<infile.getNumLines(); i++) {
      if (!infile[i].isData()) {
         continue;
      }
      newnote = 0;
      newverse = 0;
      for (j=0; j<infile[i].getFieldCount(); j++) {
         track = infile[i].getPrimaryTrack(j);
         if ((track != ntrack) && (track != vtrack)) {
            continue;
         }
         if (track == ntrack) {
            if(pre.search(infile[i][j], "[a-g]", "i")) {
               newnote = 1;
            }
         }
         if (track == vtrack) {
            if(pre.search(infile[i][j], "[a-z0-9]", "i")) {
               newverse = 1;
            }
         }
      }
      if (newverse) {
         break;
      } else if (newnote) {
         return 1;
      }
   }

   return 0;
}



//////////////////////////////
//
// convertHumdrumTextToMuseData -- convert text in the **text format into
//    text for MuseData lyrics.
//

void convertHumdrumTextToMuseData(Array<char> & text) {
   PerlRegularExpression pre;
   PerlRegularExpression pre2;

   if (pre.sar(text, "^\\s*$", " ", "")) {
      return;
   }
   // if (pre.sar(text, "^\\s*$", "\\+", "")) {
   //    // a blank syllable
   //    return;
   // }
 
   pre.sar(text, " \\*", ".", "g"); // convert period      " *" -> "."
   pre.sar(text, " ,",   ",", "g"); // convert comma       " ," -> ","
   pre.sar(text, " \\?", "?", "g"); // convert question    " ?" -> "?"
   pre.sar(text, " !",   "!", "g"); // convert exclamation " !" -> "!"
   pre.sar(text, " :",   ":", "g"); // convert colon       " :" -> ":"
   pre.sar(text, " ;",   ";", "g"); // convert semi-colon  " ;" -> ";"
   pre.sar(text, "~",    "-", "g"); // word hypen          "~"  -> "-"

   pre.sar(text, "^-", "", "");                 // remove staring hyphen marker

   pre.sar(text, "2a", "\\===a===3", "g");     // umlauts
   pre.sar(text, "2e", "\\===e===3", "g");     // umlauts
   pre.sar(text, "2i", "\\===i===3", "g");     // umlauts
   pre.sar(text, "2o", "\\===o===3", "g");     // umlauts
   pre.sar(text, "2u", "\\===u===3", "g");     // umlauts
   pre.sar(text, "2A", "\\===A===3", "g");     // umlauts
   pre.sar(text, "2E", "\\===E===3", "g");     // umlauts
   pre.sar(text, "2I", "\\===I===3", "g");     // umlauts
   pre.sar(text, "2O", "\\===O===3", "g");     // umlauts
   pre.sar(text, "2U", "\\===U===3", "g");     // umlauts
   pre.sar(text, "2y", "\\===y===3", "g");     // umlauts
   pre.sar(text, "2Y", "\\===Y===3", "g");     // umlauts

   pre.sar(text, "7n", "\\===n===1", "g");     // tildes
   pre.sar(text, "7N", "\\===N===1", "g");     // tildes
   pre.sar(text, "7a", "\\===a===1", "g");     // tildes
   pre.sar(text, "7e", "\\===e===1", "g");     // tildes
   pre.sar(text, "7i", "\\===i===1", "g");     // tildes
   pre.sar(text, "7o", "\\===o===1", "g");     // tildes
   pre.sar(text, "7u", "\\===u===1", "g");     // tildes
   pre.sar(text, "7A", "\\===A===1", "g");     // tildes
   pre.sar(text, "7E", "\\===E===1", "g");     // tildes
   pre.sar(text, "7I", "\\===I===1", "g");     // tildes
   pre.sar(text, "7O", "\\===O===1", "g");     // tildes
   pre.sar(text, "7U", "\\===U===1", "g");     // tildes
   pre.sar(text, "7y", "\\===y===1", "g");     // tildes
   pre.sar(text, "7Y", "\\===Y===1", "g");     // tildes

   pre.sar(text, "/a", "\\===a===7", "g");     // acute
   pre.sar(text, "/e", "\\===e===7", "g");     // acute
   pre.sar(text, "/i", "\\===i===7", "g");     // acute
   pre.sar(text, "/o", "\\===o===7", "g");     // acute
   pre.sar(text, "/u", "\\===u===7", "g");     // acute
   pre.sar(text, "/A", "\\===A===7", "g");     // acute
   pre.sar(text, "/E", "\\===E===7", "g");     // acute
   pre.sar(text, "/I", "\\===I===7", "g");     // acute
   pre.sar(text, "/O", "\\===O===7", "g");     // acute
   pre.sar(text, "/U", "\\===U===7", "g");     // acute
   pre.sar(text, "/y", "\\===y===7", "g");     // acute
   pre.sar(text, "/Y", "\\===Y===7", "g");     // acute

   pre.sar(text, "\\\\a", "\\===a===8", "g");   // grave
   pre.sar(text, "\\\\e", "\\===e===8", "g");   // grave
   pre.sar(text, "\\\\i", "\\===i===8", "g");   // grave
   pre.sar(text, "\\\\o", "\\===o===8", "g");   // grave
   pre.sar(text, "\\\\u", "\\===u===8", "g");   // grave
   pre.sar(text, "\\\\A", "\\===A===8", "g");   // grave
   pre.sar(text, "\\\\E", "\\===E===8", "g");   // grave
   pre.sar(text, "\\\\I", "\\===I===8", "g");   // grave
   pre.sar(text, "\\\\O", "\\===O===8", "g");   // grave
   pre.sar(text, "\\\\U", "\\===U===8", "g");   // grave
   pre.sar(text, "\\\\y", "\\===y===8", "g");   // grave
   pre.sar(text, "\\\\Y", "\\===Y===8", "g");   // grave

   pre.sar(text, "\\^a", "\\===a===9", "g");    // circumflex
   pre.sar(text, "\\^e", "\\===e===9", "g");    // circumflex
   pre.sar(text, "\\^i", "\\===i===9", "g");    // circumflex
   pre.sar(text, "\\^o", "\\===o===9", "g");    // circumflex
   pre.sar(text, "\\^u", "\\===u===9", "g");    // circumflex
   pre.sar(text, "\\^A", "\\===A===9", "g");    // circumflex
   pre.sar(text, "\\^E", "\\===E===9", "g");    // circumflex
   pre.sar(text, "\\^I", "\\===I===9", "g");    // circumflex
   pre.sar(text, "\\^O", "\\===O===9", "g");    // circumflex
   pre.sar(text, "\\^U", "\\===U===9", "g");    // circumflex
   pre.sar(text, "\\^y", "\\===y===9", "g");    // circumflex
   pre.sar(text, "\\^Y", "\\===Y===9", "g");    // circumflex

   pre.sar(text, "===", "", "g"); // get rid of buffer characters

   // macron: 1
   // cedilla: 5

   // pre.sar(text, "\\s+", "\\0", "g"); // word elision character doesn't work
 
   pre.sar(text, "\\|", "", "g");  // disable dashes for now.

}

void convertHtmlTextToMuseData(Array<char> & text) {
   PerlRegularExpression pre;
   
   pre.sar(text, "ä", "\\a3", "g");   // umlaut
   pre.sar(text, "ë", "\\e3", "g");   // umlaut
   pre.sar(text, "ï", "\\i3", "g");   // umlaut
   pre.sar(text, "ö", "\\o3", "g");   // umlaut
   pre.sar(text, "ü", "\\u3", "g");   // umlaut
   pre.sar(text, "Ä", "\\A3", "g");   // umlaut
   pre.sar(text, "Ë", "\\E3", "g");   // umlaut
   pre.sar(text, "Ï", "\\I3", "g");   // umlaut
   pre.sar(text, "Ö", "\\O3", "g");   // umlaut
   pre.sar(text, "Ü", "\\U3", "g");   // umlaut
   pre.sar(text, "ÿ", "\\y3", "g");   // umlaut
   pre.sar(text, "Ÿ", "\\Y3", "g");   // umlaut
   pre.sar(text, "ñ", "\\n1", "g"); // tilde
   pre.sar(text, "Ñ", "\\N1", "g"); // tilde
   pre.sar(text, "ã", "\\a1", "g"); // tilde
   pre.sar(text, "&etilde;", "\\e1", "g"); // tilde
   pre.sar(text, "ĩ", "\\i1", "g"); // tilde
   pre.sar(text, "õ", "\\o1", "g"); // tilde
   pre.sar(text, "ũ", "\\u1", "g"); // tilde
   pre.sar(text, "Ã", "\\A1", "g"); // tilde
   pre.sar(text, "&Etilde;", "\\E1", "g"); // tilde
   pre.sar(text, "Ĩ", "\\I1", "g"); // tilde
   pre.sar(text, "Õ", "\\O1", "g"); // tilde
   pre.sar(text, "Ũ", "\\U1", "g"); // tilde
   pre.sar(text, "&ytilde;", "\\y1", "g"); // tilde
   pre.sar(text, "&Ytilde;", "\\Y1", "g"); // tilde

   pre.sar(text, "à", "\\a8", "g"); // grave
   pre.sar(text, "è", "\\e8", "g"); // grave
   pre.sar(text, "ì", "\\i8", "g"); // grave
   pre.sar(text, "ò", "\\o8", "g"); // grave
   pre.sar(text, "ù", "\\u8", "g"); // grave
   pre.sar(text, "À", "\\A8", "g"); // grave
   pre.sar(text, "È", "\\E8", "g"); // grave
   pre.sar(text, "Ì", "\\I8", "g"); // grave
   pre.sar(text, "Ò", "\\O8", "g"); // grave
   pre.sar(text, "Ù", "\\U8", "g"); // grave
   pre.sar(text, "&ygrave;", "\\y8", "g"); // grave
   pre.sar(text, "&Ygrave;", "\\Y8", "g"); // grave

   pre.sar(text, "á", "\\a7", "g"); // acute
   pre.sar(text, "é", "\\e7", "g"); // acute
   pre.sar(text, "í", "\\i7", "g"); // acute
   pre.sar(text, "ó", "\\o7", "g"); // acute
   pre.sar(text, "ú", "\\u7", "g"); // acute
   pre.sar(text, "Á", "\\A7", "g"); // acute
   pre.sar(text, "É", "\\E7", "g"); // acute
   pre.sar(text, "Í", "\\I7", "g"); // acute
   pre.sar(text, "Ó", "\\O7", "g"); // acute
   pre.sar(text, "Ú", "\\U7", "g"); // acute
   pre.sar(text, "ý", "\\y7", "g"); // acute
   pre.sar(text, "Ý", "\\Y7", "g"); // acute

   pre.sar(text, "â", "\\a9", "g"); // circumflex
   pre.sar(text, "ê", "\\e9", "g"); // circumflex
   pre.sar(text, "î", "\\i9", "g"); // circumflex
   pre.sar(text, "ô", "\\o9", "g"); // circumflex
   pre.sar(text, "û", "\\u9", "g"); // circumflex
   pre.sar(text, "Â", "\\A9", "g"); // circumflex
   pre.sar(text, "Ê", "\\E9", "g"); // circumflex
   pre.sar(text, "Î", "\\I9", "g"); // circumflex
   pre.sar(text, "Ô", "\\O9", "g"); // circumflex
   pre.sar(text, "Û", "\\U9", "g"); // circumflex
   pre.sar(text, "ŷ", "\\y9", "g"); // circumflex
   pre.sar(text, "Ŷ", "\\Y9", "g"); // circumflex

   pre.sar(text, "ß", "\\s2", "g"); // ss
   pre.sar(text, "ç", "\\c2", "g"); // c-cedilla
   pre.sar(text, "ø", "\\o2", "g"); // o-slash

   pre.sar(text, "å", "\\a4", "g"); // ring
   pre.sar(text, "&ering;", "\\e4", "g"); // ring
   pre.sar(text, "&iring;", "\\i4", "g"); // ring
   pre.sar(text, "&oring;", "\\o4", "g"); // ring
   pre.sar(text, "ů", "\\u4", "g"); // ring
   pre.sar(text, "Å", "\\A4", "g"); // ring
   pre.sar(text, "&Ering;", "\\E4", "g"); // ring
   pre.sar(text, "&Iring;", "\\I4", "g"); // ring
   pre.sar(text, "&Oring;", "\\O4", "g"); // ring
   pre.sar(text, "Ů", "\\U4", "g"); // ring

   // 5's are v (hachek) accent (\\s5)
   // 6's?
   pre.sar(text, "¿", "\\0?", "g"); // inverted question mark

   pre.sar(text, ":", ":", "g");     // colon (:)
   pre.sar(text, "&eqals;", "=", "g");     // equals sign (=)
   pre.sar(text, "&", "&", "g");       // ampersand (&)

   pre.sar(text, "^Fine$", "Fine\\+", ""); // workaround to force Fine printing
   pre.sar(text, "^fine$", "fine\\+", ""); // workaround to force fine printing
   pre.sar(text, "D\\. C\\.", "D.\\+C.", "g"); // workaround for Da capo

}


//////////////////////////////
//
// track2column --
//

void track2column(Array<int>& trackcol, HumdrumFile& infile, int row) {
   trackcol.setSize(infile.getMaxTracks()+1);
   trackcol.setAll(-1);
   int j, track;
   for (j=0; j<infile[row].getFieldCount(); j++) {
      track = infile[row].getPrimaryTrack(j);
      if (trackcol[track] < 0) {
         // don't process sub-spines other than the first
         trackcol[track] = j; 
       }
   }
}



//////////////////////////////
//
// addTextLayout -- add some free-form text to the music
//

void addTextLayout(MuseData& tempdata, HumdrumFile& infile, int row, int col, 
      LayoutParameters& lp, const char* code) {
   int i;
   for (i=0; i<lp.getSize(); i++) {
      if (strcmp(lp.getCode(i).getBase(), code) != 0) {
         continue;
      }
      addText(tempdata, lp.getKeys(i), lp.getValues(i));
      // don't break, add more text if it is found
   }
}



//////////////////////////////
//
// addText -- add text from a layout parameter list.
//
// i = italic
// b = bold
// t = text
//

void addText(MuseData& tempdata, Array<Array<char> >& keys, 
      Array<Array<char> >& values) {

   int i;
   int italicQ = 0;
   int boldQ = 0;
   const char* text = "";

   char justification = 'D';  // left justified
   //justification = 'C';     // center justified
   //justification = 'B';     // right justified

   int dsize = 0;

   for (i=0; i<keys.getSize(); i++) {
      if (strcmp(keys[i].getBase(), "i") == 0) {
         italicQ = 1;
      } else if (strcmp(keys[i].getBase(), "b") == 0) {
         boldQ = 1;
      } else if (strcmp(keys[i].getBase(), "bi") == 0) {
         boldQ = 1;
         italicQ = 1;
      } else if (strcmp(keys[i].getBase(), "ib") == 0) {
         boldQ = 1;
         italicQ = 1;
      } else if (strcmp(keys[i].getBase(), "t") == 0) {
         text = values[i].getBase();
      } else if (strcmp(keys[i].getBase(), "rj") == 0) {
         justification = 'B';
      } else if (strcmp(keys[i].getBase(), "cj") == 0) {
         justification = 'C';
      } else if (strcmp(keys[i].getBase(), "hide") == 0) {
         // don't print text, it is turned off for some reason
         return;  
      } else if (strcmp(keys[i].getBase(), "sz") == 0) {
         if (strcmp(values[i].getBase(), "l") == 0) {
            dsize += 4;
         } else if (strcmp(values[i].getBase(), "large") == 0) {
            dsize += 4;
         } else if (strcmp(values[i].getBase(), "s") == 0) {
            dsize -= 4;
         } else if (strcmp(values[i].getBase(), "small") == 0) {
            dsize -= 4;
         } else if (strcmp(values[i].getBase(), "t") == 0) {
            dsize -= 8;
         } else if (strcmp(values[i].getBase(), "tiny") == 0) {
            dsize -= 8;
         }
      }
   }
   dsize = 0;   // font sizes don't seem to be all available
                // turning off for now.

   if (strlen(text) == 0) {
      // no text to print...
      return;
   }
   Array<char> textstring;

   Array<char> textstring2;
   textstring.setSize(strlen(text)+1);
   strcpy(textstring.getBase(), text);
   convertHtmlTextToMuseData(textstring);

   int fontnumber = 31;   // roman, regular size
   if (boldQ && !italicQ) {
      fontnumber = 32;    // bold, regular size
   } else if (italicQ && !boldQ) {
      fontnumber = 33;    // italic, regular size
   } else if (italicQ && boldQ) {
      // can't do both italic and bold at the same time? Setting to italic
      fontnumber = 33;    // italic & bold, regular size
   }
   fontnumber += dsize;

   int column = 17;
   MuseRecord arecord;
   arecord.appendString("*");
   arecord.getColumn(column) = justification;
   arecord.insertString(25, textstring.getBase());
   tempdata.append(arecord);

   // add font info
   arecord.clear();
   if ((dsize != 0) || italicQ || boldQ) {
      arecord.append("sisi", "P C", column, ":f" , fontnumber);
      tempdata.append(arecord);
   }

   addPositionParameters(tempdata, column, keys, values);

}



//////////////////////////////
//
// addHairpinStarts -- place cresc and decresc hairpins in music.  In theory
//     the dynamic markings can be added to the start/stop of the
//     hairpin, but these are handled in handleLayoutInfoChord.
//

void addHairpinStarts(MuseData& tempdata, HumdrumFile& infile, 
      int row, int col) {


   PerlRegularExpression pre;

   Array<int>& da = DynamicsAssignment;
   int j; 
   int track = infile[row].getPrimaryTrack(col);
   int targettrack = da[track];
   if (targettrack <= 0) {
      // this **kern track does not have a dynamics assignment.
      return;
   }
   int dcol = -1;
   for (j=0; j<infile[row].getFieldCount(); j++) {
      track = infile[row].getPrimaryTrack(j);
      if (track != targettrack) {
         continue;
      }
      dcol = j;
      break;
   }
   if (dcol < 0) {
      // could not find **dynam track for some reason...
      return;
   }
   if (strcmp(infile[row][dcol], ".") == 0) {
      // dynamics column only contains a null token, nothing interesting.
      return;
   }
   if (strcmp(infile[row][dcol], "(") == 0) {
      // crescendo continuation marker, not encoded in MuseData
      return;
   }
   if (strcmp(infile[row][dcol], ")") == 0) {
      // decrescendo continuation marker, not encoded in MuseData
      return;
   }

   // only one dynamic in a **dynam token expected, not checking for more.
   // but don't print any dynamic which has "yy" after it which means
   // the dynamic is invisible.
   if (pre.search(infile[row][dcol], "yy", "")) {
      // ignore entire string if yy is found.  Fix later so that
      // multiple subtokens can be processed, with perhaps one with yy
      // and another without.
      return;
   }
   if (!pre.search(infile[row][dcol], "([<>])(?!yy)", "")) {
      return;
   }

   LayoutParameters lpd;
   lpd.parseLayout(infile, LayoutInfo[row][dcol]);

   // If there is an X appended to the < or >, then it should also not
   // be printed as a hairpin since it is a written word.

   PerlRegularExpression pre2;
   if (pre2.search(infile[row][dcol], "([\\[\\]]).*[<>]X?", "")) {
      // there is a stoping crescendo/decrescendo occuring before
      // the next one start, so stop it before the new one starts.
      if (strcmp(pre2.getSubmatch(1), "[") == 0) {
         addCrescendoStop(tempdata, lpd);
      } else if (strcmp(pre2.getSubmatch(), "]") == 0) {
         addDecrescendoStop(tempdata, lpd);
      }
   }

   if (!pre.search(infile[row][dcol], "([<>]X?)", "")) {
      return;
   }

   // The "X" character after a crescendo or decrescendo start mark
   // indidates that a text version of the symbol should be printed.
   // (note that the crescento or descrescendo stop mark needs to
   // be suppresed in these cases, so also add an "X" to the end 
   // of the cresc/decresc (otherwise muse2ps will currently 
   // quit without producing any output).
   if (strcmp(">", pre.getSubmatch(1)) == 0) {
      addDecrescendoStart(tempdata, lpd);
   } else if (strcmp("<", pre.getSubmatch(1)) == 0) {
      addCrescendoStart(tempdata, lpd);
   } else if (strcmp(">X", pre.getSubmatch(1)) == 0) {
      addCrescText(tempdata, infile, row, col, dcol, lpd, "decresc.");
   } else if (strcmp("<X", pre.getSubmatch(1)) == 0) {
      addCrescText(tempdata, infile, row, col, dcol, lpd, "cresc.");
   }
}



//////////////////////////////
//
// addUnDash -- turn off a dashing which was previously turned on.
//

void addUnDash(MuseData& tempdata, HumdrumFile& infile, int row, int col, 
      int dcol, LayoutParameters& lpd) {
   int track = infile[row].getPrimaryTrack(col);
   if (!DashState[track]) {
      // dashing is not turned on for this part
      return;
   }
   MuseRecord arecord;
   int column = 17;
   arecord.getColumn(1) = '*';
   arecord.getColumn(column) = 'J';   // turns off dashing
   tempdata.append(arecord);

   // turn off the dashing state
   DashState[track] = 0;
}



//////////////////////////////
//
// addCrescText -- place the word "cresc." in the music (instead of a 
//    crescendo symbol).  If the layout code contains the parameter
//    "dash", then add a dash, and keep track of it for later turning
//    off with the paired [X.  The "text" input parameter is the
//    default text to display, unless there is some alternate text 
//    in the "t" layout parameters.
//

void addCrescText(MuseData& tempdata, HumdrumFile& infile, int row, int col, 
      int dcol, LayoutParameters& lp, const char* text) {

   int i;
   int pind;
   for (i=0; i<lp.getSize(); i++) {
      if (strcmp("HP", lp.getCode(i).getBase()) != 0) {
         continue;
      }
      pind  = lp.getParameter(i, "t");
      if (pind >= 0) {
         text = lp.getValue(i,pind).getBase();
      }
   }

   MuseRecord arecord;
   arecord.getColumn(1) = '*';
   int column = 17;
   // allow all these later:
   // D = left-justified
   // C = centered
   // B = right-justified
   arecord.getColumn(column) = 'D';
   arecord.insertString(25, text);
   tempdata.append(arecord); 
   int dQ = addDashing(tempdata,column+1, infile[row].getPrimaryTrack(col), lp);

   int fontnumber = 33;  // 33 = italic
   arecord.clear();
   arecord.append("sisi", "P C", column, ":f", fontnumber);
   tempdata.append(arecord);

   addPositionInfo(tempdata, column, lp, "TX");
   if (dQ) {
      addPositionInfo(tempdata, column+1, lp, "TX");
   }
}



//////////////////////////////
//
// addDashing --
//

int addDashing(MuseData& tempdata, int column, int track, 
      LayoutParameters& lp) {
   // check for a "DY" code with a "dash" key
   int dashQ = 0;
   int i, j;
   for (i=0; i<lp.getSize(); i++) {
      if ((strcmp(lp.getCode(i).getBase(), "DY") != 0) && 
          (strcmp(lp.getCode(i).getBase(), "HP") != 0)
            ) {
         continue;
      }
      for (j=0; j<lp.getCodeSize(i); j++) {
         if (strcmp(lp.getKey(i,j).getBase(), "dash") != 0) {
            continue;
         }
         dashQ = 1;
         break;
      }
      if (dashQ) {
         break;
      }
   }

   if (!dashQ) {
      return 0;
   }

   // add "H" in column of last element in tempdata
   MuseRecord& arecord = tempdata[tempdata.getNumLines()-1];
   if (arecord.getColumn(1) != '*') {
      cerr << "Error: line expected to be a * record: " << NEWLINE;
      cerr << arecord << NEWLINE;
   }
   if (DashState[track]) {
      cerr << "Error dashed line in track " << track 
           << " while another is being started " << NEWLINE;
      exit(1);
   }

   arecord.getColumn(column) = 'H';

   // turn on dashing state which will control turning off later
   DashState[track] = 1;
   return 1;

}



//////////////////////////////
//
// addDecrescendoStart -- Add a decrescendo hairpin start.  Also will
//       process layout information.
//

void addDecrescendoStart(MuseData& tempdata, LayoutParameters& lp) {
   MuseRecord arecord;
   char buffer[32] = {0};
   int  spread = 12;
   int  column = 18;

   arecord.getColumn(1) = '*';
   arecord.getColumn(column) = 'E';
   sprintf(buffer, "%d", spread);
   arecord.insertStringRight(23, buffer);
   tempdata.append(arecord);

   addPositionInfo(tempdata, column, lp, "HP");
}



//////////////////////////////
//
// addPositionInfo --
//

void addPositionInfo(MuseData& tempdata, int column, LayoutParameters lp, 
      const char* code) {

   int i;
   for (i=0; i<lp.getSize(); i++) {
      if (strcmp(lp.getCode(i).getBase(), code) != 0) {
         continue;
      }

      addPositionParameters(tempdata, column, lp.getKeys(i), lp.getValues(i));

      // Only process the first directive that matches.
      // Think about multiple ones later?
      break;
   }
}



//////////////////////////////
//
// addPositionParameters  -- final step from AddPostionInfo, or from
// elsewhere if parameter list from layout message has been extracted.
//

void addPositionParameters(MuseData& tempdata, int column, 
      Array<Array<char> >& keys, Array<Array<char> >& values) {

   Array<int> vals;
   Array<int> states;
   getXxYy(vals, states, keys, values);

   int& X = vals[0];    int& x = vals[1];
   int& Y = vals[2];    int& y = vals[3];
   int& XQ = states[0]; int& xQ = states[1];
   int& YQ = states[2]; int& yQ = states[3];

   char buffer[128] = {0};
   char nbuff[32] = {0};

   if (!(xQ || XQ || yQ || YQ)) {
      // no position information so don't do anything
      return;
   }
  
   if (xQ) {
      sprintf(nbuff, "%d", x); 
      strcat(buffer, "x"); 
      strcat(buffer, nbuff);
   } else if (XQ) {
      sprintf(nbuff, "%d", X);
      strcat(buffer, "x");     // X does not seem to work, to remap to x
      strcat(buffer, nbuff);
   }
  
   if (yQ) {
      sprintf(nbuff, "%d", y); strcat(buffer, "y"); strcat(buffer, nbuff);
   } else if (YQ) {
      sprintf(nbuff, "%d", Y); strcat(buffer, "Y"); strcat(buffer, nbuff);
   }

   if (strlen(buffer) == 0) {
      // something strange happend, don't print anything
      return;
   }

   char buffer2[128] = {0};
   sprintf(buffer2, " C%d:%s", column, buffer);
   
   // if there is already a print suggestion line, then the new
   // print suggestion must occur on the same line (appended to end).
   if ((!tempdata.isEmpty()) && (tempdata.last().getColumn(1) == 'P')) {
      MuseRecord& arecord = tempdata.last();
      arecord.appendString(buffer2);
   } else {
      MuseRecord arecord;
      arecord.appendString("P");
      arecord.appendString(buffer2);
      tempdata.append(arecord);
   }
}



//////////////////////////////
//
// addCrescendoStart -- Add a crescendo hairpin start.  Also will
//       process layout information.
//

void addCrescendoStart(MuseData& tempdata, LayoutParameters& lp) {
   MuseRecord arecord;
   char buffer[32] = {0};
   int  spread = 0;
   int  column = 18;

   arecord.getColumn(1) = '*';
   arecord.getColumn(column) = 'E';
   sprintf(buffer, "%d", spread);
   arecord.insertStringRight(23, buffer);
   tempdata.append(arecord);

   addPositionInfo(tempdata, column, lp, "HP");
}



//////////////////////////////
//
// addDecrescendoStop -- Add a descrescendo hairpin stop.  Also will
//       process layout information.
//

void addDecrescendoStop(MuseData& tempdata, LayoutParameters& lp) {
   MuseRecord arecord;
   char buffer[32] = {0};
   int  spread = 0;
   int  column = 17;  // can also be 18, but reserve for optional dynamic

   arecord.getColumn(1) = '*';
   arecord.getColumn(column) = 'F';
   sprintf(buffer, "%d", spread);
   arecord.insertStringRight(23, buffer);
   tempdata.append(arecord);

   // Note that the MuseData printing system ignores Y adjustments
   // to the ends of dyanmics hairpins.
   addPositionInfo(tempdata, column, lp, LO_WEDGE_END);
}



//////////////////////////////
//
// addCrescendoStop -- Add a screscendo hairpin stop.  Also will
//       process layout information.
//

void addCrescendoStop(MuseData& tempdata, LayoutParameters& lp) {
   MuseRecord arecord;
   char buffer[32] = {0};
   int  spread = 12;
   int  column = 17;  // can also be 18, but reserve for optional dynamic

   arecord.getColumn(1) = '*';
   arecord.getColumn(column) = 'F';
   sprintf(buffer, "%d", spread);
   arecord.insertStringRight(23, buffer);
   tempdata.append(arecord);

   // Note that the MuseData printing system ignores Y adjustments
   // to the ends of dyanmics hairpins.
   addPositionInfo(tempdata, column, lp, LO_WEDGE_END);
}



//////////////////////////////
//
// addHairpinStops -- place cresc and decresc hairpins in music.  In theory
//     the dynamic markings can be added to the start/stop of the
//     hairpin, but these are handled in handleLayoutInfoChord.
//

void addHairpinStops(MuseData& tempdata, HumdrumFile& infile, int row, 
      int col) {
   PerlRegularExpression pre;

   Array<int>& da = DynamicsAssignment;
   int j; 
   int track = infile[row].getPrimaryTrack(col);
   int targettrack = da[track];
   if (targettrack <= 0) {
      // this **kern track does not have a dynamics assignment.
      return;
   }
   int dcol = -1;
   for (j=0; j<infile[row].getFieldCount(); j++) {
      track = infile[row].getPrimaryTrack(j);
      if (track != targettrack) {
         continue;
      }
      dcol = j;
      break;
   }
   if (dcol < 0) {
      // could not find **dynam track for some reason...
      return;
   }
   if (strcmp(infile[row][dcol], ".") == 0) {
      // dynamics column only contains a null token, nothing interesting.
      return;
   }
   if (strcmp(infile[row][dcol], "(") == 0) {
      // crescendo continuation marker, not encoded in MuseData
      return;
   }
   if (strcmp(infile[row][dcol], ")") == 0) {
      // decrescendo continuation marker, not encoded in MuseData
      return;
   }

   // only one dynamic in a **dynam token expected, not checking for more.
   // but don't print any dynamic which has "yy" after it which means
   // the dynamic is invisible.
   if (pre.search(infile[row][dcol], "yy", "")) {
      // ignore entire string if yy is found.  Fix later so that
      // multiple subtokens can be processed, with perhaps one with yy
      // and another without.
      return;
   }
   if (!pre.search(infile[row][dcol], "([\\[\\]])(?!yy)", "")) {
      return;
   }

   PerlRegularExpression pre2;
   if (pre2.search(infile[row][dcol], "([\\[\\]]).*[<>]", "")) {
      // the stop is for the previous crescendo, and a new one is
      // starting at this note, so don't handle the stop, since it
      // was handled before the start was printed.  However, there
      // is a possible case which is currently being missed:
      // if there are two stops, and one is after start, then
      // need to print the second stop.  Add that case later...
      return;
   }

   if (!pre.search(infile[row][dcol], "([\\[\\]]X?)", "")) {
      return;
   }

   LayoutParameters lpd;
   lpd.parseLayout(infile, LayoutInfo[row][dcol]);

   // If there is an X appended to the < or >, then it should not
   // be printed as a hairpin since it is a written word.
   if (strcmp("[", pre.getSubmatch(1)) == 0) {
      addCrescendoStop(tempdata, lpd);
   } else if (strcmp("]", pre.getSubmatch(1)) == 0) {
      addDecrescendoStop(tempdata, lpd);
   } else if (strcmp("]X", pre.getSubmatch(1)) == 0) {
      // suppress printing a * record.
      addUnDash(tempdata, infile, row, col, dcol, lpd);
   } else if (strcmp("[X", pre.getSubmatch(1)) == 0) {
      // suppress printing a * record.
      addUnDash(tempdata, infile, row, col, dcol, lpd);
   }
}



//////////////////////////////
//
// getChordMapping -- chords are displayed with the note furthest from
//    the stem displayed first, then the other notes, with the last
//    note in the chord list being the one on the stem side of the chord.
//

void getChordMapping(Array<int>& chordmapping, HumdrumFile& infile, int row, 
      int col) {
  
   Array<int> pitches;
   getPitches(pitches, infile, row, col);

   int stemdir = +1;
   if (strchr(infile[row][col], '\\') != NULL) {
      stemdir = -1;
   }
   // do automatic analysis for whole notes here, but this will involve 
   // knowing the clef which the notes are placed...
   
   Array<Coord> tempp;
   tempp.setSize(pitches.getSize());
   int i;
   for (i=0; i<tempp.getSize(); i++) {
      tempp[i].i = i;
      tempp[i].j = pitches[i];
   }

   if (stemdir > 0) {
      // stemdir is up, so sort notes from low to high
      qsort(tempp.getBase(), tempp.getSize(), sizeof(Coord), numbersort);
   } else {
      // stemdir is down, so sort notes from high to low
      qsort(tempp.getBase(), tempp.getSize(), sizeof(Coord), numberRsort);
   }

   chordmapping.setSize(pitches.getSize());
   for (i=0; i<chordmapping.getSize(); i++) {
      chordmapping[i] = tempp[i].i;
   }

}



//////////////////////////////
//
// numberRsort -- sort items largest first.
//

int numberRsort(const void* A, const void* B) {
   Coord valuea = *((Coord*)A);
   Coord valueb = *((Coord*)B);
   if (valuea.j > valueb.j) {
      return -1;
   } else if (valuea.j < valueb.j) {
      return +1;
   } else {
      return 0;
   }
}



//////////////////////////////
//
// numbersort -- sort smallest largest first.
//

int numbersort(const void* A, const void* B) {
   Coord valuea = *((Coord*)A);
   Coord valueb = *((Coord*)B);
   if (valuea.j > valueb.j) {
      return +1;
   } else if (valuea.j < valueb.j) {
      return -1;
   } else {
      return 0;
   }
}



//////////////////////////////
//
// getPitches --
//

void getPitches(Array<int>& pitches, HumdrumFile& infile, int row, int col) {
   int k;
   int tokencount = infile[row].getTokenCount(col);
   pitches.setSize(tokencount);
   pitches.setAll(0);
   char buffer[128] = {0};
   for (k=0; k<tokencount; k++) {
      infile[row].getToken(buffer, col, k, 100);
      pitches[k] = Convert::kernToBase40(buffer);
   }
}



//////////////////////////////
//
// handleLayoutInfoChord --  Generate print suggestions which are related
//     only to the first note in a chord.  The chord note in question is
//     currently the last item in the tempdata.
//

void handleLayoutInfoChord(MuseData& indata, HumdrumFile& infile, int row, 
      int col, LayoutParameters& lp, int voice) {

   PerlRegularExpression pre;
   PerlRegularExpression pre2;
   Array<Array<char> > pfields;
   MuseRecord prec;
   prec.setLine("P");
   char notepsbuffer[128] = {0};
   char tbuffer[128] = {0};
   infile[row].getToken(tbuffer, col, 0);

   int i, j;
   if (lp.getSize() != 0 ) {
      for (i=0; i<lp.getSize(); i++) {

         if (slurQ && (strcmp(lp.getCode(i).getBase(), "S") == 0)) {
            // slur layout code. check for "a" or "b"
            for(j=0; j<lp.getCodeSize(i); j++) {
               if (strcmp(lp.getKey(i,j).getBase(), "a") == 0)  {
                  // add a slur-above print suggestion;
                  insertSlurUpPrintSug(indata, prec);
               } else if (strcmp(lp.getKey(i,j).getBase(), "b") == 0)  {
                  // add a slur-below print suggestion;
                  insertSlurDownPrintSug(indata, prec);
               }
            }
         }

         else if (strcmp(lp.getCode(i).getBase(), "SC") == 0)  {
            // staccato layout code (change to articulation?)
            for(j=0; j<lp.getCodeSize(i); j++) {
               if (strcmp(lp.getKey(i,j).getBase(), "a") == 0)  {
                  // add a staccato-above print suggestion;
                  insertStaccatoSuggestion(indata, prec, +1);
               } else if (strcmp(lp.getKey(i,j).getBase(), "b") == 0)  {
                  // add a staccato-below print suggestion;
                  insertStaccatoSuggestion(indata, prec, -1);
               }
            }
         }

         else if (strcmp(lp.getCode(i).getBase(), "N") == 0)  {
            convertKernLONtoMusePS(notepsbuffer, lp.getKeys(i),lp.getValues(i), 
                  tbuffer);
         }
      }
   }

   if (strstr(tbuffer, "yy") != NULL) {
      // [20111016] Invisible rests sometimes causing problems with the
      // voices placed on separate staves, so allowing a removal of the
      // hidden markers.
      if (!noinvisibleQ) {
         strcat(notepsbuffer, "p1");   // make note or rest invisible
      }
   }
   if (strlen(notepsbuffer) > 0) {
      prec.append("ss", "  C1:", notepsbuffer);
   }
    
   if ((voice == 1) && dynamicsQ) {
      addDynamics(infile, row, col, indata, prec, DynamicsAssignment, lp);
   }

   if (strlen(prec.getLine()) > 1) {
      // if data was added to the P record, then store it.
      indata.append(prec);
   }

}



//////////////////////////////
//
// addDynamics -- addDynamics to the current note if it has a **dynam
//   dynamic on the current line according to the DynamicsAssignment.
//

void addDynamics(HumdrumFile& infile, int row, int col, MuseData& indata, 
      MuseRecord& prec, Array<int>& DynamicsAssignment, 
      LayoutParameters& lp) {

   PerlRegularExpression pre;
   MuseRecord& last = indata[indata.getNumLines()-1];
   int loc;

   // first handle "z" and "zz" marks in the **kern data.  If the **kern
   // data has a "z", then it maps to "sf" dynamic.  If the **kern data
   // has a "zz", then it maps to sfz.
   // This could should perhaps go outside of the addDynamics function,
   // since the sforzandos will not be printed if there is not a **dynam
   // spine for the part. (use the sfzQ variable for that).
   if (pre.search(infile[row][col], "zz(?!y)")) {
      // produce an sfz dynamic accent
      // don't know how to do that yet, to add sf mark instead:
      loc = last.addAdditionalNotation("Z");
      // handle layout information next.
      processDynamicsLayout(loc, prec, lp);
   } else if (pre.search(infile[row][col], "z(?!y)")) {
      // produce an sf dynamic accent
      loc = last.addAdditionalNotation("Z");
      // handle layout information next.
      processDynamicsLayout(loc, prec, lp);
   }

   Array<int>& da = DynamicsAssignment;
   int j; 
   int track = infile[row].getPrimaryTrack(col);
   int targettrack = da[track];
   if (targettrack <= 0) {
      // this **kern track does not have a dynamics assignment.
      return;
   }
   int dcol = -1;
   for (j=0; j<infile[row].getFieldCount(); j++) {
      track = infile[row].getPrimaryTrack(j);
      if (track != targettrack) {
         continue;
      }
      dcol = j;
      break;
   }
   if (dcol < 0) {
      // could not find **dynam track for some reason...
      return;
   }
   if (strcmp(infile[row][dcol], ".") == 0) {
      // dynamics column only contains a null token, nothing interesting.
      return;
   }
   if (strcmp(infile[row][dcol], "(") == 0) {
      // crescendo continuation marker, not encoded in MuseData
      return;
   }
   if (strcmp(infile[row][dcol], ")") == 0) {
      // decrescendo continuation marker, not encoded in MuseData
      return;
   }

   LayoutParameters lpd; // parameters for **dynam spines
   lpd.parseLayout(infile, LayoutInfo[row][dcol]);

   // only one dynamic in a **dynam token expected, not checking for more.
   // but don't print any dynamic which has "yy" after it which means
   // the dynamic is invisible.
   if (pre.search(infile[row][dcol], "yy", "")) {
      // ignore entire string if yy is found.  Fix later so that
      // multiple subtokens can be processed, with perhaps one with yy
      // and another without.
      return;
   }
   if (!pre.search(infile[row][dcol], "([mpfrsz]+)(?!yy)", "")) {
      return;
   }

   Array<char> value;
   value.setSize(strlen(pre.getSubmatch(1))+1);
   strcpy(value.getBase(), pre.getSubmatch());
   pre.sar(value, "^sf$", "Z", "g");
   // sfz?
   pre.sar(value, "sfp", "Zp", "g");
   pre.sar(value, "rfz", "R", "g");

   // loc stores the column number where the dynamic was stored.
   loc = last.addAdditionalNotation(value.getBase());
   processDynamicsLayout(loc, prec, lpd);
}



/////////////////////////////
//
// processDynamicsLayout --
//

void processDynamicsLayout(int loc, MuseRecord& prec, LayoutParameters& lp) {
   char buffer[128]  = {0};
   int i;
   for (i=0; i<lp.getSize(); i++) {
      if (strcmp(lp.getCode(i).getBase(), "DY") != 0) {
         continue;
      }
      convertKernLODYtoMusePS(buffer, lp.getKeys(i), lp.getValues(i));
      if (strlen(buffer) > 0) {
         prec.append("siss", " C", loc, ":", buffer);
      }
      break;
   }
}



//////////////////////////////
//
// getXxYy -- return the X, x, Y, and y MuseData parameters from 
//      the Humdrum Layut paramters, X, x, Y, y, Z, z
//
//      X=1:y=-54:z=56.34
//
//      X = 1
//      y = -54
//      z = 56
//

void getXxYy(Array<int>& vals, Array<int>& states, Array<Array<char> >& keys,
      Array<Array<char> >& values) {

   vals.setSize(4);   vals.setAll(0);
   states.setSize(4); states.setAll(0);

   int& X = vals[0];    int& x = vals[1];
   int& Y = vals[2];    int& y = vals[3];
   int& XQ = states[0]; int& xQ = states[1];
   int& YQ = states[2]; int& yQ = states[3];

   double value;
   int i;

   for (i=0; i<keys.getSize(); i++) {
      if ((strcmp(keys[i].getBase(), "x") == 0) && 
            (strlen(values[i].getBase()) > 0)) {
         value = strtod(values[i].getBase(), NULL);
         x = int(value); 
         xQ = 1;
      } else if ((strcmp(keys[i].getBase(), "y") == 0) && 
            (strlen(values[i].getBase()) > 0)) {
         value = strtod(values[i].getBase(), NULL);
         y = int(value); 
         yQ = 1;
      } else if ((strcmp(keys[i].getBase(), "z") == 0) && 
            (strlen(values[i].getBase()) > 0)) {
         value = strtod(values[i].getBase(), NULL);
         y = -1 * int(value); 
         yQ = 1;
      } else if ((strcmp(keys[i].getBase(), "X") == 0) && 
            (strlen(values[i].getBase()) > 0)) {
         value = strtod(values[i].getBase(), NULL);
         X = int(value); 
         XQ = 1;
      } else if ((strcmp(keys[i].getBase(), "Y") == 0) && 
            (strlen(values[i].getBase()) > 0)) {
         value = strtod(values[i].getBase(), NULL);
         Y = int(value) + 40;
         YQ = 1;
      } else if ((strcmp(keys[i].getBase(), "Z") == 0) && 
            (strlen(values[i].getBase()) > 0)) {
         value = strtod(values[i].getBase(), NULL);
         Y = -int(value);
         YQ = 1;
      }
   }
}



//////////////////////////////
//
// convertKernLODYtoMusePS --
//

void convertKernLODYtoMusePS(char* buffer, Array<Array<char> >& keys,
      Array<Array<char> >& values) {
   if (keys.getSize() == 0) {
      return;
   }
   buffer[0] = '\0';
   char buffer2[128] = {0};

   Array<int> vals;
   Array<int> states;
   getXxYy(vals, states, keys, values);

   int& X  = vals[0];    // maps from layout X
   int& x  = vals[1];    // maps from layout x
   int& Y  = vals[2];    // maps from layout y or -z
   int& y  = vals[3];    // maps from layout Y or Z
   int& XQ = states[0]; int& xQ = states[1];
   int& YQ = states[2]; int& yQ = states[3];

   if (xQ) { sprintf(buffer2, "x%d", x); strcat(buffer, buffer2); }
   if (yQ) { sprintf(buffer2, "y%d", y); strcat(buffer, buffer2); }
   if (XQ) { sprintf(buffer2, "X%d", X); strcat(buffer, buffer2); }
   if (YQ) { sprintf(buffer2, "Y%d", Y); strcat(buffer, buffer2); }
}



//////////////////////////////
//
// convertKernLONtoMusePS -- Note layout suggestion
//

void convertKernLONtoMusePS(char* buffer, Array<Array<char> >& keys,
      Array<Array<char> >& values, const char* token) {
   if (keys.getSize() == 0) {
      return;
   }
   buffer[0] = '\0';
   char buffer2[128] = {0};

   Array<int> vals;
   Array<int> states;
   getXxYy(vals, states, keys, values);

   int& X  = vals[0];    // maps from layout X
   int& x  = vals[1];    // maps from layout x
   int& Y  = vals[2];    // maps from layout y or -z
   int& y  = vals[3];    // maps from layout Y or Z
   int& XQ = states[0]; int& xQ = states[1];
   int& YQ = states[2]; int& yQ = states[3];

   if (xQ) { sprintf(buffer2, "x%d", x); strcat(buffer, buffer2); }
   if (yQ) { sprintf(buffer2, "y%d", y); strcat(buffer, buffer2); }
   if (XQ) { sprintf(buffer2, "X%d", X); strcat(buffer, buffer2); }
   if (YQ) { sprintf(buffer2, "Y%d", Y); strcat(buffer, buffer2); }

   // check for LO:N:vis=dot layout directive:
   int i;

   if (strstr(token, "yy") != NULL) {
      // note should be hidden.  Don't do anything now, it will be
      // processed later.
   } else {
      // check for LO:N:vis=dot
      for (i=0; i<keys.getSize(); i++) {
         if (strcmp(keys[i].getBase(), "vis") != 0) {
            continue;
         }
         if (strcmp(values[i].getBase(), "dot") == 0) {
            strcat(buffer, "p2");
            break;
         }
         if (strcmp(values[i].getBase(), "x") == 0) {
            strcat(buffer, "s1");   // make an x notehead shape
            break;
         }
      }

      int value;
      // check for stem and beam length code
      for (i=0; i<keys.getSize(); i++) {
         if (strcmp(keys[i].getBase(), "smy") == 0) {
            value = atoi(values[i].getBase());
            sprintf(buffer2, " C23:y%d", value); strcat(buffer, buffer2);
         } else if (strcmp(keys[i].getBase(), "smY") == 0) {
            value = atoi(values[i].getBase());
            sprintf(buffer2, " C23:Y%d", value); strcat(buffer, buffer2);
         } else if (strcmp(keys[i].getBase(), "bmy") == 0) {
            value = atoi(values[i].getBase());
            sprintf(buffer2, " C26:y%d", value); strcat(buffer, buffer2);
         } else if (strcmp(keys[i].getBase(), "bmY") == 0) {
            value = atoi(values[i].getBase());
            sprintf(buffer2, " C26:y%d", value); strcat(buffer, buffer2);
         }
      }
  
   }

}



//////////////////////////////
//
// insertStaccatoSuggestion --
//

void insertStaccatoSuggestion(MuseData& data, MuseRecord& prec, 
      int direction) {
   int last = data.getLineCount() - 1;

   // last line should be the note which has the staccato
   int staccatoColumn = data[last].findField('.', 32, 43);
   if (staccatoColumn < 0) {
      return;
   }
   char buffer[32] = {0};
   if (direction < 0) {
      sprintf(buffer, " C%d:b", staccatoColumn);
   } else {
      sprintf(buffer, " C%d:a", staccatoColumn);
   }
   prec.appendString(buffer);
}



//////////////////////////////
//
// insertSlurUpPrintSug --
//

void insertSlurUpPrintSug(MuseData& data, MuseRecord& prec) {
   int last = data.getLineCount() - 1;

   // last line should be the note which has the slur
   int slurcolumn = data[last].getSlurStartColumn();
   if (slurcolumn < 0) {
      return;
   }
   char buffer[32] = {0};
   sprintf(buffer, " C%d:o", slurcolumn);
   prec.appendString(buffer);
}



//////////////////////////////
//
// insertSlurDownPrintSug --
//

void insertSlurDownPrintSug(MuseData& data, MuseRecord& prec) {
   int last = data.getLineCount() - 1;

   // last line should be the note which has the slur
   int slurcolumn = data[last].getSlurStartColumn();
   if (slurcolumn < 0) {
      return;
   }
   char buffer[32] = {0};
   sprintf(buffer, " C%d:u", slurcolumn);
   prec.appendString(buffer);
}



//////////////////////////////
//
// getBase36 --
//

char getBase36(int value) {
   if (value > 35) {
      cerr << "Error cannot value  " << value << NEWLINE;
      exit(1);
   }
   char output;
   if (value < 10) {
      output = '0' + value;
   } else {
      output = 'A' + value - 10;
   }
   return output;
}



//////////////////////////////
//
// addNoteLevelArtic -- might not make senese always in a chord note,
// but print anyway...
// 
//

void addNoteLevelArtic(MuseRecord&  arecord, HumdrumFile& infile, 
      int row, int col, int tnum) {
   const char* token = infile[row][col];

   char buffer[128] = {0};
   infile[row].getToken(buffer, col, tnum, 100);

   // if (TupletState[row][col] != ' ') {
   if (TupletTopNum[row][col] != 0) {
      char value = getBase36(TupletTopNum[row][col]);
      // rests are not processing the next line: 20110815
      arecord.getColumn(20) = value;
      // clear out rest of tuplet field (in case something is there):
      arecord.getColumn(21) = ' ';
      arecord.getColumn(22) = ' ';
   }

   if (hasFictaQ) {
      // presumes there is no chord in token...
      // if there is a "y" before the ficta marker, then don't make editorial.
      char tbuf[8] = {0};
      tbuf[0] = 'y';
      tbuf[1] = FictaChar;
      tbuf[2] = '\0';
      if (strstr(token, tbuf) == NULL) {
         // musical ficta markings.
         if (strchr(token, FictaChar) != NULL) {
            arecord.addAdditionalNotation('^');
            arecord.addAdditionalNotation('+');  // always force display of acc.
         }
      }
   }

   if (strstr(buffer, "-X") != NULL) {
      // explicitly written out flat (or double flat)
      arecord.addAdditionalNotation('+');
   } else if (strstr(buffer, "#X") != NULL) {
      // explicitly written out sharp (or double sharp)
      arecord.addAdditionalNotation('+');
   } else if (strstr(buffer, "nX") != NULL) {
      // doubly explicit written natural sign
      arecord.addAdditionalNotation('+');
   } else if (strchr(buffer, 'n') != NULL) {
      // written natural sign
      // but only if the string "ny" is not found:
      if (strstr(buffer, "ny") == NULL) {
         arecord.getColumn(19) = 'n';
         arecord.addAdditionalNotation('+');
      } else {
         arecord.getColumn(19) = ' ';
      }
   }

   if (strchr(token, ':') != NULL) {
      // arpeggio
      // and arpeggio is handled as a chord-level articulation 
      // (as a dummy grace note), Adding an 'S' to the 
      // additional notations section of the note.  But this 
      // does not actually print an arpeggio in the muse2ps program
      // output, so can't really use.
      // arecord.addAdditionalNotation('S');
   }

}



//////////////////////////////
//
// addChordLevelArtic --
//

void addChordLevelArtic(MuseData& tempdata, MuseRecord&  arecord, 
      MuseRecord& printsuggestion, HumdrumFile& infile, 
      int row, int col, int voice) {
   const char* token = infile[row][col];
   printsuggestion.clear();

   if (tupletQ) {
      if (TupletState[row][col] == '*') {
         int tloc = arecord.addAdditionalNotation('*');  // start of tuplet
         // if the note does not have a beam, then place
         // a bracket.  This will not work in the generalized case
         // where there may be multiple beamed/unbeamed notes under
         // the same tuplet mark.
         //
         // To add a tuplet bracket, place a print suggestion after
         // the note (or is it chord? which will have to be checked later).
         // The print suggestion for  bracket is ':'.  Exmaple
         // P C32::
         RationalNumber rn;
         rn = getDurationNoDots(infile[row][col], "4");
         RationalNumber note8th(1,2);
         if (rn > note8th) {
            printsuggestion.append("sis", "P C", tloc, "::");
         }

      } else if (TupletState[row][col] == '!') {
         arecord.addAdditionalNotation('!');  // end of tuplet
      }
   } else if (vzQ) {
      if (strchr(infile[row][col], 'V') != NULL) {
         int tloc = arecord.addAdditionalNotation('*');  // start of tuplet
         RationalNumber rn;
         rn = getDurationNoDots(infile[row][col], "4");
         RationalNumber note8th(1,2);
         if (rn > note8th) {
            printsuggestion.append("sis", "P C", tloc, "::");
         }
      } else if (strchr(infile[row][col], 'Z') != NULL) {
         arecord.addAdditionalNotation('!');  // end of tuplet
      }
   }

   if (strchr(token, ':') != NULL) {
      // insert an arpeggio
      insertArpeggio(tempdata, infile, row, col);
   }

   if ((strchr(token, ';') != NULL) && (strstr(token, ";y") == NULL)) {
      // F = fermata above staff
      // E = fermata below staff (not considered here).
      if (voice != 2) {
         arecord.addAdditionalNotation('F');
      } else {
         arecord.addAdditionalNotation('E');
      }
   }

   if ((strchr(token, '`') != NULL) || (strstr(token, "\'\'") != NULL)) {
      // staccatissmo/spiccato
      arecord.addAdditionalNotation('i');
   } else if (strchr(token, '\'') != NULL) {
      if (strchr(token, '~') != NULL) {
         // tenuto tenuto
         arecord.addAdditionalNotation('=');
      } else {
         // staccato
         arecord.addAdditionalNotation('.');
      }
   }

   if (strchr(token, '~') != NULL) {
      if (strchr(token, '\'') == NULL) {
         // tenuto
         arecord.addAdditionalNotation('_');
      }
   }

   if (strchr(token, ',') != NULL) {
      // breath mark (does not work in muse2ps?)
      arecord.addAdditionalNotation(',');
   }

   if (strchr(token, 'o') != NULL) {
      // harmonic
      arecord.addAdditionalNotation('o');
   }

   if (strchr(token, 'v') != NULL) {
      // up-bow (does not work in muse2ps?)
      arecord.addAdditionalNotation('v');
   }

   if (strchr(token, 'u') != NULL) {
      // down-bow
      arecord.addAdditionalNotation('n');
   }

   if (strchr(token, '^') != NULL) {
      // accent
      arecord.addAdditionalNotation('>');
   }

   if (slurQ) {
     
      // have to keep track of slurs when occur between 
      // two separate levels...

      if (strchr(token, ')') != NULL) {
         // slur end, only considering a single level at the moment...
         // place slurs in each layer in a separate slur level:
         switch (voice % 4) {
            case 1: arecord.addAdditionalNotation(')'); break;
            case 2: arecord.addAdditionalNotation(']'); break;
            case 3: arecord.addAdditionalNotation('}'); break;
            case 0: arecord.addAdditionalNotation('x'); break;
        }
      }
      if (strchr(token, '(') != NULL) {
         // slur end, only considering a single level at the moment...
         // place slurs in each layer in a separate slur level:
         switch (voice %4) {
             case 1: arecord.addAdditionalNotation('('); break;
             case 2: arecord.addAdditionalNotation('['); break;
             case 3: arecord.addAdditionalNotation('{'); break;
             case 0: arecord.addAdditionalNotation('z'); break;
         }
      }
   }

   if (strchr(token, 't') != NULL) {
      // trill (half-tone)
      arecord.addAdditionalNotation('t');
   } else if (strchr(token, 'T') != NULL) {
      // trill (whole-tone)
      arecord.addAdditionalNotation('t');
   }

}



//////////////////////////////
//
// insertArpeggio -- and arpeggio mark is a grace note before the
//    note(s) to which is applies.  The pitch of the grace note is arbitrary
//    and is only used to position the arpeggio sign.  If the arpeggio 
//    is to be placed in normal spacing, place the grace note pitch outside
//    stem region of the grace note  (place above highest pitch in chord if
//    the stem is down, or below the lowest pitch if the stem is down). This
//    function currently does not arpeggiate multiple voices (or staves in
//    a grand staff).
//

void insertArpeggio(MuseData& tempdata, HumdrumFile& infile, int row, int col) {
   PerlRegularExpression pre;
   int stemdir = +1;
   int& i = row;
   int& j = col;
   if (pre.search(infile[i][j], "^[^ ]*\\\\", "")) {
      // if the first note listed in the chord contains a down stem, then
      // switch the direction of the stem.
      stemdir = -1;
   }
   Array<int> pitches;
   getPitches(pitches, infile, row, col);
   Array<int> chordmapping;

   // the first chord in chordmapping is furthest from the stem
   // side of the note.
   getChordMapping(chordmapping, infile, row, col);

   if (pitches.getSize() == 0) {
      // can't arpeggiate empty chord, so don't attempt anything
      return;
   }
   if (chordmapping.getSize() == 0) {
      // can't arpeggiate empty chord, so don't attempt anything
      return;
   }

   int firstpitch = pitches[chordmapping[0]];
   int lastpitch  = pitches[chordmapping.last()];

   int firstvpos; // vertical position of the note on the staff, using
                  // diatonic steps from bottom line of staff (E4 in
                  // treble clef).
   int lastvpos;  // vertical postion of note closest  to the stem side
                  // of the chord.

   firstvpos = getScoreStaffVerticalPos(firstpitch, i, j, 
         ClefState, infile);
   lastvpos = getScoreStaffVerticalPos(lastpitch, i, j, 
         ClefState, infile);

   int gracepitch;   // pitch of dummy notes to mark arpeggio
   int posbot;  // staff position of the bottom of the arpeggio
   int postop;  // staff position of the bottom of the arpeggio
   double pbot;
   double ptop;

   if (firstvpos > lastvpos) {
      // chord is stem down
      
      // set grace note pitch above top (first) note in chord.
      gracepitch = firstpitch + 23;

      if (((firstvpos + 1000) % 2) == 0) { // note is on a space
         ptop = (12-firstvpos-2) / 2.0;    // 1 space higher than note
      } else {                             // note is on a line
         ptop = (12-firstvpos-1) / 2.0;    // 1/2 space higher than note
      }

      if (((lastvpos + 1000) % 2) == 0) {  // note is on a space
         pbot = (12-lastvpos+1) / 2.0;     // 1/2 space lower than note
      } else {                             // note is on a line
         pbot = (12-lastvpos+1) / 2.0;     // 1/2 space lower than note
      }
 
      if      (pbot > 0) { posbot = int(pbot+0.5); }
      else if (pbot < 0) { posbot = int(pbot); }
      else               { posbot = 0; }
   
      if      (ptop > 0) { postop = int(ptop+0.5); }
      else if (ptop < 0) { postop = int(ptop); }
      else               { postop = 0; }

   } else {
      // chord is stem up (or is a single note, or multiple
      // notes at the same diatonic pitch level, which may
      // cause problems).
      
      // set grace note pitch below bottom (first) note in chord.
      gracepitch = firstpitch - 23;
      pbot = int(12-firstvpos+1) / 2.0;
      ptop = int(12-lastvpos-1)  / 2.0;

      if (((lastvpos + 1000) % 2) == 0) { // note is on a space
         ptop = (12-lastvpos-2) / 2.0;    // 1 space higher than note
      } else {                             // note is on a line
         ptop = (12-lastvpos-1) / 2.0;    // 1/2 space higher than note
      }

      if (((firstvpos + 1000) % 2) == 0) {  // note is on a space
         pbot = (12-firstvpos+1) / 2.0;     // 1/2 space lower than note
      } else {                             // note is on a line
         pbot = (12-firstvpos+1) / 2.0;     // 1/2 space lower than note
      }

      if      (pbot > 0) { posbot = int(pbot+0.5); }
      else if (pbot < 0) { posbot = int(pbot); }
      else               { posbot = 0; }
   
      if      (ptop > 0) { postop = int(ptop+0.5); }
      else if (ptop < 0) { postop = int(ptop); }
      else               { postop = 0; }
   }

   // MuseData bottom position cannot be greater than two digits
   // including the negative sign.
   if (posbot >= 100) {  posbot = 99; }
   if (postop >= 100) {  postop = 99; }
   if (posbot <= -10) {  posbot = -9; }
   if (postop <= -10) {  postop = -9; }

   // the arpeggio positions are always from the highest to lowest
   // positions, so print postop in column 17/18 and posbot in column 20/21

   // the posbot (actually start of arpeggio) is stored in columns 17/18
   // the postop (actually end  of arpeggio) is stored in columns 20/21
   // if the posbot or postop are only 1 digit (1-9), then placing in
   // column 17/20 works (maybe also column 18/21).

   char buffer[32] = {0};
   
   MuseRecord arecord;
   // column 1: "g" for grace note:
   arecord.getColumn(1) = 'g'; 
   // columns 2-5: pitch (used to space arpeggio, not a real note)
   arecord.insertString(2, Convert::base40ToMuse(gracepitch, buffer));
   // columns 6-7 blank
   // columns 8: "X" which means grace-note is a dummy for arpeggios
   arecord.getColumn(8) = 'X';   
   // columns 9-12 blank
   // columns 13-15: footnote, level, track number
   // columns 16 blank
   // columns 17-21 vertical parameters of arpeggio:
   // column 17-18 are
   sprintf(buffer, "%d", postop);
   arecord.insertString(17, buffer);
   // column 19 grand staff marker (not set by this algorithm)
   // column 20-21: ending level of arpeggio.
   sprintf(buffer, "%d", posbot);
   arecord.insertString(20, buffer);
   // column 22-23 blank
   // column 24 = staff number (space is 1).
   // columns 25 to end: not used
   
   tempdata.append(arecord);
}



//////////////////////////////
//
// getScoreStaffVerticalPos --
// 

int getScoreStaffVerticalPos(int note, int line, int row, 
      Array<Array<Coord> >& clefs, HumdrumFile& infile) {

   const char* clef = "*clefG2";  // default clef if none specified.
   int i = clefs[line][row].i;
   int j = clefs[line][row].j;
   
   if ((i < 0) || (j < 0)) {
      // use the default clef
   } else {
      clef = infile[i][j];
   }

   int sclef = 0;
   if (strncmp(clef, "*clefG", 5) == 0) {
      sclef = 0;
   } else if (strncmp(clef, "*clefF", 5) == 0) {
      sclef = 1;
   } else if (strncmp(clef, "*clefC", 5) == 0) {
      sclef = 2;
   }
   int position = Convert::base40ToScoreVPos(note, sclef);

   if (strstr(clef, "vv") != NULL) {
      // transpose up two octaves for printed pitch
      position += 14;
   } else if (strchr(clef, 'v') != NULL) {
      // transpose up one octave for printed pitch
      position += 7;
   } else if (strstr(clef, "^^") != NULL) {
      // transpose down two octaves for printed pitch
      position += -14;
   } else if (strchr(clef, '^') != NULL) {
      // transpose down one octaves for printed pitch
      position += -7;
   }

   PerlRegularExpression pre;
   // make adjustments for some other more exotic clefs
   if (pre.search(clef, "^\\*clefC.*?(\\d)", "")) {
      if (strcmp(pre.getSubmatch(1), "2") == 0) {        // mezzo-soprano clef
         position += -2;
      } else if (strcmp(pre.getSubmatch(1), "1") == 0) { // soprano clef
         position += -4;
      } else if (strcmp(pre.getSubmatch(1), "4") == 0) { // tenor clef
         position += +2;
      } else if (strcmp(pre.getSubmatch(1), "5") == 0) { // baritone clef
         position += +4;
      }
   } else if (pre.search(clef, "^\\*clefG.*?(\\d)", "")) {
      if (strcmp(pre.getSubmatch(1), "1") == 0) {        // violin treble clef
         position += -2;
      }
   }
   return position;
}



/////////////////////////////
//
// setNoteheadShape --
//

void setNoteheadShape(MuseRecord& arecord, const char* token) {
   int dotcount = 0;
   int i;
   PerlRegularExpression pre;
   if ((!pre.search(token, "\\d", "")) && (!pre.search(token, "q", "i"))) {
      // look at the default duration for a dot.
      token = defaultDur;
   }
   int len = strlen(token);

   for (i=0; i<len; i++) {
      if (token[i] == ' ') {
         break;
      }
      if (token[i] == '.') { // presuming no null token input.
         dotcount++;
      }
   }

   arecord.setDots(dotcount);


   RationalNumber rn;
   rn = getDurationNoDots(token, defaultDur);
   //if (dotcount == 1) {
   //   rn *= 2;
   //   rn /= 3;
   //} else if (dotcount == 2) {
   //   rn *= 4;
   //   rn /= 7;
   //}

   RationalNumber zeroR(0,1);

   if (rn != zeroR) {
      if (!mensuralQ) {
         arecord.setNoteheadShape(rn);
      } else {
         arecord.setNoteheadShapeMensural(rn);
      }
   } else {
      setGracenoteVisualRhythm(arecord, token);
   }

}



//////////////////////////////
//
// setGracenoteVisualRhythm -- set the grace note as a quarter, eighth, 
//     sixteenth, etc. 
//
//     q = slash on note
//     Q = no slash on note (probably a appogiatura)
//

void setGracenoteVisualRhythm(MuseRecord& arecord, const char* token) {
   PerlRegularExpression pre;
   RationalNumber eighth(1,2);
   Array<char> strang;
   strang.setSize(strlen(token) + 1);
   strcpy(strang.getBase(), token);
   pre.sar(strang, "[^0-9]", "", "g");
   RationalNumber rn;
   if (strlen(strang.getBase()) == 0) {
      rn = 1;
      rn /= 2;   // 1/2 = eighth note
   } else {
      rn = Convert::kernToDurationR(strang.getBase());
   }
   if (rn == 0) { 
     rn = 1;
     rn /= 8;   // 1/2 = eighth note
   }
   if ((rn == eighth) && (pre.search(token, "q", ""))) {
      // eighth note shaped grace note with a q should have a slash.
      arecord.getColumn(8) = '0';
      // all 'q' notes should have a slash, but muse2ps will only
      // print on an eighth note.
   } else {
      arecord.setNoteheadShape(rn);
   }
}



//////////////////////////////
//
// checkColor --
//

void checkColor(Array<char>& colorchar, MuseRecord& arecord, 
      const char* token, Array<char>& colorout) {
   int i;
   for (i=0; i<colorchar.getSize(); i++) {
      if (strchr(token, colorchar[i]) != NULL) {
         arecord.getColumn(14) = colorout[i];
         break;
      }
   }
}



///////////////////////////////
//
// getTickDur --
//

int getTickDur(int tpq, HumdrumFile& infile, int row, int col) {
   RationalNumber rn = getDuration(infile[row][col], defaultDur);
   rn *= tpq;

   if (rn.getNumerator() == 0) {
      return 0;
   }

   if (rn.getDenominator() != 1) {
      cerr << "Error in token: " << infile[row][col] << NEWLINE;
      cerr << "Tick value = " << rn << NEWLINE;
      cerr << "Ticks per quarter: " << tpq << NEWLINE;
      cerr << "Original rhythmic duration: " << (rn / tpq) << NEWLINE;
      exit(1);
   }

   return rn.getNumerator();
}



//////////////////////////////
//
// addMeasureEntry --  print a measure, and print suggestions immediately
//    afterwards.
//

void addMeasureEntry(MuseData& tempdata, HumdrumFile& infile, int line, 
      int col) {

   MuseRecord arecord;
   char buffer[128] = {0};
   strcpy(buffer, "measure");
   PerlRegularExpression pre;
   int measurenum = -1;


   if (pre.search(infile[line][col], "-", "")) {
      strcpy(buffer, "msilent");
   } else if (pre.search(infile[line][col], "\\.", "")) {
      strcpy(buffer, "mdotted");
   } else if (pre.search(infile[line][col], "^==", "")) {
      strcpy(buffer, "mheavy2");
   } 

   if (pre.search(infile[line][col], "^=.*?(\\d+)", "")) {
      measurenum = atoi(pre.getSubmatch(1));
      if (measurenum >= 0) {
        strcat(buffer, " ");
        strcat(buffer, pre.getSubmatch());
      }
   } 

   int track = infile[line].getPrimaryTrack(col);
   int hasUnterminatedTies = 0;
   if (hangtieQ) {
      hasUnterminatedTies = printUnterminatedTies(tempdata, infile, line, 
         track);
      if (hasUnterminatedTies) {
         // add a marker to indicate that there are unterminated ties
         // at this barline.
         arecord.getColumn(17) = '&';
      }
   }

   arecord.insertString(1, buffer);


   // #define MDOUBLE "measure"
   #define MDOUBLE "mdouble"
   // process measure style
   if (pre.search(infile[line][col], "yy")) {
      arecord.insertString(1, "msilent");
   } else if (pre.search(infile[line][col], ":\\|!\\|:", "")) {
      arecord.addMeasureFlag(":||:");
      kflag |= k_lrbar;
      arecord.insertString(1, "mheavy4");
   } else if (pre.search(infile[line][col], ":!!:", "")) {
      arecord.addMeasureFlag(":||:");
      arecord.insertString(1, "mheavy4");
   } else if (pre.search(infile[line][col], ":\\|\\|:", "")) {
      arecord.addMeasureFlag(":||:");
      arecord.insertString(1, MDOUBLE);
   } else if (pre.search(infile[line][col], ":\\|!", "")) {
      arecord.addMeasureFlag(":|");
      arecord.insertString(1, "mheavy2");
   } else if (pre.search(infile[line][col], ":\\|\\|", "")) {
      arecord.addMeasureFlag(":|");
      arecord.insertString(1, MDOUBLE);
   } else if (pre.search(infile[line][col], ":\\|", "")) {
      arecord.addMeasureFlag(":|");
   } else if (pre.search(infile[line][col], "!\\|:", "")) {
      arecord.addMeasureFlag("|:");
      arecord.insertString(1, "mheavy3");
   } else if (pre.search(infile[line][col], "\\|\\|:", "")) {
      arecord.insertString(1, MDOUBLE);
      arecord.addMeasureFlag("|:");
   } else if (pre.search(infile[line][col], "\\|\\|", "")) {
      arecord.insertString(1, MDOUBLE);
   } else if (pre.search(infile[line][col], "\\|:", "")) {
      arecord.addMeasureFlag("|:");
   }
   LayoutParameters lp;
   LayoutParameters glp; // global layout parameters
   if (col < LayoutInfo[line].getSize()) {
      lp.parseLayout(infile, LayoutInfo[line][col]);
   }
   glp.parseLayout(infile, GlobalLayoutInfo[line]);

   // print any text associated with the barline
   addTextLayout(tempdata, infile, line, col, lp, "TX");

   // print any global text associated with the barline

   // check for global layout text codes add above/below the system.

// addMeasureFlag("start-end#1")
// addMeasureFlag("stop-end#1")
// addMeasureFlag("start-end#2")
// addMeasureFlag("disc-end#2")
  
   char mbuffer[1024] = {0};
   int ii;
   int ending;
   LayoutParameters tempparam;
   if ((glp.getSize() > 0) && isTopStaffOnSystem(infile, line, col)) {
      // Only display text if placed above the staff
      for (ii=0; ii<glp.getSize(); ii++) {

         if (strcmp(glp.getCode(ii).getBase(), "BAR") == 0) {
            // handle a barline code: only on top part?
            if(glp.hasKeyName(ii, "start")) {
               // print a start-ending marker
               ending = glp.getKeyValueInt(ii, "start");
               sprintf(mbuffer, "start-end%d", ending);
               arecord.addMeasureFlag(mbuffer);
            }
            if(glp.hasKeyName(ii, "stop")) {
               // print a stop-ending marker
               ending = glp.getKeyValueInt(ii, "stop");
               sprintf(mbuffer, "stop-end%d", ending);
               arecord.addMeasureFlag(mbuffer);
            }
            if(glp.hasKeyName(ii, "disc")) {
               // print a discontinue-ending marker
               ending = glp.getKeyValueInt(ii, "disc");
               sprintf(mbuffer, "disc-end%d", ending);
               arecord.addMeasureFlag(mbuffer);
            }
         }

         if (strcmp(glp.getCode(ii).getBase(), "TX") != 0) {
            continue;
         }
         if (glp.hasKeyName(ii, "Z")) {
            tempparam.clear();
            tempparam.addEntry(glp, ii);
            addTextLayout(tempdata, infile, line, col, tempparam, "TX");
         }
      }
   } else if ((glp.getSize() > 0) && isBottomStaffOnSystem(infile, line, col)) {
      // Only display text if placed below the staff
      for (ii=0; ii<glp.getSize(); ii++) {

         if (strcmp(glp.getCode(ii).getBase(), "TX") != 0) {
            continue;
         }
         if (!glp.hasKeyName(ii, "Z")) {
            tempparam.clear();
            tempparam.addEntry(glp, ii);
            addTextLayout(tempdata, infile, line, col, tempparam, "TX");
         }
      }
   }

   tempdata.append(arecord);
   processMeasureLayout(tempdata, infile, line, col, lp, glp);

}



//////////////////////////////
//
// printUnterminatedTies --
//

int printUnterminatedTies(MuseData& tempdata, HumdrumFile& infile, 
      int line, int track) {

   if (!tieQ) {
      return 0;
   }

   if (!infile[line].isMeasure()) {
      return 0;
   }

   // First check to see if this is the last barline in the music.
   // Deal with intermediate concatenation cases later.
   int i;
   for (i=line; i<infile.getNumLines(); i++) {
      if (infile[i].isData()) {
         return 0;
      }
   }

   MuseRecord arecord;
   //         1      1  2    2
   //1234567890123456789012345
   //*               X       C5   // this is the last barline in the data, so print any
   // * records marking a tie which is not finished.
   
   int output = 0;
   char buffer[1024] = {0};
   for (i=0; i<TieConditionsForward[line][track].getSize(); i++) {
      if (TieConditionsForward[line][track][i]) {
         arecord.getColumn(1) = '*';
         arecord.getColumn(17) = 'X';
         Convert::base40ToMuse(i, buffer);
         arecord.insertString(25, buffer);
         tempdata.append(arecord);
         arecord.clear();
         output = 1;
      }
   }
   return output;
}



//////////////////////////////
//
// processMeasureLayout -- The last line of tempdata should contain the 
//    barline.
//

void processMeasureLayout(MuseData& tempdata, HumdrumFile& infile, int line, 
      int col, LayoutParameters& lp, LayoutParameters& glp) {

   int i;
   for (i=0; i<lp.getSize(); i++) {
      handleMeasureLayoutParam(tempdata, infile, lp, i);
   }

   // search the global layout parameters for items (particularly
   // line/system breaks).
   for (i=0; i<glp.getSize(); i++) {
      handleMeasureLayoutParam(tempdata, infile, glp, i);
   }


   // search for segno sign parameter (only searching global parameters)
   for (i=0; i<glp.getSize(); i++) {
      if (strcmp(glp.getCode(i).getBase(), "SEGNO") == 0) {
         // currently LO:SEGNO has no parameters, so don't check
         MuseRecord segnoline;
         segnoline.getColumn(1) = '*';
         segnoline.getColumn(17) = 'A';   // segno musical directive
         segnoline.getColumn(19) = '+';   // above the staff
         // don't know what will happen if a linebreak and a segno
         // happen on the same barline...
         tempdata.append(segnoline);
      }
   }

}



//////////////////////////////
//
// handleMeasureLayoutParam -- do final generation of print suggestions
//     for Measures (this function called by processMeasureLayout).
//

void handleMeasureLayoutParam(MuseData& tempdata, HumdrumFile& infile,
      LayoutParameters& lp, int index) {

   int linebreakQ = 0;
   int pagebreakQ = 0;

   if (strcmp(lp.getCode(index).getBase(), "LB") == 0) {
      linebreakQ = 1;
   } else if (strcmp(lp.getCode(index).getBase(), "PB") == 0) {
      pagebreakQ = 1;
      linebreakQ = 1;
   } else if (strcmp(lp.getCode(index).getBase(), "REP") == 0) {
     addRepeatLines(tempdata, infile, lp, index);
     return;
   }

   // only processing line (system) and page breaks
   // can only handle line breaks at the moment.
   if (!(linebreakQ || pagebreakQ)) {
      return;
   }

   PerlRegularExpression pre;
   int index2 = lp.getParameter(index, "g");
   if (index2 < 0) {
      // cannot find a group parameter, and not allowing a global
      // grouping (at least at the moment).
      return;
   }

   Array<Array<char> > tokens;
   pre.getTokens(tokens, "\\s,\\s", lp.getValue(index,index2).getBase());

   // true if a part instruction
   int partQ = pre.search(lp.getValue(index,index2), "\\bP\\b", "");

   // currently only printing z groups which are used to control
   // the directions for a particular music size.
   MuseRecord arecord;
   int i;
   int musicsize=6;
   for (i=0; i<tokens.getSize(); i++) {
      if (!pre.search(tokens[i].getBase(), "^z(\\d*)", "")) {
         continue;
      }
      if (strlen(pre.getSubmatch(1)) > 0) {
         // for a specific size
         musicsize = atoi(pre.getSubmatch(1));
         arecord.clear();
         arecord.appendString("P");
         if (partQ) {
            arecord.appendString("p");
         } else {
            arecord.appendString("a");
         }
         arecord.append("sis", "#", musicsize, " C1:]");
         // have to check for multiple print suggestions and combine them.
         tempdata.append(arecord);
      } else {
         // for any MuseData size
         musicsize = atoi(pre.getSubmatch(1));
         arecord.clear();
         arecord.appendString("P");
         if (partQ) {
            arecord.appendString("p");
         } else {
            arecord.appendString("a");
         }
         arecord.appendString(" C1:]");
         // have to check for multiple print suggestions and combine them.
         tempdata.append(arecord);
      }
      break;
   }
}



//////////////////////////////
//
// addRepeatLines --
//    LO:REP:start=1 --> start-end1
//    LO:REP:stop=1  --> stop-end1
//    LO:REP:open=1  --> disc-end1
//

void addRepeatLines(MuseData& tempdata, HumdrumFile& infile,
      LayoutParameters& lp, int index) {

   int start = lp.getParameter(index, "start");
   int stop  = lp.getParameter(index, "stop");
   int open  = lp.getParameter(index, "open");

   // check for aliases start==begin, stop==end, open==disc
   if (start < 0) {
      start = lp.getParameter(index, "begin");
   }
   if (stop < 0) {
      stop = lp.getParameter(index, "end");
   }
   if (open < 0) {
      open = lp.getParameter(index, "disc");
   }

   int startnum = 0;
   int stopnum  = 0;
   int opennum  = 0;
   char buffer[1024] = {0};

   // probably check that the last entry in MuseData list is a measure...

   if (start >= 0) {
      startnum = atoi(lp.getValue(index,start).getBase());
      sprintf(buffer, "start-end%d", startnum);
      tempdata.last().addMeasureFlag(buffer);
   }
   if (stop >= 0) {
      stopnum = atoi(lp.getValue(index,stop).getBase());
      sprintf(buffer, "stop-end%d", stopnum);
      tempdata.last().addMeasureFlag(buffer);
   }
   if (open >= 0) {
      opennum = atoi(lp.getValue(index,open).getBase());
      sprintf(buffer, "disc-end%d", opennum);
      tempdata.last().addMeasureFlag(buffer);
   }


}



//////////////////////////////
//
// getMaxVoices -- the maximum number of spines with the same primary track
//    found on the line.
//

int getMaxVoices(HumdrumFile& infile, int startline, int stopline, int track) {
   int tcount;
   int i, j;
   int output = 0;
   for (i=startline; i<stopline; i++) {
      if (!infile[i].isData()) {
         continue;
      }
      tcount = 0;
      for (j=0; j<infile[i].getFieldCount(); j++) {
         if (infile[i].getPrimaryTrack(j) == track) {
            tcount++;
         }      
      }
      if (output < tcount) {
         output = tcount;
      }
   }
   return output;
}



//////////////////////////////
//
// getMeasureInfo -- returns a list of the measures in the score listed
//     as line indexes in the Humdurm score.
//

void getMeasureInfo(Array<int>& measures, HumdrumFile& infile) {
   measures.setSize(infile.getNumLines());
   measures.setSize(0);
   int i;
   int datafound = 0;
   int exinterpline = -1;
   for (i=0; i<infile.getNumLines(); i++) {
      if ((exinterpline < 0) && (strncmp(infile[i][0], "**", 2) == 0)) {
         exinterpline = i;
         continue;
      }
      if (infile[i].isData()) {
         datafound = 1;
         continue;
      }
      if (infile[i].isMeasure()) {
         if (measures.getSize() == 0) {
            if(datafound) {
               measures.append(exinterpline);
            }
         }
         measures.append(i);
      }
   }


   // determine if there is data after the last measure
   // in the file.

   datafound = 0;
   int lastline = -1;
   for (i=infile.getNumLines()-1; i>0; i--) {
      if (strcmp(infile[i][0], "*-") == 0) {
         lastline = i;
         continue;
      }
      if (infile[i].isData()) {
         datafound = 1;
         continue;
      }
      if (infile[i].isMeasure()) {
         if (datafound) {
            measures.append(lastline);
         }
      }
   }

   // remove duplicate endings
   for (i=measures.getSize()-1; i>=1; i--) {
      if (measures[i] == measures[i-1]) {
         measures.setSize(measures.getSize()-1);
      } else {
         break;
      }
   }

   if (debugQ) {
      cout << "MEASURE LINES:\n";
      for (i=0; i<measures.getSize(); i++) {
         cout << "\t" << measures[i] << NEWLINE;
      }
   }
}



//////////////////////////////
//
// getKernTracks --  Return a list of the **kern primary tracks found
//     in the Humdrum data.  Currently all tracks are independent parts.
//     No grand staff parts are considered if the staves are separated 
//     into two separate spines.
//
//

void getKernTracks(Array<int>& tracks, HumdrumFile& infile) {
   tracks.setSize(infile.getMaxTracks());
   tracks.setSize(0);
   int i;
   for (i=1; i<=infile.getMaxTracks(); i++) {
      if (strcmp(infile.getTrackExInterp(i), "**kern") == 0) {
         tracks.append(i);
      }
   }

   if (debugQ) {
      cout << "\t**kern tracks:\n";
      for (i=0; i<tracks.getSize(); i++) {
         cout << "\t" << tracks[i] << NEWLINE; 
      }
   }
}



//////////////////////////////
//
// getTupletState -- mark basic start/stop of tuplets.  Not perfect,
// and does not segment tuplets into smaller tuplet groups in a generalized
// manner
//

void getTupletState(Array<int>& hasTuplet, Array<Array<char> >& TupletState, 
      Array<Array<int> >& TupletTopNum, 
      Array<Array<int> >& TupletBotNum, HumdrumFile& infile) {
   char tupletstart = '*';
   // char tupletstop = '!';
   Array<Array<int> > tstate;
   Array<Array<int> > lastrow;
   Array<Array<int> > lastcol;

   hasTuplet.setSize(infile.getMaxTracks()+1);
   hasTuplet.setAll(0);

   int i, j, ii, lasti, lastj;
   tstate.setSize(infile.getMaxTracks()+1);
   lastrow.setSize(infile.getMaxTracks()+1);
   lastcol.setSize(infile.getMaxTracks()+1);
   for (i=0; i<tstate.getSize(); i++) {
      tstate[i].setSize(100);
      tstate[i].setAll(-1);
      lastrow[i].setSize(100);
      lastrow[i].setAll(-1);
      lastcol[i].setSize(100);
      lastcol[i].setAll(-1);
   }

   TupletState.setSize(infile.getNumLines());
   TupletTopNum.setSize(infile.getNumLines());
   TupletBotNum.setSize(infile.getNumLines());
   Array<int> curlayer;
   curlayer.setSize(infile.getMaxTracks() + 1);
   int layer;
   int track;
   RationalNumber rn;
   
   for (i=0; i<infile.getNumLines(); i++) {
      if (infile[i].isMeasure()) {
         // don't allow tuplets across barlines.  
         for (ii=0; ii<tstate.getSize(); ii++) {
            layer = 1;
            lasti = lastrow[ii][layer];
            lastj = lastcol[ii][layer];
            if((tstate[ii][layer] != ' ') && (lasti >= 0) && (lastj >= 0)) {
               TupletState[lasti][lastj] = '!';
            }
            tstate[ii].setAll(' ');
         }
         continue;
      }
      if (!infile[i].isData()) {
         continue;
      }

      TupletState[i].setSize(infile[i].getFieldCount());
      TupletState[i].setAll(0);

      TupletTopNum[i].setSize(infile[i].getFieldCount());
      TupletBotNum[i].setSize(infile[i].getFieldCount());

      TupletTopNum[i].setAll(0);
      TupletBotNum[i].setAll(0);

      curlayer.setAll(0);

      for (j=0; j<infile[i].getFieldCount(); j++) {
         track = infile[i].getPrimaryTrack(j);
         curlayer[track]++;
         layer = curlayer[track];
         if (!infile[i].isExInterp(j, "**kern")) {
            continue;
         }
         if (strcmp(infile[i][j], ".") == 0) {
            continue;
         }
         rn = getDurationNoDots(infile[i][j], defaultDur);
         if (!isPowerOfTwo(rn)) {
            hasTuplet[track] = 1;
            // The note is a tuplet
            if(tstate[track][layer] == 't') {
               TupletState[i][j] = ' ';
            } else {
               TupletState[i][j] = tupletstart;
            }
            TupletTopNum[i][j] = getTupletTop(infile, i, j);
            // TupletBotNum always set to 0 for now.
            // to calculate the TupletBotNum, need to know beaming.
            tstate[track][layer] = 't';
         } else {
            // The note is a not a tuplet
            lasti = lastrow[track][layer];
            lastj = lastcol[track][layer];
            if((tstate[track][layer] != ' ') && (lasti >= 0) && (lastj >= 0)) {
               TupletState[lasti][lastj] = '!';
            }
            tstate[track][layer] = ' ';
         }
         lastrow[track][layer] = i;
         lastcol[track][layer] = j;
      }
   }
}



//////////////////////////////
//
// getTupletTop --
//

int getTupletTop(HumdrumFile& infile, int row, int col) {
   RationalNumber rn;
   rn = getDurationNoDots(infile[row][col], defaultDur);
   // basic algorithm when works for most simple tuplets:
   // extract all prime factors which are not 2.
   

   if (rn.getNumerator() != 1) {
      if (rn.isEqualTo(16,3)) {
         // triplet breve note
         return 3;
      }
      if (rn.isEqualTo(8,3)) {
         // triplet whole note
         return 3;
      }
      if (rn.isEqualTo(3,8)) {
         // four sixteenth notes in the time of three eighth notes
         return 4;
      }
      if (rn.isEqualTo(4,3)) {
         // triplet half note
         return 3;
      }
      if (rn.isEqualTo(2,3)) {
         // triplet quarter note
         return 3;
      }
      if (rn.isEqualTo(9,2)) {
         // dotted half note duplet
         return 2;
      }
      if (rn.getDenominator() == 1) {
         return 1;
      }
      cerr << "Error: Cannot handle exotic tuplets " << rn << NEWLINE;
      exit(1);
   }

   Array<int> factors;
   primeFactorization(factors, rn.getDenominator());
   int product = 1;
   int i;
   for (i=factors.getSize()-1; i>=0; i--) {
      if (factors[i] == 2) {
         break;
      }
      product *= factors[i];
   }

   return product;
}



//////////////////////////////
//
// getBeamState -- Analyze structure of beams and store note layout
//      directives at the same time.
//
// Type          Humdrum     MuseData
// start          L           [
// continue                   =
// end            J           ]
// forward hook   K           /
// backward hook  k           \   x
//
// Column 26-31 are for beams
//    26   is for 8th-note level
//    27   is for 16th-note level
//    28   is for 32th-note level
//    29   is for 64th-note level
//    30   is for 128th-note level
//    31   is for 256th-note level
//
// LayoutInfo and ClefState are also filled in this function since
// it is a convenient place to do so.  Clefstate is currently set
// by track rather than subtrack, so clef changes in subtokens which
// differ will cause problems.
//
// The GlobalLayout (glayout) is for layout information stored in global
// comments.  These get stored at the next line in the file which contains 
// data or measures.
//

void getBeamState(Array<Array<Array<char> > >& beams, 
    Array<Array<Array<Coord> > >& layout,  Array<Array<Coord> >& glayout,
    Array<Array<Coord> >& clefs, HumdrumFile& infile) {
   int i, j, t;
   int ii;
   int len;
   int contin;
   int start;
   int stop;
   int flagr;
   int flagl;
   int track;
   int layoutisinitQ = 0;
   RationalNumber rn;

   Array<Coord> clefstate;
   Array<Array<Array<Coord> > > laystate;  // state of layout info

   Array<Coord> glaystate; // global layout temporary storage

   Array<Array<int> > beamstate;   // state of beams in tracks/layers
   Array<Array<int> > gracestate;  // independents state for grace notes

   Array<char> gbinfo;
   gbinfo.setSize(100);
   gbinfo.allowGrowth(0);

   beamstate.setSize(infile.getMaxTracks() + 1);
   gracestate.setSize(infile.getMaxTracks() + 1);
   laystate.setSize(infile.getMaxTracks() + 1);
   clefstate.setSize(infile.getMaxTracks() + 1);
   beamstate.allowGrowth(0);
   gracestate.allowGrowth(0);
   laystate.allowGrowth(0);
   clefstate.allowGrowth(0);
   for (i=0; i<beamstate.getSize(); i++) {
      clefstate[i].i = -1;  // should already by -1, but just in case
      clefstate[i].j = -1;  // should already by -1, but just in case
      beamstate[i].setSize(100);     // maximum of 100 layers in each track...
      gracestate[i].setSize(100);    // maximum of 100 layers in each track...
      beamstate[i].allowGrowth(0);
      gracestate[i].allowGrowth(0);
      beamstate[i].setAll(0);
      gracestate[i].setAll(0);
   }
   glaystate.setSize(0);

   beams.setSize(infile.getNumLines());
   layout.setSize(infile.getNumLines());
   glayout.setSize(infile.getNumLines());
   clefs.setSize(infile.getNumLines());
   Array<int> curlayer;
   curlayer.setSize(infile.getMaxTracks() + 1);
   Array<int> laycounter;

   for (i=0; i<infile.getNumLines(); i++) {
      layout[i].setSize(infile[i].getFieldCount());

      if (infile[i].isMeasure()) {
         // don't allow beams across barlines.  Mostly for 
         // preventing buggy beams from propagating...
         for (t=1; t<=infile.getMaxTracks(); t++) {
            beamstate[t].setAll(0);
            gracestate[t].setAll(0);
         }
         // but do allow for measures to posses layout information
         storeLayoutInformation(infile, i, laystate, layout, layoutisinitQ++);
      }

      if (infile[i].isGlobalComment()) {
         addGlobalLayoutInfo(infile, i, glaystate);
      }

      if (infile[i].isLocalComment()) {
         addLayoutInfo(infile, i, laystate);
      }

      if (infile[i].isInterpretation()) {
         adjustClefState(infile, i, clefstate);
      }

      if (!infile[i].isData() && !infile[i].isMeasure()) {
         continue;
      }

      if (infile[i].isMeasure()) {
         // store any layout information collected since the last note/measure
         storeLayoutInformation(infile, i, laystate, layout, layoutisinitQ++);
         storeGlobalLayoutInfo(infile, i, glaystate, glayout);
      }

      if (!infile[i].isData()) {
         continue;
      }

      // store any layout information collected since the last note/measure
      storeLayoutInformation(infile, i, laystate, layout, layoutisinitQ++);
      storeGlobalLayoutInfo(infile, i, glaystate, glayout);

      storeClefInformation(infile, i, clefstate, clefs);

      beams[i].setSize(infile[i].getFieldCount());
      for (j=0; j<beams[i].getSize(); j++) {
         beams[i][j].setSize(1);
         beams[i][j][0] = '\0';
      }

      curlayer.setAll(0);
      for (j=0; j<infile[i].getFieldCount(); j++) {
         track = infile[i].getPrimaryTrack(j);
         curlayer[track]++;
         if (strcmp(infile[i][j], ".") == 0) {
            // ignore null tokens
            continue;
         }         
         if (strchr(infile[i][j], 'r') != NULL) {
            // ignore rests.  Might be useful to not ignore
            // rests if beams extent over rests...
            continue;
         }         
         rn = Convert::kernToDurationR(infile[i][j]);
         if (rn >= 1) {
            beamstate[track][curlayer[track]] = 0;
            continue;
         }
         if (rn == 0) {

            // grace notes;
            countBeamStuff(infile[i][j], start, stop, flagr, flagl);
            if((start != 0) && (stop != 0)) {
               cerr << "Funny error in grace note beam calculation" << NEWLINE;
               exit(1);
            }
            if(start > 7) {
               cerr << "Too many beam starts" << NEWLINE;
            }
            if(stop > 7) {
               cerr << "Too many beam ends" << NEWLINE;
            }
            if(flagr > 7) {
               cerr << "Too many beam flagright" << NEWLINE;
            }
            if(flagl > 7) {
               cerr << "Too many beam flagleft" << NEWLINE;
            }
            contin = gracestate[track][curlayer[track]];
            contin -= stop;
            gbinfo.setAll(0);
            for(ii=0; ii<contin; ii++) { 
               gbinfo[ii] = '=';
            }
            if(start > 0) {
               for(ii=0; iiif(stop > 0) {
               for(ii=0; iifor(ii=0; ii<flagr; ii++) {
               strcat(gbinfo.getBase(), "/");
            }
            for(ii=0; ii<flagl; ii++) {
               strcat(gbinfo.getBase(), "\\");
            }
            len = strlen(gbinfo.getBase());
            if(len > 6) {
               cerr << "Error too many grace note beams" << NEWLINE;
               exit(1);
            }
            beams[i][j].setSize(len+1);
            strcpy(beams[i][j].getBase(), gbinfo.getBase());
            gracestate[track][curlayer[track]] = contin;
            gracestate[track][curlayer[track]] += start;

         } else {
            // regular notes which are shorter than a quarter note
            // (including tuplet quarter notes which should be removed):

            countBeamStuff(infile[i][j], start, stop, flagr, flagl);
            if((start != 0) && (stop != 0)) {
               cerr << "Funny error in note beam calculation" << NEWLINE;
               exit(1);
            }
            if(start > 7) {
               cerr << "Too many beam starts" << NEWLINE;
            }
            if(stop > 7) {
               cerr << "Too many beam ends" << NEWLINE;
            }
            if(flagr > 7) {
               cerr << "Too many beam flagright" << NEWLINE;
            }
            if(flagl > 7) {
               cerr << "Too many beam flagleft" << NEWLINE;
            }
            contin = beamstate[track][curlayer[track]];
            contin -= stop;
            gbinfo.setAll(0);
            for(ii=0; ii<contin; ii++) { 
               gbinfo[ii] = '=';
            }
            if(start > 0) {
               for(ii=0; iiif(stop > 0) {
               for(ii=0; iifor(ii=0; ii<flagr; ii++) {
               strcat(gbinfo.getBase(), "/");
            }
            for(ii=0; ii<flagl; ii++) {
               strcat(gbinfo.getBase(), "\\");
            }
            len = strlen(gbinfo.getBase());
            if(len > 6) {
               cerr << "Error too many grace note beams" << NEWLINE;
               exit(1);
            }
            beams[i][j].setSize(len+1);
            strcpy(beams[i][j].getBase(), gbinfo.getBase());
            beamstate[track][curlayer[track]] = contin;
            beamstate[track][curlayer[track]] += start;

         }
      }
   }
}



//////////////////////////////
//
// addGlobalLayoutInfo --
//

void addGlobalLayoutInfo(HumdrumFile& infile, int line, 
      Array<Coord>& glaystate) {
   if (strncmp(infile[line][0], "!!LO:", 5) != 0) {
      return;
   }
   glaystate.increase(1);
   glaystate.last().i = line;
   glaystate.last().j = 0;
}



//////////////////////////////
//
// storeGlobalLayoutInfo --
//

void storeGlobalLayoutInfo(HumdrumFile& infile, int line, 
   Array<Coord>& glaystate, Array<Array<Coord> >& glayout) {
   int i;
   glayout[line].setSize(glaystate.getSize());
   for (i=0; i<glaystate.getSize(); i++) {
      glayout[line][i].i = glaystate[i].i;
      glayout[line][i].j = glaystate[i].j;
   }
   glaystate.setSize(0);
}



//////////////////////////////
//
// storeClefInformation --
//

void storeClefInformation(HumdrumFile& infile, int line, 
      Array<Coord>& clefstate, Array<Array<Coord> >& clefs) {

   int j;
   int track;
   clefs[line].setSize(infile[line].getFieldCount());
   for (j=0; j<infile[line].getFieldCount(); j++) {
      track = infile[line].getPrimaryTrack(j);
      clefs[line][j].i = clefstate[track].i;
      clefs[line][j].j = clefstate[track].j;
   }
}



//////////////////////////////
//
// adjustClefState --
//

void adjustClefState(HumdrumFile& infile, int line, 
      Array<Coord>& clefstate) {
   int j;
   int track;
   for (j=0; j<infile[line].getFieldCount(); j++) {
      if (strncmp(infile[line][j], "*clef", 5) == 0) {
         track = infile[line].getPrimaryTrack(j);
         clefstate[track].i = line;
         clefstate[track].j = j;
      }
   }
}



//////////////////////////////
//
// addLayoutInfo -- accumulate layot info for a note, rest or measure token
//    into the current layout buffer.  storeLayoutInfo() function actually 
//    stores it in the final data structure.
//

void addLayoutInfo(HumdrumFile& infile, int line, 
      Array<Array<Array<Coord> > > &laystate) {

   int track;
   int layer;
   Array<int> layerinfo;
   layerinfo.setSize(infile.getMaxTracks()+1);
   layerinfo.setAll(0);
   Coord xy;

   int j;
   for (j=0; j<infile[line].getFieldCount(); j++) {
      track = infile[line].getPrimaryTrack(j);
      layerinfo[track]++;
      layer = layerinfo[track];
      if (strncmp(infile[line][j], "!LO:", 4) == 0) {
         xy.i = line;
         xy.j = j;
         laystate[track][layer].append(xy);
      }
   }

}



/////////////////////////////
//
// storeLayoutInformation --  Global layout information
//    gets processed into track 0.
//

void storeLayoutInformation(HumdrumFile& infile, int line, 
      Array<Array<Array<Coord> > >& laystate, 
      Array<Array<Array<Coord> > >& layout,
      int layoutisinitQ) {

   Array<int> layerinfo;
   layerinfo.setSize(infile.getMaxTracks()+1);
   layerinfo.setAll(0);

   if (!layoutisinitQ) {
      layout[line].setSize(infile[line].getFieldCount());
   }

   Coord xy;
   int j, k;
   int track;
   int layer;
   for (j=0; j<infile[line].getFieldCount(); j++) {
      track = infile[line].getPrimaryTrack(j);
      layerinfo[track]++;
      if (strcmp(infile[line][j], ".") == 0) {
         // null token: don't store any layout data
         continue;
      }
      layer = layerinfo[track];
      if (!layoutisinitQ) {
         layout[line][j].setSize(0);
      }
      for (k=0; k<laystate[track][layer].getSize(); k++) {
         xy = laystate[track][layer][k];
         if ((xy.i < 0) || (xy.j < 0)) {
            continue;
         }

         layout[line][j].append(xy);
      }
      // clear the current layer information
      laystate[track][layer].setSize(0);
   }


}



//////////////////////////////
//
// countBeamStuff --
//

void countBeamStuff(const char* token, int& start, int& stop, int& flagr, 
      int& flagl) {
   int i = 0;
   start = stop = flagr = flagl = 0;
   while (token[i] != '\0') {
      switch (token[i]) {
         case 'L': start++;  break;
         case 'J': stop++;   break;
         case 'K': flagr++;  break;
         case 'k': flagl++;  break;
      }
      i++;
   }
}



//////////////////////////////
//
// verifyMuseDataFiles --
//

void verifyMuseDataFiles(Options& options) {
   int i;

   if (options.getArgCount() == 0) {
      verifyMuseDataFile(std::cin);
   } else {
      for (i=0; i<options.getArgCount(); i++) {
         verifyMuseDataFile(options.getArg(i+1));
      }
   }
}



//////////////////////////////
//
// verifyMuseDataFile -- standard input version
//

void verifyMuseDataFile(istream& input) {
   MuseDataSet mds;
   mds.read(input);
   int i;
   int vp;
   for (i=0; i<mds.getPartCount(); i++) {
      vp = verifyPart(mds[i]);
      if (vp) {
         cout << ".\tpart_" << i+1 << ":\t" << "verified" << endl;
      } else {
         cout << ".\tpart_" << i+1 << ":\t" << "modified" << endl;
      }
   }
}



//////////////////////////////
//
// verifyMuseDataFile --
//

void verifyMuseDataFile(const char* filename) {
   MuseDataSet mds;
   mds.read(filename);
   int i;
   int vp;
   for (i=0; i<mds.getPartCount(); i++) {
      vp = verifyPart(mds[i]);
      if (vp) {
         cout << filename << "\tpart_" << i+1 << ":\t" << "verified" << endl;
      } else {
         cout << filename << "\tpart_" << i+1 << ":\t" << "modified" << endl;
      }
   }
}



//////////////////////////////
//
// verifyPart -- check to see if the MD5sum matches the part
//

int verifyPart(MuseData& part) {
   int output = 0;
   int startline = -1;
   int stopline = -1;
   int i;
   int commentstate = 0;
   for (i=0; i<part.getNumLines(); i++) {
      if (part[i][0] == '@') {
         // single line comment
         continue;
      }
      if (part[i][0] == '&') {
         commentstate = !commentstate;
         continue;
      }
      if (commentstate) {
         continue;
      }
      startline = i;
      break;
   }

   for (i=part.getNumLines()-1; i>=0; i--) {
      if (strncmp(part[i].getLine(), "/eof", 4) == 0) {
         stopline = i-1;
         break;
      }
      if (strncmp(part[i].getLine(), "/END", 4) == 0) {
         stopline = part.getNumLines()-1;
         break;
      }
      if (strncmp(part[i].getLine(), "/FINE", 4) == 0) {
         stopline = part.getNumLines()-1;
         break;
      }
   }

   if (debugQ) {
      cout << "-============================================" << endl;
      cout << part;
      cout << "-============================================" << endl;
   }

   if ((startline < 0) || (stopline < 0)) {
      cerr << "Syntax error in part" << endl;
      exit(1);
   }
   if (startline >= stopline) {
      cerr << "Strange Syntax error in part" << endl;
      exit(1);
   }

   int timestampindex = -1;
   for (i=startline; i<=stopline; i++) {
      if (strncmp(part[i].getLine(), "TIMESTAMP", strlen("TIMESTAMP")) == 0) {
         timestampindex = i;
         break;
      }
   }

   if (timestampindex < 0) {
      cerr << "Cannot find timestamp index" << endl;
      exit(1);
   }

   PerlRegularExpression pre;
   if (!pre.search(part[timestampindex].getLine(), "^(.*\\[)(.*)(\\].*)$", "")) {
      cerr << "Cannot find checksum on timestamp line:" << endl;
      cerr << part[timestampindex] << endl;
      exit(1);
   }

   Array<char> checksum(strlen(pre.getSubmatch(2))+1);
   strcpy(checksum.getBase(), pre.getSubmatch(2));

   PerlRegularExpression pre2;
   if (!pre2.search(checksum, ":?([^:]+)$", "")) {
      return 0;
      cerr << "Cannot find checksum on timestamp line, here is the checksum: " 
           <<  checksum << endl;
   }
   Array<char> oldmd5sum(strlen(pre2.getSubmatch(1))+1);
   strcpy(oldmd5sum.getBase(), pre2.getSubmatch());

   // currently only presuming md5sum as the checksum type.

   // default is for MS-DOS
   Array<char> LNEWLINE(3);
   LNEWLINE[0] = (char)0x0d;
   LNEWLINE[1] = (char)0x0a;
   LNEWLINE[2] = '\0';

   if (pre2.search(checksum, ":([^:]+):", "")) {
      int len = strlen(pre2.getSubmatch(1));
      int value;
      int value2;
      if (len == 1) {
         if (sscanf(pre2.getSubmatch(), "%1x", &value)) {
            LNEWLINE[0] = (char)value;
            LNEWLINE[1] = '\0';
         }
      } else if (len == 2) {
         if (sscanf(pre2.getSubmatch(), "%2x", &value)) {
            LNEWLINE[0] = (char)value;
            LNEWLINE[1] = '\0';
         }
      } else if (len == 4) {
         if (sscanf(pre2.getSubmatch(), "%2x%2x", &value, &value2)) {
            LNEWLINE[0] = (char)value;
            LNEWLINE[1] = (char)value2;
            LNEWLINE[2] = '\0';
         }
      } // ignore if longer than 4 characters
   } else if (pre2.search(checksum, "::", "")) {
      // no newlines at all when calculating checksum
      LNEWLINE[0] = '\0';
   }

   part[timestampindex].clear();
   part[timestampindex].appendString(pre.getSubmatch(1));
   part[timestampindex].appendString(pre.getSubmatch(3));
    
   SSTREAM tstream;
   for (i=startline; i<=stopline; i++) {
      tstream << part[i] << LNEWLINE;
   }
   tstream << ends;
   Array<char> partdata(strlen(tstream.CSTRING)+1);
   strcpy(partdata.getBase(), tstream.CSTRING);

   Array<char> newmd5sum;
   CheckSum::getMD5Sum(newmd5sum, partdata);

   if (debugQ) {
      cout << "OLD MD5SUM: " << oldmd5sum << endl;
      cout << "NEW MD5SUM: " << newmd5sum << endl;
      cout << "PART ===================================" << endl;
      cout << partdata;
      cout << "==========================================" << endl;
   }

   if (strcmp(newmd5sum.getBase(), oldmd5sum.getBase()) == 0) {
      output = 1;
   } else {
      output = 0;
   }

   return output;
}



//////////////////////////////
//
// updateMuseDataFileTimeStamps -- Update timestamps if the part
//     has been changed according to the timestamp.
//

void updateMuseDataFileTimeStamps(Options& options) {
   if (options.getArgCount() == 0) {
      updateFileTimeStamps(std::cin, options);
   } else {
      int i;
      for (i=0; i<options.getArgCount(); i++) {
         updateFileTimeStamps(options.getArg(i+1), options);
      }
   }
}



//////////////////////////////
//
// removeOldTimeStamps -- remove old timestamps (and reasons) at the bottom
// of the file.
//

void removeOldTimeStamps(MuseData& part) {
   int i;
   int atend = 0;
   SSTREAM tstream;
   for (i=0; i<part.getLineCount(); i++) {
      if (strncmp(part[i].getLine(), "/END", 4) == 0) {
         atend = 1;
      }
      if (strncmp(part[i].getLine(), "/FINE", 4) == 0) {
         atend = 1;
      }
      if (!atend) {
         tstream << part[i] << "\n";  // newline version does not matter
         continue;
      }
      if (strncmp(part[i].getLine(), "@SUPERSEDES:", 
            strlen("@SUPERSEDES:")) == 0) {
         continue;
      }
      if (strncmp(part[i].getLine(), "@REASON:", 
            strlen("@REASON:")) == 0) {
         continue;
      }
      tstream << part[i] << "\n";
   }

   tstream << ends;
   part.clear();
   part.read(tstream);

}



//////////////////////////////
//
// getTimeStampLine -- return the line in the header which starts with 
//     "TIMESTAMP".  Returns -1 if there is no timestamp.
//

int getTimeStampLine(MuseRecord& arecord, MuseData& part) {
   int i;
   arecord.clear();
   for (i=0; i<part.getLineCount(); i++) {
      if (strncmp(part[i].getLine(), "TIMESTAMP", strlen("TIMESTAMP")) == 0) {
         arecord = part[i];
         return i;
      }
   }
   return -1;
}



//////////////////////////////
//
// updateFileTimeStamps -- standard input version
//

void updateFileTimeStamps(istream& input, Options& options) {
   MuseDataSet mds;
   mds.read(input);
   doUpdateWork(mds);

   // print result to standard output
   Array<char> newline;
   int i, j;
   for (i=0; i<mds.getPartCount(); i++) {
      getNewLine(newline, mds[i]);
      for (j=0; j<mds[i].getLineCount(); j++) {
         cout << mds[i][j] << newline;
      }
   }

}


void updateFileTimeStamps(const char* filename,  Options& options) {
   MuseDataSet mds;
   mds.read(filename);
   doUpdateWork(mds);

   // print new contents to file.
   ofstream outfile;
   outfile.open(filename);
   if (!outfile.is_open()) {
      cerr << "Error: could not open " << filename << " for rewriting" << endl;
      exit(1);
   }

   Array<char> newline;
   int i, j;
   for (i=0; i<mds.getPartCount(); i++) {
      getNewLine(newline, mds[i]);
      for (j=0; j<mds[i].getLineCount(); j++) {
         outfile << mds[i][j] << newline;
      }
   }
   outfile.close();

}


//////////////////////////////
//
// doUpdateWork -- Update the timestams in a MuseData multi-part file.
//

void doUpdateWork(MuseDataSet& mds) {

   int i;
   int vp;
   int tindex;
   MuseRecord trecord;
   MuseRecord arecord;
   MuseRecord rrecord;
   if (options.getBoolean("reason")) {
      rrecord.append("ss", "@REASON: ", options.getString("reason"));
   }

   for (i=0; i<mds.getPartCount(); i++) {
      if (options.getBoolean("remove-old")) {
         removeOldTimeStamps(mds[i]);
      }
      tindex = getTimeStampLine(trecord, mds[i]);
      if (tindex < 0) {
         cerr << "ERROR: part " << i+1 << " does not have a timestamp" << endl;
      }
      vp = verifyPart(mds[i]);
      if (vp) {
         // do nothing: the part is up-to-date.
      } else {
         arecord.clear();
         // need to keep track of /eof and // lines and insert before them.
         // just appending for now.
         if ((!options.getBoolean("remove-old")) && 
                  (!options.getBoolean("no-append"))) {

            arecord.append("ss", "@SUPERSEDES: ", trecord.getLine());
            appendToEndOfPart(mds[i], arecord);
            if(options.getBoolean("reason")) {
               appendToEndOfPart(mds[i], rrecord);
            }
         }
         updatePart(mds[i]);
      }
   }
}



//////////////////////////////
//
// appendToEndOfPart -- place record at end of part file, but before
// any /eof or // lines.
//

void appendToEndOfPart(MuseData& md, MuseRecord& arecord) {
   int i;
   int ending = -1;
   MuseData mend;
   for (i=md.getLineCount()-1; i>=0; i--) {
      if (strncmp(md[i].getLine(), "//", 2) == 0) {
         mend.append(md[i]);
         continue;
      } 
      if (strncmp(md[i].getLine(), "/eof", 2) == 0) {
         mend.append(md[i]);
         continue;
      }
      ending = i;
      break;
   }
   if (ending < 0) {
      md.append(arecord);
      return;
   }
   if (ending == md.getLineCount() - 1) {
      md.append(arecord);
      return;
   }

   md.append(arecord);  // add a dummy record to increase by one
   md[ending+1] = arecord;
   for (i=0; i<mend.getNumLines(); i++) {
      md[ending+2+i] = mend[mend.getNumLines()-i-1];
   }
}



//////////////////////////////
//
// updatePart -- change the timestamp date to today and calculate
// a new checksum.
//

void updatePart(MuseData& part) {
   Array<char> newline;
   getNewLine(newline, part);

   // make timestamp record with current date
   MuseData timestamp;
   addTimeStamp(timestamp);

   // put new time stamp in part
   MuseRecord arecord;
   int tline;
   tline = getTimeStampLine(arecord, part);
   if (tline < 0) {
      cerr << "Error: no timestamp line to update" << endl;
      exit(1);
   }
   part[tline] = timestamp[0];

   // calculate the new checksum and store in file
   Array<char> checksum;
   getCheckSum(checksum, part, newline);

   Array<char> tdata;
   tdata.setSize(strlen(part[tline].getLine()) + 1);
   strcpy(tdata.getBase(), part[tline].getLine());
   PerlRegularExpression pre;
   pre.sar(tdata, "\\[\\]", "[md5sum:AAYYYBB:AAZZZBB]", "");
   char newlinestring[128] = {0};
   char buffer[16] = {0};
   int i;
   for (i=0; i<newline.getSize()-1; i++) {
      sprintf(buffer, "%02x", (int)newline[i]);
      strcat(newlinestring, buffer);
   }
   pre.sar(tdata, "AAYYYBB", newlinestring, "");
   pre.sar(tdata, "AAZZZBB", checksum.getBase(), "");
   part[tline].clear();
   part[tline].insertString(1, tdata.getBase());
}



//////////////////////////////
//
// getCheckSum -- get the checksum for the given part (which should
// not contain anything within the [] on the TIMESAMP line.
//

void getCheckSum(Array<char>& checksum, MuseData& part, Array<char>& newline) {
   int startline = -1;
   int stopline = -1;
   int i;
   int commentstate = 0;
   for (i=0; i<part.getNumLines(); i++) {
      if (part[i][0] == '@') {
         // single line comment
         continue;
      }
      if (part[i][0] == '&') {
         commentstate = !commentstate;
         continue;
      }
      if (commentstate) {
         continue;
      }
      startline = i;
      break;
   }

   for (i=part.getNumLines()-1; i>=0; i--) {
      if (strncmp(part[i].getLine(), "/eof", 4) == 0) {
         stopline = i-1;
         break;
      }
      if (strncmp(part[i].getLine(), "/END", 4) == 0) {
         stopline = part.getNumLines()-1;
         break;
      }
      if (strncmp(part[i].getLine(), "/FINE", 4) == 0) {
         stopline = part.getNumLines()-1;
         break;
      }
   }

   if (debugQ) {
      cout << "-============================================" << endl;
      cout << part;
      cout << "-============================================" << endl;
   }

   if ((startline < 0) || (stopline < 0)) {
      cerr << "Syntax error in part" << endl;
      exit(1);
   }
   if (startline >= stopline) {
      cerr << "Strange Syntax error in part" << endl;
      exit(1);
   }

   Array<char>& LNEWLINE = newline;

   SSTREAM tstream;
   for (i=startline; i<=stopline; i++) {
      tstream << part[i] << LNEWLINE;
   }
   tstream << ends;
   Array<char> partdata(strlen(tstream.CSTRING)+1);
   strcpy(partdata.getBase(), tstream.CSTRING);

   Array<char>& newmd5sum = checksum;
   CheckSum::getMD5Sum(newmd5sum, partdata);

   if (debugQ) {
      cout << "NEW MD5SUM: " << newmd5sum << endl;
      cout << "PART ===================================" << endl;
      cout << partdata;
      cout << "==========================================" << endl;
   }
}



//////////////////////////////
//
// getNewLine -- return the newline convention used in the file.
//    Using MS-DOS convention if none found.
//

void getNewLine(Array<char>& newline, MuseData& part) {
  
   // default style:
   newline.setSize(3);
   newline[0] = (char)0x0d;
   newline[1] = (char)0x0a;
   newline[2] = '\0';

   int timestampindex = -1;
   int i;
   for (i=0; i<part.getLineCount(); i++) {
      if (strncmp(part[i].getLine(), "TIMESTAMP", strlen("TIMESTAMP")) == 0) {
         timestampindex = i;
         break;
      }
   }
   if (timestampindex < 0) {
      return;
   }

   PerlRegularExpression pre;
   if (!pre.search(part[timestampindex].getLine(), 
         "^.*\\[[^:]*?:([^:]+):.*\\].*$", "")) {
      return;
   }

   int len = strlen(pre.getSubmatch(1));
   int value;
   int value2;
   if (len == 1) {
      if (sscanf(pre.getSubmatch(), "%1x", &value)) {
         newline[0] = (char)value;
         newline[1] = '\0';
      }
   } else if (len == 2) {
      if (sscanf(pre.getSubmatch(), "%2x", &value)) {
         newline[0] = (char)value;
         newline[1] = '\0';
      }
   } else if (len == 4) {
      if (sscanf(pre.getSubmatch(), "%2x%2x", &value, &value2)) {
         newline[0] = (char)value;
         newline[1] = (char)value2;
         newline[2] = '\0';
      }
   } // ignore if longer than 4 characters

}



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

void checkOptions(Options& opts, int argc, char** argv) {
   opts.define("C|no-composer=b", "do not display automatic composer");
   opts.define("T|no-title=b", "do not display automatic title");
   opts.define("v|verify=b",  "Verify input MuseData file(s)");
   opts.define("d|debug=b",    "Debugging information");
   opts.define("ns|no-slur|no-slurs|noslur|noslurs=b",  "do not convert slurs");
   opts.define("no-hang-tie=b",  "try to deal with hanging ties");
   // tuplet-bracket no longer being used:
   opts.define("tb|tuplet-bracket=b", "print brackets on tuplet");
   opts.define("vz=b", "only display explicit tuplet marks");
   opts.define("abbreviation|a=b", "Use instrument abbreviations");
   opts.define("nm|nomet|nomen|no-met|no-men=b",  "do not print metrical symbols instead of time signatures");
   opts.define("nd|no-dynamics|no-dynamic|no-dyn|nodynamics|nodyn|nodynamic=b", "do not convert dynamics");
   opts.define("nb|no-beams|no-beam|nobeam|nobeams=b", "do not convert beams");
   opts.define("no-tie|no-ties|notie|noties=b", "do not encode ties");
   opts.define("no-invisible=b", "don't hide any notes/rests");
   opts.define("no-tuplet|no-tuplets|notuplet|notuplets|no-triplet|no-triplets|notriplet|notriplets=b", "do not encode tuplet groupings");
   opts.define("no-checksum=b", "do not print checksum information");
   opts.define("no-extensions=b", "do not work extensions after lyric words");
   opts.define("round=b", "rounded breves (double whole notes)");
   opts.define("nt|no-text|notext=b", "do not convert unknown spines to text");
   opts.define("text=s:", "list of spine types to treat as lyrics");
   opts.define("x|exclude=s:", "list of parameters to exclude");
   opts.define("U|unix|linux=b", "use Unix/Linux newlines");
   opts.define("MAC=b", "use Apple Macintosh old-style newlines");
   opts.define("men|mensural=b", "display in quasi-mensural notation");
   opts.define("men2|mensural2=b", "display in quasi-mensural notation, model 2");
   opts.define("R|no-reference-records|norr|nor|norefernce|no-bib|nobib|no-bibliographic|nobibliographic=b", "do not add reference records");
   opts.define("mono=i:0", "display rhythms in mono-spacing");
   opts.define("vl|verse-limit=i:5", "maximum number of verses to display");
   opts.define("update=b", "update timestamps in MuseData file(s)");
   opts.define("f|foot|footer=s", "Add footer information");
   opts.define("sepbracket=b", "Bracket all staves, with separate barlines");
   opts.define("reason=s", "reason for updating a timestamp");
   opts.define("ro|removeoold|remove-old=b", "remove old timestamps");
   opts.define("na|noappend|no-append=b", "do not append old timestamp");
   opts.define("ls|lyrics-spines=s:", "spines to display as lyrics");
   opts.define("dd|dr|default-duration|default-rhythm=s:4", 
       "rhythm to use if note does not have a rhythm and is not a grace note");
   opts.define("mo|muse2ps-options=s:", "muse2ps option string to append to data");
   opts.define("encoder=s:", "Person to place on line fixed header line 4");
   opts.define("copyright|copy|cr=s:", "Copyright to place on line 1 of fixed header");
   opts.define("wk|work=s:", "WK# number to set in header");
   opts.define("mv|movement=s:", "MV# number to set in header");
   opts.define("textvadd=b", "Add vertical space for **text lyrics");

   opts.define("author=b",    "Program author");
   opts.define("version=b",   "Program version");
   opts.define("example=b",   "Program examples");
   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, June 2010" << NEWLINE;
      exit(0);
   } else if (opts.getBoolean("version")) {
      cout << argv[0] << ", version: 2 June 2010" << NEWLINE;
      cout << "compiled: " << __DATE__ << NEWLINE;
      cout << MUSEINFO_VERSION << NEWLINE;
      exit(0);
   } else if (opts.getBoolean("help")) {
      usage(opts.getCommand());
      exit(0);
   } else if (opts.getBoolean("example")) {
      example();
      exit(0);
   }

   if (opts.getBoolean("verify")) {
      verifyMuseDataFiles(options);
      exit(0);
   } else if (opts.getBoolean("update")) {
      updateMuseDataFileTimeStamps(options);
      exit(0);
   }

   debugQ        =  opts.getBoolean("debug");
   roundQ        =  opts.getBoolean("round");
   slurQ         = !opts.getBoolean("no-slur");
   dynamicsQ     = !opts.getBoolean("no-dynamics");
   checksumQ     = !opts.getBoolean("no-checksum");
   mensuralQ     =  opts.getBoolean("mensural");
   metQ          = !opts.getBoolean("no-met");
   extensionQ    = !opts.getBoolean("no-extensions");
   sepbracketQ   =  opts.getBoolean("sepbracket");
   abbreviationQ =  opts.getBoolean("abbreviation");
   mensural2Q    =  opts.getBoolean("mensural2");
   if (mensural2Q) {
      mensuralQ  = 1;
   }
   composerQ     = !opts.getBoolean("no-composer");
   hangtieQ      = !opts.getBoolean("no-hang-tie");
   titleQ        = !opts.getBoolean("no-title");
   referenceQ    = !opts.getBoolean("no-reference-records");
   textQ         = !opts.getBoolean("no-text");
   TextSpines    =  opts.getString("text");
   beamQ         = !opts.getBoolean("no-beams");
   tieQ          = !opts.getBoolean("no-ties");
   excludeQ      =  opts.getBoolean("exclude");
   excludeString = opts.getString("exclude");
   noinvisibleQ  = opts.getBoolean("no-invisible");
   textvaddQ     = opts.getBoolean("textvadd");

   if (opts.getBoolean("work")) {
      workNumber = opts.getString("work");
   }
   if (opts.getBoolean("movement")) {
      movementNumber = opts.getString("movement");
   }

   footerQ    =  opts.getBoolean("footer");
   if (footerQ) {
      footertext = opts.getString("footer");
   }
   tupletQ    = !opts.getBoolean("no-tuplets");
   vzQ        =  opts.getBoolean("vz");
   if (vzQ) {
      // only draw explicit tuplets
      tupletQ = 0;
   }
   defaultDur =  opts.getString("default-duration");
   verselimit =  opts.getInteger("verse-limit");
   if (opts.getBoolean("encoder")) {
      encoderQ = 1;
      Encoder  = opts.getString("encoder");
   }
   if (opts.getBoolean("copyright")) {
      copyrightQ = 1;
      Copyright = opts.getString("copyright");
   }
   sfzQ = dynamicsQ;
   muse2psoptionstring = opts.getString("muse2ps-options");
   LyricSpines  = opts.getString("lyrics-spines");

   if (opts.getBoolean("unix")) {
      NEWLINE.setSize(2);
      NEWLINE[0] = (char)0x0a;
      NEWLINE[1] = '\0';
   } else if (opts.getBoolean("MAC")) {
      NEWLINE.setSize(2);
      NEWLINE[0] = (char)0x0d;
      NEWLINE[1] = '\0';
   } // otherwise default of 0x0d 0x0a (MS-DOS) will be used.

}



//////////////////////////////
//
// procesSLyricsSpines -- Generate a list of spines which should be printed
//     as lyrics.
//

void processLyricsSpines(Array<Array<int> >& spineinfo, 
      HumdrumFile& infile, const char* data) {
   spineinfo.setSize(infile.getMaxTracks()+1);
   int i, j;
   for (i=0; i<spineinfo.getSize(); i++) {
      spineinfo[i].setSize(0);
   }

   Array<int> kerntracks;
   getKernTracks(kerntracks, infile);

   Array<char> adata(strlen(data) + 1);
   strcpy(adata.getBase(), data);
   PerlRegularExpression pre;
   PerlRegularExpression pre2;
   pre.sar(adata, "\\s+", ",", "gi");
   pre.sar(adata, ",+", ",", "gi");
   pre.sar(adata, "^[^s]*s", "s", "i");
   Array<Array<char> > tokens;
   int staff, spine;
   Array<Array<char> > subtokens;
   pre.getTokens(tokens, ",?s", adata.getBase());

   for (i=0; i<tokens.getSize(); i++) {
      if (!pre.search(tokens[i], "^(\\d+):(.*)", "")) {
         continue;
      }
      staff = abs(atoi(pre.getSubmatch(1)));
      if (staff > infile.getMaxTracks()) {
         continue;
      }
      pre2.getTokens(subtokens, ",", pre.getSubmatch(2));
      for (j=0; j<subtokens.getSize(); j++) {
         spine = abs(atoi(subtokens[j].getBase()));
         if (spine > infile.getMaxTracks()) {
            continue;
         }
         spineinfo[kerntracks[kerntracks.getSize()-staff-1+1]].append(spine);
      }
   }
}


//////////////////////////////
//
// GCD -- greatest common divisor
//
 
int GCD(int a, int b) {
   if (b == 0) {
      return a;
   }
   int z = a % b;
   a = b;
   b = z;
   int output = GCD(a, b);
   if (debugQ) {  
      cout << "GCD of " << a << " and " << b << " is " << output << NEWLINE;
   }
   return output;
}    



//////////////////////////////
//
// LCM -- find the least common multiple between rhythms
//

int LCM(Array<int>& rhythms) {
   if (rhythms.getSize() == 0) {
      return 0;
   }
   int output = rhythms[0];
   for (int i=1; i<rhythms.getSize(); i++) {
      output = output * rhythms[i] / GCD(output, rhythms[i]);
   }

   return output;
}



//////////////////////////////
//
// isPowerOfTwo --
//

int isPowerOfTwo(RationalNumber& rn) {
   if (rn == 0) {
      return 1;
   }
   double val1 = log2((double)rn.getNumerator());
   double val2 = log2((double)rn.getDenominator());
   const double roundofferr  = 0.00001;
   const double roundofferr2 = 0.000001;
   if ((val1 - (int)(val1 + roundofferr2)) < roundofferr) {
      if ((val2 - (int)(val2 + roundofferr2)) < roundofferr) {
         return 1;
      }
   }
   return 0;
}



//////////////////////////////
//
// primeFactorization -- return a list of the prime factors of
//     the given input number, sorted from smallest to largest
//     factors.
//

void primeFactorization(Array<int>& factors, int input) {
   int i   = 3;
   int c   = input;
   int two = 2;
   factors.setSize(0);
   while ((c >= 2) && ((c%2) == 0)) {
      factors.append(two);
      c = c/2;
   }
   while (i <= (sqrt((double)c)+1)) {
      if ((c%i) == 0) {
         factors.append(i);
         c = c/i;
      }
      else {
         i = i+2;
      }
   }
   if (c > 1) {
      factors.append(c);
   }
}



//////////////////////////////
//
// getRhythms -- returns a list of rhythms found in the given range
// in the score.  The output is sorted by largest duration rhythm first, 
// towards the shortest.  zero-length notes are ignored (grace notes).  
// A quarter note is always reported to be present, since this function 
// is intended to be used with the Q: field of $ records in MuseData files.
//
// If the stop index is -1, then use the last line of the file ast tne
// stopping index.
//

void getRhythms(Array<RationalNumber>& rns, HumdrumFile& infile, 
       int startindex, int stopindex) {
   if (stopindex < 0) {
      stopindex = infile.getNumLines() - 1;
   }
   rns.setSize(100);
   rns.setSize(0);
   RationalNumber rn;
   rn = 4;
   int found;
   // rns.append(rn);   // stick a quarter note in the list
   int i, j, k;
   for (i=startindex; i<=stopindex; i++) {
      if (!infile[i].isData()) {
         continue;
      }
      for (j=0; j<infile[i].getFieldCount(); j++) {
         if (!infile[i].isExInterp(j, "**kern")) {
            continue;
         }
         if (strcmp(infile[i][j], ".") == 0) {
            // ignore null records
            continue;
         }
         if (strchr(infile[i][j], 'q') != NULL) {
            // ignore grace notes with a slash
            continue;
         }
         if (strchr(infile[i][j], 'Q') != NULL) {
            // ignore grace notes without a slash
            continue;
         }
         rn = Convert::kernToDurationR(infile[i][j]);
         if (rn == 0) {
            continue;
         }
         found = 0;
         for (k=0; k<rns.getSize(); k++) {
            if (rns[k] == rn) {
               found = 1;
               break;
            }
         }
         if (found == 0) {
            rns.append(rn);
         }
      }
   }

   qsort(rns.getBase(), rns.getSize(), sizeof(RationalNumber), rnsort);

   for (i=0; i<rns.getSize(); i++) {
      rns[i].invert();
   }
}



//////////////////////////////
//
// rnsort -- sort Rational numbers, largest first.
//

int rnsort(const void* A, const void* B) {
   RationalNumber valuea = *((RationalNumber*)A);
   RationalNumber valueb = *((RationalNumber*)B);
   if (valuea > valueb) {
      return -1;
   } else if (valuea < valueb) {
      return +1;
   } else {
      return 0;
   }
}



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

void example(void) {


}



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

void usage(const char* command) {

}


//////////////////////////////
//
// addTextVerticalSpace -- add extra vertical spacing based on the
//     number of verses underneath each staff.  
//

void addTextVertcialSpace(Array<char>& ostring, HumdrumFile& infile) {
   int i;
   Array<int> spaces;

   int firstv = 30;
   int otherv = 20;

   PerlRegularExpression pre;
   if (!pre.search(ostring, "v(\\d[\\d,]+)")) {
      return;
   }
   Array<Array<char> > numbers;
   pre.getTokens(numbers, ",", pre.getSubmatch(1));

   Array<int> values;
   values.setSize(numbers.getSize());
   values.setAll(0);

   for (i=0; i<values.getSize(); i++) {
      values[i] = atoi((const char*)numbers[i].getBase());
   }

   Array<int> lyrics;
   lyrics.setSize(values.getSize());
   lyrics.setAll(0);

   int kindex = -1;
   for (i=0; i<infile.getMaxTracks(); i++) {
      if (strcmp(infile.getTrackExInterp(i+1), "**kern") == 0) {
         kindex++;
         continue;
      }
      if (kindex < 0) {
         continue;
      }
      if (kindex >= values.getSize()) {
         break;
      }

      if (strcmp(infile.getTrackExInterp(i+1), "**text") == 0) {
         lyrics[lyrics.getSize() - kindex - 1]++;
      }
   }

   int tvalue;
   for (i=0; i<values.getSize(); i++) {
      if (lyrics[i] >= 1) {
         tvalue = values[i];
         tvalue += firstv;
         lyrics[i]--;
         tvalue += lyrics[i] * otherv;
         values[i] = tvalue;
      }
   }   

   char buff1[1024] = {0};
   char buff2[1024] = {0};
   strcpy(buff2, "v");
   for (i=0; i<values.getSize(); i++) {
      sprintf(buff1, "%d", values[i]);
      if (i > 0) {
         strcat(buff2, ",");
      } 
      strcat(buff2, buff1);
   }

   pre.sar(ostring, "v\\d[\\d,]+", buff2);
}




/* Function hierarchy: *
main:
	CALLS:
		checkOptions
		convertData
		getBeamState
		getDynamicsAssignment
		getMeasureDuration
		getPartNames
		getTupletState
		printMuse2PsOptions
		printWithMd5sum
		setColorCharacters
		setupMusicaFictaVariables
		setupTextAssignments
checkOptions:
	CALLS:
		example
		updateMuseDataFileTimeStamps
		usage
		verifyMuseDataFiles
	CALLED BY:
		main
example:
	CALLED BY:
		checkOptions
updateMuseDataFileTimeStamps:
	CALLS:
		updateFileTimeStamps
	CALLED BY:
		checkOptions
updateFileTimeStamps:
	CALLS:
		doUpdateWork
	CALLED BY:
		updateMuseDataFileTimeStamps
doUpdateWork:
	CALLS:
		getTimeStampLine
		removeOldTimeStamps
		updatePart
		verifyPart
	CALLED BY:
		updateFileTimeStamps
getTimeStampLine:
	CALLED BY:
		doUpdateWork
removeOldTimeStamps:
	CALLED BY:
		doUpdateWork
updatePart:
	CALLED BY:
		doUpdateWork
verifyPart:
	CALLS:
		getMD5Sum
	CALLED BY:
		doUpdateWork
		verifyMuseDataFile
getMD5Sum:
	CALLS:
		MD5Final
		MD5Init
		MD5Update
	CALLED BY:
		printWithMd5sum
		verifyPart
MD5Final:
	CALLS:
		Encode
		MD5Update
	CALLED BY:
		getMD5Sum
Encode:
	CALLED BY:
		MD5Final
MD5Update:
	CALLS:
		MD5Transform
	CALLED BY:
		MD5Final
		getMD5Sum
MD5Transform:
	CALLS:
		Decode
	CALLED BY:
		MD5Update
Decode:
	CALLED BY:
		MD5Transform
MD5Init:
	CALLED BY:
		getMD5Sum
usage:
	CALLED BY:
		checkOptions
verifyMuseDataFiles:
	CALLS:
		verifyMuseDataFile
	CALLED BY:
		checkOptions
verifyMuseDataFile:
	CALLS:
		verifyPart
	CALLED BY:
		verifyMuseDataFiles
convertData:
	CALLS:
		convertTrackToMuseData
		getKernTracks
		isBarlineBeforeData
		prepareLocalTickChanges
	CALLED BY:
		main
convertTrackToMuseData:
	CALLS:
		appendReferenceRecords
		getMeasureData
		getMeasureInfo
		insertDollarRecord
		insertHeaderRecords
	CALLED BY:
		convertData
appendReferenceRecords:
	CALLED BY:
		convertTrackToMuseData
getMeasureData:
	CALLS:
		getMaxVoices
		processVoice
	CALLED BY:
		convertTrackToMuseData
getMaxVoices:
	CALLED BY:
		getMeasureData
processVoice:
	CALLS:
		addBackup
		addMeasureEntry
		addNoteToEntry
		getTick
		insertDollarRecord
	CALLED BY:
		getMeasureData
addBackup:
	CALLED BY:
		processVoice
addMeasureEntry:
	CALLS:
		addTextLayout
		processMeasureLayout
	CALLED BY:
		processVoice
addTextLayout:
	CALLS:
		addText
	CALLED BY:
		addMeasureEntry
		addNoteToEntry
addText:
	CALLS:
		addPositionParameters
		convertHtmlTextToMuseData
	CALLED BY:
		addTextLayout
addPositionParameters:
	CALLS:
		getXxYy
	CALLED BY:
		addPositionInfo
		addText
getXxYy:
	CALLED BY:
		addPositionParameters
		convertKernLODYtoMusePS
convertHtmlTextToMuseData:
	CALLED BY:
		addLyrics
		addMovementTitle
		addSourceRecord
		addText
		addWorkTitle
		printMuse2PsOptions
processMeasureLayout:
	CALLS:
		handleMeasureLayoutParam
	CALLED BY:
		addMeasureEntry
handleMeasureLayoutParam:
	CALLED BY:
		processMeasureLayout
addNoteToEntry:
	CALLS:
		addChordLevelArtic
		addHairpinStarts
		addHairpinStops
		addLyrics
		addNoteLevelArtic
		addTextLayout
		checkColor
		getChordMapping
		getDuration
		getTickDur
		handleLayoutInfoChord
		setNoteheadShape
	CALLED BY:
		processVoice
addChordLevelArtic:
	CALLS:
		insertArpeggio
	CALLED BY:
		addNoteToEntry
insertArpeggio:
	CALLS:
		getChordMapping
		getPitches
		getScoreStaffVerticalPos
	CALLED BY:
		addChordLevelArtic
getChordMapping:
	CALLS:
		getPitches
		numberRsort
		numbersort
	CALLED BY:
		addNoteToEntry
		insertArpeggio
getPitches:
	CALLED BY:
		getChordMapping
		insertArpeggio
numberRsort:
	CALLED BY:
		getChordMapping
numbersort:
	CALLED BY:
		getChordMapping
getScoreStaffVerticalPos:
	CALLED BY:
		insertArpeggio
addHairpinStarts:
	CALLS:
		addCrescText
		addCrescendoStart
		addCrescendoStop
		addDecrescendoStart
		addDecrescendoStop
	CALLED BY:
		addNoteToEntry
addCrescText:
	CALLS:
		addDashing
		addPositionInfo
	CALLED BY:
		addHairpinStarts
addDashing:
	CALLED BY:
		addCrescText
addPositionInfo:
	CALLS:
		addPositionParameters
	CALLED BY:
		addCrescText
		addCrescendoStart
		addCrescendoStop
		addDecrescendoStart
		addDecrescendoStop
addCrescendoStart:
	CALLS:
		addPositionInfo
	CALLED BY:
		addHairpinStarts
addCrescendoStop:
	CALLS:
		addPositionInfo
	CALLED BY:
		addHairpinStarts
		addHairpinStops
addDecrescendoStart:
	CALLS:
		addPositionInfo
	CALLED BY:
		addHairpinStarts
addDecrescendoStop:
	CALLS:
		addPositionInfo
	CALLED BY:
		addHairpinStarts
		addHairpinStops
addHairpinStops:
	CALLS:
		addCrescendoStop
		addDecrescendoStop
		addUnDash
	CALLED BY:
		addNoteToEntry
addUnDash:
	CALLED BY:
		addHairpinStops
addLyrics:
	CALLS:
		convertHtmlTextToMuseData
		convertHumdrumTextToMuseData
		track2column
	CALLED BY:
		addNoteToEntry
convertHumdrumTextToMuseData:
	CALLED BY:
		addLyrics
track2column:
	CALLED BY:
		addLyrics
addNoteLevelArtic:
	CALLS:
		getBase36
	CALLED BY:
		addNoteToEntry
getBase36:
	CALLED BY:
		addNoteLevelArtic
checkColor:
	CALLED BY:
		addNoteToEntry
getDuration:
	CALLED BY:
		addNoteToEntry
		getGlobalTicksPerQuarter
		getTickDur
		setNoteheadShape
getTickDur:
	CALLS:
		getDuration
	CALLED BY:
		addNoteToEntry
handleLayoutInfoChord:
	CALLS:
		addDynamics
		insertSlurDownPrintSug
		insertSlurUpPrintSug
		insertStaccatoSuggestion
	CALLED BY:
		addNoteToEntry
addDynamics:
	CALLS:
		processDynamicsLayout
	CALLED BY:
		handleLayoutInfoChord
processDynamicsLayout:
	CALLS:
		convertKernLODYtoMusePS
	CALLED BY:
		addDynamics
convertKernLODYtoMusePS:
	CALLS:
		getXxYy
	CALLED BY:
		processDynamicsLayout
insertSlurDownPrintSug:
	CALLED BY:
		handleLayoutInfoChord
insertSlurUpPrintSug:
	CALLED BY:
		handleLayoutInfoChord
insertStaccatoSuggestion:
	CALLED BY:
		handleLayoutInfoChord
setNoteheadShape:
	CALLS:
		getDuration
		setNoteheadShape
	CALLED BY:
		addNoteToEntry
		setNoteheadShape
getTick:
	CALLED BY:
		processVoice
insertDollarRecord:
	CALLS:
		addMovementDesignation
		appendClef
		appendKeySignature
		appendTimeSignature
	CALLED BY:
		convertTrackToMuseData
		processVoice
addMovementDesignation:
	CALLED BY:
		insertDollarRecord
appendClef:
	CALLED BY:
		insertDollarRecord
appendKeySignature:
	CALLED BY:
		insertDollarRecord
appendTimeSignature:
	CALLED BY:
		insertDollarRecord
getMeasureInfo:
	CALLED BY:
		convertTrackToMuseData
		prepareLocalTickChanges
insertHeaderRecords:
	CALLS:
		addControlNumber
		addCopyrightLine
		addDateAndEncoder
		addMovementTitle
		addPartName
		addSourceRecord
		addTimeStamp
		addWorkNumberInfo
		addWorkTitle
	CALLED BY:
		convertTrackToMuseData
addControlNumber:
	CALLED BY:
		insertHeaderRecords
addCopyrightLine:
	CALLED BY:
		insertHeaderRecords
addDateAndEncoder:
	CALLED BY:
		insertHeaderRecords
addMovementTitle:
	CALLS:
		convertHtmlTextToMuseData
	CALLED BY:
		insertHeaderRecords
addPartName:
	CALLED BY:
		insertHeaderRecords
addSourceRecord:
	CALLS:
		convertHtmlTextToMuseData
	CALLED BY:
		insertHeaderRecords
addTimeStamp:
	CALLED BY:
		insertHeaderRecords
addWorkNumberInfo:
	CALLS:
		getWorkAndMovement
	CALLED BY:
		insertHeaderRecords
getWorkAndMovement:
	CALLED BY:
		addWorkNumberInfo
addWorkTitle:
	CALLS:
		convertHtmlTextToMuseData
	CALLED BY:
		insertHeaderRecords
getKernTracks:
	CALLED BY:
		convertData
		processLyricsSpines
		setupTextAssignments
isBarlineBeforeData:
	CALLED BY:
		convertData
prepareLocalTickChanges:
	CALLS:
		LCM
		getGlobalTicksPerQuarter
		getMeasureInfo
		getRhythms
		getTPQByRhythmSet
	CALLED BY:
		convertData
LCM:
	CALLS:
		GCD
	CALLED BY:
		getGlobalTicksPerQuarter
		getTPQByRhythmSet
		prepareLocalTickChanges
GCD:
	CALLS:
		GCD
	CALLED BY:
		GCD
		LCM
getGlobalTicksPerQuarter:
	CALLS:
		LCM
		getDuration
	CALLED BY:
		prepareLocalTickChanges
getRhythms:
	CALLS:
		rnsort
	CALLED BY:
		prepareLocalTickChanges
rnsort:
	CALLED BY:
		getRhythms
getTPQByRhythmSet:
	CALLS:
		LCM
	CALLED BY:
		prepareLocalTickChanges
getBeamState:
	CALLS:
		addGlobalLayoutInfo
		addLayoutInfo
		adjustClefState
		countBeamStuff
		storeClefInformation
		storeGlobalLayoutInfo
		storeLayoutInformation
	CALLED BY:
		main
addGlobalLayoutInfo:
	CALLED BY:
		getBeamState
addLayoutInfo:
	CALLED BY:
		getBeamState
adjustClefState:
	CALLED BY:
		getBeamState
countBeamStuff:
	CALLED BY:
		getBeamState
storeClefInformation:
	CALLED BY:
		getBeamState
storeGlobalLayoutInfo:
	CALLED BY:
		getBeamState
storeLayoutInformation:
	CALLED BY:
		getBeamState
getDynamicsAssignment:
	CALLED BY:
		main
getMeasureDuration:
	CALLED BY:
		main
getPartNames:
	CALLED BY:
		main
getTupletState:
	CALLS:
		getDurationNoDots
		getTupletTop
		isPowerOfTwo
	CALLED BY:
		main
getDurationNoDots:
	CALLED BY:
		getTupletState
		getTupletTop
getTupletTop:
	CALLS:
		getDurationNoDots
		primeFactorization
	CALLED BY:
		getTupletState
primeFactorization:
	CALLED BY:
		getTupletTop
isPowerOfTwo:
	CALLED BY:
		getTupletState
printMuse2PsOptions:
	CALLS:
		convertHtmlTextToMuseData
	CALLED BY:
		main
printWithMd5sum:
	CALLS:
		getMD5Sum
	CALLED BY:
		main
setColorCharacters:
	CALLS:
		getColorCategory
	CALLED BY:
		main
getColorCategory:
	CALLED BY:
		setColorCharacters
setupMusicaFictaVariables:
	CALLED BY:
		main
setupTextAssignments:
	CALLS:
		getKernTracks
		processLyricsSpines
	CALLED BY:
		main
processLyricsSpines:
	CALLS:
		getKernTracks
	CALLED BY:
		setupTextAssignments
getNewLine:
	ORPHAN

*/

// md5sum: e86e33edcf63e09feb94dc06286a42a2 hum2muse.cpp [20130504]