//
// Programmer:    Craig Stuart Sapp <craig@ccrma.stanford.edu>
// Creation Date: Sat Oct 11 02:27:50 PDT 2008
// Last Modified: Wed Oct 15 08:45:23 PDT 2008 adding tuplet rhythm parsing
// Last Modified: Fri Oct 17 19:00:46 PDT 2008 added layers, invisible, caut.
// Last Modified: Sun Oct 26 04:58:48 PST 2008 added misc features
// Last Modified: Wed Dec  3 20:19:29 PST 2008 end of data format fix
// Last Modified: Wed Jun 24 15:37:21 PDT 2009 updated for GCC 4.4
// Last Modified: Mon Jul 20 16:32:01 PDT 2009 added 8ba treble clef
// Last Modified: Mon Jul 20 16:32:01 PDT 2009 added M:none time signature
// Last Modified: Wed Jul 22 13:03:44 PDT 2009 added editorial slur dashing
// Last Modified: Mon Aug 24 14:00:34 PDT 2009 more work on 8ba treble clef
// Last Modified: Mon Sep 21 17:13:53 PDT 2009 added --no-tempo option
// Last Modified: Mon Sep 21 17:29:50 PDT 2009 accidental spelling by octave
// Last Modified: Sun Jan 16 09:56:09 PST 2011 adj. off-by-1 in bar num update
// Last Modified: Sun Jan 16 11:25:19 PST 2011 added marks and --no-marks
// Last Modified: Sun Jan 16 16:31:23 PST 2011 added --no-slur option
// Last Modified: Sat Oct  6 07:45:11 PDT 2012 added --cb and --db options
// Last Modified: Sun Oct 21 13:26:51 PDT 2012 added --key for keysig modality
// Last Modified: Fri Dec 14 00:39:32 PST 2012 made compiler OS X happy
// Filename:      ...sig/examples/all/hum2abc.cpp
// Web Address:   http://sig.sapp.org/examples/museinfo/humdrum/hum2abc.cpp
// Syntax:        C++; museinfo
//
// Description:   Converts Humdrum files into ABC+ (polyphonic ABC) files
//
// Note: Not completed yet, particularly dynamics and lyrics.
//
// Todo:	   
//                 lyrics
//                 add local comments as [r: comment] fields
//                 cross staff stemming/beaming?
//                 up/down stem controls (if possible)
//                 display numeric plots underneath a staff
//                 dynamics
//                 figured bass
//                 suppress accidentals on tied notes after barlines
//                 handle invisible articulations 
//                 allow trills on a single note in a chord (e.g. s03-1;m23)
//                 don't display accidental on tied notes (_ and ])
//                 don't store accidental state of tied notes
//                 some characters with accents (incomplete list implemented)
//                 allow fermatas on barlines (including final / repeats)
//
// Done in v1.0.0  accidentals
//                 measure numbers can be boxed with --box option
//                 invisible rests using "y"
//                 added option to print invisible rests and other objects
//                 first/second endings
//                 cautionary accidentals (with X)
//                 deal with tenor and etc clef as an initial clef
//                 printing invisible rests in second layer
//                 print the first barnumber if there a pickup and barnum'g = 1
//                 multi-measure rests
//		   tuplet rhythms (make rhythms more robust)
//                 notes w/no rhythms are displayed as quarter notes w/no stems
//		   break tuplet rhythm groupings by beaming
//                 added veritas checking/marking
//		   breaking subbeams
//                 add brokenrhythms   A>B = A3/2B/2
//                 abc+ extended information records
//                 -sharp -flat -natural map to \# \b \= in titles; \b\= no work
//                 slurs (some bugs in abcm2ps rendering of dashed slurs)
//                 ties
//                 gracenotes (with [q] and without [Q] slashes)
//                 phrases
//                 piano system automatic identification (partially done)
//                 clef changes during music
//                 time signature changes during the piece
//                 add rhythm shorthands / = /2, // = /4
//                 key signtures changes during piece
//                 beaming
//                 ornaments
//                 articulations
//                 if the initial keysignature comes after the
//                    first barline, but before the music, set it
//                    as the initial meter in the header.
//
// Notes:
//
// * abcm2ps src:  http://trillian.mit.edu/~jc/music/abc/src/abcm2ps/abc2ps.c
// * Format parameters for abcm2ps 5.4.4: 
//     http://br.geocities.com/hfmlacerda/abc/format.html
// * Creating new symbols in abcm2ps:
//     http://br.geocities.com/hfmlacerda/abc/decomanual-en.pdf 
//     http://hudsonlacerda.webs.com/abc/decomanual-en.pdf
// * interesting webpage: http://www.ucolick.org/~sla/abcmusic/piano/piano.html
// 
// * http://www.formulus.com/hymns/abcm2ps.txt
//    alto1= C1 soprano cleff, alto2 = C2 mezzo-soprano clef
//    bass3 = F3
//
// * Use the F: record to encode the File URL
//   e.g.: hum2abc -F "http://this.location" file.krn > file.abc
// 

#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <string.h>
#include <dirent.h>

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

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

#define EMPTY '\0'
#define TOLERANCE 0.0001

#define AA('A'-'A')
#define BB('B'-'A')
#define CC('C'-'A')
#define DD('D'-'A')
#define EE('E'-'A')
#define FF('F'-'A')
#define GG('G'-'A')
#define HH('H'-'A')
#define II('I'-'A')
#define JJ('J'-'A')
#define KK('K'-'A')
#define LL('L'-'A')
#define MM('M'-'A')
#define NN('N'-'A')
#define OO('O'-'A')
#define PP('P'-'A')
#define QQ('Q'-'A')
#define RR('R'-'A')
#define SS('S'-'A')
#define TT('T'-'A')
#define UU('U'-'A')
#define VV('V'-'A')
#define WW('W'-'A')
#define XX('X'-'A')
#define YY('Y'-'A')
#define ZZ('Z'-'A')

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

class Coordinate {
   public:
      int row;
      int col;
};

class VoiceMap {
   public:
      VoiceMap(void);
     ~VoiceMap();
      int primary;               // primary spine of the staff
      int voicenumber;           // voice number in abc output
      void clear(void);
      // store dynamics location 
      // store lyrics location(s)
};

VoiceMap::VoiceMap(void) {
   clear();
}

VoiceMap::~VoiceMap() {
   clear();
}

void VoiceMap::clear(void) {
   primary = -1;
   voicenumber = -1;
}

class MeasureInfo {
   public:
      MeasureInfo(void);
     ~MeasureInfo();
      void clear(void);
      int startline;              // line number in the humdrum file which
                                  // start the measure (with a barline)
      int endline;                // line number in the humdrum file which
                                  // ends the measure (may be a barline or not)
      int measurenum;             // an explicit number found for the barline
      int currkey;
      int newkey;                 
      const char* kernkey;        // for printing ABC modality with --key option
      int fullrest;               // -1 or 0 if not a full measure of rest
                                  // otherwise a count of the number of
                                  // full measures of rests including
                                  // the current one which have occured
                                  // in sequence before the current one.
      int ending;                 // -1 if not the start of an ending
                                  // 0 if the end of a set of repeat
                                  // endings.  1 or greater if the start
                                  // of a repeat.
};

MeasureInfo::MeasureInfo(void) {
   clear();
}

MeasureInfo::~MeasureInfo() {
   clear();
}

void MeasureInfo::clear(void) {
   startline  = -1;
   endline    = -1;
   measurenum = -1;
   newkey     = -100;
   currkey    = -100;
   fullrest   = -100;
   ending     = -1;
   kernkey    = NULL;
}
   
class TupletInfo {
   public:
      TupletInfo(void);
     ~TupletInfo();
      void clear(void);
      int top;
      int bot;
      int shortcut;
      int count;
};

TupletInfo::TupletInfo(void) {
   clear();
}

TupletInfo::~TupletInfo() {
   clear();
}

void TupletInfo::clear(void) {
   shortcut = count = top = bot = -1;
}

class BeamInfo {
   public:
        BeamInfo(void);
       ~BeamInfo();
   void clear(void);
   int left;                   // number of beams on left side of note
   int right;                  // number of beams on right side of note
   // maybe add flags as well
};

BeamInfo::BeamInfo(void) {
   clear();
}

BeamInfo::~BeamInfo() {
   clear();
}

void BeamInfo::clear(void) {
   left = right = -1;
}



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

// function declarations:
void      checkOptions(Options& opts, int argc, char** argv, 
                                int fcount, HumdrumFile& cinfile);
void      storeOptionSet(Options& opts);
void      example(void);
void      usage(const char* command);
void      printHeader(ostream& out, HumdrumFile& infile,
                                Array<char*>& header,
                                Array<MeasureInfo>& measures, int xval, 
                                const char* filename);
void      parseBibliographic(Array<char*>& header, HumdrumFile& infile);
void      convertHumdrumToAbc(ostream& out, HumdrumFile& infile, int xval,
                                const char* filename);
void      calculateBestRhythmUnit(HumdrumFile& infile, int& Ltop, int& Lbot);
void      getBibPieces(const char* string, const char* string2, 
		                  Array<char>& marker,  Array<char>& contents);
void      storeHeaderRecord(Array<char*>& header, char recordletter, 
                                const char* string);
void      translateSpecialCharacters(Array<char>& output, const char* input);
void      calculateQRecord(Array<char>& QRecord, double tempo, 
                                const char* omdstring, int top, int bot);
void      printBody(ostream& out, HumdrumFile& infile,
                                Array<MeasureInfo>& measures);
void      createVoiceMap(Array<VoiceMap>& voicemap, HumdrumFile& infile);
void      printVoiceDeclaration(ostream& out, VoiceMap vmap, int voicenum, 
                                HumdrumFile& infile);
void      getMeasureInfo(Array<MeasureInfo>& measures, 
                                HumdrumFile& infile);
void      printMeasures(ostream& out, HumdrumFile& infile, 
                                Array<MeasureInfo>& measures, 
                                int startmeasure, int endmeasure,
				Array<VoiceMap>& voicemap);
void      printSingleMeasure(ostream& out, const Array<VoiceMap>& voiceinfo, 
                                int vindex, int vmapsize, 
                                Array<MeasureInfo>& measureinfo, 
                                int mindex, HumdrumFile& infile, 
                                int staffnumber);
void      buildNoteAddresses(Array<Array<Coordinate> >& noteaddresses,
                                HumdrumFile& infile, const VoiceMap& voiceinfo, 
                                const MeasureInfo& measureinfo);
void      printLayer(int layer, ostream& out, 
                                const VoiceMap& voiceinfo, 
                                const MeasureInfo& measureinfo, 
                                HumdrumFile& infile, 
                                Array<Array<Coordinate> >& address,
                                Array<Coordinate>& meterclef);
int       getMaxLayer(int track, int start, int endd, 
                                HumdrumFile& infile);
void      printMeasureLine(ostream& out, HumdrumFile& infile, int line,
                                int primary, int staffnumber,
                                Array<MeasureInfo>& measureinfo, int mindex,
                                int vindex);
void      getNoteDurations(Array<double>& notedurations, 
		                Array<int>& iindex, int layer, 
                                Array<Array<Coordinate> >& address, 
                                HumdrumFile& infile);
void      printKernTokenAsAbc(ostream& out, HumdrumFile& infile, int row, 
                                int col, Array<int>& accident, double notedur, 
                                int brokenq, double brokendur, int top, 
                                int bot, int& slursuppress, int groupflag);
void      getBrokenRhythms(Array<int>& broken, Array<double>& brokendurs, 
                                Array<Coordinate>& nogracelist, 
                                Array<double>& nogracedurs, 
                                HumdrumFile& infile);
int       getBeamState(const char* token);
char*     base40ToAbcPitch(char* buffer, int base40);
int       countDots(char* buffer);
int       getRhythm(const char* buffer);
void      printKeySignature(ostream& out, HumdrumFile& infile);
void      setAccidentals(Array<int>& accident, int key);
void      printAbcKeySignature(ostream& out, int keynum);
void      printAbcKeySignature(ostream& out, const char* kernkey);
void      printAccidental(ostream& out, int base40, const char* token, 
                                Array<int>& accident);
void      adjustAccidentalStates(Array<int>& accident, int step, 
                                Array<Array<Coordinate> >& address, 
                                HumdrumFile& infile);
void      adjustAccidentalStates2(Array<int>& accident, HumdrumFile& infile, 
                                int row, int col);
void      printArticulations(ostream& out, const char* string);
void      getMeterAndClefChanges(Array<Coordinate>& meterchanges, 
                                HumdrumFile& infile, const VoiceMap& voiceinfo,
                                const MeasureInfo& measureinfo);
void      printMeterAndClefChanges(ostream& out, int testrow, 
                                Array<Coordinate>& meterclef, 
                                HumdrumFile& infile);
void      printAbcClef(ostream& out, const char* string, int track,
                                Array<int>& cleftranspose);
void      printAbcMeter(ostream& out, const char* string);
int       checkForAllTies(HumdrumFile& infile, int row, int col);
int       findRecord(const char* key, Array<char>& value, 
                                HumdrumFile& infile, Array<int>& bibfields);
int       findMultipleRecords(const char* key, Array<Array<char> >& values, 
                                HumdrumFile& infile, Array<int>& bibfields);
void      printAbcExtendedInformationFields(ostream& out, HumdrumFile& infile);
void      primeFactorization(Array<int>& factors, int input);
void      printRhythm1(ostream& out, char* buffer, int top, int bot);
int       printRhythm2(ostream& out, double dur, int brokenstate, 
                                double brokendur, int top, int bot, 
                                int chordQ, const char* token);
void      simplifyFraction(int& top, int& bot);
void      printDurationAsRhythm(ostream& out, double dur, int top, int bot);
void      getNoGraceList(Array<Coordinate>& notelist, 
                                Array<Array<Coordinate> >& address, 
                                int layer, HumdrumFile& infile);
void      getNoGraceDurs(Array<double>& nogracedurs, 
                                Array<double>& notedurs, 
                                Array<Array<Coordinate> >& address, 
                                int layer);
void      identifyTuplets(Array<TupletInfo>& tupletstuff, 
                                Array<double>& nogracedurs, 
                                Array<Coordinate>& nogracelist, 
	                        HumdrumFile& infile, Array<int>& broken, 
                                Array<double>& brokendurs,
                                Array<BeamInfo>& beaminfo);
int       getTupletInfo(TupletInfo& tupletsutff, 
                                Coordinate& nogracelist, HumdrumFile& infile);
int       getNonDuplePrimes(Array<int>& factors);
int       getDuplePrimes(Array<int>& factors);
int       getNextLowerPowerOfTwo(int number);
void      identifyTupletShortCuts(Array<TupletInfo>& tupletstuff,
                                Array<BeamInfo>& beaminfo, 
                                Array<double>& nogracedurs,
                                Array<Coordinate>& nogracelist,
                                HumdrumFile& infile);
void      printTupletInfo(ostream& out, TupletInfo& tinfo);
void      flipCommaParts(Array<char>& contents);
int       roundTempoToNearestStandard(double tempo);
int       characterCount(const char* string, char character);
void      getBeamInfo(Array<BeamInfo>& beaminfo, 
                                Array<Coordinate>& nogracelist, 
                                HumdrumFile& infile);
void      separateCountsByBeams(Array<TupletInfo>& tupletstuff, 
                                Array<BeamInfo>& beaminfo,
                                Array<Coordinate>& nogracelist,
                                HumdrumFile& infile);
void      identifyWholeRestsInMeasures(Array<MeasureInfo>& measures, 
                                VoiceMap& voicemap, HumdrumFile& infile);
int       checkForWholeRestInMeasure(int startline, int endline, int primary,
                                HumdrumFile& infile);
void      printMultiRest(ostream& out, Array<MeasureInfo>& measureinfo, 
                                int mindex);
void      printInvisibleRest(ostream& out, double duration);
void      getRepeatInfo(Array<int>& segments, Array<int>& repeatinfo, 
                                HumdrumFile& infile);
int       checkForRepeatMeasure(HumdrumFile& infile, Array<int>& segments, 
                                Array<int>& repeatinfo, int mindex);
int       getFirstMeasureNumber(HumdrumFile& infile);
void      printVoiceClef(ostream& out, VoiceMap voicemap, 
		                HumdrumFile& infile);
void      getRestInfo(Array<int>& rests, 
                                Array<Coordinate>& nogracelist, 
                                HumdrumFile& infile);
void     printVeritas(ostream& out, HumdrumFile& infile);
void     prepareBibliographicData(Array<Array<char> >& markers, 
                                Array<Array<char> >& contentses, 
                                HumdrumFile& infile);
void     addContentToString(Array<char>& newtitle, Array<char>& key, 
                                Array<Array<char> >& markers, 
                                Array<Array<char> >& contentses);
void     printNumberFromString(ostream& out, const char* filename, 
                                const char* ending);
void     printFilenameBase(ostream& out, const char* filename);
int      norhythm(const char* buffer);
void     getFileListing(Array<Array<char> >& filelist, 
		                const char* directoryname, 
                                const char* filemask);
int      sortfiles(const void* a, const void* b);
void     printGraceRhythm(ostream& out, const char* buffer, 
		                int top, int bot, int groupflag);
int     getGraceNoteGroupFlag(Array<double>& notedurs, int index);
void    checkForLineBreak(ostream& out, HumdrumFile& infile, 
                                int row, int col);
void    getColorAssignment(double& red, double& green, double& blue, 
                                const char* line);
void    checkMarks(HumdrumFile& infile);
int     getMarkState(Array<char>& marks, HumdrumFile& infile);
void    printMarks(ostream& out, const char* buffer, 
                                Array<char>& marks,
                                Array<Array<double> > markcolors, 
                                int notecount);
void    printMarkCodes(ostream& out, Array<char>& marks, 
                                Array<Array<double> >& markcolors);
void    printNoteHeadShape(ostream& out, const char* buffer, int mindex);
void    getNoteShape(RationalNumber&, const char* buffer);

// User interface variables:
Options options;
int    debugQ    = 0;             // used with --debug option
int    labelQ    = 0;             // used with --label option
int    veritasQ  = 1;             // used with --no-veritas option
int    continueQ = 1;             // used with --no-autoformat option
int    boxQ      = 0;             // used with --box option
int    notespacingQ = 0;          // used with --spacing option
double notespacing  = 0;          // used with --spacing option
int    linemeasure  = 4;          // used with -m option
int    barnumberingstyle = 0;     // used with -n option
int    invisibleQ   = 1;          // used with --no-invisible
int    footerQ      = 0;          // used with -f option
const char* footer   = "";        // used with -f option
int    headerQ      = 0;          // used with -h option
const char* header   = "";        // used with -h option
int    musicscaleQ  = 0;          // used with -s option
double musicscale   = 0.75;       // used with -s option
int    landscapeQ   = 0;          // used with --landscape option
const char* parameterstring = ""; // used with -p option
const char* invisiblerest = "x";  // used with --no-invisible option
int    filenumQ     = 0;          // used with --filenum option
const char* filenumstring = "";   // used with --filenum option
int    filenametitleQ = 0;        // used with --filetitle option
int    titleexpansionQ = 0;       // used with --TT option
const char* titleexpansion = "";  // used with --TT option
int    graceQ = 1;                // used with --no-grace option
int    directoryQ = 0;            // used with --dir option
const char* directoryname = ".";  // used with --dir option
const char* filemask = ".krn";    // used with --mask option
int    nonaturalQ    = 0;         // used with --nn option
int    linebreakQ    = 0;         // used with --linebreak option
int    notempoQ      = 0;         // used with --no-tempo option
int    slurQ         = 1;         // used with --no-slur option
int    databarnumQ   = 0;         // used with --data-barnum option
int    commentbarnumQ = 0;        // used with --comment-barnum option
int    keyQ           = 0;        // used with --key option

// mark data
int    markQ       = 1;           // used with --no-mark option
int    hasmarksQ   = 0;           // used with markQ
Array<char> marks;                // marking characters in **kern data
Array<Array<double> > markcolors; // color for markings in **kern data
Array<Array<int> > usedMarks;     // keep track of which noteheads used.
const int USEDSIZE  = 9;
int    usedAnnotation = 0;        // keep track of whether annotation code
                                  // needs to be added to header

Array<char*> Header;

// score variables
int Ltop = 1;              // used for creating rhythm values
int Lbot = 4;              // used for creating rhythm values
int StartKey = 0;          
Array<VoiceMap> Voicemap;  // used printing polyphonic data
Array<int> clefstates;     // keep track of transpositions for tenor clef
int stemlessQ = 0;         // used with -p "%%nostems" spoofing

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

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

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

   int i = 0;
   if (directoryQ) {
      Array<Array<char> > filelist;
      getFileListing(filelist, directoryname, filemask);

      for (i=0; i<filelist.getSize(); i++) {
         hfile.read(filelist[i].getBase());
         checkMarks(hfile);
         checkOptions(options, argc, argv, i+1, hfile);
         convertHumdrumToAbc(cout, hfile, i+1, filelist[i].getBase());
         if (i < filelist.getSize() - 1) {
            cout << "\n\n\n";
         }
      }

   } else if (options.getArgumentCount() == 0) {
      checkMarks(hfile);
      convertHumdrumToAbc(cout, hfile, 1, "");
   } else {
      for (i=1; i<=options.getArgumentCount(); i++) {
         // process the command-line options
         checkOptions(options, argc, argv, i, hfile);
         hfile.read(options.getArg(i));
         checkMarks(hfile);
         convertHumdrumToAbc(cout, hfile, i, options.getArg(i));
         if (i < options.getArgumentCount()) {
            cout << "\n\n\n";
         }
      }
   }

   return 0;
}

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


//////////////////////////////
//
// checMarks --
//

void checkMarks(HumdrumFile& infile) {
   if (!markQ) {
      marks.setSize(0);
      markcolors.setSize(0);
      hasmarksQ = 0;
      return;
   }

   Array<char> mchar; // list of characters which are marks
   marks.setSize(0);
   markcolors.setSize(0);
   int i;
   char target;
   double red   = 0; 
   double green = 0;
   double blue  = 0;
   PerlRegularExpression pre;
   for (i=0; i<infile.getNumLines(); i++) {
      if (!infile[i].isBibliographic()) {
         continue;
      }
      if (pre.search(infile[i][0], 
            "!!!RDF\\*\\*kern\\s*:\\s*([^=])\\s*=\\s*match", "i")) {
         target = pre.getSubmatch(1)[0];
         marks.append(target);
         // check for color assignment
         getColorAssignment(red, green, blue, infile[i][0]);
         markcolors.setSize(markcolors.getSize()+1);
         markcolors.last().setSize(3);
         markcolors.last()[0] = red;
         markcolors.last()[1] = green;
         markcolors.last()[2] = blue;
      } else if (pre.search(infile[i][0], 
            "!!!RDF\\*\\*kern\\s*:\\s*([^=])\\s*=\\s*mark", "i")) {
         target = pre.getSubmatch(1)[0];
         marks.append(target);
         // check for color assignment
         getColorAssignment(red, green, blue, infile[i][0]);
         markcolors.setSize(markcolors.getSize()+1);
         markcolors.last().setSize(3);
         markcolors.last()[0] = red;
         markcolors.last()[1] = green;
         markcolors.last()[2] = blue;
         
      }
   }

   // if the markcount is greater than 1, then print %%postscript code
   // in abc plus header.
   if (marks.getSize() <= 0) {
      hasmarksQ = 0;
   } else {
      hasmarksQ = getMarkState(marks, infile);
   }

   // keep track of what noteheads have been displayed in the score
   // so that only the ones used will be printed in the header.
   usedMarks.setSize(marks.getSize());
   for (i=0; i<usedMarks.getSize(); i++) {
      usedMarks[i].setSize(USEDSIZE);
      usedMarks[i].setAll(0);
   }

}



//////////////////////////////
//
// getMarkState -- return true if there are marks in the **kern data, otherwise
//     false.

int getMarkState(Array& marks, HumdrumFile& infile) {
   const char* str;
   int i, j, k, m;
   for (i=0; i<infile.getNumLines(); i++) {
      if (!infile[i].isData()) {
         continue;
      }
      for (j=0; j<infile[i].getFieldCount(); j++) {
         if (infile[i].isExInterp(j, "**kern")) {
            k=0;
            str = infile[i][j];
            while (str[k] != '\0') {
               for (m=0; m<marks.getSize(); m++) {
                  if (str[k] == marks[m]) {
                     return 1;
                  }
               }
               k++;
            }
         }
      }
   }
   return 0;
}



//////////////////////////////
//
// getColorAssignment -- get a color indication on a !!!RDF**kern: ?=mark|match
//    line.  The color assignment is a code such as:
//         color="#ff00ff"
//

void getColorAssignment(double& red, double& green, double& blue, 
      const char* line) {

   // the default color if none found in string:
   red = 1.0;
   green = 0.2;
   blue = 0.2;

   PerlRegularExpression pre;
   if (!pre.search(line, "color\\s*=\\s*\"([^\"]+)\"", "i")) {
      return;
   }
   const char* colorstring = pre.getSubmatch(1);
   PerlRegularExpression pre2;
   if (!pre2.search(colorstring, "#?([\\da-f][\\da-f])([\\da-f][\\da-f])([\\da-f][\\da-f])", "i")) {
      return;
      // maybe handle color names or decimal colors here
   }

   red   = strtol(pre2.getSubmatch(1), NULL, 16);
   green = strtol(pre2.getSubmatch(2), NULL, 16);
   blue  = strtol(pre2.getSubmatch(3), NULL, 16);

   red   = red   / 256.0;
   green = green / 256.0;
   blue  = blue  / 256.0;

   red   = ((int)(red   * 100 + 0.5))/100.0;
   green = ((int)(green * 100 + 0.5))/100.0;
   blue  = ((int)(blue  * 100 + 0.5))/100.0;

   if (red   > 1.0) { red   = 1.0; }
   if (green > 1.0) { green = 1.0; }
   if (blue  > 1.0) { blue  = 1.0; }
}



//////////////////////////////
//
// getFileListing --
//

void getFileListing(Array<Array<char> >& filelist, const char* directoryname, 
      const char* filemask) {

   filelist.setSize(1000000);
   filelist.setGrowth(1000000);
   filelist.setSize(0);
   int length;
   int dirlen = strlen(directoryname);
   const char* connector = "/";
   if (directoryname[dirlen-1] == '/') {
      connector = "";
   }
   int conlen = strlen(connector);
   int storlen;

   DIR *d;
   struct dirent *dir;
   d = opendir(directoryname);

   if (!d) {
      cerr << "Error: could not open directory " << directoryname
           << " for reading." << endl;
      exit(1);
   }

   while ((dir = readdir(d)) != NULL) {
      if (strstr(dir->d_name, filemask) == NULL)  {
         // ignore files which do not match to mask.
         continue;
      }
      filelist.setSize(filelist.getSize()+1);
      length = strlen(dir->d_name);
      storlen = length + dirlen + conlen + 1;
      filelist[filelist.getSize()-1].setSize(storlen);
      strcpy(filelist[filelist.getSize()-1].getBase(), directoryname);
      strcat(filelist[filelist.getSize()-1].getBase(), connector);
      strcat(filelist[filelist.getSize()-1].getBase(), dir->d_name);
   }
   closedir(d);


   // sort the filelist (it is not sorted currently)
   
   qsort(filelist.getBase(), filelist.getSize(), sizeof(Array<char>), 
      sortfiles); 

}



//////////////////////////////
//
// sortfiles --
//

typedef Array<char> chararray;

int sortfiles(const void* a, const void* b) {
   return strcmp((((chararray*)a))->getBase(), (((chararray*)b))->getBase());
}



//////////////////////////////
//
// convertHumdrumToAbc --
//

void convertHumdrumToAbc(ostream& out, HumdrumFile& infile, int xval,
      const char* filename) {
   clefstates.setSize(infile.getMaxTracks()+1);
   clefstates.setAll(0);
   clefstates.allowGrowth(0);
   infile.analyzeRhythm("4");
   Array<MeasureInfo> measures;
   createVoiceMap(Voicemap, infile);
   getMeasureInfo(measures, infile);

   SSTREAM sheader;
   SSTREAM smarks;
   SSTREAM sbody;

   printHeader(sheader, infile, Header, measures, xval, filename);
   printBody(sbody, infile, measures);

   // if (hasmarksQ) {
      printMarkCodes(smarks, marks, markcolors);
   // }

   sheader << ends;
   smarks << ends;
   sbody << ends;

   out << sheader.CSTRING;
   out << smarks.CSTRING;
   out << sbody.CSTRING;
   out << flush;
}



//////////////////////////////
//
// printBody -- print the musical data for the score.
//

void printBody(ostream& out, HumdrumFile& infile, 
      Array<MeasureInfo>& measures) {

   // print on a single line if requested by the
   // user here; otherwise, currently print in
   // 4-measure chunks.

   int i;
   if (debugQ) {
      out << "%\n% Measure information in input file:\n";
      out << "%\tindex\tnumber\tstart\tstop\tlines\tcurkey\tnewkey\n";
      for (i=0; i<measures.getSize(); i++) {
         out << "%\tM:" << i << "\t" 
             << measures[i].measurenum << "\t"
             << measures[i].startline  << "\t"
             << measures[i].endline    << "\t"
             << measures[i].endline - measures[i].startline << "\t"
             << measures[i].currkey    << "\t"
             << measures[i].newkey
	     << endl;
      } 
      out << "%\n";
   }

   int increment = linemeasure;
   int maxmeasure;
   for (i=0; i<measures.getSize(); i+=increment) {
      maxmeasure = i+increment;
      if (maxmeasure >= measures.getSize()) {
         maxmeasure = measures.getSize();
      }
      printMeasures(out, infile, measures, i, maxmeasure, Voicemap);
   }
}



//////////////////////////////
//
// printMeasures --
//

void printMeasures(ostream& out, HumdrumFile& infile, 
      Array<MeasureInfo>& measures, int startmeasure, int stopmeasure, 
      Array<VoiceMap>& voicemap) {
 
   int i, j;
   int asize = voicemap.getSize();
   SSTREAM *pstaves[asize];
   for (i=0; i<asize; i++) {
      pstaves[i] = new SSTREAM;
   }
   // SSTREAM staves[voicemap.getSize()];

   for (i=startmeasure; i<stopmeasure; i++) {
      for (j=0; j<voicemap.getSize(); j++) {
         if (commentbarnumQ && (j==0)) {
            if (measures[i].measurenum > 0) {
               out << "%BAR: " << measures[i].measurenum << "\n";
            }
         }
         printSingleMeasure(*(pstaves[j]), voicemap, j, voicemap.getSize(), 
               measures, i, infile, j);
      }
   }

   for (i=0; i<voicemap.getSize(); i++) {
      (*(pstaves[i])) << ends;
      if ((*(pstaves[i])).CSTRING[0] == '\0') {
         continue;
      }
      if (voicemap.getSize() > 1) {
         out << "[V:" << voicemap[i].voicenumber << "] ";
         out << (*(pstaves[i])).CSTRING << "\n";
      } else {
         // multi-line rests will generate blank lines, so
         // suppress them here when blank lines occur.
         if (strlen((*(pstaves[i])).CSTRING) > 0) {
            out << (*(pstaves[i])).CSTRING << "\n";
         }
      }
   }

   for (i=0; i<asize; i++) {
      delete pstaves[i];
   }

}



//////////////////////////////
//
// printSingleMeasure --
//

void printSingleMeasure(ostream& out, const Array<VoiceMap>& voiceinfo, 
      int vindex, int vmapsize, Array<MeasureInfo>& measureinfo, int mindex, 
      HumdrumFile& infile, int staffnumber) {

   MeasureInfo& MI = measureinfo[mindex];

   int maxlayers = getMaxLayer(voiceinfo[vindex].primary, MI.startline,
      MI.endline, infile);

   if (keyQ) {
      if (MI.kernkey != NULL) {
         out << " [K:";
         printAbcKeySignature(out, MI.kernkey);
         out << "] ";
      }
   } else {
      if (MI.newkey > -10) {
         out << " [K:";
         printAbcKeySignature(out, MI.newkey);
         out << "] ";
      }
   }

   Array<Coordinate> meterclef;
   getMeterAndClefChanges(meterclef, infile, voiceinfo[vindex], MI);

   Array<Array<Coordinate> > noteaddresses;
   noteaddresses.setSize(maxlayers);
   noteaddresses.allowGrowth(0);
   buildNoteAddresses(noteaddresses, infile, voiceinfo[vindex], MI);

   if (noteaddresses.getSize() == 0) {
      // no notes to print in this measure
      return;
   }

   int multirestQ = 0;
   if ((vmapsize == 1) && measureinfo[mindex].fullrest > 1) {
      if (mindex < measureinfo.getSize() - 1) {
         if (measureinfo[mindex+1].fullrest < 1) {
            multirestQ = 1;
         } else {
            multirestQ = 0;
         }
      } else {
         multirestQ = 1;
      }
   }

   if (multirestQ) {
      // print a multi-rest if at the end of a string
      // of measures of full rests.  This algorithm
      // is not fully generalized, and needs to be 
      // extended eventually.  It will absorb repeat
      // barlines, which is should not do.  It will also
      // count split bars as two measures instead of
      // one at the moment.
      printMultiRest(out, measureinfo, mindex);
      if (infile[MI.endline].getType() == E_humrec_data_measure) {
         printMeasureLine(out, infile, MI.endline, voiceinfo[vindex].primary, 
         staffnumber, measureinfo, mindex, vindex);
      } 
   } else if (measureinfo[mindex].fullrest > 1) {
      // do nothing;
   } else if ((mindex < measureinfo.getSize() - 1) && 
              (measureinfo[mindex+1].fullrest > 1) ) {
      // do nothing; still waiting for end of multi rest
   } else {
      printLayer(1, out, voiceinfo[vindex], MI, infile, noteaddresses, 
         meterclef);
   
      if (maxlayers > 1) {
         out << " & ";
         printLayer(2, out, voiceinfo[vindex], MI, infile, noteaddresses,
            meterclef);
      }
      // don't know how to print more than 2 layers...
      // so ignore layers greater than 2

      if (infile[MI.endline].getType() == E_humrec_data_measure) {
         printMeasureLine(out, infile, MI.endline, voiceinfo[vindex].primary, 
         staffnumber, measureinfo, mindex, vindex);
      } 
   }
}



//////////////////////////////
//
// printMultiRest --
//

void printMultiRest(ostream& out, Array& measureinfo, int mindex) {
   int count = 0;
   int i;
   for (i=mindex; i>=0; i--) {
      if (measureinfo[i].fullrest) {
         count++;
      } else {
         break;
      }
   }
   out << "Z";
   if (count > 1) {
      out << count;
   }
}



//////////////////////////////
//
// getMeterAndClefChanges -- find the location of meter changes in the music
//    for the particular measure and voice.
//

void getMeterAndClefChanges(Array<Coordinate>& meterchanges, 
      HumdrumFile& infile, const VoiceMap& voiceinfo, 
      const MeasureInfo& measureinfo) {

   Array<Coordinate> temp;
   temp.setSize(0);
   Coordinate tcord;
   int top;
   int bot;

   int i, j;
   for (i=measureinfo.startline; i<=measureinfo.endline; i++) {
      if (infile[i].getType() == E_humrec_interpretation) {
         for (j=0; j<infile[i].getFieldCount(); j++) {
            if (infile[i].getPrimaryTrack(j) != voiceinfo.primary) {
               continue;
            }
            if (sscanf(infile[i][j], "*M%d/%d", &top, &bot) == 2) {
               tcord.row = i;
               tcord.col = j;
               temp.append(tcord);
            } else if (strcmp(infile[i][j], "*met(C)") == 0) {
               tcord.row = i;
               tcord.col = j;
               if (temp.getSize() == 0) {
                  temp.append(tcord);
               } else {
                  temp[temp.getSize()-1] = tcord;
               }
            } else if (strcmp(infile[i][j], "*met(c)") == 0) {
               tcord.row = i;
               tcord.col = j;
               if (temp.getSize() == 0) {
                  temp.append(tcord);
               } else {
                  temp[temp.getSize()-1] = tcord;
               }
            } else if (strcmp(infile[i][j], "*met(C|)") == 0) {
               tcord.row = i;
               tcord.col = j;
               if (temp.getSize() == 0) {
                  temp.append(tcord);
               } else {
                  temp[temp.getSize()-1] = tcord;
               }
            } else if (strcmp(infile[i][j], "*met(c|)") == 0) {
               tcord.row = i;
               tcord.col = j;
               if (temp.getSize() == 0) {
                  temp.append(tcord);
               } else {
                  temp[temp.getSize()-1] = tcord;
               }
            } else if (strncmp(infile[i][j], "*clef", 5) == 0) {
               tcord.row = i;
               tcord.col = j;
               temp.append(tcord);
            }
            break;
         }
      }
   }

   // reverse the order of the meter and key changes 
   // so that they can be popped off of the array after being
   // printed.
   meterchanges.setSize(temp.getSize());
   meterchanges.setSize(0);
   for (i=temp.getSize()-1; i>=0; i--) {
      tcord = temp[i];
      meterchanges.append(tcord);
   }
}



//////////////////////////////
//
// printMeasureLine --
//     .| or : = dashed measure line
//     |       = regular line
//     [|      = !| style for humdrum measures
//     |[|     = |!| style for humdrum measures
//     ::      = :!!: style for humdrum measures
//     [|]     = invisible measurel line (- for humdrum measures)
//     |]      = |! or == for humdrum scores
//     ||      = || or (end of second repeat)
//     [1      = first repeat
//     [2      = second repeat
//     |1      = short first repeat
//     |2      = short second repeat
//

void printMeasureLine(ostream& out, HumdrumFile& infile, int line, 
      int primary, int staffnumber, Array<MeasureInfo>& measureinfo,
      int mindex, int vindex) {
   if (infile[line].getType() != E_humrec_data_measure) {
      return;
   }
   int col = -1;
   for (col=0; col<infile[line].getFieldCount(); col++) {
      if (infile[line].getPrimaryTrack(col) == primary) {
         break;
      }
   }

   if (infile[line].getPrimaryTrack(col) != primary) {
      return;
   }


   const char* token = infile[line][col];
   int measurenum = -100;
   sscanf(token, "=%d", &measurenum);

   int mdiff = 0;
   if (mindex < measureinfo.getSize() - 1) {
      mdiff = measureinfo[mindex+1].measurenum -measureinfo[mindex].measurenum;
   }

   out << " ";  // separate barline from notes by a space

   if ((staffnumber == 0) && databarnumQ && (mindex < measureinfo.getSize()-1)) {
      if (measureinfo[mindex+1].measurenum >= 0) {
         out << "[I:setbarnb " << measureinfo[mindex+1].measurenum << "]";
      }
   } else if ((staffnumber == 0) 
         && ((mindex < measureinfo.getSize()-1) && 
             (measureinfo[mindex+1].measurenum >= 0))
         && ((mdiff > 1) || labelQ)) {
      out << "[I:setbarnb " << measureinfo[mindex+1].measurenum << "]";
   }

   if (strchr(token, '-') != NULL) {
      // print an invisible barline
      out << "[|] ";
      return;
   }
   
   if ((measureinfo[mindex].ending < 0) || (vindex != 0)) {
      if (strstr(token, ":|!|:") != NULL) { out << ":|]|: "; }
      else if (strstr(token, ":|!")   != NULL) { out << ":|] "; }
      else if (strstr(token, "!|:")   != NULL) { out << "]|: "; }
      else if (strstr(token, ":||:")  != NULL) { out << ":||: "; }
      else if (strstr(token, "||")    != NULL) { out << "|| "; }
      else if (strstr(token, "==")    != NULL) { out << "|] "; }
      else { out << "|"; } 
   } else {
      if (strstr(token, ":|!|:") != NULL) { out << ":|]|:"; }
      else if (strstr(token, ":|!")   != NULL) { out << ":|]"; }
      else if (strstr(token, "!|:")   != NULL) { out << "]|:"; }
      else if (strstr(token, ":||:")  != NULL) { out << ":||:"; }
      else if (strstr(token, "||")    != NULL) { out << "||"; }
      else if (strstr(token, "==")    != NULL) { out << "|]"; }
      else {
         if (measureinfo[mindex].ending == 0) {
            // the second ending must be ended by || barline in abcm2ps
            // and not a single barline
            out << "|";
            //out << " ||";
         } else {
            out << "|"; 
         } 
      }
      if (measureinfo[mindex].ending > 0) {
         out << "[";
         out << measureinfo[mindex].ending;
      }
   }
  
   out << " ";
}



//////////////////////////////
//
// getMaxLayer -- returns the maximum number of layers in a 
//      particular voice (staff).
//

int getMaxLayer(int track, int start, int endd, HumdrumFile& infile) {
   int i, j;
   int counter = 0;
   int maxx = 0;
   for (i=start; i<=endd; i++) {
      if (infile[i].getType() != E_humrec_data) {
         continue;
      }
      counter = 0;
      for (j=0; j<infile[i].getFieldCount(); j++) {
         if (infile[i].getPrimaryTrack(j) == track) {
            counter++;
         }
      }
      if (counter > maxx) {
         maxx = counter;
      }
   }

   return maxx;
}




//////////////////////////////
//
// printLayer -- print a single layer of a single staff.  But
//     the accidental states need to be monitored for all 
//     layers in the staff.
//

void printLayer(int layer, ostream& out, const VoiceMap& voiceinfo, 
      const MeasureInfo& measureinfo, HumdrumFile& infile,
      Array<Array<Coordinate> >& address, Array<Coordinate>& meterclef) {

   if (debugQ) {
      cout << "\%\% Printing measure";
      if (measureinfo.measurenum  > 0) {
         cout << " " << measureinfo.measurenum;
      }
      cout << " voice " << voiceinfo.primary 
	   << " layer " << layer << "==================\n%";
      cout << endl;
   }
	  
   // abcm2ps 5.9.1 has problems with slurs originating in
   // gracenote regions when there is another uncompleted
   // slur in progress, so suppress all slurs starting
   // from grace notes (at least for now).  In any case,
   // abcm2ps will automatically attach slurs between
   // grace notes and the next non-grace note in the
   // layer unless a flag is set to turn that feature off.
   int slursuppress = 0;

   int ilayer = layer - 1;
   const char* token;

   Array<int> accident(7*9);
   accident.allowGrowth(0);
   accident.setAll(0);
   setAccidentals(accident, measureinfo.currkey);

   Array<double> notedurs;   // duration of note events in layer, 
                             // (including grace notes)
   Array<int>    iindex;
   getNoteDurations(notedurs, iindex, layer, address, infile);

   // note list contains a list of notes in the current layer
   // without the grace notes
   Array<Coordinate> nogracelist;
   getNoGraceList(nogracelist, address, layer, infile);
   Array<double> nogracedurs;
   getNoGraceDurs(nogracedurs, notedurs, address, layer);

   Array<BeamInfo> beaminfo;
   getBeamInfo(beaminfo, nogracelist, infile);

   // broken rhythms are dotted rhythm patterns
   Array<int> broken;   // stores the brokenness for the first of a broken pair
   Array<double> brokendurs;  // store the revised duration for broken notes
   getBrokenRhythms(broken, brokendurs, nogracelist, nogracedurs, infile);

   Array<TupletInfo> tupletstuff;
   identifyTuplets(tupletstuff, nogracedurs, nogracelist, infile, broken, 
		         brokendurs, beaminfo);

   int beamstate = 0;
   int oldbeamstate = 0;
   int gracestate = 0;
  
   int lastrow = -1; 
   int lastcol = -1;
   int row = -1; 
   int col = -1;
   int counter = 0;
   int j;
   int nogracei = -1;
   double finaldur;
   int lastnotegraceQ = 0;
   
   double currentbeat = infile[measureinfo.startline].getAbsBeat();

   for (j=0; j<address[0].getSize(); j++) {
      row = address[ilayer][j].row;
      col = address[ilayer][j].col;
      if (linebreakQ) {
         checkForLineBreak(out, infile, row, col);
      }
      if (row >= 0) {
         lastrow = row;
         lastcol = col;

         //if (notedurs[counter] > 0.0) {
         if ((strchr(infile[row][col], 'q') == NULL)  &&
             (strchr(infile[row][col], 'Q') == NULL)) {
            nogracei++;
	    if (lastnotegraceQ) {
               // if the last note was a grace note, then do not
               // place a corrective invisible rest in the layer
               // To prevent the invisible rest from occurring
               // set the current beat to the absolute beat of the
               // note which is currently being processed.
               currentbeat = infile[row].getAbsBeat();
            }
	    lastnotegraceQ = 0;
	 } else {
// cout << "GOT HERE LAST NOTE WAS A GRACE NOTE" << endl;
            notedurs[counter] = 0.0;
	    lastnotegraceQ = 1;
         }

         if (currentbeat != infile[row].getAbsBeat()) {
//cout << "% current beat = " << currentbeat << " score beat "
//<< infile[row].getAbsBeat() << " diff = " 
//<< infile[row].getAbsBeat()  - currentbeat << endl;
            if ((strchr(infile[row][col], 'q') == NULL)  &&
                (strchr(infile[row][col], 'Q') == NULL)) {
               if (layer != 1) {
                  // don't allow invisible rests to occur in the first layer
                  printInvisibleRest(out, 
                     infile[row].getAbsBeat() - currentbeat);
               }
	        
            } else {
               // don't create any invisible rests when printing out
               // a grace note, since there metrical position in the
               // score is irrelevant.
            }
	      
         }

	 currentbeat = infile[row].getAbsBeat() + notedurs[counter];

         if ((layer == 1) && (meterclef.getSize() > 0) &&
             (row > meterclef[meterclef.getSize()-1].row)) {
            printMeterAndClefChanges(out, row, meterclef, infile);
         }
         token = infile[row][col];
         oldbeamstate = beamstate;
         beamstate += getBeamState(token);
         if ((counter > 0) && (oldbeamstate == 0) &&
            (notedurs[counter] < 1.0) && (notedurs[counter-1] < 1.0)) {
            out << " ";  // put a space to prevent beaming
         }
         if ((!gracestate) && (nogracei>0) ) {
            // if not a grace note and note the first note:
            // It is assumbed that bi[i].left == bi[i-1].right
            // but might need to check it if this is no longer true.

            if (beaminfo[nogracei].right > beaminfo[nogracei].left) {
               if (beaminfo[nogracei-1].right < beaminfo[nogracei-1].left) {
                  if (beaminfo[nogracei].left > 0) {
                     out << "!beambr" << beaminfo[nogracei].left << "!";
                  }
               } 
            }

         }

	 if ((gracestate == 0) && (notedurs[counter] == 0.0)) {
             // grace notes (q) will have slashes added
	     // groupetto notes (Q) will not have slashes added
             if (strchr(token, 'q') != NULL) {
                out << "{/"; //q: start a grace note sequence with a slash
             } else {
                out << "{";  //Q: start a grace note sequence without a slash
             }
             gracestate = 1;
         }

	 if ((notedurs[counter] > 0) && (nogracei >= 0)) {
            printTupletInfo(out, tupletstuff[nogracei]);
            finaldur = nogracedurs[nogracei];
	 } else {
            finaldur = 0.0;
         }
		  

         // print pitches of chords or single pitches (with accidentals)
         // plus articulations for note/chord.
	 
         int groupflag = getGraceNoteGroupFlag(notedurs, counter);

         if (nogracei >= 0) {
            printKernTokenAsAbc(out, infile, row, col, accident, 
                  finaldur, broken[nogracei], brokendurs[nogracei], Ltop,
                  Lbot, slursuppress, groupflag);
         } else {
            // print grace note
            printKernTokenAsAbc(out, infile, row, col, accident, 
                  finaldur, 0, 0, Ltop, Lbot, slursuppress, groupflag);
         }

         if (notedurs[counter] >= 1) {
            beamstate = 0;   // fix any problems with beaming info
         } 

         if (gracestate) {
            if (counter == notedurs.getSize()-1) {
               out << "}";
               gracestate = 0;
            } else if (notedurs[counter+1] > 0.0) {
               out << "}";
               gracestate = 0;
            }
         }

         counter++;
      }
      adjustAccidentalStates(accident, j, address, infile);
   }

   if (currentbeat != infile[measureinfo.endline].getAbsBeat()) {
      // don't do a final correction if the last note was a grace note:
      if ((lastrow >= 0) && (lastcol >= 0) && 
          (strchr(infile[lastrow][lastcol], 'q') == NULL) &&
          (strchr(infile[lastrow][lastcol], 'Q') == NULL)) {
        printInvisibleRest(out, 
            infile[measureinfo.endline].getAbsBeat() - currentbeat);
      }
   }        

   // print any meter or clef changes at the end of a measure:
   if ((layer == 1) && (meterclef.getSize() > 0)) {
      printMeterAndClefChanges(out, 100000, meterclef, infile);
   }
}



//////////////////////////////
//
// checkForLineBreak --
//

void checkForLineBreak(ostream& out, HumdrumFile& infile, int row, int col) {
   int i;
   for (i=row-1; i>=0; i--) {
      if (infile[i].getType() == E_humrec_data) {
         return;
      }
      if (infile[i].getType() == E_humrec_local_comment) {
         if (strstr(infile[i][0], "linebreak") != NULL) {
            out << "\n";
         }
      }
   }
}



//////////////////////////////
//
// getGraceNoteGroupFlag --  There is probably a bug in abcm2ps
//     which requires that when more than one grace notes occurs
//    in a group together, the duration has to be increased by 4.0
//    to print the correct graphical rhythm, or increase by 2.0 
//    when there is only one grace note...
//

int getGraceNoteGroupFlag(Array& notedurs, int index) {
   int counter = 1;
   int i;
   for (i=index+1; i<notedurs.getSize(); i++) {
      if (notedurs[i] < TOLERANCE) {
         counter++;
      } else {
         break;
      }
   }
   for (i=index-1; i>=0; i--) {
      if (notedurs[i] < TOLERANCE) {
         counter++;
      } else {
         break;
      }
   }

   if (counter == 1) {
      return 0;
   } else {
      return 1;
   }

}



//////////////////////////////
//
// printInvisibleRest --
//

void printInvisibleRest(ostream& out, double duration) {
   if (duration <= TOLERANCE) {
      return;
   }
   if (duration <= 0.001) {
      cout << "% INVISIBILE DURATION SUPPRESSED: " << duration << endl;
      // more aggressive suppression of tiny invisible rests...
      return;
   }
	  
   double value    = duration * Lbot / Ltop / 4.0;
   int a           = int(value + TOLERANCE);
   double fraction = value - a;
   int top;
   int bot;
   int b;
   if (fraction > TOLERANCE) {
      b = int(1.0 / fraction + TOLERANCE);   
      top = a * b + 1;   
      bot = b;
      simplifyFraction(top, bot);
   } else {
      top =  a;
      bot =  1;
   }

   out << invisiblerest;
   if (top > 1) {
      out << top;
   }
   if (bot > 1) {
      out << "/";
      if (bot == 2) {
      } else if (bot == 4) {
         out << "/";
      } else {
         out << bot;
      }
   }
}



//////////////////////////////
//
// getBeamInfo --
//

void getBeamInfo(Array<BeamInfo>& beaminfo, Array<Coordinate>& nogracelist, 
      HumdrumFile& infile) {

   beaminfo.setSize(nogracelist.getSize());
   beaminfo.allowGrowth(0);
   int i;
   int Lcount;
   int Jcount;
   int current = 0;
   int row;
   int col;

   for (i=0; i<nogracelist.getSize(); i++) {
      row = nogracelist[i].row;
      col = nogracelist[i].col;
      Lcount = characterCount(infile[row][col], 'L');
      Jcount = characterCount(infile[row][col], 'J');
      if (i == 0) {
         Jcount = 0;  // ignore cross-measure beaming if it occurs
      }
      beaminfo[i].left = current;
      current -= Jcount;
      current += Lcount;
      beaminfo[i].right = current;
   }
   beaminfo[beaminfo.getSize()-1].right = 0;
}



//////////////////////////////
//
// characterCount --
//

int characterCount(const char* string, char character) {
   int i = 0;
   int count = 0;
   while (string[i] != '\0') {
      if (string[i] == character) {
         count++;
      }
      i++;
   }
   return count;
}



//////////////////////////////
//
// printTupletInfo --
//

void printTupletInfo(ostream& out, TupletInfo& tinfo) {
   if (tinfo.top < 2) {
      return;
   }
   if (tinfo.count < 0) {
      return;
   }

   switch (tinfo.shortcut) {
      case 2: out << "(2"; return;
      case 3: out << "(3"; return;
      case 4: out << "(4"; return;
      case 5: out << "(5"; return;
      case 6: out << "(6"; return; // danger: as expected: factor of two off
      case 7: out << "(7"; return;
      case 8: out << "(8"; return;
      case 9: out << "(9"; return;
   }

   // print a longcut:
   out << "(" << tinfo.top << ":" << tinfo.bot << ":" << tinfo.count;
}



//////////////////////////////
//
// identifyTuplets -- find tuplets in the data.  The notedurs and 
//     nogracedurs, and brokendurs will be modified by this function.
//     to remove non-power-of-two rhythms from the list of notes to
//     print on the layer.
//

void identifyTuplets(Array<TupletInfo>& tupletstuff, 
      Array<double>& nogracedurs, Array<Coordinate>& nogracelist, 
      HumdrumFile& infile, Array<int>& broken, Array<double>& brokendurs,
      Array<BeamInfo>& beaminfo) {

   tupletstuff.setSize(nogracelist.getSize());
   tupletstuff.allowGrowth(0);

   if (tupletstuff.getSize() == 0) {
      // no notes (other than perhaps grace notes) in the measure
      return;
   }

   int dupleprimes;
   double newdur;
   int i;

   if (debugQ) {
      cout << "%\n% TUPLET INFO INPUT\n";
      cout << "%tuptop\ttupbot\tcount\tshort\tnewdur\ttoken\n";
      for (i=0; i<tupletstuff.getSize(); i++) {
         cout << "%" 
              << tupletstuff[i].top      << "\t"
              << tupletstuff[i].bot      << "\t"
              << tupletstuff[i].count    << "\t"
              << tupletstuff[i].shortcut << "\t"
              << nogracedurs[i]          << "\t"
              << infile[nogracelist[i].row][nogracelist[i].col] 
              << "\n";
      }
      cout << "%\n";
   }

   int scale;

   for (i=0; i<nogracelist.getSize(); i++) {
      dupleprimes = getTupletInfo(tupletstuff[i], nogracelist[i], infile);
      if (tupletstuff[i].top > 2) {
         newdur = 4.0 / (dupleprimes * tupletstuff[i].bot);
         scale = 0;
	 if (brokendurs[i] > 0) {
            if (broken[i] < 0) {
               // the current note is the first note in a broken
               // rhythm and it is the smaller 
               scale = -broken[i];
            } else if (i > 0) {
               // the previous note was part of the broken rhythm
               // and was longer than the current note.
               if (broken[i-1] > 0) {
                  scale = broken[i-1];
               }
            }
         }
         if (scale > 0) {
            newdur = newdur * (1 << scale);
         }
         nogracedurs[i] = newdur;
      }
   }

   identifyTupletShortCuts(tupletstuff, beaminfo, nogracedurs, nogracelist,
         infile);

   if (debugQ) {
      cout << "%\n% TUPLET INFO OUTPUT\n";
      cout << "%tuptop\ttupbot\tcount\tshort\tbeam\tbrok\tbrkdur\tnewdur\ttoken\n";
      for (i=0; i<tupletstuff.getSize(); i++) {
         cout << "%" 
              << tupletstuff[i].top      << "\t"
              << tupletstuff[i].bot      << "\t"
              << tupletstuff[i].count    << "\t"
              << tupletstuff[i].shortcut << "\t"
              << beaminfo[i].right << ":" << beaminfo[i].left << "\t"
	      << broken[i]               << "\t"
	      << int(brokendurs[i]*10000.0+0.5)/10000.0 << "\t"
              << nogracedurs[i]          << "\t"
              << infile[nogracelist[i].row][nogracelist[i].col] 
              << "\n";
      }
      cout << "%\n";
   }

}



//////////////////////////////
//
// separateCountsByBeams -- should also be separated by meter
//    maybe at the same time...
//

void separateCountsByBeams(Array<TupletInfo>& tupletstuff, 
     Array<BeamInfo>& beaminfo, Array<Coordinate>& nogracelist,
     HumdrumFile& infile) {
   int i, j;
   int counter;
   Array<int> rests;
   getRestInfo(rests, nogracelist, infile);
   for (i=0; i<tupletstuff.getSize(); i++) {
      if ((tupletstuff[i].count > 0) && 
          ((beaminfo[i].right > 0) || rests[i])) {
         counter = 1;
         for (j=1; j<tupletstuff[i].count; j++) {
            if ((beaminfo[i+j].left == 0) && (!rests[i+j-1])) {
               tupletstuff[i+j].count = tupletstuff[i].count - j;
               tupletstuff[i].count = j;
            }
         }
      }
   }
}



//////////////////////////////
//
// getRestInfo --
//

void getRestInfo(Array<int>& rests, Array<Coordinate>& nogracelist, 
      HumdrumFile& infile) {
   rests.setSize(nogracelist.getSize());
   int i;
   for (i=0; i<nogracelist.getSize(); i++) {
      if (strchr(infile[nogracelist[i].row][nogracelist[i].col], 'r') != NULL) {
         rests[i] = 1;
      } else {
         rests[i] = 0;
      }
   }
}



//////////////////////////////
//
// identifyTupletShortCuts --
//

void identifyTupletShortCuts(Array<TupletInfo>& tupletstuff,
      Array<BeamInfo>& beaminfo, Array<double>& nogracedurs,
      Array<Coordinate>& nogracelist, HumdrumFile& infile) {
   int top = -1;
   int bot = -1;
   int i;
   int length = tupletstuff.getSize();
   int counter = 1;

   top = tupletstuff[length-1].top;
   bot = tupletstuff[length-1].bot;
   for (i=tupletstuff.getSize()-2; i>= 0; i--) {
      if ((top == tupletstuff[i].top) && (bot == tupletstuff[i].bot)) {
         counter++;
      } else {
         if (top == -1) {
            counter = 1;
            top = tupletstuff[i].top;
            bot = tupletstuff[i].bot;
            continue;
         } else {
            tupletstuff[i+1].count = counter;
            counter = 1;
            top = tupletstuff[i].top;
            bot = tupletstuff[i].bot;
         }
      }
   }
   if (tupletstuff[0].top > 0) {
      tupletstuff[0].count = counter;
   }

   Array<TupletInfo>& t = tupletstuff;

   // separateCountsByBeams(tupletstuff, beaminfo, nogracelist, infile);

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

   Array<double>& ng = nogracedurs;

   Array<double> metric;
   Array<double> frac;
   metric.setSize(nogracelist.getSize());
   frac.setSize(nogracelist.getSize());
   for (i=0; i<nogracelist.getSize(); i++) {
      metric[i] = infile[nogracelist[i].row].getAbsBeat();
      frac[i] = metric[i] - (int)metric[i];
   }

   Array<int> beamy;
   beamy.setSize(nogracelist.getSize());
   beamy.setAll(0);
   int bcount = 0;
   for (i=1; i<beaminfo.getSize(); i++) {
      if (beaminfo[i].left == 0) {
         bcount++;
      }
      beamy[i] = bcount;
   }

   // check for shortcuts
   for (i=0; i<t.getSize(); i++) {
      if ((t[i].top == 3) && (t[i].bot == 2) && (t[i].count == 3)) { 
         t[i].shortcut = 3;
         t[i].count = 0;    // shortcut, so don't use longcut
      }

      // triplet types of notes
      if ((t[i].top == 3) && (t[i].bot == 2) && (t[i].count % 3 == 0)
             && (t[i].count > 0)) { 

	 if ((t[i].count == 6) && (fabs(ng[i]   - 0.5) < TOLERANCE) &&
	     (fabs(ng[i+1] - 0.5) < TOLERANCE) && 
             (fabs(ng[i+2] - 0.5) < TOLERANCE)) {
	    // six triplet eighths in a row cannot be given a (6 shortcut)
	    // since it will be treated as six triplet sixteenths.
	    // The durations have already been de-tupletized, so
	    // a triplet eight is now encoded as an eighth
            t[i].shortcut = 3;
            t[i+3].shortcut = 3;
            t[i].count = 0;
            t[i+3].count = 0;
         } else if ((t[i].count > 3) &&
               (fabs(frac[i]) < TOLERANCE) &&
               (fabs(frac[i+1] - 1.0/3.0) < TOLERANCE) &&
               (fabs(frac[i+2] - 2.0/3.0) < TOLERANCE)) {
            // triplet eigths inside of one beat (assuming quarter-note beat)
            t[i].shortcut= 3;
            t[i+3].count = t[i].count - 3;
            t[i].count = 0;  // t[i] is now a shortcut so remove the count
         } else if ((t[i].count > 3) &&
               (fabs(frac[i] - 3.0/6.0) < TOLERANCE) &&
               (fabs(frac[i+1] - 4.0/6.0) < TOLERANCE) &&
               (fabs(frac[i+2] - 5.0/6.0) < TOLERANCE)) {
               // triplet 16ths on the second half of a beat
            t[i].shortcut = 3;
            t[i+3].count = t[i].count - 3;
            t[i].count = 0;  // t[i] is now a shortcut so remove the count
         } else if ((t[i].count > 3) &&
               (fabs(frac[i] - 0.0/6.0) < TOLERANCE) &&
               (fabs(frac[i+1] - 1.0/6.0) < TOLERANCE) &&
               (fabs(frac[i+2] - 2.0/6.0) < TOLERANCE)) {
            t[i].shortcut = 3;
            t[i+3].count = t[i].count - 3;
            t[i].count = 0;  // t[i] is now a shortcut so remove the count
         }
      }
   }

   // go back and fix tuplets which are beamed together:
   int lasti = -1;
   for (i=0; i<nogracelist.getSize(); i++) {
      if (t[i].shortcut == 3) {
         if (lasti < 0) {
            lasti = i;
         } else if (beamy[i] == beamy[lasti]) {
            if ((t[i].shortcut == 3) && (t[lasti].shortcut == 3)) {
               t[lasti].shortcut = 0;
               t[lasti].count = 6;
               t[i].shortcut = 0;
               t[i].count = -1;
            }
         }
         lasti = i;
      }

 
   }

}



//////////////////////////////
//
// getTupletInfo --
//
// identification algorithm for a non-duple tuplet:
//     (1) prime factorize the rhythm
//     (2) identify if there are any prime numbers other than 2
//     (3) if there are no other prime numbers then the note is
//         not a tuplet
//     (4) if there are other prime numbers, then multiply all of
//         the other prime numbers together and search for the next
//         lowest power of two.  The prime number will be the top
//         number in the tuplet ratio, and the next lower power of
//         two will be the bottom part of the ratio
//

int getTupletInfo(TupletInfo& tupletstuff, Coordinate& nogracelist, 
      HumdrumFile& infile) {
   int row = nogracelist.row;
   int col = nogracelist.col;
   char buffer[32] = {0};
   int rhythm;
   int nondupleprimes;
   int dupleprimes;
   int nextlowerpoweroftwo;
   Array<int> factors;

   infile[row].getToken(buffer, col, 0, 32);
   int i=0;
   while ((buffer[i] != EMPTY) && (!isdigit(buffer[i]))) {
     i++;
   }
   if (sscanf(&buffer[i], "%d", &rhythm) == 1) {
      primeFactorization(factors, rhythm);
      nondupleprimes = getNonDuplePrimes(factors);
      if (nondupleprimes <= 2) {
         tupletstuff.clear();
         return -1;
      } else {  // a tuplet has been identified, so mark it.
         dupleprimes = getDuplePrimes(factors);
         nextlowerpoweroftwo = getNextLowerPowerOfTwo(nondupleprimes);
         tupletstuff.top = nondupleprimes;
         tupletstuff.bot = nextlowerpoweroftwo;
         return dupleprimes;
      }
   } else {
      tupletstuff.clear();
   }

   return -1;
}



//////////////////////////////
//
// getNextLowerPowerOfTwo -- return the next lower power of two
//    for the given input number.  Example: 25 -> 16, 15 -> 8, 7->4, 5->4
//    if you give a power of two, it will return that same power of two.
//    if you give 1, then it will return 1.  if you give zero,
//    then it will return 0.
//

int getNextLowerPowerOfTwo(int number) {
   if (number <= 0) {
      return 0;
   } 
   if (number == 1) {
      return 1;
   }

   double value = log((double)number)/log(2.0);
   int output = int(pow(2.0, int(value+0.00001)));

   return output;
}



//////////////////////////////
//
// getNonDuplePrimes -- return the product of all numbers 
//    in the list which are greater than two.  All numbers
//    in the list are assumed to be prime numbers.
//

int getNonDuplePrimes(Array& factors) {
   int i;
   int output = 1;
   for (i=0; i<factors.getSize(); i++) {
      if (factors[i] <= 2) {
         continue;
      }
      output = output * factors[i];
   }

   return output;
}



//////////////////////////////
//
// getDuplePrimes -- return the product of all numbers 
//    in the list which are greater than two.  All numbers
//    in the list are assumed to be prime numbers.
//

int getDuplePrimes(Array& factors) {
   int i;
   int output = 1;
   for (i=0; i<factors.getSize(); i++) {
      if (factors[i] != 2) {
         continue;
      }
      output = output * 2;
   }

   return output;
}



//////////////////////////////
//
// printRhythm2 -- print the duration, version 2 which does not
//    yet address the tuplet notation, but does address the broken
//    rhythms.  Returns the number of broken rhythm characters
//    which should be printed after the ']' character in chords.
//

int printRhythm2(ostream& out, double dur, int brokenstate, 
      double brokendur, int top, int bot, int chordQ, const char* token) {
   int output = 0;

   if (norhythm(token)) {
      // this will remove stems from noteheads:
      out << "0";
   }

   if (stemlessQ) {
      // this will remove stems from noteheads:
      out << "0";
   }


   if (fabs(dur) <= TOLERANCE) {
      // don't print durations of grace notes.
      return 0;
   }

   char bch = 'X';
   if (brokenstate != 0) {
      // print Broken rhythm start.
      //printDurationAsRhythm(out, brokendur, top, bot);
      printDurationAsRhythm(out, dur, top, bot);
      if (brokenstate > 0) {
         bch = '>';
      } else {
         bch = '<';
      }
      int i;
      if (chordQ) {
         output = brokenstate;
      } else {
         for (i=0; i<abs(brokenstate); i++) {
            out << bch;
         }
      }
   } else if (brokendur != 0) {
      printDurationAsRhythm(out, dur, top, bot);
   } else {
      printDurationAsRhythm(out, dur, top, bot);
   }


   return output;
}



//////////////////////////////
//
// printDurationAsRhythm -- presuming non-tuplets at the moment
//

void printDurationAsRhythm(ostream& out, double dur, int top, int bot) {

   double fraction;
   double invfraction;
   double durunits;
   int durtop;
   int durbot;

   if (debugQ) {
//      cout << "\%PRINTING DURATION: " << dur 
//           << " in UNITS: " << top << "/" << bot << endl;
   }

   // fraction should be a power of two of some sort...
   durunits = dur * bot / top;
   fraction = durunits - (int)durunits;

   if (fabs(fraction) < TOLERANCE) {
      durtop = (int)durunits;
      durbot = 1;
   } else {
      invfraction = 1.0 / fraction;
      durtop = (int)durunits;
      durbot = (int)invfraction;
      fraction = invfraction - (int)invfraction;
      if (fabs(fraction) > TOLERANCE) {
         cout << "% FRACTION = " << fraction << "BUT SHOULD BE 0" << endl;
      }
   }

   durbot = durbot * 4;

   simplifyFraction(durtop, durbot);

   if (debugQ) {
//      cout << "%FINAL OUTPUT: " << durtop << "/" << durbot << endl;
   }

   if (durtop > 1) {
      out << durtop;
   }

   if (durbot == 1) {
      // don't print anything
   } else if (durbot == 2) {
      out << "/";
   } else if (durbot == 4) {
      out << "//";
   } else {
      out << "/" << durbot;
   }
}



//////////////////////////////
//
// simplifyFraction --
//

void simplifyFraction(int& top, int& bot) {
   Array<int> factorstop;
   Array<int> factorsbot;

   primeFactorization(factorstop, top);
   primeFactorization(factorsbot, bot);
   int i;
   int j;
   for (i=0; i<factorstop.getSize(); i++) {
      for (j=0; j<factorsbot.getSize(); j++) {
         if (factorstop[i] == factorsbot[j]) {
            factorstop[i] = 1;
            factorsbot[j] = 1;
            break;
         }
      }
   }

   top = 1;
   for (i=0; i<factorstop.getSize(); i++) {
      if (factorstop[i] > 1) {
         top = top * factorstop[i];
      }
   }

   bot = 1;
   for (i=0; i<factorsbot.getSize(); i++) {
      if (factorsbot[i] > 1) {
         bot = bot * factorsbot[i];
      }
   }

}



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

void primeFactorization(Array& 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);
   }
}



//////////////////////////////
//
// getNoGraceDurs -- get the durations of all notes which are not
//  grace notes in the current layer.
//

void getNoGraceDurs(Array<double>& nogracedurs, Array<double>& notedurs, 
      Array<Array<Coordinate> >& address, int layer) {
   int i;
   int row;
   int col;
   double tdur;
   int ilayer = layer - 1;
   int counter = 0;
   nogracedurs.setSize(0);

   for (i=0; i<address[ilayer].getSize(); i++) {
      row = address[ilayer][i].row;
      col = address[ilayer][i].col;
      if (row >= 0) {
         if (notedurs[counter] > 0) {
            tdur = notedurs[counter];
            nogracedurs.append(tdur);
         }
	 counter++;
      }
   }
}



//////////////////////////////
//
// getNoGraceList -- get a list of notes for the current layer, 
//     ignoring the grace notes in the layer.
// 

void getNoGraceList(Array<Coordinate>& notelist, 
      Array<Array<Coordinate> >& address, int layer, HumdrumFile& infile) {
   Coordinate tcord;
   int ilayer = layer - 1;
   int i;
   notelist.setSize(address[0].getSize());
   notelist.setSize(0);
   for (i=0; i<address[ilayer].getSize(); i++) {
      tcord.row = address[ilayer][i].row;
      tcord.col = address[ilayer][i].col;
      if ((tcord.row > 0) && 
          (strchr(infile[tcord.row][tcord.col], 'q') == NULL) &&
          (strchr(infile[tcord.row][tcord.col], 'Q') == NULL)) {
         notelist.append(tcord);
      }
   }
}



//////////////////////////////
//
// printMeterAndClefChanges --
//

void printMeterAndClefChanges(ostream& out, int testrow, 
      Array<Coordinate>& meterclef, HumdrumFile& infile) {

   int row;
   int col;
   while ((meterclef.getSize() > 0) && 
          (meterclef[meterclef.getSize()-1].row < testrow)) {

         row = meterclef[meterclef.getSize()-1].row;
         col = meterclef[meterclef.getSize()-1].col;

         if (strncmp(infile[row][col], "*clef", 5) == 0) {
            out << "[K:clef=";
            printAbcClef(out, infile[row][col], 
                  infile[row].getPrimaryTrack(col), clefstates);
            out << "]";
         } else { // not a clef so must be a meter:
            out << "[M:";
            printAbcMeter(out, infile[row][col]);
            out << "]";
         }

      // shrink the array since the clef or meter signature at end was printed
      meterclef.setSize(meterclef.getSize()-1);
   }
}



///////////////////////////////
//
// printAbcMeter --
//

void printAbcMeter(ostream& out, const char* string) {
   int top;
   int bot;

   if (sscanf(string, "*M%d/%d", &top, &bot) == 2) {
      out << top << "/" << bot;
      return;
   }

   if (strcmp(string, "*met(C)") == 0) {
      out << "C";
      return;
   }
   
   if (strcmp(string, "*met(c)") == 0) {
      out << "C";
      return;
   }

   if (strcmp(string, "*met(C|)") == 0) {
      out << "C|";
      return;
   }
   
   if (strcmp(string, "*met(c|)") == 0) {
      out << "C|";
      return;
   }
   
   out << "?";

}



//////////////////////////////
//
// printAbcClef --
//

void printAbcClef(ostream& out, const char* string, int track, 
      Array<int>& cleftranspose) {
   if (strncmp(string, "*clef", 5) != 0) {
      out << "perc";
      cleftranspose[track] = 0;
      return;
   } else {
      if (strcmp(string, "*clefGv2") == 0) {
         out << "treble-8";
         cleftranspose[track] = +40;
         return;
      }
      if (strcmp(string, "*clefG2") == 0) {
         out << "treble";
         cleftranspose[track] = 0;
         return;
      }
      if (strcmp(string, "*clefF4") == 0) {
         out << "bass";
         cleftranspose[track] = 0;
         return;
      }
      if (strcmp(string, "*clefC3") == 0) {
         out << "alto";
         cleftranspose[track] = 0;
         return;
      }
      if (strcmp(string, "*clefC4") == 0) {
         out << "tenor";
         cleftranspose[track] = 0;
         return;
      }
      if ((string[5] == 'F') || (string[5] == 'C') || (string[5] == 'G')) {
         if (isdigit(string[6])) {
            out << string[5] << string[6];
            cleftranspose[track] = 0;
            return;
         }
      }

   }

   out << "perc";
   cleftranspose[track] = 0;
}



//////////////////////////////
//
// adjustAccidentalStates -- update the accidental states for the
//     notes at the current time (in all layers)
//

void adjustAccidentalStates(Array<int>& accident, int step, 
       Array<Array<Coordinate> >& address, HumdrumFile& infile) {

   int i;
   int row, col;
   for (i=0; i<address.getSize(); i++) { 
      if (address[i][step].row < 0) {
         continue;
      }
      row = address[i][step].row;
      col = address[i][step].col;
      adjustAccidentalStates2(accident, infile, row, col);
   }
}



//////////////////////////////
//
// adjustAccidentalStates2 --
//

void adjustAccidentalStates2(Array<int>& accident, HumdrumFile& infile, 
      int row, int col) {

   int count = infile[row].getTokenCount(col);
   char buffer[128] = {0};
   int acci;
   int diatonic;
   int base40;
   int i;
   int octave;

   for (i=0; i<count; i++) {
      infile[row].getToken(buffer, col, i);
      if (strchr(buffer, 'r') != NULL) {
         continue;
      }
      if (strchr(buffer, '_') != NULL) {
         // ignore storing the state of a continued tied note
         continue;
      }
      if (strchr(buffer, ']') != NULL) {
         // ignore storing the state of a ending tied note
         continue;
      }
      base40 = Convert::kernToBase40(buffer);
      octave = base40 / 40;
      acci = Convert::base40ToAccidental(base40);
      diatonic = Convert::base40ToDiatonic(base40) % 7;
      accident[diatonic+7*octave] = acci;
   }
}



//////////////////////////////
//
// setAccidentals --
//

void setAccidentals(Array& accident, int key) {
   if (key < -7) {
      return;
   }
   #define XCX 0
   #define XDX 1
   #define XEX 2
   #define XFX 3
   #define XGX 4
   #define XAX 5
   #define XBX 6

   int octaves = accident.getSize() / 7;

   for (int i=0; i<octaves; i++) {
      if (key <= -7) { accident[XFX+i*7] = -1; }
      if (key <= -6) { accident[XCX+i*7] = -1; }
      if (key <= -5) { accident[XGX+i*7] = -1; }
      if (key <= -4) { accident[XDX+i*7] = -1; }
      if (key <= -3) { accident[XAX+i*7] = -1; }
      if (key <= -2) { accident[XEX+i*7] = -1; }
      if (key <= -1) { accident[XBX+i*7] = -1; }

      if (key >= +7) { accident[XBX+i*7] = +1; }
      if (key >= +6) { accident[XEX+i*7] = +1; }
      if (key >= +5) { accident[XAX+i*7] = +1; }
      if (key >= +4) { accident[XDX+i*7] = +1; }
      if (key >= +3) { accident[XGX+i*7] = +1; }
      if (key >= +2) { accident[XCX+i*7] = +1; }
      if (key >= +1) { accident[XFX+i*7] = +1; }
   }
}



//////////////////////////////
//
// printKernTokenAsAbc -- print the pitch and articulation
//     data for a note or a chord.  
//

void printKernTokenAsAbc(ostream& out, HumdrumFile& infile, int row, int col,
      Array<int>& accident, double notedur, int brokenq, double brokendur,
      int top, int bot, int& slursuppress, int groupflag) {
   int count = infile[row].getTokenCount(col);
   char buffer[128] = {0};
   char pitchbuffer[128] = {0};

   int base40;


   int alltie = 0;

   if (count == 1) {
      alltie = 1;
   } else {
      alltie = checkForAllTies(infile, row, col);
   }

   const char* token = infile[row][col];

   if (slurQ) {
      // phrase start, the ' marker after the
      // dashed slur indicates that it should 
      // be placed above the staff
      // Warning: if a phrase and slur start on the
      // same token, then the slur will also be
      // dashed in abcm2ps (probably a bug).
      if (strchr(token, '{') != NULL) {
         out << ".('";
      }

      // slur start
      const char* tptr;
      if ((tptr = strchr(token, '(')) != NULL) {
         if (notedur == 0.0) {
            slursuppress++;
         } else {
            if ((tptr+1)[0] == 'x') {
               // The slur mark is an editorial slur, so make it dashed
               out << ".";
            }
            out << "(";
         }
      }
      // multiple slurs and phrase markings cannot
      // be elided, but can be nested in ABC.
   }

   infile[row].getToken(buffer, col, 0);
   printArticulations(out, buffer);

   int chordQ = 0;
   int brokencount = 0;  // used for printing broken rhythms with chords
   if (count > 1) {  // chord of more than one note
      out << "[";
      chordQ = 1;
   }
   int i;
   for (i=0; i<count; i++) {
      infile[row].getToken(buffer, col, i);
      base40 = Convert::kernToBase40(buffer);
      base40 += clefstates[infile[row].getPrimaryTrack(col)];

// cout << "CLEFSTATES" << infile[row].getPrimaryTrack(col) << " is "  << 
// clefstates[infile[row].getPrimaryTrack(col)] << endl;

      if ((strchr(buffer, 'r') != NULL) && (strchr(buffer, 'y') != NULL)) {
         strcpy(pitchbuffer, invisiblerest);      
      } else {
         base40ToAbcPitch(pitchbuffer, base40);
      }
      // print staccatos here (if not chord)
      if (hasmarksQ) {
         printMarks(out, buffer, marks, markcolors, count);
      }
      printAccidental(out, base40, buffer, accident);
      out << pitchbuffer;

      // printRhythm1(out, buffer, Ltop, Lbot);
      if (notedur > 0.00000001) {
         brokencount = printRhythm2(out, notedur, brokenq, brokendur, 
            top, bot, chordQ, buffer);
      } else {
         // the note is a grace note, so suppress the
         // printing of any duration (maybe change later
         // to control the graphical view of the grace note).
         // Here is the change:
         printGraceRhythm(out, buffer, Ltop, Lbot, groupflag);
      }

      // print rhythm here...
      if ((alltie == 0) && (count != 1)) {
         if (strchr(buffer, '[') != NULL) {
            out << "-";
         } else if (strchr(buffer, '_') != NULL) {
            out << "-";
         }
      }
   }
      
   if (count > 1) {  // chord of more than one note
      out << "]";
      if (brokencount > 0) {
         for (i=0; i<brokencount; i++) {
            out << '>';
         }      
      } else if (brokencount < 0) {
         for (i=0; i<-brokencount; i++) {
            out << '<';
         }      
      }
   }

   // slur end
   if (slurQ) {
      if (strchr(infile[row][col], ')') != NULL) {
         if (slursuppress > 0) {
            slursuppress--;
         } else {
            out << ")";
         }
      }

      // phrasing end
      if (strchr(infile[row][col], '}') != NULL) {
         out << ")";
      }
   }

   if (alltie) {
      infile[row].getToken(buffer, col, 0);
      if (strchr(buffer, '[') != NULL) {
         out << "-";
      } else if (strchr(buffer, '_') != NULL) {
         out << "-";
      }
   }


}



//////////////////////////////
//
// printMarks -- print marks accoridng to the color and size .
//    only the first mark found will be printed, since the mark
//    colors the note head (this may change later). Rests cannot 
//    be marked (the codes used are changes in note-head shape).
//    
//    Marked gracenotes which are halfnotes/wholenote etc probably don't work.
//

void printMarks(ostream& out, const char* buffer, Array<char>& marks,
      Array<Array<double> > markcolors, int notecount) {

   if (strchr(buffer, 'r') != NULL) {
      // no rests allowed.
      return;
   }
   if (strcmp(buffer, ".") == 0) {
     // just in case: ignore null tokens
     return;
   }

   SSTREAM firstmark;

   // print the first mark found on the note as a colored notehead
   // subsequence marks are circles on the note with increasing 
   // diameter.
   int i = 0;
   double markdiameter = 6.0;
   int m;
   int markcount = 0;
   RationalNumber rn;
   while (buffer[i] != '\0') {
      for (m=0; m<marks.getSize(); m++) {
         if ((markcount == 0) && (buffer[i] == marks[m])) {
            firstmark <<"!head-m" << m+1;
            printNoteHeadShape(firstmark, buffer, m);
            firstmark << "!";
            markcount++;
         } else if ((notecount == 1) && (buffer[i] == marks[m])) {
            // single note with a secondary mark which will be printed
            // as a circle around the note.
            usedAnnotation = 1;
            out << "\"@0,0:gsave cp ";
            // Determine the notehead shape.  If rn > 2.0, then the notehead
            // is a wholenote or larger, so add one to the horizontal offset
            // so that the circle is centered on the note.
            // add this text to code at this point:
            //    exch 1 add exch 
            getNoteShape(rn, buffer);
            if (rn > 2) {
               out << "exch 1 add exch ";
            }
            out << markcolors[m][0] << " ";
            out << markcolors[m][1] << " ";
            out << markcolors[m][2] << " ";
            out << "setrgbcolor newpath ";
            out << markdiameter; 
            out << " 360 0 arcn stroke grestore\"";
            markdiameter += 2.0;

         } else if (buffer[i] == marks[m]) {
            // mark a chord note
         }
      }
      i++;
   }

   firstmark << ends;
   out << firstmark.CSTRING;

}


//////////////////////////////
//
// printNoteHeadShape -- print the head of the note
//     0: hd = solid head (quarter note and smaller durantion)
//     1: Hd = half-note notehead
//     2: HD = whole-note notehead
//     3: HDD = rounded breve
//     4: breve = square breve
//     5: longa = longa
//     6: pshhd = sharp note when clef is perc (percussion sharp head)
//     7: pflhd = flat note when clef is perc (percussion flat head)
//     8: ghd = black head for grace notes (ornaments)
//

void printNoteHeadShape(ostream& out, const char* buffer, int mindex) {
   RationalNumber rn;

   getNoteShape(rn, buffer);

   if (rn == 0) {            // grace note
      out << "ghd"; 
      usedMarks[mindex][8] = 1; 
   } else if (rn <= 1) {     // quarer note or smaller
      out << "hd";  
      usedMarks[mindex][0] = 1; 
   } else if (rn <= 2) {       // half note
      out << "Hd"; 
      usedMarks[mindex][1] = 1; 
   }   else if (rn <= 4) {   // whole note
      out << "HD"; 
      usedMarks[mindex][2] = 1; 
   } else if (rn <= 8) {     // square breve (always not round)
      out << "breve"; 
      usedMarks[mindex][4] = 1; 
   } else if (rn <= 16) {    // longa
      out << "longa"; 
      usedMarks[mindex][5] = 1; 
   } else    {               // don't know what large rhythm is...
      out << "hd"; 
      usedMarks[mindex][0] = 1; 
   }
}



//////////////////////////////
//
// getNoteShape -- return the rhythm of the note with no augmentation dots.
//    Should do a secondary check on grace notes: If the grace note is a 
//    whole note should be considered?
//

void getNoteShape(RationalNumber& rn, const char* buffer) {
   int dotcount = 0;
   int i = 0;
   while (buffer[i] != '\0') {
      if (buffer[i] == '.') {
         dotcount++;
      }
      i++;
   }
   rn = Convert::kernToDurationR(buffer);
   if (dotcount > 0) {
      rn = rn * (int)(pow(2.0, dotcount));
      rn = rn / (int)(pow(2.0, dotcount+1) - 1);
   }
}



//////////////////////////////
//
// printGraceRhythm -- print the graphically displayed rhythm
//    for a grace note (not the logical rhythm which is zero).
// groupflag: true if the grace note is part of a larger group of
// grace notes than just a single grace note.
//

void printGraceRhythm(ostream& out, const char* buffer, int top, int bot,
      int groupflag) {
   int length = strlen(buffer);
   Array<char> buff2;
   buff2.setSize(length+1+20);
   buff2.setSize(0);
   int digitQ = 0;
   char ch;

   int i;
   for (i=0; i<length; i++) {
      if (toupper(buffer[i]) == 'Q') {
         continue;
      }
      if (isdigit(buffer[i])) {
         digitQ = 1;
      }
      ch = buffer[i];
      buff2.append(ch);
   }
   ch = '\0';
   buff2.append(ch);

   if (digitQ == 0) {
      strcpy(buff2.getBase(), "8");
   }
   double dur = Convert::kernToDuration(buff2.getBase());
   if (groupflag == 1) {
      // don't know why this has to be done.  Bug in abcm2ps?
      dur *= 4.0;
   } else if (groupflag == 0) {
      // don't know why this has to be done.  Bug in abcm2ps?
      dur *= 2.0;
   }
	     
// cout << "% BUFFER = " << buff2.getBase() << endl;

   printRhythm2(out, dur, 0, dur, 1, 4, 0, buff2.getBase());
}



//////////////////////////////
//
// checkForAllTies --
//

int checkForAllTies(HumdrumFile& infile, int row, int col) {
   char buffer[32] = {0};
   int count = infile[row].getTokenCount(col);
   int i;

   int counter = 0;
   for (i=0; i<count; i++) {
      infile[row].getToken(buffer, col, i);
      if ((strchr(buffer, '_') != NULL) || (strchr(buffer, '[') != NULL)) {
         counter++;
      }
   }
   
   if (counter == count) {
      return 1;
   } else {
      return 0;
   }
}



//////////////////////////////
//
// printArticulations --
//
// Articulations which are recognized:
//    NAME	HUMDRUM	ABC
//    staccato	'	.
//    staccatissimo `   !wedge!
//    sometime, Craig uses '' instead of `, so the following code reflects this
//    tenuto	~	!tenuto!
//    accent	^	!accent! or !>!
//    fermata	;	!fermata!
//    harmonic	o	!open!
//    mordent   m or M  !mordent!
//    inverted	w or W	!uppermordent!
//    mordent
//    breath    ,       !breath!
//    arpeggio  :       !arpeggio!
//    downbow   u       !downbow!
//    trill     t       !trill!           (semitone)
//    trill     T       !trill!           (wholetone)
//    upbow     v       !upbow!
//    sforzando z       !sfz!
//    general   O       ~
//    ornament
//
//    glissando H       no mapping
//    start
//    glissando h       no mapping
//    end
//    spiccato  s       no mapping   (spiccato in Humdrum might be martellato)
//    pizzacato "       no mapping?
//    Wagnerial $       no mapping?
//    turn  
//    general       I   no mapping?
//    articulation
//    con sordino   U   no mapping? or maybe !plus!
//    or mute
//

void printArticulations(ostream& out, const char* string) {
   // print articulations such as staccato
   // these must come before the "[" of a chord
   // and not inside of the chord brackets.
   
   if (strchr(string, '\'') != NULL)  {  // staccato present 
      if (strstr(string, "''") != NULL) {
         out << "!wedge!";   // staccatissimo
      } else {
         out << ".";         // staccato
      }
   }
   if (strchr(string, '`') != NULL)  {  // staccatissimo present 
      out << "!wedge!";
   }
   if (strchr(string, 'O') != NULL)  {  // general ornament (unspecified)
      out << "~";
   }

   if (strchr(string, '~') != NULL)  {  // tenuto present 
      out << "!tenuto!";
   }
   if (strchr(string, '^') != NULL)  {  // accent present 
      out << "!accent!";
   }
   if ((strchr(string, ';') != NULL) && (strstr(string, ";y") == NULL))  {  // fermata present 
      out << "!fermata!";
   }
   if (strchr(string, 'o') != NULL)  {  // fermata present 
      out << "!open!";
   }
   if (strchr(string, 'S') != NULL)  {  // some sort of turn present
      if (strchr(string, 'R') != NULL)  {  // inverted turn
         out << "!invertedturn!"; 
      } else {                             // regular turn
         out << "!turn!";
      }
   }
   if (strchr(string, 'm') != NULL)  {  // mordent present (semitone)
      out << "!mordent!";
   }
   if (strchr(string, 'M') != NULL)  {  // mordent present (wholetone)
      out << "!mordent!";
   }
   if (strchr(string, 'w') != NULL)  {  // inverted mordent present (semitone)
      out << "!uppermordent!";
   }
   if (strchr(string, 'W') != NULL)  {  // inverted mordent present (wholetone)
      out << "!uppermordent!";
   }
   if (strchr(string, ',') != NULL)  {  // breath mark (after note)
      out << "!breath!";
   }
   if (strchr(string, ':') != NULL)  {  // breath mark (after note)
      out << "!arpeggio!";
   }
   if (strchr(string, 'z') != NULL)  {  // sforzando
      out << "!sfz!";
   }
   if (strchr(string, 'u') != NULL)  {  // downbow
      out << "!downbow!";
   }
   if (strchr(string, 'v') != NULL)  {  // upbow
      out << "!upbow!";
   }
   if (strchr(string, 't') != NULL)  {  // upbow
      out << "!trill!";
   }
   if (strchr(string, 'T') != NULL)  {  // upbow
      out << "!trill!";
   }



}



//////////////////////////////
//
// printAccidental --
//

void printAccidental(ostream& out, int base40, const char* token, 
      Array<int>& accident) {
   if (base40 < 0) {
      return;  // ignore rests
   }
   int octave = base40 / 40;
   if (strchr(token, 'n') != NULL) {
      out << "=";
      return;
   }

   if (strchr(token, '_') != NULL) {
      // don't display the accidental on a continuation tied note
      return;
   }

   if (strchr(token, ']') != NULL) {
      // don't display the accidental on an ending tied note
      return;
   }

   int cautionary = 0;
   if (strchr(token, 'X') != NULL) {
      cautionary = 1;
   }

   int acci = Convert::base40ToAccidental(base40);
   int diatonic = Convert::base40ToDiatonic(base40) % 7;
   if ((!cautionary) && (acci == accident[diatonic+7*octave])) {
      return;   // the accidental matches the current display, so don't print
   }

   switch (acci) {
      case +2: out << "^^"; return;
      case +1: out << "^";  return;
      case  0: if (!nonaturalQ) { out << "="; } return;
      case -1: out << "_";  return;
      case -2: out << "__"; return;
   }

}



//////////////////////////////
//
// printRhythm1 -- handle tuplets later... still buggy at the moment
//

void printRhythm1(ostream& out, char* buffer, int top, int bot) {
   int rhythm = getRhythm(buffer);
   int dots = countDots(buffer);

   int rtop = 1;
   int rbot = rhythm;

   rtop = rtop * bot;
   rbot = rbot * top;
   int rtemptop;
   int rtempbot;


   if (dots == 1) {
      rtemptop = 3 * rtop * rbot;
      rtempbot = 2 * rtop * rbot;
	     
      rtop = rtemptop;
      rbot = rtempbot;
   } // handle other rhythms later


   if (((rtop % 8) == 0) && ((rbot % 8) == 0)) {
      rtop /= 8;
      rbot /= 8;
   }
   if (((rtop % 4) == 0) && ((rbot % 4) == 0)) {
      rtop /= 4;
      rbot /= 4;
   }
   if (((rtop % 2) == 0) && ((rbot % 2) == 0)) {
      rtop /= 2;
      rbot /= 2;
   }
   if (((rtop % 2) == 0) && ((rbot % 2) == 0)) {
      rtop /= 2;
      rbot /= 2;
   }

   if (rtop > 1) {
      out << rtop;
   }
   if (rbot > 1) {
      if (rbot == 2) {
         out << "/";           // shorthand for a /2 rhythm
      } else if (rbot == 4) {
         out << "//";          // shorthand for a /4 rhythm
      } else {
         out << "/" << rbot;
      }
   }

}



//////////////////////////////
//
// getRhythm --
//

int getRhythm(const char* buffer) {
   int rhythm = 0;
   int length = strlen(buffer);
   int i;
   for (i=0; i<length; i++) {
      if (isdigit(buffer[i])) {
         if (buffer[i] == '0') {
            if (isdigit(buffer[i+1])) {
               // do something for large durations greater than whole note:
               rhythm = -1 * (buffer[i+1] - '0');
            }
         } else {
            sscanf(&buffer[i], "%d", &rhythm);
         }
         break;
      }
   }
   return rhythm;
}



//////////////////////////////
//
// countDots --
//

int countDots(char* buffer) {
   int length = strlen(buffer);
   int i;
   int output = 0;
   for (i=0; i<length; i++) {
      if (buffer[i] == '.') {
         output++;
      }
   }
   return output;
}



//////////////////////////////
//
// getBrokenRhythm --
//

void getBrokenRhythms(Array<int>& broken, Array<double>& brokendurs, 
      Array<Coordinate>& nogracelist, Array<double>& nogracedurs, 
      HumdrumFile& infile) {

   broken.setSize(nogracelist.getSize());
   brokendurs.setSize(nogracelist.getSize());
   broken.setAll(0);
   brokendurs.setAll(0);
   int i;
   int row;
   int col;
   int row2;
   int col2;

   for (i=0; i<broken.getSize()-2; i++) {
      row = nogracelist[i].row;
      col = nogracelist[i].col;
      row2 = nogracelist[i+1].row;
      col2 = nogracelist[i+1].col;
      if ((strchr(infile[row][col], '.') == NULL) &&
          (strchr(infile[row2][col2], '.') == NULL)) {
         continue;
      }
	     
      if (fabs(nogracedurs[i] - 3.0 * nogracedurs[i+1]) <= TOLERANCE) {
         broken[i]   = +1;
         broken[i+1] =  0;
         brokendurs[i] = nogracedurs[i+1] * 2.0;
         brokendurs[i+1] = brokendurs[i];
         i+=1; // skip next note 
      } else if (fabs(3.0 * nogracedurs[i] - nogracedurs[i+1]) <= TOLERANCE) {
         broken[i]   = -1;
         broken[i+1] =  0;
         brokendurs[i] = nogracedurs[i] * 2.0;
         brokendurs[i+1] = brokendurs[i];
         i+=1; // skip next note 
      } else if (fabs(nogracedurs[i] - 7.0 * nogracedurs[i+1]) <= TOLERANCE) {
         broken[i]   = +2;
         broken[i+1] =  0;
         brokendurs[i] = nogracedurs[i+1] * 4.0;
         brokendurs[i+1] = brokendurs[i];
         i+=1; // skip next note 
      } else if (fabs(7.0 * nogracedurs[i] - nogracedurs[i+1]) <= TOLERANCE) {
         broken[i]   = -2;
         broken[i+1] =  0;
         brokendurs[i] = nogracedurs[i] * 4.0;
         brokendurs[i+1] = brokendurs[i];
         i+=1; // skip next note 
      } else if (fabs(nogracedurs[i] - 15.0 * nogracedurs[i+1]) <= TOLERANCE) {
         broken[i]   = +3;
         broken[i+1] =  0;
         brokendurs[i] = nogracedurs[i+1] * 8.0;
         brokendurs[i+1] = brokendurs[i];
         i+=1; // skip next note 
      } else if (fabs(15.0 * nogracedurs[i] - nogracedurs[i+1]) <= TOLERANCE) {
         broken[i]   = -3;
         broken[i+1] =  0;
         brokendurs[i] = nogracedurs[i] * 8.0;
         brokendurs[i+1] = brokendurs[i];
         i+=1; // skip next note 

      // abcm2ps cannot handle the following two cases of a quadruple dot:
      /*      } else if (fabs(nogracedurs[i] - 31.0 * nogracedurs[i+1]) <= TOLERANCE) {
         broken[i]   = +4;
         broken[i+1] =  0;
         brokendurs[i] = nogracedurs[i+1] * 16.0;
         brokendurs[i+1] = brokendurs[i];
         i+=1; // skip next note 
      } else if (fabs(31.0 * nogracedurs[i] - nogracedurs[i+1]) <= TOLERANCE) {
         broken[i]   = -4;
         broken[i+1] =  0;
         brokendurs[i] = nogracedurs[i] * 16.0;
         brokendurs[i+1] = brokendurs[i];
         i+=1; // skip next note 
       */
      }
   }

   if (debugQ) {
      cout << "%\n%BROKEN RHYTHM ANALYSIS:\n";
      cout << "%broken\tbrokdur\tdur\ttoken\n";
      for (i=0; i<broken.getSize(); i++) {
         cout << "%"   << broken[i]
              << "\t"  << int(brokendurs[i] * 10000.0 + 0.5)/10000.0
              << "\t"  << int(nogracedurs[i] * 10000.0 + 0.5)/10000.0
              << "\t"  << infile[nogracelist[i].row][nogracelist[i].col]
              << endl;
      }
      cout << "%" << endl;
   }


   // replace the non-brokendurs with the broken duration:
   for (i=0; i<brokendurs.getSize(); i++) {
      if (brokendurs[i] != 0.0) {
         nogracedurs[i] = brokendurs[i];
      }
   }

}



//////////////////////////////
//
// getBrokenRhythms -- two rhythms which sum to a quarter note.
//    probably should check to see if starting on a beat, but
//    maybe that wouldn't be good anyway. Currently ignores
//    tuplet broken rhythms.  Don't know if that would be useful
//    to add...
//

void getBrokenRhythmsOld(Array<int>& broken, Array<int>& brokenrhythm, 
      Array<double>& notedurations, HumdrumFile& infile) {

   Array<double>& ND = notedurations;

   broken.setSize(ND.getSize());
   broken.setAll(0);

   brokenrhythm.setSize(ND.getSize());
   brokenrhythm.setAll(0);

   int i;
   for (i=0; i<ND.getSize()-1; i++) {
      if (ND[i] + ND[i+1] == 1.0) {

         if ((ND[i] == 0.75) && (ND[i] == 0.25)) {
            broken[i] = 1;  brokenrhythm[i] = 4;
         }
         else if ((ND[i] == 0.25) && (ND[i] == 0.75)) {
            broken[i] = -1;  brokenrhythm[i] = 4;
         }
         if ((ND[i] == 0.875) && (ND[i] == 0.125)) {
            broken[i] = 2;  brokenrhythm[i] = 4;
         }
         else if ((ND[i] == 0.125) && (ND[i] == 0.875)) {
            broken[i] = -2;  brokenrhythm[i] = 4;
         }
         if ((ND[i] == 0.9375) && (ND[i] == 0.0625)) {
            broken[i] = 3;  brokenrhythm[i] = 4;
         }
         else if ((ND[i] == 0.0625) && (ND[i] == 0.9375)) {
            broken[i] = -3;  brokenrhythm[i] = 4;
         }
      }
      else if (ND[i] + ND[i+1] == 0.5) {
         if ((ND[i] == 0.875) && (ND[i] == 0.125)) {
            broken[i] = 1;  brokenrhythm[i] = 8;
         }
         else if ((ND[i] == 0.125) && (ND[i] == 0.875)) {
            broken[i] = -1;  brokenrhythm[i] = 8;
         }
         if ((ND[i] == 0.9375) && (ND[i] == 0.0625)) {
            broken[i] = 2;  brokenrhythm[i] = 8;
         }
         else if ((ND[i] == 0.0625) && (ND[i] == 0.9375)) {
            broken[i] = -2;  brokenrhythm[i] = 8;
         }
         if ((ND[i] == 0.96875) && (ND[i] == 0.0625)) {
            broken[i] = 3;  brokenrhythm[i] = 8;
         }
         else if ((ND[i] == 0.03125) && (ND[i] == 0.96875)) {
            broken[i] = -3;  brokenrhythm[i] = 8;
         }
      }  
      else if (ND[i] + ND[i+1] == 0.25) {
         if ((ND[i] == 0.9375) && (ND[i] == 0.0625)) {
            broken[i] = 1;  brokenrhythm[i] = 16;
         }
         else if ((ND[i] == 0.0625) && (ND[i] == 0.9375)) {
            broken[i] = -1;  brokenrhythm[i] = 16;
         }
         if ((ND[i] == 0.96875) && (ND[i] == 0.0625)) {
            broken[i] = 2;  brokenrhythm[i] = 16;
         }
         else if ((ND[i] == 0.03125) && (ND[i] == 0.96875)) {
            broken[i] = -2;  brokenrhythm[i] = 16;
         }

      }

   }

   // don't allow for two broken rhythms in a row:
   for (i=0; i<broken.getSize() - 1; i++) {
      if (broken[i] && broken[i+1]) {
         broken[i+1] = 0;
         brokenrhythm[i+1] = 0;
      }
   }
}



//////////////////////////////
//
// getNoteDurations --
//

void getNoteDurations(Array<double>& notedurations, Array<int>& iindex, 
      int layer, Array<Array<Coordinate> >& address, HumdrumFile& infile) {

   int ilayer = layer - 1;
   notedurations.setSize(address[ilayer].getSize());
   notedurations.setSize(0);


   // iindex is a list of the indices into address for each sequential
   // note in the layer.
   iindex.setSize(address[ilayer].getSize());
   iindex.setSize(0);

   char buffer[128] = {0};
   double dur;
   int row, col;
   int i;
   for (i=0; i<address[ilayer].getSize(); i++) {
      if (address[ilayer][i].row >= 0) {
         row = address[ilayer][i].row;
         col = address[ilayer][i].col;
         infile[row].getToken(buffer, col, 0);
         if (norhythm(buffer)) {
            dur = 1.0;  // assign quarter note 
         } else {
            dur = Convert::kernToDuration(buffer);
         }
	 notedurations.append(dur);
	 iindex.append(i);
      }
   }

   if (debugQ) {
      int tcount = 0;
      cout << "%dur\ttoken\n";
      for (i=0; i<address[ilayer].getSize(); i++) {
         if (address[ilayer][i].row < 0) { 
            continue;
         }
         cout << "%" << int(notedurations[tcount]*10000.0 + 0.5)/10000.0 
              << "\t"
              << infile[address[ilayer][i].row][address[ilayer][i].col]
              << endl;
         tcount++;
      }
   }

}



//////////////////////////////
//
// norhythm -- return true if there is no rhythm specified in the
//    **kern data (for Gregorian Chant for example). 
//

int norhythm(const char* buffer) {
   int length = strlen(buffer);
   int i;
   for (i=0; i<length; i++) {
      if (isdigit(buffer[i]) || (toupper(buffer[i]) == 'Q')) {
         return 0;
      }
   }

   return 1;
}



//////////////////////////////
//
// checkForBeamStarts -- Ignoring partial beams (K to the right)
//    and k for partial beam to the left.  All chord notes are
//    expected to have the same beaming pattern, so only the
//    first note is examined for beams.
//

int getBeamState(const char* token) {
   int length = strlen(token);
   int i;
   int output = 0;
   for (i=0; i<length; i++) {
      if (token[i] == ' ') {
         // don't break if a space is found
	 // because the beaming info only can
	 // occur once in a chord, and can occur on 
	 // any note in the chord.
      }
      if (token[i] == 'L') {  // beam to right
         output++;
      }
      if (token[i] == 'J') {  // beam to right
         output--;
      }
   }

   return output;
}


//////////////////////////////
//
// buildNoteAddresses -- store the line and column number for
//    each note in the part;
//

void buildNoteAddresses(Array<Array<Coordinate> >& noteaddresses, 
      HumdrumFile& infile, const VoiceMap& voiceinfo, 
      const MeasureInfo& measureinfo) {

   int maxlines = measureinfo.endline - measureinfo.startline + 1;
   int i, j;
   // empty the previous contents, if any
   // the first dimension is given by the calling function and
   // represents the maximum voice layers in the part
   for (i=0; i<noteaddresses.getSize(); i++) {
      noteaddresses[i].setSize(maxlines);
      noteaddresses[i].setSize(0);
   }

   // rowinfo is used to keep track of what notes are played
   // for each Humdrum file line (for the given voice).
   // If there are any notes for that voice on each line,
   // then store the note address data in the noteaddresses 
   // list.
   Array<int> rowinfo;
   rowinfo.setSize(noteaddresses.getSize());
   rowinfo.allowGrowth(0);
   int notesfound;
   int layercounter;
   Coordinate coord;

   for (i=measureinfo.startline; i<=measureinfo.endline; i++) {
      if (infile[i].getType() != E_humrec_data) {
         continue;
      }
      rowinfo.setAll(-1);
      notesfound = 0;
      layercounter = -1;
      for (j=0; j<infile[i].getFieldCount(); j++) {
         if (infile[i].getPrimaryTrack(j) == voiceinfo.primary) {
            layercounter++;
            if (strcmp(infile[i][j], ".") == 0) { // ignore null tokens in layer
               continue;
            } else {
               rowinfo[layercounter] = j;
               notesfound = 1;
            }
         }
      }
      if (notesfound) {
         for (j=0; j<rowinfo.getSize(); j++) {
            coord.row  = -1;
            coord.col  = -1;
            if (rowinfo[j] >= 0) {
               coord.row = i;
               coord.col = rowinfo[j];
            }
            noteaddresses[j].append(coord);
         }
      }
   }
}



//////////////////////////////
//
// getMeasureInfo --  only check the primary layer for a staff
//     Also checks for repeat endings in a particular format.
//

void getMeasureInfo(Array& measures, HumdrumFile& infile) {
   int i;
   int lastmeasure = -1;
   double duration;
   PerlRegularExpression pre;

   measures.setSize(infile.getNumLines());
   measures.setSize(0);
   MeasureInfo mtemp;
   int measureno;
   int lastmeasureno = -1;
   //infile.analyzeRhythm("4");
   int currentKey = -100;
   int datafound = 0;
   int sindex;

   Array<int> repeatinfo;
   Array<int> segments;
   getRepeatInfo(segments, repeatinfo, infile);

   for (i=0; i<infile.getNumLines(); i++) {
      if (infile[i].getType() == E_humrec_data) {
         if (datafound == 0) {
            datafound = 1;
            if (lastmeasure == -1) {
               lastmeasure = i;
            }
         }
         continue;
      }

      if ((infile[i].getType() == E_humrec_data_measure) ||
          ((infile[i].getType() == E_humrec_interpretation) && 
           (strcmp(infile[i][0], "*-") == 0)) ) {
         if (lastmeasure < 0) {
            lastmeasure = i;
         } else {
            mtemp.clear();
            mtemp.startline = lastmeasure;
            mtemp.measurenum = lastmeasureno;
            mtemp.endline   = i;
	    mtemp.newkey = currentKey;
            lastmeasure = i;
	    duration = infile[mtemp.startline].getAbsBeat();
	    duration = infile[mtemp.endline].getAbsBeat() - duration;
            sindex = checkForRepeatMeasure(infile, segments, 
                  repeatinfo, lastmeasure);
            if (sindex >= 0) {
               mtemp.ending = repeatinfo[sindex];
            } else {
               mtemp.ending = -1;
            }

	    if (duration > 0) {
               measures.append(mtemp);
            } else if (datafound && (mtemp.endline - mtemp.startline > 1)) {
               // also have to allow for non-rhythm notation
               // such as Gregorian chant...
               measures.append(mtemp);
            }
	    currentKey = -100;
         }
         if (sscanf(infile[i][0], "=%d", &measureno) == 1) {
	    lastmeasureno = measureno;
         } else {
	    lastmeasureno = -1;	     
         }
      } else if (infile[i].getType() == E_humrec_interpretation) {
         if (strncmp(infile[i][0], "*k[", 3) == 0) {
            currentKey = Convert::kernKeyToNumber(infile[i][0]);      
         }
         if (pre.search(infile[i][0], "^\\*[A-G-a-g][-#]?:")) {
            mtemp.kernkey = infile[i][0];
         }
      }
   }

/// This code is now obsolete, since StartKey is a naughty global variable,
/// and the getMeasureInfo is now called before StartKey is set...
//   if (measures.getSize() > 0) {
//      measures[0].currkey = StartKey;
//   }
   for (i=0; i<measures.getSize(); i++) {
      if (measures[i].newkey > -50) {
         measures[i].currkey = measures[i].newkey;
      } else if (i > 0) {
         measures[i].currkey = measures[i-1].currkey;
      }
   }

   // a key signature in the first measure is controlled by the
   // header K: field, so turn off any new key markers in the
   // first measure.
   if (measures.getSize() > 0) {
      measures[0].newkey = -100;
   }

   if (Voicemap.getSize() == 1) {
      identifyWholeRestsInMeasures(measures, Voicemap[0], infile);
      for (i=1; i<measures.getSize(); i++) {
         if (measures[i].fullrest) {
            measures[i].fullrest += measures[i-1].fullrest;
         }
      }
   }

}



//////////////////////////////
//
// checkForRepeatMeasure --
//

int checkForRepeatMeasure(HumdrumFile& infile, Array<int>& segments, 
      Array<int>& repeatinfo, int mindex) {

   int i;
   double measuredur = infile[mindex].getAbsBeat();
   double sdur;
   for (i=0; i<segments.getSize(); i++) {
      sdur = infile[segments[i]].getAbsBeat();
      if (fabs(sdur - measuredur) < TOLERANCE) {
         return i;
      }
   }

   return -1;
}



//////////////////////////////
//
// getRepeatInfo --
//

void getRepeatInfo(Array<int>& segments, Array<int>& repeatinfo, 
      HumdrumFile& infile) {
   int i;
   int length;

   segments.setSize(100);
   segments.setSize(0);

   for (i=0; i<infile.getNumLines(); i++) {
      if (infile[i].getType() == E_humrec_interpretation) {
         if (strchr(infile[i][0], '[') != NULL) {
            continue;
         }
         if (strncmp(infile[i][0], "*>", 2) == 0) {
            segments.append(i);
         }
      }
   }

   repeatinfo.setSize(segments.getSize());
   repeatinfo.setAll(-1);

   int number;
   int base;
   int baselength;
   for (i=1; i<segments.getSize(); i++) {
      length = strlen(infile[segments[i-1]][0]);
      if (strncmp(infile[segments[i]][0], infile[segments[i-1]][0],
            length) == 0) {
         base = i-1;
         baselength = length;
         length = strlen(infile[segments[i]][0]);
         while ((i < segments.getSize()) && (length > baselength) && 
                (sscanf(&infile[segments[i]][0][length-1], "%d", 
                      &number) == 1) &&
                (strncmp(infile[segments[i]][0], infile[segments[base]][0], 
                  baselength) == 0)) {
            repeatinfo[i] = number;            
            i++;
         }
      }
   }


   for (i=1; i<repeatinfo.getSize(); i++) {
      if ((repeatinfo[i-1] > 0) && (repeatinfo[i] < 0)) {
         repeatinfo[i] = 0;
      }
   }

   if (debugQ) {
      cout << "% MUSIC SEGMENTS: " << endl;
      for (i=0; i<repeatinfo.getSize(); i++) {
         cout << "% SEGMENT: " << infile[segments[i]][0] 
              << " number " << repeatinfo[i] << endl;
      }
      cout << "%\n";
   }

}



//////////////////////////////
//
// identifyWholeRestsInMeasures --
//

void identifyWholeRestsInMeasures(Array<MeasureInfo>& measures, 
      VoiceMap& voicemap, HumdrumFile& infile) {
   int i;

   for (i=0; i<measures.getSize(); i++) {
      measures[i].fullrest = checkForWholeRestInMeasure(measures[i].startline, 
            measures[i].endline, voicemap.primary, infile);
   }

}



//////////////////////////////
//
// checkForWholeRestInMeasure -- should also check for the duration
//    of the rest, but then would also need to know the meter 
//    of the measure which is currently not being analyzed.
//

int checkForWholeRestInMeasure(int startline, int endline, int primary,
      HumdrumFile& infile) {
   int i, j;
   int notecount = 0;
   int restcount = 0;
   for (i=startline; i<=endline; i++) {
      if (infile[i].getType() != E_humrec_data) {
         continue;
      }
      for (j=0; j<infile[i].getFieldCount(); j++) {
         if (infile[i].getPrimaryTrack(j) == primary) {
            if (strcmp(infile[i][j], ".") == 0) {
               continue;
            }
            if (strchr(infile[i][j], 'r') != NULL) {
               restcount++;
            } else {
               notecount++;
            }
         }
      }
   }

   if (notecount == 0) {
      return 1;
   }  else {
      return 0;
   }
}



//////////////////////////////
//
// printHeader -- 
//

void printHeader(ostream& out, HumdrumFile& infile, Array<char*>& header,
      Array<MeasureInfo>& measures, int xval, const char* filename) {

   parseBibliographic(header, infile);

   // first indicate the required tune number
   // which must come as the first record in the header
   // (excluding comment/blank lines)
   // Use the number 1 if there is no command-line input
   if (xval > 0) {
      out << "X: " << xval << "\n"; 
   } else if (strcmp(header[XX], "") == 0) {
      out << "X: 1\n";
   } else {
      out << "X: " << header[XX] << "\n";
   }

   int firstmeasure = getFirstMeasureNumber(infile);

   // display the title next (if given)
   if (strcmp(header[TT], "") != 0) {
      out << "T:";
      if (filenumQ) {
         printNumberFromString(out, filename, filenumstring);
      }
      if (filenametitleQ) {
         out << " ";
         printFilenameBase(out, filename);
      } 
      out << " " << header[TT] << "\n";
   } else if (filenametitleQ) {
      out << "T:";
      out << " ";
      printFilenameBase(out, filename);
      out << "\n";
   }

   // display the composer next if given
   if (strcmp(header[CC], "") != 0) {
      out << "C: " << header[CC] << "\n";
   } 

   printAbcExtendedInformationFields(out, infile);

   if ((barnumberingstyle == 1) && 
       (fabs(infile.getPickupDuration()) > TOLERANCE)) {
      // do something so that the first bar number will be printed
      // if there is a pickup measure
   } 
   if (firstmeasure > 1) {
      out << "%%setbarnb " << firstmeasure << "\n";
   }

   if (Voicemap.getSize() > 5) {
      // automatically suppress staff lines in music with more
      // than 5 staves when the line of music contains only
      // rests
      // Doesn't seem to work:
      //out << "%%staffnonote" << "\n";
   }
	     


   int i;   
   for (i=0; i<26; i++) {
      switch (i+'A') {
         case 'X':      // number of piece, must come at start of header
         case 'T':      // title already printed
         case 'C':      // composer already printed
         case 'K':	// key of piece, must come at end of header
         case 'V':	// ignore user input of this field (Voice declarations)
            // ignore
            break;

         case 'L':      // rhythmic unit already printed
            // determine and display the rhythmic unit (L: record)
            if (strcmp(header[LL], "") == 0) {
               calculateBestRhythmUnit(infile, Ltop, Lbot);
               if (Lbot == 1) {
                  out << "L: " << Ltop << "\n";
               } else {
                  out << "L: " << Ltop << "/" << Lbot << "\n";
               }
            } else {  // L field is specified in command-line arguments
               if (sscanf(header[LL], "%d/%d", &Ltop, &Lbot) == 2) {
                  out << "L: " << Ltop << "/" << Lbot << "\n";
               } else if (sscanf(header[LL], "%d", &Ltop) == 1) {
                  Lbot = 1;
                  out << "L: " << Ltop << "\n";
               } else {
                  calculateBestRhythmUnit(infile, Ltop, Lbot);
                  if (Lbot == 1) {
                     out << "L: " << Ltop << "\n";
                  } else {
                     out << "L: " << Ltop << "/" << Lbot << "\n";
                  }
               }
            }
            break;
         default:
            if (strcmp(header[i], "") != 0) {
               out << char(i+'A') << ": " << header[i] << "\n";
            }
      }
   }
   if (Voicemap.getSize() > 1) {
      // polyphonic music
      
      if (Voicemap.getSize() == 2) {
         // presume a piano staff brace. refine the decision later
         out << "%%staves {1 2}\n";
      } else if (Voicemap.getSize() == 4) {
         // presume a string-quartet-like system with a brace
         out << "%%staves [1 2 3 4]\n";
      }

      for (i=0; i<Voicemap.getSize(); i++) {
         printVoiceDeclaration(out, Voicemap[i], i+1, infile);
      }
   }

   if (strcmp(header[KK], "") == 0) {
      out << "K: ";
      printKeySignature(out, infile);
   } else {
      out << "K: " << header[KK];
   }
   if (Voicemap.getSize() == 1) {
      printVoiceClef(out, Voicemap[0], infile);
   }
   out << "\n";
}



//////////////////////////////
//
// printFilenameBase --
//

void printFilenameBase(ostream& out, const char* filename) {
   Array<char> strang;
   const char* cptr;
   char* ptr;
   int length = strlen(filename);
   strang.setSize(length+1);

   cptr = strrchr(filename, '/');
   if (cptr != NULL) {
      strcpy(strang.getBase(), cptr+1);
   } else {
      strcpy(strang.getBase(), filename);
   }

   ptr = strrchr(strang.getBase(), '.');
   if (ptr != NULL) {
      *ptr = '\0';
   }

   out << strang.getBase();
}



//////////////////////////////
//
// printNumberFromString --
//

void printNumberFromString(ostream& out, const char* filename, 
      const char* ending) {
   int foundQ = 0;
   int starti = -1;
   int length = strlen(filename);
   int slashi = -1;
   int i;

   if (strchr(filename, '/') != NULL) {
      for (i=length-1; i>=0; i--) {
         if (filename[i] == '/') {
            slashi = i + 1;
            break;
         }
      }
   } else {
      slashi = 0;
   }

   for (i=slashi; i<length; i++) {
      if (!isdigit(filename[i]) || (filename[i] == '0')) { 
         continue;
      } 
      foundQ = 1;
      starti = i;
      break;
   }

   if (!foundQ) {
      return;
   }
   out << " ";

   i = starti;
   while ((i<length) && (filename[i] != '.')) {
      out << filename[i];
      i++;
   }

   if (foundQ) {
      out << ending;
   }

}



//////////////////////////////
//
// printVoiceClef --
//

void printVoiceClef(ostream& out, VoiceMap voicemap, HumdrumFile& infile) {
   int i, j;
   for (i=0; i<infile.getNumLines(); i++) {
      if (infile[i].getType() == E_humrec_interpretation) {
         for (j=0; j<infile[i].getFieldCount(); j++) {
            if (infile[i].getPrimaryTrack(j) == voicemap.primary) {
               if (strncmp(infile[i][j], "*clef", 5) == 0) {
                  out << " " << "clef=";
                  printAbcClef(out, infile[i][j], 
                        infile[i].getPrimaryTrack(j), clefstates);
                  return;
               }
            }
         }
      } else if (infile[i].getType() == E_humrec_data) {
         // no clef found before start of music, so don't choose a clef
         return;
      }
	       
   }
}



//////////////////////////////
//
// getFirstMeasureNumber --
//

int getFirstMeasureNumber(HumdrumFile& infile) {
   int i;
   int output = -1;
   for (i=0; i<infile.getNumLines(); i++) {
      if (infile[i].getType() == E_humrec_data) {
         break;
      }
      if (infile[i].getType() == E_humrec_data_measure) {
         sscanf(infile[i][0], "=%d", &output);
         break;
      }
   }
   return output;
}



//////////////////////////////
//
// printAbcExtendedInformationFields --
//

void printAbcExtendedInformationFields(ostream& out, HumdrumFile& infile) {

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

   int i;
   for (i=0; i<infile.getNumLines(); i++) {
      if (infile[i].getType() == E_humrec_bibliography) {
         bibfields.append(i);
      }
   }

   Array<Array<char> > values;
   Array<char> value;
   value[0] = EMPTY;
   value.setSize(0);

   out << "%%abc-version 2.0" << "\n";
   out << "%%abcx-abcm2ps-target-version 5.9.1 (29 Sep 2008)" << "\n";
   out << "%%abc-creator hum2abc beta\n";

   struct tm *current;
   time_t now;
   time(&now);
   current = localtime(&now);
   out << "%%abcx-conversion-date ";
   out << current->tm_year + 1900 << "/";
   if (current->tm_mon+1 < 10) {
      out << "0";
   }
   out << current->tm_mon + 1     << "/";
   if (current->tm_mday < 10) {
      out << "0";
   }
   out << current->tm_mday        << " ";

   if (current->tm_hour < 10) {
      out << "0";
   }
   out << current->tm_hour        << ":";

   if (current->tm_min < 10) {
      out << "0";
   }
   out << current->tm_min         << ":";
   if (current->tm_sec < 10) {
      out << "0";
   }
   out << current->tm_sec         << "\n";

   // YEC = copyright notice
   if (findRecord("YEC", value, infile, bibfields)) {
      out << "%%abc-copyright " << value.getBase() << "\n";
   }

   if (findMultipleRecords("EED", values, infile, bibfields)) {
      for (i=0; i<values.getSize(); i++) {
         out << "%%abc-edited-by " << values[i].getBase() << "\n";
      }
   } 
   if (findMultipleRecords("ENC", values, infile, bibfields)) {
      for (i=0; i<values.getSize(); i++) {
         out << "%%abc-edited-by " << values[i].getBase() << "\n";
      }
   }
   if (findRecord("END", value, infile, bibfields)) {
      out << "%%abcx-initial-encoding-date " << value.getBase() << "\n";
   } else if (findRecord("RDT", value, infile, bibfields)) {
      out << "%%abcx-initial-encoding-date " << value.getBase() << "\n";
   }

   // make a use option eventually
   // but in any case, a check should be made
   // beforehand that there are grace notes in the file
   // before printing the following line:
   // doesn't look so good for monophonic parts, so turn off
   // if there is only one voice
   if ((Voicemap.getSize() > 1) && graceQ) {   
      if ((parameterstring != NULL) && 
          (strstr(parameterstring, "gracespace") == NULL)) {
         // only print default gracespace if not in -p option
         out << "%%gracespace 0 6 6\n";
      }
   }

   // it would be nice to be able to control the indent from the
   // data file, but the following does not work in abcm2ps 5.9.1:
   // out << "%indent 2.25 cm\n";
   
   if (notespacingQ) {
      // a value of 2.0 will increase the density of the music
      // a value of 1.0 will stretch out the music
      // the default value is the square-root of 2 (1.414)
      out << "%%notespacingfactor " << notespacing << "\n";
   }

   if (veritasQ) {
      printVeritas(out, infile);
   }
   if (landscapeQ) {
      out << "%%landscape" << "\n";
   }
   if (footerQ) {
      out << "%%footer \"" << footer << "\"" << "\n";
   }
   if (headerQ) {
      out << "%%header \"" << footer << "\"" << "\n";
   }

   if (musicscaleQ) {
      out << "%%scale " << musicscale << "\n";
   }

   if (continueQ) {
      out << "%%continueall 1" << "\n";
   }

   if (strlen(parameterstring) > 0) {
      Array<char> cleanedstring; 
      translateSpecialCharacters(cleanedstring, parameterstring);
      out << cleanedstring.getBase() << "\n";
   }
   
   // by default measure numbering will be turned on here.
   // add a user-option control to turn off the bar numbering.
   // %%barnumbers 0 = number the start of each system
   // %%barnumbers -1 = don't number measures
   // %%barnumbers 5 = number every fifth measure
   // %%barnumbers 1 = number every measure
   out << "%%barnumbers " << barnumberingstyle << "\n";
   if (boxQ) {
      out << "%%measurebox 1" << "\n";
   }

    // print postscript code for marks if there are any.
    //if (hasmarksQ) {
    //   printMarkCodes(out, marks, markcolors);
    // }

}


//////////////////////////////
//
// printMarkCodes -- print codes to use for marks
//     0: hd = solid head (quarter note and smaller durantion)
//     1: Hd = half-note notehead
//     2: HD = whole-note notehead
//     3: HDD = rounded breve
//     4: breve = square breve
//     5: longa = longa
//     6: pshhd = sharp note when clef is perc (percussion sharp head)
//     7: pflhd = flat note when clef is perc (percussion flat head)
//     8: ghd = black head for grace notes (ornaments)

void printMarkCodes(ostream& out, Array<char>& marks, 
      Array<Array<double> >& markcolors) {

   if (usedAnnotation) {
      out << "%%postscript /cp {currentpoint}!\n";
      out << "%%postscript /anshow { dup 0 get (:) 0 get ne\n";
      out << "%%postscript    {/gchshow load cshow}\n";
      out << "%%postscript    {dup length 1 sub 1 exch ";
      out << "getinterval cvx exec}ifelse}!\n";
   }

   int i;
   for (i=0; i<usedMarks.getSize(); i++) {

      if (usedMarks[i][0]) {
         out << "%%postscript /mark" << i+1 << "hd {gsave ";
         out << markcolors[i][0] << " "; 
         out << markcolors[i][1] << " "; 
         out << markcolors[i][2] << " "; 
         out << "setrgbcolor hd grestore}!\n";
      }

      if (usedMarks[i][1]) {
         out << "%%postscript /mark" << i+1 << "Hd {gsave ";
         out << markcolors[i][0] << " "; 
         out << markcolors[i][1] << " "; 
         out << markcolors[i][2] << " "; 
         out << "setrgbcolor Hd grestore}!\n";
      }

      if (usedMarks[i][2]) {
         out << "%%postscript /mark" << i+1 << "HD {gsave ";
         out << markcolors[i][0] << " "; 
         out << markcolors[i][1] << " "; 
         out << markcolors[i][2] << " "; 
         out << "setrgbcolor HD grestore}!\n";
      }

      if (usedMarks[i][3]) {
         out << "%%postscript /mark" << i+1 << "HDD {gsave ";
         out << markcolors[i][0] << " "; 
         out << markcolors[i][1] << " "; 
         out << markcolors[i][2] << " "; 
         out << "setrgbcolor HDD grestore}!\n";
      }

      if (usedMarks[i][4]) {
         out << "%%postscript /mark" << i+1 << "breve {gsave ";
         out << markcolors[i][0] << " "; 
         out << markcolors[i][1] << " "; 
         out << markcolors[i][2] << " "; 
         out << "setrgbcolor breve grestore}!\n";
      }

      if (usedMarks[i][5]) {
         out << "%%postscript /mark" << i+1 << "longa {gsave ";
         out << markcolors[i][0] << " "; 
         out << markcolors[i][1] << " "; 
         out << markcolors[i][2] << " "; 
         out << "setrgbcolor longa grestore}!\n";
      }

      if (usedMarks[i][6]) {
         out << "%%postscript /mark" << i+1 << "pshhd {gsave ";
         out << markcolors[i][0] << " "; 
         out << markcolors[i][1] << " "; 
         out << markcolors[i][2] << " "; 
         out << "setrgbcolor pshhd grestore}!\n";
      }

      if (usedMarks[i][7]) {
         out << "%%postscript /mark" << i+1 << "pflhd {gsave ";
         out << markcolors[i][0] << " "; 
         out << markcolors[i][1] << " "; 
         out << markcolors[i][2] << " "; 
         out << "setrgbcolor pflhd grestore}!\n";
      }

      if (usedMarks[i][8]) {
         out << "%%postscript /mark" << i+1 << "ghd {gsave ";
         out << markcolors[i][0] << " "; 
         out << markcolors[i][1] << " "; 
         out << markcolors[i][2] << " "; 
         out << "setrgbcolor ghd grestore}!\n";
      }

   }

   for (i=0; i<usedMarks.getSize(); i++) {
      if (usedMarks[i][0]) {
         out << "%%deco head-m" << i+1 << "hd 0 mark" << i+1 << "hd 0 0 0 \n";
      }
      if (usedMarks[i][1]) {
         out << "%%deco head-m" << i+1 << "Hd 0 mark" << i+1 << "Hd 0 0 0 \n";
      }
      if (usedMarks[i][2]) {
         out << "%%deco head-m" << i+1 << "HD 0 mark" << i+1 << "HD 0 0 0 \n";
      }
      if (usedMarks[i][3]) {
         out << "%%deco head-m" << i+1 << "HDD 0 mark" << i+1 << "HDD 0 0 0 \n";
      }
      if (usedMarks[i][4]) {
         out << "%%deco head-m" << i+1 << "breve 0 mark" << i+1 << "breve 0 0 0 \n";
      }
      if (usedMarks[i][5]) {
         out << "%%deco head-m" << i+1 << "longa 0 mark" << i+1 << "longa 0 0 0 \n";
      }
      if (usedMarks[i][6]) {
         out << "%%deco head-m" << i+1 << "pshhd 0 mark" << i+1 << "pshhd 0 0 0 \n";
      }
      if (usedMarks[i][7]) {
         out << "%%deco head-m" << i+1 << "pflhd 0 mark" << i+1 << "pflhd 0 0 0 \n";
      }
      if (usedMarks[i][8]) {
         out << "%%deco head-m" << i+1 << "ghd 0 mark" << i+1 << "ghd 0 0 0 \n";
      }
   }

}



//////////////////////////////
//
// printVeritas -- print cksum data for input file
//

void printVeritas(ostream& out, HumdrumFile& infile) {
   unsigned long veritas = 0;
   int i;

   SSTREAM alllines;
   SSTREAM onlydata;

   Array<char> marker;
   marker[0] = EMPTY;
   marker.setSize(0);

   Array<char> contents;
   contents[0] = EMPTY;
   contents.setSize(0);

   for (i=0; i<infile.getNumLines(); i++) {
      if (infile[i].getType() == E_humrec_bibliography) {
         if (strncmp(infile[i][0], "!!!VTS:", 7) == 0) {
            if (infile[i].getFieldCount() > 1) {
               getBibPieces(infile[i][0], infile[i][1], marker, contents);
            } else {
               getBibPieces(infile[i][0], "", marker, contents);
            }
            sscanf(contents.getBase(), "%lu", &veritas);
            // don't store this line in the buffers since
            // it is not counted when calculating the veritas...
            continue;
         }
      }

      // printing with 0x0a which is the linux version
      // of newlines.  That is the only newline which
      // I am considering valid.  Apple will tend to
      // used 0x0d in text files, and Windows will use
      // both to make things difficult.  So in any case,
      // you should have converted the newlines in the
      // file to linux format before calculating your VTS
      // entry in the original file if you want hum2abc
      // to validate the file properly.
      alllines << infile[i] << char(0x0a);
      if (infile[i].getType() == E_humrec_data) {
         onlydata << infile[i] << char(0x0a);
      }

   }


   alllines << ends;
   onlydata << ends;

   int lenall = strlen(alllines.CSTRING);
   int lendata = strlen(onlydata.CSTRING);

   unsigned long crcall  = CheckSum::crc32(alllines.CSTRING, lenall);
   unsigned long crcdata = CheckSum::crc32(onlydata.CSTRING, lendata);

   if (veritas != 0) {
      if (veritas == crcall) {
         out << "%%humdrum-veritas " << veritas << "\n";
      } else {
         out << "%%humdrum-veritas " << crcall << "\n";
         out << "%%humdrum-veritas-invalid: " << veritas << "\n";
      }
   } else {
      out << "%%humdrum-veritas " << crcall << "\n";
   }
   out << "%%humdrum-veritas-data " << crcdata << "\n";

}



//////////////////////////////
//
// findMultipleRecords -- find multiple records with the same key
//

int findMultipleRecords(const char* key, Array<Array<char> >& values, 
      HumdrumFile& infile, Array<int>& bibfields) {

   values.setSize(bibfields.getSize());
   values.setSize(0);

   Array<char> marker;
   Array<char> tempv;

   tempv.setSize(1);
   tempv[0] = EMPTY;
   tempv.setSize(0);

   const char* ptr;
   int keylen = strlen(key);
   int i;
   for (i=0; i<bibfields.getSize(); i++) {
      ptr = &(infile[bibfields[i]][0][3]);
      if (strncmp(ptr, key, keylen) == 0) {
         if (infile[bibfields[i]].getFieldCount() > 1) {
            getBibPieces(infile[bibfields[i]][0], infile[bibfields[i]][1], 
               marker, tempv);
         } else {
            getBibPieces(infile[bibfields[i]][0], "", marker, tempv);
         }
	 if (tempv.getSize() > 1) {
            values.append(tempv);
            tempv.setSize(1);
            tempv[0] = EMPTY;
            tempv.setSize(0);
	 } else {
            // do nothing since the record is empty
         }
      }
   }
   return values.getSize();
}



//////////////////////////////
//
// findRecord --
//

int findRecord(const char* key, Array<char>& value, HumdrumFile& infile,
      Array<int>& bibfields) {

   Array<char> marker;
   value.setSize(1);
   value[0] = EMPTY;
   value.setSize(0);

   const char* ptr;
   int keylen = strlen(key);
   int i;
   for (i=0; i<bibfields.getSize(); i++) {
      ptr = &(infile[bibfields[i]][0][3]);
      if (strncmp(ptr, key, keylen) == 0) {
         if (infile[bibfields[i]].getFieldCount() > 1) {
            getBibPieces(infile[bibfields[i]][0], infile[bibfields[i]][1], 
                  marker, value);
         } else {
            getBibPieces(infile[bibfields[i]][0], "", marker, value);
         }
	 if (value.getSize() > 0) {
            return 1;
	 } else {
            return 0;
         }
      }
   }
   return 0;
}



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

void printKeySignature(ostream& out, HumdrumFile& infile) {
   int i;
   int keynum;
   PerlRegularExpression pre;
   for (i=0; i<infile.getNumLines(); i++) {
      if (infile[i].getType() == E_humrec_data) {
         break;
      }
      if (infile[i].getType() != E_humrec_interpretation) {
         continue;
      }
      if (keyQ) {
         if (pre.search(infile[i][0], "^\\*[A-G][-#]?:", "i")) {
            printAbcKeySignature(out, infile[i][0]);
            return;
         }
      } else {
         if (strncmp(infile[i][0], "*k[", 3) == 0) {
            keynum = Convert::kernKeyToNumber(infile[i][0]);      
            StartKey = keynum;
            printAbcKeySignature(out, keynum);
	    return;
         }
      }
   }

   // didn't find a key-signature, so use C major's key signature:
   out << "C";
}



//////////////////////////////
//
// printAbcKeySignature -- print key signature from key signature
//    Such as *k[f#] for G major or E minor.  Always will print 
//    as a major key.  In other words, the key will be incorrect
//    if the key is minor, but the printed key signature would
//    be correct.
//

void printAbcKeySignature(ostream& out, int keynum) {
   switch (keynum) {
      case  7:  out << "C#"; return;
      case  6:  out << "F#"; return;
      case  5:  out << "B";  return;
      case  4:  out << "E";  return;
      case  3:  out << "A";  return;
      case  2:  out << "D";  return;
      case  1:  out << "G";  return;
      case  0:  out << "C";  return;
      case -1:  out << "F";  return;
      case -2:  out << "Bb"; return;
      case -3:  out << "Eb"; return;
      case -4:  out << "Ab"; return;
      case -5:  out << "Db"; return;
      case -6:  out << "Gb"; return;
      case -7:  out << "Cb"; return;
   }
   return;
}


// print key signature from key field rather than key-signature field.

void printAbcKeySignature(ostream& out, const char* kernkey) {
   PerlRegularExpression pre;

   Array<char> tonic;
   tonic.setSize(1);
   tonic[0] = '\0';
   if (pre.search(kernkey, "^\\*([A-Ga-g][-#]*):")) {
      tonic.setSize(strlen(pre.getSubmatch(1))+1);
      strcpy(tonic.getBase(), pre.getSubmatch());
   } else {
      out << "C"; 
      return;
   }

   tonic[0] = toupper(tonic[0]);   // upper case for tonic
   pre.sar(tonic, "-", "b", "g");
   out << tonic.getBase();
   
   // print modality: add three-leter mode. 
   //  For major, mode is optional (but added here always).
   //  For minor, "m" or "min" can be used.
   //  For other modes, any unique combination and case
   //  with optional space can be used.  But only use first three
   //  letters in the style of major/minor modes:
   //  Cmaj = C major (0 sharp/flats)
   //  Cmin = C minor (3 flats)
   //  Cdor = C dorian (2 flats)
   //  Cphr = C phrygian (4 flats)
   //  Clyd = C lydian (1 sharp)
   //  Cmix = C mixolydian (1 flat)
   //  Caeo = C aeolian (3 flats) essentially the same as Cmin
   //  Cloc = C locrian (5 flats)

   if      (pre.search(kernkey, "loc", "i"))  { out << "loc"; }
   else if (pre.search(kernkey, "aeo", "i"))  { out << "aeo"; }
   else if (pre.search(kernkey, "mix", "i"))  { out << "mix"; }
   else if (pre.search(kernkey, "lyd", "i"))  { out << "lyd"; }
   else if (pre.search(kernkey, "phr", "i"))  { out << "phr"; }
   else if (pre.search(kernkey, "dor", "i"))  { out << "dor"; }
   else if (pre.search(kernkey, "[a-g]", "")) { out << "min"; }
   else if (pre.search(kernkey, "[A-G]", "")) { out << "maj"; }
   else  { out << "?"; }
   
}



//////////////////////////////
//
// printVoiceDeclaration -- Consider coordinating with printAbcClef().
//

void printVoiceDeclaration(ostream& out, VoiceMap vmap, int voicenum, 
      HumdrumFile& infile) {
 
   out << "V: " << voicenum;

   char clef[1024] = {0};

   int i, j;
   for (i=0; i<infile.getNumLines(); i++) {
      if (infile[i].getType() == E_humrec_data) {
         // stop processing when the data is found
         break;
      }
      if (infile[i].getType() == E_humrec_interpretation) {
         for (j=0; j<infile[i].getFieldCount(); j++) {
            if (vmap.primary != infile[i].getPrimaryTrack(j)) {
               continue;
            }
            if (strcmp(infile[i][j], "*clefG2") == 0) {
               strcpy(clef, "clef=treble");
            } else if (strcmp(infile[i][j], "*clefGv2") == 0) {
               strcpy(clef, "clef=treble-8");
               clefstates[infile[i].getPrimaryTrack(j)] = +40;
            } else if (strcmp(infile[i][j], "*clefF4") == 0) {
               strcpy(clef, "clef=bass");
            } else if (strcmp(infile[i][j], "*clefC3") == 0) {
               strcpy(clef, "clef=alto");
            } else if (strcmp(infile[i][j], "*clefC4") == 0) {
               strcpy(clef, "clef=tenor");
            }
         }
      }
   }

   // if there is no clef, probably check here for
   // what a good default clef would be: do a histogram
   // of all of the notes in the part (until an 
   // explicit clef is found)

   if (strcmp(clef, "") != 0) {
      out << " " << clef;
   }

   out << "\n";
}



//////////////////////////////
//
// createVoiceMap
//

void createVoiceMap(Array& voicemap, HumdrumFile& infile) {

   voicemap.setSize(200);
   voicemap.setGrowth(200);
   voicemap.setSize(0);

   int i, j;
   VoiceMap vtemp;
   

   for (i=0; i<infile.getNumLines(); i++) {
      if (infile[i].getType() != E_humrec_interpretation) {
         continue;
      }
      if (strncmp(infile[i][0], "**", 2) != 0) {
         cerr << "Error: expecting an exclusive interpretation line." << endl;
         cerr << "Line is instead: " << infile[i] << endl;
         exit(1);
      }

      for (j=infile[i].getFieldCount()-1; j>=0; j--) {
         if (infile[i].getExInterpNum(j) == E_KERN_EXINT) {
            vtemp.primary = j+1;             // primary tracks start at 1
            voicemap.append(vtemp);
         }
      }
      break;
   }

   for (i=0; i<voicemap.getSize(); i++) {
      voicemap[i].voicenumber = i+1;
   }
}



//////////////////////////////
//
// parseBibliographic -- read the bibliographic records
//     in the humdrum file and fill in any ABC header records.
// Mapping:
//    !!!COM --> C (composer)
//    !!!OTL --> T (title) (also XEN = Title Translated into English)
//       ??? --> A (Area)
//       ??? --> B (Book)
//       ??? --> D (Discography)
//       ??? --> F (Filename)
//       ??? --> G (Group)
//       ??? --> H (History)
//       ??? --> I (Information)
//     *?:   --> K ([initial] Key)
//       ??? --> N (Notes)
//     *M?/? --> M ([initial] Meter)
//       ??? --> O (Origin)
//       ??? --> R (Rhythm)
//       ??? --> S (Source)
//       ??? --> Z (Transcription notes)
// Notes:
//    Only the first !!OTL/XEN record in the file will be used.
//    Only the first !!COM record in the file will be used.
//    Only the last  !!OMD record in the file will be used.
//

void parseBibliographic(Array& header, HumdrumFile& infile) {
   int i;
   Array<char> marker;
   marker.setSize(100);
   marker.setGrowth(100);
   marker.setSize(0);
   Array<char> contents;
   contents.setSize(100);
   contents.setGrowth(100);
   contents.setSize(0);
   int length;
   char omdstring[1024] = {0};

   Array<Array<char> > markers;
   Array<Array<char> > contentses;

   prepareBibliographicData(markers, contentses, infile);

   for (i=0; i<markers.getSize(); i++) {
      marker = markers[i];
      contents = contentses[i];
      length = strlen(contents.getBase());
      if (length == 0) {
         continue;
      }

      if (strncmp(marker.getBase(), "OTL", 3) == 0) {
         //if (strcmp(header[TT], "") == 0) {
         if ((!options.getBoolean("T")) && (strcmp(header[TT], "") == 0)) {
            storeHeaderRecord(header, 'T', contents.getBase());
         }
      } else if (strcmp(marker.getBase(), "XEN") == 0) {
         //if (strcmp(header[TT], "") == 0) {
         if ((!options.getBoolean("T")) && (strcmp(header[TT], "") == 0)) {
            storeHeaderRecord(header, 'T', contents.getBase());
         }
      }

      if (strcmp(marker.getBase(), "COM") == 0) {
         //if (strcmp(header[CC], "") == 0) {
         if ((!options.getBoolean("C")) && (strcmp(header[CC], "") == 0)) {
            flipCommaParts(contents);
            storeHeaderRecord(header, 'C', contents.getBase());
         }
      }

      if (strcmp(marker.getBase(), "OMD") == 0) {
         strcpy(omdstring, contents.getBase());
      }

   }

   // also read the first meter sign in the file which
   // occurs before any musical data as well as the
   // key.
 
   char meterbuffer[1024] = {0};
   double tempo = -1;
   int j;
   int top, bot;
   for (i=0; i<infile.getNumLines(); i++) {
      if (infile[i].getType() == E_humrec_data) {
         break;
      }
      if (infile[i].getType() == E_humrec_interpretation) {
         for (j=0; j<infile[i].getFieldCount(); j++) {
            if (infile[i].getExInterpNum(j) == E_KERN_EXINT) {

               if (sscanf(infile[i][j], "*M%d/%d", &top, &bot) == 2) {
                  sprintf(meterbuffer, "%d/%d", top, bot);
                  storeHeaderRecord(header, 'M', meterbuffer);
                  break;  // don't get redundant info on same line
               }

               if (strcmp(infile[i][j], "*met(C)") == 0) {
                  storeHeaderRecord(header, 'M', "C");
                  top = bot = 4;
                  break;  // don't get redundant info on same line
               }
               if (strcmp(infile[i][j], "*met(c)") == 0) {
                  storeHeaderRecord(header, 'M', "C");
                  top = bot = 4;
                  break;  // don't get redundant info on same line
               }
               if (strcmp(infile[i][j], "*met(C|)") == 0) {
                  storeHeaderRecord(header, 'M', "C|");
                  top = bot = 2;
                  break;  // don't get redundant info on same line
               }
               if (strcmp(infile[i][j], "*met(c|)") == 0) {
                  storeHeaderRecord(header, 'M', "C|");
                  top = bot = 2;
                  break;  // don't get redundant info on same line
               }

               if (sscanf(infile[i][j], "*MM%lf", &tempo) == 1) {
                  break;  // don't get redundant info on same line
               }

            }
         }
      }
   }

   if ((strcmp(meterbuffer, "") == 0) && (strcmp(header[MM],"") == 0)) { 
      storeHeaderRecord(header, 'M', meterbuffer);
   }
   
   if (!options.getBoolean("Q")) {
      Array<char> QRecord;
      QRecord.setSize(0);
      if (strcmp(header[QQ], "") == 0) {
         if (((!notempoQ) && (tempo > 0)) || ((strcmp(omdstring, "") != 0))) {
            if (options.getBoolean("q")) {
               calculateQRecord(QRecord, -1, omdstring, top, bot);
            } else {
               calculateQRecord(QRecord, tempo, omdstring, top, bot);
            }
         }
         if (QRecord.getSize() > 0) {
            storeHeaderRecord(header, 'Q', QRecord.getBase());
         }
      }
   }
        
}



//////////////////////////////
//
// parseBibliographicData -- read in the bibliographic data from the
//      Humdrum file, and interpret any !!!title: record and place
//      its parsed contents into the !!!OTL: record.
//

void prepareBibliographicData(Array<Array<char> >& markers, 
      Array<Array<char> >& contentses, HumdrumFile& infile) {

   markers.setSize(infile.getNumLines());
   markers.setSize(0);

   contentses.setSize(infile.getNumLines());
   contentses.setSize(0);

   Array<char> marker;
   Array<char> contents;

   int length;
   int titleindex = -1;
   int OTLindex   = -1;

   int i;
   for (i=0; i<infile.getNumLines(); i++) {
      if (infile[i].getType() != E_humrec_bibliography) {
         continue;
      }
      if (infile[i].getFieldCount() > 1) {
         getBibPieces(infile[i][0], infile[i][1], marker, contents);
      } else {
         getBibPieces(infile[i][0], "", marker, contents);
      }

      length = strlen(contents.getBase());
      if (length == 0) {
         continue;
      }
      markers.append(marker);
      contentses.append(contents);
      if (strcmp(marker.getBase(), "title") == 0) {
         titleindex = markers.getSize()-1;
      } else if (strncmp(marker.getBase(), "OTL", 3) == 0) {
         OTLindex = markers.getSize()-1;
      } 
//     cout << "% REF = " << marker.getBase() 
//          << " : " << contents.getBase()
//          << endl;	        
   }


   if (titleexpansionQ) {
      length = strlen("title");
      marker.setSize(length+1);
      strcpy(marker.getBase(), "title");
      markers.append(marker);

      length = strlen(titleexpansion);
      contents.setSize(length+1);
      strcpy(contents.getBase(), titleexpansion);
      contentses.append(contents);
      
      titleindex = markers.getSize() - 1;
   }

   if (titleindex < 0) {
      return;
   }

   Array<char>& ct = contentses[titleindex];

   int keyq = 0;
   Array<char> key;
   key.setSize(100);
   key.setGrowth(100);
   key.setSize(0);

   Array<char> newtitle;
   newtitle.setSize(1000);
   newtitle.setGrowth(1000);
   newtitle.setSize(0);
   char ch;

   for (i=0; i<ct.getSize(); i++) {
      ch = ct[i];
      if ((keyq == 0) && (ct[i] != '@')) {
         newtitle.append(ch);
      } else if ((ct[i] == '@') && (i < ct.getSize()-1) && (ct[i+1] == '{')) {
         keyq = 1;
         i++;
      } else if (keyq && (ct[i] == '}')) {
         keyq = 0;
         ch = '\0';
         key.append(ch);
         addContentToString(newtitle, key, markers, contentses);
	 key.setSize(0);
      } else if (keyq) {
         key.append(ch);
      } else {
         ch = ct[i];
         newtitle.append(ch);
      }
   }

   ch = '\0';
   newtitle.append(ch);

   if (OTLindex < 0) {
      // create a new OTL record
      Array<char> key;
      key.setSize(4);
      strcpy(key.getBase(), "OTL");
      Array<char> con;
      con = newtitle;
      markers.append(key);
      contentses.append(con);
   } else {
      // replace the OTL record
      contentses[OTLindex] = newtitle;
   }
}



//////////////////////////////
//
// addContentToString --
//

void addContentToString(Array<char>& newtitle, Array<char>& key, 
      Array<Array<char> >& markers, Array<Array<char> >& contentses) {
   int i;
   int j;
   char ch;
   for (i=0; i<markers.getSize(); i++) {
      if (strcmp(markers[i].getBase(), key.getBase()) == 0) {
         for (j=0; j<contentses[i].getSize(); j++) {
            if (contentses[i][j] == '\0') {
               break;
            } else {
               ch = contentses[i][j];
               newtitle.append(ch);
            }
         }
         break;
      }
   }
}



//////////////////////////////
//
// flipCommaParts -- reverse the order of the strings
//    which has one comma in it.   Intended for reversing 
//    the composer's last and first names.
//

void flipCommaParts(Array& contents) {
   char* ptr1;  // the first occurence of a comma
   char* ptr2;  // the last occurence of a comma
   char* ptr3;  // the start of the string
   int length = contents.getSize();
   char buffer[length*2];
   int i;
   for (i=0; i<length; i++) {
      buffer[i] = EMPTY;
   }

   ptr1 = strchr(contents.getBase(), ',');
   ptr2 = strrchr(contents.getBase(), ',');
   ptr3 = contents.getBase();

   if (ptr1 == NULL) {
      return;  // there was no comma in the string.
   }

   if (ptr1 != ptr2) { 
      return;  // there were two commas in the string.
               // so return, since the correct comma
               // to flip cannot be decided.
   }


   i = 0;
   ptr1++;
   while ((*ptr1 != EMPTY) && isspace(*ptr1)) {
      ptr1++;
   }
   while ((i<length-1) && (*ptr1 != EMPTY)) {
      buffer[i++] = *ptr1++;
   }
   while (i>0) {
      if (isspace(buffer[i-1])) {
         i--;
      } else {
         break;
      }
   }
   buffer[i++] = ' ';
   while ((*ptr3 != EMPTY) && isspace(*ptr3)) {
      ptr3++;
   }
   while ((i<length-1) && (*ptr3 != ',') && (*ptr3 != EMPTY)) {
      buffer[i++] = *ptr3++;
   }
   while (i>0) {
      if (isspace(buffer[i-1])) {
         i--;
      } else {
         break;
      }
   }
  
   buffer[i] = EMPTY; 
   length = strlen(buffer);
   contents.setSize(length+1);
   strcpy(contents.getBase(), buffer);
}



///////////////////////////////
//
// calculateQRecord -- assemble the tempo and movement designation
// to be stored on a Q: line.  Traditional tempo markings are:
//      40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 63, 66, 69, 
//      72, 76, 80, 84, 88, 92, 96, 100, 104, 108, 112, 116, 120,
//      126, 132, 138, 144, 152, 160, 168, 176, 184, 192, 200, and 208.
//  Round to the nearest of these markings when converting to a
//  base duration other than quarter notes.  Probably should have a
//  user flag which prevents this rounding from occurring if an
//  explicit non-standard tempo marking is desired. (or allow 
//  decimal digits in the tempo as well?)
//

void calculateQRecord(Array<char>& QRecord, double tempo, 
      const char* omdstring, int top, int bot) {

   char ch;
   int i;
   char tempostring[1024] = {0};
   QRecord.setSize(1024);
   QRecord.setSize(0);
   char textbuffer[1024] = {0};
   int length;

   // probably check the omdstring for quotes and remove/handle them
   length = strlen(omdstring);
   if (strlen(omdstring) > 0) {
      strcpy(textbuffer, "\"");
      strcat(textbuffer, omdstring);
      strcat(textbuffer, "\"");
      translateSpecialCharacters(QRecord, textbuffer);
      QRecord.setSize(QRecord.getSize()-1); // remove string terminator
      if (tempo > 0) {
         ch = ' ';
         QRecord.append(ch);
      }
   }

   int temportop = 1;
   int temporbot = 4;

   double fraction = (double)bot / (double)temporbot;
   temporbot = bot;
   tempo = tempo * fraction;

   if (top % 3 == 0) {
      // check for compound tempo display
      if (tempo >= 180) {
         tempo = tempo / 3.0;
         temportop = temportop * 3;
      }
   } else {
      // duple type meters
      if (tempo < 40) {
         // don't print a ridiculously slow tempo
         // this is for a duple meter, also do for triple meters...
         tempo *= 2;
         temporbot *= 2;
      } else if (tempo > 208) {
         // don't print a ridiculously fast tempo
         // this is for a duple meter, also do for triple meters...
         tempo /= 2;
         temportop *= 2;
      }
	        
   }

   simplifyFraction(temportop, temporbot);

   int tempoint;
   tempoint = roundTempoToNearestStandard(tempo);
   // tempoint = int(tempo + 0.5);



   sprintf(tempostring, "%d/%d=%d", temportop, temporbot, tempoint);

   if ((!notempoQ) && (tempoint > 0)) {
      length = strlen(tempostring);
      for (i=0; i<length; i++) {
         ch = tempostring[i];
         QRecord.append(ch);
      }
   }
   
   ch = EMPTY;
   QRecord.append(ch);
}


//////////////////////////////
//
// roundTempoToNearestStandard -- round to one of the values:
//      40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 63, 66, 69, 
//      72, 76, 80, 84, 88, 92, 96, 100, 104, 108, 112, 116, 120,
//      126, 132, 138, 144, 152, 160, 168, 176, 184, 192, 200, and 208.
// (from the original Maezel Metronome device).
//


int roundTempoToNearestStandard(double tempo) {
   if (tempo < 39.0) {
      return int(tempo+0.5);
   }
   if (tempo > 209.0) {
      return int(tempo+0.5);
   }

   Array<int> standardtempos;
   standardtempos.setSize(100);

   int stm;  // a standard tempo mark
   standardtempos.setSize(0);
   stm = 40; standardtempos.append(stm);
   stm = 42; standardtempos.append(stm);
   stm = 44; standardtempos.append(stm);
   stm = 46; standardtempos.append(stm);
   stm = 48; standardtempos.append(stm);
   stm = 50; standardtempos.append(stm);
   stm = 52; standardtempos.append(stm);
   stm = 54; standardtempos.append(stm);
   stm = 56; standardtempos.append(stm);
   stm = 58; standardtempos.append(stm);
   stm = 60; standardtempos.append(stm);
   stm = 63; standardtempos.append(stm);
   stm = 66; standardtempos.append(stm);
   stm = 69; standardtempos.append(stm);
   stm = 72; standardtempos.append(stm);
   stm = 76; standardtempos.append(stm);
   stm = 80; standardtempos.append(stm);
   stm = 84; standardtempos.append(stm);
   stm = 88; standardtempos.append(stm);
   stm = 92; standardtempos.append(stm);
   stm = 96; standardtempos.append(stm);
   stm = 100; standardtempos.append(stm);
   stm = 104; standardtempos.append(stm);
   stm = 108; standardtempos.append(stm);
   stm = 112; standardtempos.append(stm);
   stm = 116; standardtempos.append(stm);
   stm = 120; standardtempos.append(stm);
   stm = 126; standardtempos.append(stm);
   stm = 132; standardtempos.append(stm);
   stm = 138; standardtempos.append(stm);
   stm = 144; standardtempos.append(stm);
   stm = 152; standardtempos.append(stm);
   stm = 160; standardtempos.append(stm);
   stm = 168; standardtempos.append(stm);
   stm = 176; standardtempos.append(stm);
   stm = 184; standardtempos.append(stm);
   stm = 192; standardtempos.append(stm);
   stm = 200; standardtempos.append(stm);
   stm = 208; standardtempos.append(stm);

   Array<int>& ST = standardtempos;

   double diff1;
   double diff2;

   int i;
   for (i=1; i<ST.getSize(); i++) {
      diff1 = ST[i] - tempo;
      if (diff1 < 0) {
         continue;
      }
      if (diff1 == 0.0) {
         return ST[i];
      }
      diff2 = tempo - ST[i-1];
      if (diff1 < diff2) {
         return ST[i];
      } else {
         return ST[i-1];
      }
   }

   return int(tempo+0.5);
}



//////////////////////////////
//
// translateSpecialCharacters -- convert HTML accents (or other
//     accented character formats) into ABC accents.
//
// "To typeset a macron on a letter x, type \=x. To typeset an ogonek,
// type \;x. To typeset a caron, type \vx. To typeset a breve, type \ux. To
// typeset a long Hungarian umlaut, type \:x. Finally, to typeset a dotted
// letter, type \.x.
// http://abc.sourceforge.net/standard/abc2-draft.html#Full%20table%20of%20accented%20letters
//

void translateSpecialCharacters(Array& output, const char* input) {
   int length = strlen(input);
   output.setSize(int(length * 1.5));
   output.setGrowth(output.getSize());
   output.setSize(0);
   char ch;

   int i;
   for (i=0; i<length; i++) {
      // if (input[i] == '&') { // now checking for many types of 
      // special characters... not just starting with &

      // lower-case grave accented vowels:
      if (strncmp(&(input[i]), "à", strlen("à")) == 0) {
         ch = '\\'; output.append(ch); ch = '`'; output.append(ch);
         ch = 'a'; output.append(ch);  i += strlen("à");
      } else if (strncmp(&(input[i]), "è", strlen("è")) == 0) {
         ch = '\\'; output.append(ch); ch = '`'; output.append(ch);
         ch = 'e'; output.append(ch);  i += strlen("è");
      } else if (strncmp(&(input[i]), "ì", strlen("ì")) == 0) {
         ch = '\\'; output.append(ch); ch = '`'; output.append(ch);
         ch = 'i'; output.append(ch);  i += strlen("ì");
      } else if (strncmp(&(input[i]), "ò", strlen("ò")) == 0) {
         ch = '\\'; output.append(ch); ch = '`'; output.append(ch);
         ch = 'o'; output.append(ch);  i += strlen("ò");
      } else if (strncmp(&(input[i]), "ù", strlen("ù")) == 0) {
         ch = '\\'; output.append(ch); ch = '`'; output.append(ch);
         ch = 'u'; output.append(ch);  i += strlen("ù");

      // upper-case grave accented vowels:
      } else if (strncmp(&(input[i]), "À", strlen("À")) == 0) {
         ch = '\\'; output.append(ch); ch = '`'; output.append(ch);
         ch = 'A'; output.append(ch);  i += strlen("À");
      } else if (strncmp(&(input[i]), "È", strlen("È")) == 0) {
         ch = '\\'; output.append(ch); ch = '`'; output.append(ch);
         ch = 'E'; output.append(ch);  i += strlen("È");
      } else if (strncmp(&(input[i]), "Ì", strlen("Ì")) == 0) {
         ch = '\\'; output.append(ch); ch = '`'; output.append(ch);
         ch = 'I'; output.append(ch);  i += strlen("Ì");
      } else if (strncmp(&(input[i]), "Ò", strlen("Ò")) == 0) {
         ch = '\\'; output.append(ch); ch = '`'; output.append(ch);
         ch = 'O'; output.append(ch);  i += strlen("Ò");
      } else if (strncmp(&(input[i]), "Ù", strlen("Ù")) == 0) {
         ch = '\\'; output.append(ch); ch = '`'; output.append(ch);
         ch = 'U'; output.append(ch);  i += strlen("Ù");

      // lower-case acute accented vowels:
      } else if (strncmp(&(input[i]), "á", strlen("á")) == 0) {
         ch = '\\'; output.append(ch); ch = '\''; output.append(ch);
         ch = 'a'; output.append(ch);  i += strlen("á");
      } else if (strncmp(&(input[i]), "é", strlen("é")) == 0) {
         ch = '\\'; output.append(ch); ch = '\''; output.append(ch);
         ch = 'e'; output.append(ch);  i += strlen("é");
      } else if (strncmp(&(input[i]), "í", strlen("í")) == 0) {
         ch = '\\'; output.append(ch); ch = '\''; output.append(ch);
         ch = 'i'; output.append(ch);  i += strlen("í");
      } else if (strncmp(&(input[i]), "ó", strlen("ó")) == 0) {
         ch = '\\'; output.append(ch); ch = '\''; output.append(ch);
         ch = 'o'; output.append(ch);  i += strlen("ó");
      } else if (strncmp(&(input[i]), "ú", strlen("ú")) == 0) {
         ch = '\\'; output.append(ch); ch = '\''; output.append(ch);
         ch = 'u'; output.append(ch);  i += strlen("ú");

      // upper-case acute accented vowels:
      } else if (strncmp(&(input[i]), "Á", strlen("Á")) == 0) {
         ch = '\\'; output.append(ch); ch = '\''; output.append(ch);
         ch = 'A'; output.append(ch);  i += strlen("Á");
      } else if (strncmp(&(input[i]), "É", strlen("É")) == 0) {
         ch = '\\'; output.append(ch); ch = '\''; output.append(ch);
         ch = 'E'; output.append(ch);  i += strlen("É");
      } else if (strncmp(&(input[i]), "Í", strlen("Í")) == 0) {
         ch = '\\'; output.append(ch); ch = '\''; output.append(ch);
         ch = 'I'; output.append(ch);  i += strlen("Í");
      } else if (strncmp(&(input[i]), "Ó", strlen("Ó")) == 0) {
         ch = '\\'; output.append(ch); ch = '\''; output.append(ch);
         ch = 'O'; output.append(ch);  i += strlen("Ó");
      } else if (strncmp(&(input[i]), "Ú", strlen("Ú")) == 0) {
         ch = '\\'; output.append(ch); ch = '\''; output.append(ch);
         ch = 'U'; output.append(ch);  i += strlen("Ú");

      // lower-case umlaut accented vowels:
      } else if (strncmp(&(input[i]), "ä", strlen("ä")) == 0) {
         ch = '\\'; output.append(ch); ch = '\"'; output.append(ch);
         ch = 'a'; output.append(ch);  i += strlen("ä");
      } else if (strncmp(&(input[i]), "ë", strlen("ë")) == 0) {
         ch = '\\'; output.append(ch); ch = '\"'; output.append(ch);
         ch = 'e'; output.append(ch);  i += strlen("ë");
      } else if (strncmp(&(input[i]), "ï", strlen("ï")) == 0) {
         ch = '\\'; output.append(ch); ch = '\"'; output.append(ch);
         ch = 'i'; output.append(ch);  i += strlen("ï");
      } else if (strncmp(&(input[i]), "ö", strlen("ö")) == 0) {
         ch = '\\'; output.append(ch); ch = '\"'; output.append(ch);
         ch = 'o'; output.append(ch);  i += strlen("ö");
      } else if (strncmp(&(input[i]), "ü", strlen("ü")) == 0) {
         ch = '\\'; output.append(ch); ch = '\"'; output.append(ch);
         ch = 'u'; output.append(ch);  i += strlen("ü");

      // upper-case umlaut accented vowels:
      } else if (strncmp(&(input[i]), "Ä", strlen("Ä")) == 0) {
         ch = '\\'; output.append(ch); ch = '\''; output.append(ch);
         ch = 'A'; output.append(ch);  i += strlen("Ä");
      } else if (strncmp(&(input[i]), "Ë", strlen("Ë")) == 0) {
         ch = '\\'; output.append(ch); ch = '\''; output.append(ch);
         ch = 'E'; output.append(ch);  i += strlen("Ë");
      } else if (strncmp(&(input[i]), "Ï", strlen("Ï")) == 0) {
         ch = '\\'; output.append(ch); ch = '\''; output.append(ch);
         ch = 'I'; output.append(ch);  i += strlen("Ï");
      } else if (strncmp(&(input[i]), "Ö", strlen("Ö")) == 0) {
         ch = '\\'; output.append(ch); ch = '\''; output.append(ch);
         ch = 'O'; output.append(ch);  i += strlen("Ö");
      } else if (strncmp(&(input[i]), "Ü", strlen("Ü")) == 0) {
         ch = '\\'; output.append(ch); ch = '\''; output.append(ch);
         ch = 'U'; output.append(ch);  i += strlen("Ü");
      
      // other types of accented characters ////////////////////////////

      // aring : a with a circle above it (for Nordic langauges):
      } else if (strncmp(&(input[i]), "å", strlen("å")) == 0) {
         ch = '\\'; output.append(ch); ch = 'a'; output.append(ch);
         ch = 'a'; output.append(ch);  i += strlen("å");
      } else if (strncmp(&(input[i]), "Å", strlen("Å")) == 0) {
         ch = '\\'; output.append(ch); ch = 'a'; output.append(ch);
         ch = 'a'; output.append(ch);  i += strlen("Å");


      // accidental symbols in titles ///////////////////////////////////
      // \b --> flat sign does not work...
      } else if (strncmp(&(input[i]), "\\n", strlen("\\n")) == 0) {
         ch = '\n'; output.append(ch); i += strlen("\\n");
      } else if (strncmp(&(input[i]), "-flat", strlen("-flat")) == 0) {
         ch = '\\'; output.append(ch); ch = 'b'; output.append(ch);
                                       i += strlen("-flat");
      // \# --> sharp sign in titles does work.
      } else if (strncmp(&(input[i]), "-sharp", strlen("-sharp")) == 0) {
         ch = '\\'; output.append(ch); ch = '#'; output.append(ch);
                                       i += strlen("-sharp");
      // Haven't tested if \= --> natural sign works.
      } else if (strncmp(&(input[i]), "-natural", strlen("-natural")) == 0) {
         ch = '\\'; output.append(ch); ch = '='; output.append(ch);
                                       i += strlen("-natural");


      // other accented characters
      } else if (strncmp(&(input[i]), "ç", strlen("ç")) == 0) {
         ch = '\\'; output.append(ch); ch = 'c'; output.append(ch);
         ch = 'c'; output.append(ch);  i += strlen("ç");
      } else if (strncmp(&(input[i]), "&Ccdeil;", strlen("&Ccdeil;")) == 0) {
         ch = '\\'; output.append(ch); ch = 'C'; output.append(ch);
         ch = 'C'; output.append(ch);  i += strlen("&Ccdeil;");
      } else if (strncmp(&(input[i]), "ß", strlen("ß")) == 0) {
         ch = '\\'; output.append(ch); ch = 's'; output.append(ch);
         ch = 's'; output.append(ch);  i += strlen("ß");


      }
   
      // other accented characters which map to non-number strings:
      /* Need to add these       * Ntilde = \~N, ntilde = \~n
       * oslash = \/o, Oslash = \/O
       * ocirc = \^o, Ocirc = \^O
       * aelig = \ae, AElig = \AE, OElig = \OE, oelig = \oelig
       * Also, others less common ones like thorn, Atilde, etc.
       * -sharp -> sharp sign, likewise for -flat and -natural
       */
         
      ch = input[i];
      output.append(ch);
   }

   ch = EMPTY;
   output.append(ch);
}



//////////////////////////////
//
// storeHeaderRecord --
//

typedef char const * charstring;

void storeHeaderRecord(Array<char*>& header, char recordletter, 
      const char* inputstring) {

   Array<char> parsedstring;
   translateSpecialCharacters(parsedstring, inputstring);

   int length = strlen(parsedstring.getBase());
   
   int index = recordletter - 'A';

   int startindex = 0;
   while (startindex<length) {
      if (isspace(parsedstring[startindex])) {
         startindex++;
      } else {
         break;
      }
   }

   int newlength = length - startindex;

   delete [] header[index];
   header[index] = new char[newlength+1];
   strcpy(header[index], parsedstring.getBase()+startindex);

   length = strlen(header[index]);
   int i;
   for (i=length-1; i>0; i--) {
      if (isspace(header[index][i])) {
         header[index][i] = EMPTY;
      } else {
         break;
      }
   }
}



//////////////////////////////
//
// getBibPieces --
//

void getBibPieces(const char* string, const char* string2, 
      Array<char>& marker, Array<char>& contents) {

   char bufftemp[strlen(string) + strlen(string2)];
   strcpy(bufftemp, string);
   strcat(bufftemp, string2);

   char ch;
   char empty = EMPTY;

   marker.setSize(1);
   contents.setSize(1);
   marker[0]   = EMPTY;
   contents[0] = EMPTY;
   marker.setSize(0);
   contents.setSize(0);

   int length = strlen(bufftemp);
   if (length < 4) {
      return;
   }
   
   int i = 3;
   while ((i<length) && (bufftemp[i] != '\0') && (bufftemp[i] != ':')) {
      ch = bufftemp[i];
      marker.append(ch);
      i++;
   }
   i++;
   marker.append(empty);

   while ((i<length) && (bufftemp[i] != '\0') && isspace(bufftemp[i])) {
      i++;
   }
   while ((i < length) && (bufftemp[i] != '\0')) {
      ch = bufftemp[i];
      contents.append(ch);
      i++;
   }

   
   for (i=contents.getSize()-1; i>0; i--) {
      if (isspace(contents[i])) {
         contents[i] = empty;
	 contents.setSize(i);
      } else if (contents[i] == 0x0d) {
         contents[i] = empty;
	 contents.setSize(i);
      } else if (contents[i] == 0x0a) {
         contents[i] = empty;
	 contents.setSize(i);
      } else if (contents[i] == EMPTY) {
         contents[i] = empty;
	 contents.setSize(i);
      } else {
         break;
      }
   }
   contents.append(empty);

   char buffer[1024];
   strcpy(buffer, contents.getBase());
   translateSpecialCharacters(contents, buffer);


}



//////////////////////////////
//
// calculateBestRhythmUnit --
//

void calculateBestRhythmUnit(HumdrumFile& infile, int& Ltop, int& Lbot) {
   int i, j;
   int tcount;
   char buffer[128] = {0};
   Array<int> rhythms;
   rhythms.setSize(17);
   rhythms.allowGrowth(0);
   rhythms.setAll(0);
   int rhythm;
   double duration;

   for (i=0; i<infile.getNumLines(); i++) {
      if (infile[i].getType() != E_humrec_data) {
         continue;
      }
      for (j=0; j<infile[i].getFieldCount(); j++) {
         if (infile[i].getExInterpNum(j) != E_KERN_EXINT) {
            continue;
         }
         if (strcmp(infile[i][j], ".") == 0) {
            continue;  // ignore null tokens
         }
         tcount = infile[i].getTokenCount(j);
         if (tcount > 1) {
            infile[i].getToken(buffer, j, 0);
	    if (norhythm(buffer)) {
               duration = 1.0;
            } else {
               duration = Convert::kernToDuration(buffer);
            }
         } else {
            if (norhythm(buffer)) {
               duration = 1.0;
            } else {
               duration = Convert::kernToDuration(infile[i][j]);
            }
         }
         Convert::durationToKernRhythm(buffer, duration);
         if (sscanf(buffer, "%d", &rhythm) == 1) {
            if (rhythm >= 16) {
               rhythm = 16;
            } else if (rhythm >= 8) {
               rhythm = 8;
            } else if (rhythm >= 4) {
               rhythm = 4;
            } else {
               rhythm = 0;
            }
            rhythms[rhythm] += tcount;
         }
      }
   }

   int maxx = 8;
   if (rhythms[4] > rhythms[8]) {
      maxx = 4;
   }
   if (rhythms[16] > rhythms[maxx]) {
      maxx = 16;
   }
   if ((maxx == 4) && (rhythms[4] < rhythms[8] + rhythms[16])) {
      maxx = 8;
   }

   Ltop = 1;
   Lbot = maxx;
}



//////////////////////////////
//
// base40ToAbcPitch -- convert a base40 pitch to ABC format
//    C=c4, c=c5, c'=c6, C,=c3, C,,=c2, c''=c7
// The pitch is really ambiguous, since in a bass clef, c can
//    be c3 rather than c5.  
//

char* base40ToAbcPitch(char* buffer, int base40) {

   if (base40 < 0) {
      strcpy(buffer, "z");
      return buffer;
   }
	  
   int octave = base40 / 40;
   int diatonic = Convert::base40ToDiatonic(base40) % 7;

   // int accidental = Convert::base40ToAccidental(
   // the accidental will not be printed with this
   // function, you will have to decide from the
   // musical context if the accidental should be
   // displayed or suppressed based on the key
   // signature;

   char dianame[2];
   strcpy(dianame, "x");

   switch (diatonic) {
      case 0:  strcpy(dianame, "c"); break;
      case 1:  strcpy(dianame, "d"); break;
      case 2:  strcpy(dianame, "e"); break;
      case 3:  strcpy(dianame, "f"); break;
      case 4:  strcpy(dianame, "g"); break;
      case 5:  strcpy(dianame, "a"); break;
      case 6:  strcpy(dianame, "b"); break;
   }
   if (octave < 5) {
      dianame[0] = toupper(dianame[0]);
   }
   
   strcpy(buffer, dianame);
 
   switch (octave) {
      case 6: strcat(buffer, "'");    break;
      case 7: strcat(buffer, "''");   break;
      case 8: strcat(buffer, "'''");  break;
      case 9: strcat(buffer, "''''"); break;
      case 3: strcat(buffer, ",");    break;
      case 2: strcat(buffer, ",,");   break;
      case 1: strcat(buffer, ",,,");  break;
      case 0: strcat(buffer, ",,,,"); break;
   }

   return buffer;
}



//////////////////////////////
//
// storeOptionSet --
//

void storeOptionSet(Options& opts) {
   opts.define("debug=b",    "Print extract debugging statements");
   opts.define("m|measures-per-line=i:1", "Number of measures to print on a text line");
   opts.define("header=s:", "Command string for printing headers on pages");
   opts.define("f|footer=s:", "Command string for printing footers on pages");
   opts.define("label=b",    "Explicitly write measure number for every bar");
   opts.define("dir|directory=s", "directory for reading files from");
   opts.define("mask=s:.krn",  "filemask for reading from a directory");
   opts.define("no-invisible=b", "Print all invisible items as visible");
   opts.define("no-veritas=b", "Don't calculate veritas data");
   opts.define("filenum=s:",   "Prepend a filenumber value infront of title");
   opts.define("filetitle=b",  "Print filename at start of title field");
   opts.define("TT=s:",        "Title expansion");
   opts.define("no-grace=b",   "Suppress gracespace redefinition");
   opts.define("no-tempo=b",   "Suppress tempo marking at start of music");
   opts.define("no-slur|no-slurs|noslur|noslurs|ns=b",   "Suppress displaying all slurs in music");
   opts.define("no-mark|no-marks|nomark|nomarks=b",    "Suppress marks");
   opts.define("nn|no-auto-natural=b",   "Suppress automatic naturals");
   opts.define("linebreak=b",   "Break lines at !linebreak tokens");
   opts.define("db|data-barnum=b",   "display all bar numbers in data");
   opts.define("cb|comment-barnum=b",   "display all bar numbers as comments");
   opts.define("k|key=b",   "use key designation as key signature");

   // abcm2ps parameter settings
   opts.define("box=b",           "Put boxes around measure numbers");
   opts.define("no-autoformat=b", "Linewrap music same as in text");
   opts.define("spacing=d:1.141", "Note spacing factor");
   opts.define("n|barnums=i:0",   "Measure numbering control");
   opts.define("s|scale=d:0.75",  "Set the music scaling factor");
   opts.define("landscape=b",     "Rotate the music 90 degrees");
   opts.define("p=s:",            "Parameter strings to echo into header");
   opts.define("no-indent=b",     "Not actually used directly");

   opts.define("A=s",   "A header record (Area -- origin inside of country)");
   opts.define("B=s",   "B header record (Book -- source publication)");
   opts.define("C=s",   "C header record (Composer)");
   opts.define("D=s",   "D header record (Discography)");
   opts.define("E=s",   "E header record");
   opts.define("F=s",   "F header record (Filename)");
   opts.define("G=s",   "G header record (Group -- instrumentation)");
   opts.define("H=s",   "H header record (History -- source notes)");
   opts.define("I=s",   "I header record (Information)");
   opts.define("J=s",   "J header record");
   opts.define("K=s",   "K header record (Key signature)");
   opts.define("L=s",   "L header record");
   opts.define("M=s",   "M header record (Meter)");
   opts.define("N=s",   "N header record (Notes)");
   opts.define("O=s",   "O header record (Origin -- country of origin)");
   opts.define("P=s",   "P header record");
   opts.define("Q=s",   "Q header record");
   opts.define("q=b",   "don't display tempo, only OMD record");
   opts.define("R=s",   "R header record (Rhythm)");
   opts.define("S=s",   "S header record (Source -- source of data)");
   opts.define("T=s",   "T header record (Title)");
   opts.define("U=s",   "U header record");
   opts.define("V=s",   "V header record");
   opts.define("W=s",   "W header record");
   opts.define("X=s",   "X header record (tune serial number)");
   opts.define("Y=s",   "Y header record");
   opts.define("Z=s",   "Z header record (Transcription note)");

   opts.define("author=b",  "author of program"); 
   opts.define("version=b", "compilation info");
   opts.define("example=b", "example usages");   
   opts.define("h|help=b",  "short description");

}



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

void checkOptions(Options& opts, int argc, char* argv[], int fcount,
      HumdrumFile& cinfile) {
   opts.reset();
   storeOptionSet(opts);
   opts.setOptions(argc, argv);
   opts.process();
   debugQ = opts.getBoolean("debug"); // needed before final processing
   int tdirectoryQ = opts.getBoolean("directory");

   HumdrumFile tempfile;
   if (opts.getArgumentCount() == 0) {
      if (!tdirectoryQ) {
         cinfile.read(std::cin);
         tempfile = cinfile;
      }
   } else {
      tempfile.read(opts.getArg(fcount));
   }

   int i;
   Array<char> marker;
   Array<char> contents;

   opts.reset();
   storeOptionSet(opts);

   opts.setOptions(1, argv); // store only the command name

   if (!tdirectoryQ) {
      for (i=0; i<tempfile.getNumLines(); i++) {
         if (tempfile[i].getType() == E_humrec_bibliography) {
            if (strncmp(tempfile[i][0], "!!!hum2abc:", strlen("!!!hum2abc:"))==0) {
               if (tempfile[i].getFieldCount() > 1) {	
                  getBibPieces(tempfile[i][0], tempfile[i][1], marker,  contents);
               } else {
                  getBibPieces(tempfile[i][0], "", marker,  contents);
               }
	        

               if (debugQ) {
                  cout << "%Command-line addition: " << marker.getBase()
                       << " :: " << contents.getBase() << endl;
               }
               opts.appendOptions(contents.getBase());
            }
         }
      }
   }
   opts.appendOptions(argc-1, argv+1); // exclude the command name
   opts.process();

   // handle basic options:
   if (opts.getBoolean("author")) {
      cout << "Written by Craig Stuart Sapp, "
           << "craig@ccrma.stanford.edu, October 2008" << endl;
      exit(0);
   } else if (opts.getBoolean("version")) {
      cout << argv[0] << ", version: 11 October 2008" << endl;
      cout << "compiled: " << __DATE__ << endl;
      cout << MUSEINFO_VERSION << endl;
      exit(0);
   } else if (opts.getBoolean("help")) {
      usage(opts.getCommand());
      exit(0);
   } else if (opts.getBoolean("example")) {
      example();
      exit(0);
   }
   
   // Array<char*> header;
   
   Header.setSize(26);
   for (i=0; i<Header.getSize(); i++) {
      Header[i] = new char[1];
      Header[i][0] = EMPTY;
   }

   // store any changes user input into the header records
   char optchar[2] = {0};
   int length;
   for (i=0; i<26; i++) {
      optchar[0] = char('A'+i);
      length = strlen(opts.getString(optchar));
      if (length > 0) {
         delete [] Header[i];
         Header[i] = new char[length+1];
      }
      storeHeaderRecord(Header, char('A'+i), opts.getString(optchar));
   }

   debugQ =  opts.getBoolean("debug");
   slurQ  = !opts.getBoolean("no-slur");
   linemeasure = opts.getInteger("measures-per-line");
   if (linemeasure < 0) {
      linemeasure = 1;
   }

   barnumberingstyle = opts.getInteger("barnums");
   if (strcmp(opts.getString("barnums"), "none") == 0) {
      barnumberingstyle = -1;
   }

   continueQ = !opts.getBoolean("no-autoformat");
   footerQ   = opts.getBoolean("footer");
   headerQ   = opts.getBoolean("header");
   if (footerQ) {
      footer = opts.getString("footer");
   }
   if (headerQ) {
      header = opts.getString("header");
   }

   labelQ     =  opts.getBoolean("label");
   graceQ     = !opts.getBoolean("no-grace");
   notempoQ   =  opts.getBoolean("no-tempo");
   markQ      = !opts.getBoolean("no-mark");
   veritasQ   = !opts.getBoolean("no-veritas");
   boxQ       =  opts.getBoolean("box");
   databarnumQ =  opts.getBoolean("data-barnum");
   commentbarnumQ = opts.getBoolean("comment-barnum");
   keyQ        = opts.getBoolean("key");

   if (strchr(opts.getString("barnums"), 'b') != NULL) {
      // the presence of a b after the measure number option
      // indicates that the user wants the bar numbers enclosed
      // in a box.
      boxQ = 1;
   }

   notespacingQ = opts.getBoolean("spacing");
   notespacing  = opts.getDouble("spacing");
   invisibleQ   = !opts.getBoolean("no-invisible");
   if (!invisibleQ) {
      invisiblerest = "z";
   }

   directoryQ      = opts.getBoolean("directory");
   directoryname   = opts.getString("directory");
   filemask        = opts.getString("mask");

   landscapeQ      = opts.getBoolean("landscape");
   musicscaleQ     = opts.getBoolean("scale");
   musicscale      = opts.getDouble("scale");
   parameterstring = opts.getString("p");
   if (strstr(parameterstring, "nostems") != NULL) {
      stemlessQ = 1;
   }
   filenumQ        = opts.getBoolean("filenum");
   filenumstring   = opts.getString("filenum");
   filenametitleQ  = opts.getBoolean("filetitle");
   titleexpansionQ = opts.getBoolean("TT");
   titleexpansion  = opts.getString("TT");
   nonaturalQ      = opts.getBoolean("no-auto-natural");

   linebreakQ      = opts.getBoolean("linebreak");
   if (linebreakQ) {
      continueQ = 0;      // don't allow abcm2ps to reformat the lines of music
      linemeasure = 10000;  // set measures per line very high
   }
          
}



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

void example(void) {


}



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

void usage(const char* command) {

}



// md5sum: 18b0ed82be549a7e5933841b5d64c7ab hum2abc.cpp [20130206]