//
// Programmer:    Craig Stuart Sapp <craig@ccrma.stanford.edu>
// Creation Date: Tue Mar 26 09:12:54 PST 2002
// Last Modified: Sat Mar 30 14:24:32 PST 2002
// Filename:      ...sig/examples/all/scorepitch.cpp
// Web Address:   http://sig.sapp.org/examples/museinfo/score/scorepitch.cpp
// Syntax:        C++; museinfo
//
// Description:   Extract pitch information from a SCORE data file.
//
// Todo:          generate thru directive, beam markings, stem directions
// Done:          metronome pitch, measure numbers, Slurs, ties
//                first & second endings, segmentation for thru command
//

#include "humdrum.h"
#include "ScorePageSimple.h"

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

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

// P1_Note 		= 1,
// P1_Rest			= 2,
// P1_Clef			= 3,
// P1_Line			= 4,
// P1_Slur			= 5,
// P1_Beam			= 6,
// P1_Trill		= 7,
// P1_Staff		= 8,
// P1_Symbol		= 9,
// P1_Number		= 10,
// P1_User			= 11,
// P1_Special		= 12,
// P1_BadLuck		= 13,
// P1_Barline		= 14,
// P1_ImportedEPSGraphic	= 15,
// P1_Text			= 16,
// P1_KeySignature		= 17,
// P1_MeterSignature	= 18



class Thing {
   public:
      int type;        // type of data: note, rest, barline, key, or meter
      int loc;         // staff number of location in score data
      int index;       // index number of item in staff
      int pitch;       // the pitch or negative if a rest
      double dur;      // duration;
      double ndur;     // duration of following measure of music;
      double nndur;    // duration of next following measure of measure;
      int clef;        // clef type
      int control;     // 1 = controlling barline
      int slstart;     // start of slur
      int slend;       // end of slur marker
      int slstart2;    // start of slur 2
      int slend2;      // end of slur marker 2
      int tiestart;    // start of tie marker
      int tiecont;     // continuation of tie
      int tieend;      // end of tie
      int measure;     // measure number, if a measure
      int mstyle;      // measure style, -1 = use P5 value
      int metronome;   // metronome marking on first note.
      int finalbar;    // 1 = final barline;
      double meterdur; // the proper duration of a measure of music
      int segment;     // thru section control marker
      int ending;      // ending marker for multiple endings
      int repeattype;  // 1 = forward repeat, 2 = end repeat, 3 = both
      double absbeat;  // absolute beat position in music (quarter note = beat)
      
      Thing(void) { clear(); }
      void clear(void) {
         type = 0; loc = -1; dur = 0.0; clef = 0; index = 0; finalbar = 0;
         slstart = 0; slend = 0; tiestart = 0; tiecont = 0; tieend = 0;
         meterdur = 0.0; control = 0; ndur = 0.0; nndur = 0.0; pitch = 0;
         measure = -1000; segment = 0; slstart2 = 0; slend2 = 0;
         metronome = 0; absbeat = 0.0; mstyle = -1; ending = 0;
         repeattype = 0;
      }
};


class char1024 { public: char c[1024]; char1024(void) { c[0] = '\0'; } };


class Extra {
   public:
      char     text[1024];
      double   duration;
      int      measure;
      double   measurebeat;
      int      precedence;
      int      mark;
    
      Extra(void) { clear(); }
      void clear(void) {
         text[0] = '\0'; duration = -1; measure = -1; measurebeat = 1.0;
         precedence = 'b'; mark = 0;
      }
};


class Thru {
   public: 
      int seg; 
      int ending;
   Thru(void) { clear(); }
   void clear(void) { seg = ending = 0; }
   int operator==(Thru& b) { return (seg == b.seg) && (ending == b.ending); }
};


// function declarations:
void  extractPitches(ScorePageSimple& score);
void  printKern(ostream& out, ScorePageSimple& score, Array<Thing>& things, 
                          Array<char1024>& header, Array<char1024>& trailer,
                          Array<Extra>& extras, Array<Thru>& thruinfo);
void  printClef(ostream& out, int clef);
void  applySlurs(ScorePageSimple& score, Array<Thing>& things);
void  findMetronome(ScorePageSimple& score, Array<Thing>& things);
void  assignMeasure(ScorePageSimple& score, Array<Thing>& things);
void  storeItem(ScoreRecord& record, int& clef, int& key, 
                          Array<Thing>& things, int location, int index);
void  cleanupThings(ScorePageSimple& score, Array<Thing>& things, 
                          Array<Extra>& extras, Array<Thru>& thruinfo);
void  getThings(ScorePageSimple& score, Array<Thing>& things);
void  printKey(ostream& out, int key);
int   findNearestNote(ScorePageSimple& score, float pos, int staff);
int   findIndex(Array<Thing>& things, int staff, int index);
void  checkForTie(Array<Thing>& things, int id, int start);
char* getTitle(char* buffer, ScorePageSimple& score);
void  getHeaderAndTrailer(const char* globalfilename, const char* localfilename,
                          Array<char1024>& header, Array<char1024>& trailer,
                          Array<Extra>& extras);
void  readExtraData(Extra& extra, const char* string);
void  printExtras(Array<Thing>& things, Array<Extra>& extras, int index,
                          ostream& out);
void  generateThruInfo(Array<Thing>& things, Array<Thru>& thruinfo);
int   findNearestMeasure(ScorePageSimple& score, float pos, int staff);
double   findDuration(Array<Thing>& things, int measure);
ostream& operator<<      (ostream& out, Thru& thru);
void     printNoRep(Array<Thru>& thruinfo, ostream& out);


// interface variables:
Options options;
int     verboseQ  = 0;    // Display debugging information
int     rhythmQ   = 1;    // used with the -r option
int     titleQ    = 0;    // used with the -t option
int     checksumQ = 0;    // used with the -c option


int main(int argc, char** argv) {
   options.define("v|verbose=b",      "print debugging information");
   options.define("t|title=b",        "extract title from SCORE data");
   options.define("c|checksum=b",     "Append !!!VTS record to end of output");
   options.define("g|global-ref=s",   "global reference record file");
   options.define("l|local-ref=s",    "local reference record file");
   options.define("R|no-rhythm=b",    "don't display rhythm information");
   options.process(argc, argv);
   checksumQ =  options.getBoolean("checksum");
   verboseQ  =  options.getBoolean("verbose");
   rhythmQ   = !options.getBoolean("no-rhythm");
   titleQ    =  options.getBoolean("title");
   const char* globalfilename = "";
   const char* localfilename  = "";
   if (options.getBoolean("global-ref")) {
      globalfilename = options.getString("global-ref");
   }
   if (options.getBoolean("local-ref")) {
      localfilename = options.getString("local-ref");
   }
   Array<char1024> header;    // header bibliographic records
   Array<char1024> trailer;   // trailer bibliographic records
   Array<Extra> extras;       // extra comments to interleave with data
   extras.setSize(100);
   extras.allowGrowth(100);
   extras.setSize(0);
   header.setSize(0);
   trailer.setSize(0);
   getHeaderAndTrailer(globalfilename, localfilename, header, trailer, extras);

   if (options.getArgCount() == 0) {
      cout << "Usage: " << argv[0] << " input.mus " << endl;
      exit(1);
   }

   ScorePageSimple score;
   Array<Thing> things;
   Array<Thru> thruinfo;
   int i;
   for (i=1; i<=options.getArgCount(); i++) {
      score.clear();
      score.readFile(options.getArg(i), verboseQ); 
      getThings(score, things);
      assignMeasure(score, things);
      applySlurs(score, things);
      findMetronome(score, things);
      cleanupThings(score, things, extras, thruinfo);

      printKern(cout, score, things, header, trailer, extras, thruinfo);
#ifndef VISUAL
      if (checksumQ) {
         fstream outfile("/tmp/scrmono2hum-temp", ios::out);
         printKern(outfile, score, things, header, trailer, extras, thruinfo);
         outfile.close();
         system("chmod 0666 /tmp/scrmono2hum-temp");
         system("cksum /tmp/scrmono2hum-temp | sed 's/ .*//' > /tmp/scrmono2hum-temp2");
         system("chmod 0666 /tmp/scrmono2hum-temp2");
         #ifndef OLDCPP
            fstream infile("/tmp/scrmono2hum-temp2", ios::in);
         #else
            fstream infile("/tmp/scrmono2hum-temp2", ios::in | ios::nocreate);
         #endif
         unsigned long value;
         infile >> value; 
         cout << "!!!VTS: " << value << endl;
         infile.close();
         system("rm /tmp/scrmono2hum-temp2");
         system("rm /tmp/scrmono2hum-temp");
      }
#endif /* VISUAL */
   }

   cout << flush;
   return 0;
}


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



//////////////////////////////
//
// findMetronome -- look for metronome markings and apply to 
//   things.
//

void findMetronome(ScorePageSimple& score, Array& things) {
   int i, j;
   int tempo = 0;
   int sindex;
   int tindex;
   const char* pos;
   for (i=0; i<=score.getMaxStaff(); i++) {
      for (j=0; j<score.getStaffSize(i); j++) {
         tempo = 0;
         if (score.getStaff(i, j).isTextItem()) {
            if ((pos = strstr(score.getStaff(i, j).getTextData(), "[ = "))) {
               sscanf(pos, "[ = %d", &tempo);
            } else if ((pos = strstr(score.getStaff(i, j).getTextData(), "[= "))) {
               sscanf(pos, "[= %d", &tempo);
            } else if ((pos = strstr(score.getStaff(i, j).getTextData(), "[ ="))) {
               sscanf(pos, "[ =%d", &tempo);
            } else if ((pos = strstr(score.getStaff(i, j).getTextData(), "[="))) {
               sscanf(pos, "[=%d", &tempo);
            }
            if (tempo > 0) {
               sindex = findNearestNote(score, score.getStaff(i, j).getPValue(3), i);
               tindex = findIndex(things, i, sindex);
               if (tindex >= 0) {
                  things[tindex].metronome = tempo;
               }
            }
         } 
      }
   }


}



//////////////////////////////
//
// cleanupThings -- do finalization of items before printing in **kern format.
//

void cleanupThings(ScorePageSimple& score, Array<Thing>& things, 
      Array<Extra>& extras, Array<Thru>& thruinfo) {
   int i;

   // mark start of verse endings as segment
   for (i=0; i<things.getSize(); i++) {
      if (things[i].type == P1_Barline && things[i].ending != 0) {
         things[i].segment = 1;
      }
   }

   // check for segment indicators
   int segfound = 0;
   for (i=0; i<things.getSize(); i++) {
      if (things[i].segment) {
         segfound = 1;
         break;
      }
   }

   // add a starting segment marker if there is a segment
   // marker present and no segment marker already before the
   // first note
   if (segfound) {
      for (i=0; i<things.getSize(); i++) {
         if (things[i].type == P1_Note || things[i].type == P1_Rest) {
            things[i].segment = 1;
            break;
         } else if (things[i].segment) {
            // already a segment indicate before the first note.
            // so don't put in another one.
            break;
         }
      }
   }

   // adjust segment numbers
   int segment = 0;
   if (segfound) {
      for (i=0; i<things.getSize(); i++) {
         if (things[i].ending) {
            things[i].segment = segment;
         } else if (things[i].segment && !things[i].finalbar) {
            things[i].segment = ++segment;
         }
      }
   }

   // generate segment traversal array
   if (segfound) {
      generateThruInfo(things, thruinfo);
   } else {
      thruinfo.setSize(0);
   }

   // calculate absbeat values
   double absbeat = 0.0;
   for (i=0; i<things.getSize(); i++) {
      things[i].absbeat = absbeat;
      absbeat += things[i].dur;
   }

   // convert extra things with beats greater than 1.0 to
   // duration extras
   for (i=0; i<extras.getSize(); i++) {
      if (extras[i].measure > 0 && extras[i].measurebeat > 1.0) {
         extras[i].duration = findDuration(things, extras[i].measure) +
               extras[i].measurebeat + 2.0;
         if (extras[i].duration >= 0) {
            extras[i].measure = -1;
            extras[i].measurebeat = 1.0;
         }
      }
   }

   // remove consecutive barline markers due to repeats
   // at the starts of lines.
   for (i=0; i<things.getSize(); i++) {
      if (i > 0 && things[i].type == P1_Barline && 
          things[i-1].type == P1_Barline) {
         things[i-1].mstyle = (int)score.getStaff(things[i].loc,
               things[i].index).getPValue(5);
         if (things[i-1].measure < 0) {
            things[i-1].measure = things[i].measure;
         }
         things[i].type = -P1_Barline;
      } else if (i > 1 && things[i].type == P1_Barline &&
          things[i-1].type == P1_MeterSignature &&
          things[i-2].type == P1_Barline) {
         // catch cases where there is a new time signature
         // just before the repeat sign at the start of a line
         things[i-2].mstyle = (int)score.getStaff(things[i].loc,
               things[i].index).getPValue(5);
         if (things[i-2].measure < 0) {
            things[i-2].measure = things[i].measure;
         }
         things[i].type = -P1_Barline;
      }
   }

}



//////////////////////////////
//
// assignMeasures -- place measure numbers.
//

void assignMeasure(ScorePageSimple& score, Array& things) {
   int i;

   // identify the final barline of the piece
   for (i=things.getSize()-1; i>=0; i--) {
      if (things[i].type == P1_Barline) {
         things[i].finalbar = 1;
         break;
      }
   }

   // measure the duration of each measure:
   double duration = 0.0;
   double meterdur = 0.0;
   int top;
   int bottom;
   for (i=0; i<things.getSize(); i++) {
      switch (things[i].type) {
         case P1_Note:
         case P1_Rest:
            duration += things[i].dur;
            break;
         case P1_Barline:
            things[i].dur = duration;
            things[i].meterdur = meterdur;
            duration = 0.0;
            break;
         case P1_MeterSignature:
            // composite time signature are not yet handled.
            top = (int)score.getStaff(things[i].loc, 
                  things[i].index).getPValue(5);
            bottom = (int)score.getStaff(things[i].loc, 
                  things[i].index).getPValue(6);
            if (top == 0) {
               top = bottom;
               bottom = 4;
            }
            meterdur = top * 4.0 / bottom;
            break;
      }
   }


   // mark controlling measures
   double olddur = 0.0;
   double oldolddur = 0.0;
   int control = 0;
   for (i=things.getSize()-1; i>=0; i--) {
      if (things[i].type == P1_Barline) {
         if (fabs(things[i].meterdur - things[i].dur) < 0.01) {
            things[i].control = control;
            control = 1;
         } else {
            things[i].control = control;
            control = 0;
         }
         things[i].ndur  = olddur;
         things[i].nndur = oldolddur;
         oldolddur       = olddur;
         olddur          = things[i].dur;
      }
   }


   // the penultimate measure is controlling even if 
   // the last measure does not have a complete duration
   int count = 0;
   for (i=things.getSize()-1; i>=0; i--) {
      if (things[i].type == P1_Barline) {
         count++;
         if (count == 2) {
            things[i].control = 1;
            break;
         }
      }
   }


   // shift meter durations back one measure
   double lastmeterdur = -1.0;
   double tempmeterdur = 0.0;
   for (i=things.getSize()-1; i>=0; i--) {
      if (things[i].type == P1_Barline) {
         if (lastmeterdur < 0.0) {
            lastmeterdur = things[i].meterdur;
         }
         tempmeterdur = things[i].meterdur;
         things[i].meterdur = lastmeterdur;
         lastmeterdur = tempmeterdur;
      } 
   }


   // look for non-controlling barlines.
   int setcontrol = 0;
   int marker = 0;
   for (i=0; i<things.getSize(); i++) {
      if (things[i].type == P1_Barline) {
         if (setcontrol == 1) {
            marker = 1;
            setcontrol = 0;
         }
         if (things[i].control == 0) {
            if (fabs(things[i].ndur + things[i].nndur - things[i].meterdur) < 
                  0.01) {
               things[i].control = 1;
               setcontrol = 1;
            }
         }
         if (marker) {
            things[i].control = -1;
            marker = 0;
         }
      }
   }


   // determine if the first measure is a pick-up measure or a
   // complete measure.
   int pickupQ = 1;
   for (i=0; i<things.getSize(); i++) {
      if (things[i].type == P1_Barline) {
         if (things[i].dur != 0.0 && 
               fabs(things[i].dur - things[i].meterdur) < 0.01) {
            pickupQ = 0; 
            break;
         } else if (things[i].dur != 0.0) {
            pickupQ = 1;
            break;
         } else {
            pickupQ = 1;
            break;
         }
      }
   }


   // now ready to number the measures
   int measurenum = 2;
   if (pickupQ) {
      measurenum = 1;
   }
   for (i=0; i<things.getSize(); i++) {
      if (things[i].type == P1_Barline) {
         if (things[i].control == 1) {
            things[i].measure = measurenum++;
         }
      }
   }


}



//////////////////////////////
//
// applySlurs -- associate slurs and ties with the correct notes.
//   elided slurs are not handled although id mechanism is in place
//   to be able to do so.
//

void applySlurs(ScorePageSimple& score, Array& things) {
   int i;
   int staff;
   float startpos;
   float endpos;
   float pos;
   int nearstart;
   int nearend;
   int slurid = 1;
   int index;

   for (i=0; i<score.getSize(); i++) {
      if ((score[i].getPValue(1) == P1_Slur) && (score[i].getPValue(8) < 0)) {
         // if a real slur
         staff     = (int)score[i].getPValue(2);
         startpos  = score[i].getPValue(3);
         endpos    = score[i].getPValue(6);
         nearstart = findNearestNote(score, startpos, staff);
         nearend   = findNearestNote(score, endpos,   staff);
         if (nearstart < 0 || nearend < 0) {
            continue;
         }
         if (nearstart == nearend) {
            // choose the end of the slur which is closest to the note
            pos = score.getStaff(staff, nearstart).getPValue(3);
            if (fabs(pos - startpos) <= fabs(pos - endpos)) {
               index = findIndex(things, staff, nearstart);
               if (things[index].slstart) {
                  things[index].slstart2 = slurid;
               } else {
                  things[index].slstart = slurid;
               }
            } else {
               index = findIndex(things, staff, nearend);
               if (things[index].slend) {
                  things[index].slend2 = slurid;
               } else {
                  things[index].slend = slurid;
               }
            }
            slurid++;
         } else {
            index = findIndex(things, staff, nearstart);
            if (things[index].slstart) {
               things[index].slstart2 = slurid;
            } else {
               things[index].slstart = slurid;
            }
            index = findIndex(things, staff, nearend);
            if (things[index].slend) {
               things[index].slend2 = slurid;
            } else {
               things[index].slend = slurid;
            }
            slurid++;
         }
      } else if ((score[i].getPValue(1) == P1_Slur) && 
              (score[i].getPValue(8) > 0)       &&
              (score[i].getPValue(9) >= 0)      ) {
         // a verse ending marker
         int endingstart = findNearestMeasure(score, score[i].getPValue(3),
            (int)score[i].getPValue(2));
         index = findIndex(things, (int)score[i].getPValue(2), endingstart);
         if (score[i].getPValue(9) == 0) {
            things[index].ending = 1;
         } else {
            things[index].ending = (int)score[i].getPValue(9);
         }
      }
   }


   // identify slurs which are actually ties
   for (i=0; i<things.getSize(); i++) {
      if (things[i].type == P1_Note || things[i].type == P1_Rest) {
         if (things[i].slstart != 0) {
            checkForTie(things, things[i].slstart, i);
         }
         if (things[i].slstart2 != 0) {
            checkForTie(things, things[i].slstart2, i);
         }
      }
   }


   // collapse tie start/stops
   for (i=0; i<things.getSize(); i++) {
      if (things[i].tiestart != 0 && things[i].tieend != 0) {
         things[i].tiecont  = 1;
         things[i].tiestart = 0;
         things[i].tieend   = 0;
      }
   }

   // adjust slur set2s into slur set1s for printing
   for (i=0; i<things.getSize(); i++) {
      if (things[i].slstart2 != 0 && things[i].slstart == 0) {
         things[i].slstart = things[i].slstart2;
         things[i].slstart2 = 0;
      }
      if (things[i].slend2 != 0 && things[i].slend == 0) {
         things[i].slend = things[i].slend2;
         things[i].slend2 = 0;
      }

   }


}


//////////////////////////////
//
// checkForTie -- look to see if slur is actually a tie and fix it 
//   if necessary.
//

void checkForTie(Array& things, int id, int start) {
   int intervene = 0;
   int tieQ = 0;
   int i;
   for (i=start+1; i<things.getSize(); i++) {
      if (things[i].type == P1_Note || things[i].type == P1_Rest) {
         intervene++;
         if (intervene != 1) {
            break;
         }
         if (things[i].pitch != things[start].pitch) {
            break;
         }

         if (things[i].slend == id || things[i].slend == id - 1) {
            things[i].tieend = id;
            things[i].slend  = 0;
            tieQ = 1;
            break;
         } else if (things[i].slend2 == id || things[i].slend2 == id - 1) {
            things[i].tieend = id;
            things[i].slend2 = 0;
            tieQ = 1;
            break;
         } 
      }
   }

   if (tieQ) {
      if (things[start].slstart == id) {
         things[start].tiestart = id;
         things[start].slstart  = 0;
      } else if (things[start].slstart2 == id) {
         things[start].tiestart = id;
         things[start].slstart2 = 0;
      }
   }

}



//////////////////////////////
//
// findIndex -- find the item with the specified position in the
//    score.  Returns -1 if not found in list.
//

int findIndex(Array& things, int staff, int index) {
   int i;
   int output = -1;
   for (i=0; i<things.getSize(); i++) {
      if (things[i].loc == staff && things[i].index == index) {
         output = i;
         return output;
      }
   }

   return output;
}



//////////////////////////////
//
// findNearestNote -- return the index on the staff for the nearest
//     note OR rest.
//

int findNearestNote(ScorePageSimple& score, float pos, int staff) {
   int i;
   int bestindex = -1;
   float bestdistance = 1000.0;
   float testdistance;
   for (i=0; i<score.getStaffSize(staff); i++) {
      if (score.getStaff(staff, i).hasDurationQ()) {
         testdistance = fabs(score.getStaff(staff, i).getPValue(3) - pos);
         if (testdistance < bestdistance) {
            bestdistance = testdistance;
            bestindex = i;
         }
      }
   }

   return bestindex;
}



//////////////////////////////
//
// findNearestMeasure -- return the index on the staff for the nearest
//     note OR rest.
//

int findNearestMeasure(ScorePageSimple& score, float pos, int staff) {
   int i;
   int bestindex = -1;
   float bestdistance = 1000.0;
   float testdistance;
   for (i=0; i<score.getStaffSize(staff); i++) {
      if (score.getStaff(staff, i).isBarlineItem()) {
         testdistance = fabs(score.getStaff(staff, i).getPValue(3) - pos);
         if (testdistance < bestdistance) {
            bestdistance = testdistance;
            bestindex = i;
         }
      }
   }

   return bestindex;
}



//////////////////////////////
//
// printKern -- print the things in the Thing array in **kern format.
//

void printKern(ostream& out, ScorePageSimple& score, Array<Thing>& things, 
      Array<char1024>& header, Array<char1024>& trailer, 
      Array<Extra>& extras, Array<Thru>& thruinfo) {
   int i, j;
   char pbuffer[128] = {0};
   char dbuffer[128] = {0};

   // clear the already-printed marker
   for (i=0; i<extras.getSize(); i++) {
      extras[i].mark = 0;
   }

   // print all of the bibliographic header
   for (j=0; j<header.getSize(); j++) {
      out << header[j].c << "\n";
   }

   char buffer[1024] = {0};
   if (titleQ) {
      getTitle(buffer, score);
      if (strlen(buffer) > 0) {
         out << "!!!OTL: " << buffer << endl;
      }
   }

   int thruQ = 0;
   out << "**kern\n";
   int bartype = 0;
   for (i=0; i<things.getSize(); i++) {
      if (things[i].segment > 0 && things[i].segment <= 26 &&
          !things[i].finalbar) {
         if (thruQ == 0 && thruinfo.getSize()) {
            thruQ = 1;
            out << "*>[";
            for (j=0; j<thruinfo.getSize(); j++) {
               out << thruinfo[j];
               if (j < thruinfo.getSize()-1) {
                  out << ",";
               }
            }
            out << "]\n";
            printNoRep(thruinfo, out);
         }
         out << "*>" << (char)('A' + things[i].segment - 1);
         if (things[i].ending) {
            out << things[i].ending;
         }
         out << "\n";
      }

      printExtras(things, extras, i, out);

      switch (things[i].type) {

      case P1_Note:
      case P1_Rest:
         if (things[i].metronome > 0) {
            out << "*MM" << things[i].metronome << "\n";
         }
         if (things[i].slstart) {
            out << "(";
         }
         if (things[i].tiestart) {
            out << "[";
         }
         out << Convert::durationToKernRhythm(dbuffer, things[i].dur);
         if (things[i].type == P1_Note) {
            out << Convert::base40ToKern(pbuffer, 
                  score.getStaff(things[i].loc, things[i].index).getPitch());
         } else {
            out << "r";
         }
         if (things[i].tiecont) {
            out << "_";
         } else if (things[i].tieend) {
            out << "]";
         }
         if (things[i].slend) {
            out << ")";
         }
         out << "\n";
         break;

      case P1_Barline:
         out << "=";
         if (things[i].finalbar) {
            out << "=";
         } else if ((int)score.getStaff(things[i].loc,
               things[i].index).getPValue(5) == 2) {
            out << "=";
            things[i].finalbar = 1;
         }
         if (things[i].measure != -1000 && things[i].finalbar != 1) {
            out << things[i].measure;
         }
         if (things[i].mstyle >= 0) {
            bartype = things[i].mstyle;
         } else {
            bartype = (int)score.getStaff(things[i].loc, 
                  things[i].index).getPValue(5);
         }
         switch (bartype) {
            case 1:    out << "||"; break;
            // case 2: out << "="; break;
            case 3:    out << ":|!"; break;
            case 4:    out << "!|:"; break;
            case 5:    out << ":|!|:"; break;
            case 6:    out << ":!!:"; break;
         }
         if (verboseQ) {
            out << "(" << things[i].dur << ":" << things[i].ndur 
                                         << ":" << things[i].nndur << ")";
            out << "[" << things[i].meterdur << "]";
            out << "{" << things[i].control << "}";
         }
         out << "\n";

         // print which ending is starting, if applicable
         if (things[i].ending != 0) {
            cout << "!! ending " << things[i].ending << "\n";
         }
        
         break;
      case P1_MeterSignature:
         out << "*M" 
              << (int)score.getStaff(things[i].loc,things[i].index).getPValue(5)
              << "/" 
              <<(int)score.getStaff(things[i].loc,things[i].index).getPValue(6);
         out << "\n";
         break;
      case P1_KeySignature:
         printKey(out, (int)score.getStaff(things[i].loc,things[i].index).getPValue(5));
         out << "\n";
         break;
      case P1_Clef:
         printClef(out, things[i].clef);
         out << "\n";
         break;

      }
   }
   out << "*-" << endl;
   for (j=0; j<trailer.getSize(); j++) {
      out << trailer[j].c << "\n";
   }
}



//////////////////////////////
//
// getThings --
//

void getThings(ScorePageSimple& score, Array& things) {
   things.setSize(score.getSize());
   things.setSize(0);
   things.allowGrowth(1);

   score.analyzePitch();
   int i, j;
   int clef = -1000;
   int key  = -1000;
   for (i=score.getMaxStaff(); i>=0; i--) {
      if (verboseQ) {
         cout << "Staff: " << i << "\titems: " << score.getStaffSize(i) << endl;
      }
      for (j=0; j<score.getStaffSize(i); j++) {
         storeItem(score.getStaff(i, j), clef, key, things, i, j);
      }
   }

}



//////////////////////////////
//
// storeItem -- store the SCORE item for later processing.
//

void storeItem(ScoreRecord& record, int& clef, int& key, 
      Array<Thing>& things, int staffno, int index) {
   Thing tempthing;
   int tempkey;
   int tempclef;
   int bartype;

   switch ((int)record.getPValue(1)) {
      case P1_Note:
         tempthing.type = P1_Note;
         tempthing.loc = staffno;
         tempthing.index = index;
         tempthing.pitch = record.getPitch();
         if (record.getPValue(7) == 0.0 || record.getPValue(7) > 64.0) {
            tempthing.dur = 0.0;
         } else {
            tempthing.dur = record.getPValue(7);
         }
         things.append(tempthing);
         break;
      case P1_Rest:
         tempthing.type = P1_Rest;
         tempthing.dur = record.getPValue(7);
         tempthing.loc = staffno;
         tempthing.index = index;
         tempthing.pitch = -1000;
         things.append(tempthing);
         break;
      case P1_Barline:
         tempthing.type = P1_Barline;
         tempthing.loc = staffno;
         tempthing.index = index;
         tempthing.dur = 0.0;
         bartype = (int)record.getPValue(5);
         if (bartype >= 3 && bartype <= 6) {
            tempthing.segment = 1;
            switch (bartype) {
               case 3:
                  tempthing.repeattype = 2;
                  break;
               case 4:
                  tempthing.repeattype = 1;
                  break;
               case 5: case 6:
                  tempthing.repeattype = 3;
                  break;
            }
         }
         things.append(tempthing);
         break;
      case P1_MeterSignature:
         tempthing.type = P1_MeterSignature;
         tempthing.loc = staffno;
         tempthing.index = index;
         tempthing.dur = 0.0;
         things.append(tempthing);
         break;
      case P1_KeySignature:
         tempkey = (int)record.getPValue(5);
         if (key < -10 || key != tempkey) {
            key = tempkey;
            tempthing.type = P1_KeySignature;
            tempthing.dur = 0.0;
            tempthing.loc = staffno;
            tempthing.index = index;
            things.append(tempthing);
         }
         break;
      case P1_Clef:
         tempclef = (int)record.getPValue(5);
         if (clef < -10 || clef != tempclef) {
            clef = tempclef;
            tempthing.type  = P1_Clef;
            tempthing.clef  = clef;
            tempthing.dur   = 0.0;
            tempthing.loc   = staffno;
            tempthing.index = index;
            things.append(tempthing);
         }

   }
}



//////////////////////////////
//
// printClef -- print the **kern clef token.
//   Ignoring Soprano, Baritone, and Mezzo-soprano clefs for now.
//

void printClef(ostream& out, int clef) {
   switch (clef) {
      case  0:  out << "*clefG2";    break;   // treble
      case  1:  out << "*clefF4";    break;   // bass
      case  2:  out << "*clefC3";    break;   // alto
      case  3:  out << "*clefC4";    break;   // tenor
      case  4:  out << "*clefX";     break;   // percussion
   }
}



//////////////////////////////
//
// printKey -- print the **kern key token.
//

void printKey(ostream& out, int key) {
   switch (key) {
      case -7:  out << "*k[b-e-a-d-g-c-f-]";  break;
      case -6:  out << "*k[b-e-a-d-g-c-]";    break;
      case -5:  out << "*k[b-e-a-d-g-]";      break;
      case -4:  out << "*k[b-e-a-d-]";        break;
      case -3:  out << "*k[b-e-a-]";          break;
      case -2:  out << "*k[b-e-]";            break;
      case -1:  out << "*k[b-]";              break;
      case  0:  out << "*k[]";                break;
      case  1:  out << "*k[f#]";              break;
      case  2:  out << "*k[f#c#]";            break;
      case  3:  out << "*k[f#c#g#]";          break;
      case  4:  out << "*k[f#c#g#d#]";        break;
      case  5:  out << "*k[f#c#g#d#a#]";      break;
      case  6:  out << "*k[f#c#g#d#a#e#]";    break;
      case  7:  out << "*k[f#c#g#d#a#e#b#]";  break;
   }
}


//////////////////////////////
//
// getTitle -- A Highly interpreted value which may not be
//    appropriate for all data files.
//

char* getTitle(char* buffer, ScorePageSimple& score) {
   buffer[0] = '\0';
   int i;
   int staff = score.getMaxStaff();
   for (i=0; i<score.getStaffSize(staff); i++) {
      if (score.getStaff(staff, i).isTextItem()) {
         ScoreRecord& record = score.getStaff(staff, i);
         if (record.getPValue(4) >= 20) {
            if (record.getTextData()[0] == '_') {
               strcpy(buffer, &record.getTextData()[3]);
            } else {
               strcpy(buffer, record.getTextData());
            }
            break;
         }
      }
   }

   return buffer;
}



//////////////////////////////
//
// getHeaderAndTrailer --  Read the bibliographic information the given
//     files contain.
//

void getHeaderAndTrailer(const char* globalfilename, const char* localfilename,
      Array<char1024>& header, Array<char1024>& trailer, 
      Array<Extra>& extras) {
   char1024 buffer;
   Array<char1024> gfile;
   Array<char1024> lfile;
   gfile.setSize(100);
   gfile.setGrowth(100);
   gfile.setSize(0);
   lfile.setSize(100);
   lfile.setGrowth(100);
   lfile.setSize(0);

   if (strlen(globalfilename) > 0) {
      #ifndef OLDCPP
         fstream ginfile(globalfilename, ios::in);
      #else
         fstream ginfile(globalfilename, ios::in | ios::nocreate);
      #endif
      if (!ginfile.is_open()) {
         cout << "Error: cannot open file " << globalfilename 
              << " for reading" << endl;
         exit(1);
      }
      while (!ginfile.eof()) {
         ginfile.getline(buffer.c, 1000, '\n');
         if (buffer.c[0] == '@' || buffer.c[0] == '!') {
            gfile.append(buffer);
         }
      }
      ginfile.close();
   }

   if (strlen(localfilename) > 0) {
      #ifndef OLDCPP
         fstream linfile(localfilename, ios::in);
      #else
         fstream linfile(localfilename, ios::in | ios::nocreate);
      #endif
      if (!linfile.is_open()) {
         cout << "Error: cannot open file " << localfilename 
              << " for reading" << endl;
         exit(1);
      }
      while (!linfile.eof()) {
         linfile.getline(buffer.c, 1000, '\n');
         if (buffer.c[0] == '@' || buffer.c[0] == '!' ||
             buffer.c[0] == 'a' || buffer.c[0] == 'b' ||
             buffer.c[0] == '=' || 
             buffer.c[0] == 'A' || buffer.c[0] == 'B') {
            lfile.append(buffer);
         }
      }
      linfile.close();
   }

   int i;
   Array<int> gloc;
   Array<int> lloc;
   gloc.setSize(gfile.getSize());
   lloc.setSize(lfile.getSize());
   int state = 0;  // 1 = header, 2 = trailer, -1 read lheader, -2 read ltrailer

   for (i=0; i<gfile.getSize(); i++) {
      if (strncmp(gfile[i].c, "@TOP", 4) == 0) {
         state = 1;
         gloc[i] = 0;
         continue;
      } else if (strncmp(gfile[i].c, "@MOVEMENT-TOP", 13) == 0) {
         gloc[i] = -1;
         continue;
      } else if (strncmp(gfile[i].c, "@MOVEMENT-BOTTOM", 16) == 0) {
         gloc[i] = -2;
         continue;
      } else if (strncmp(gfile[i].c, "@BOTTOM", 7) == 0) {
         state = 2;
         gloc[i] = 0;
         continue;
      } else if (strncmp(gfile[i].c, "@DATA", 5) == 0) {
         state = 3;
         gloc[i] = 0;
         continue;
      } else if (state != 3 && strncmp(gfile[i].c, "!", 1) != 0) {
         gloc[i] = 0;
         continue;
      }
      gloc[i] = state;
   }

   for (i=0; i<lfile.getSize(); i++) {
      if (strncmp(lfile[i].c, "@TOP", 4) == 0) {
         state = 1;
         lloc[i] = 0;
         continue;
      } else if (strncmp(lfile[i].c, "@BOTTOM", 7) == 0) {
         state = 2;
         lloc[i] = 0;
         continue;
      } else if (strncmp(lfile[i].c, "@DATA", 5) == 0) {
         state = 3;
         lloc[i] = 0;
         continue;
      } else if (state != 3 && strncmp(lfile[i].c, "!", 1) != 0) {
         lloc[i] = 0;
         continue;
      }
 
      lloc[i] = state;
   }

   // build the header and trailer
   int j;
   header.setSize(lloc.getSize() + gloc.getSize());
   header.setSize(0);
   trailer.setSize(lloc.getSize() + gloc.getSize());
   trailer.setSize(0);
   for (i=0; i<gfile.getSize(); i++) {
      switch (gloc[i]) {
         case 1:
            header.append(gfile[i]);
            break;
         case 2:
            trailer.append(gfile[i]);
            break;
         case -1:
            for (j=0; j<lfile.getSize(); j++) {
               if (lloc[j] == 1) {
                  header.append(lfile[j]);
               }
            }
            break;
         case -2:
            for (j=0; j<lfile.getSize(); j++) {
               if (lloc[j] == 2) {
                  trailer.append(lfile[j]);
               }
            }
            break;
      }
   }

   // build the extras array
   Extra tempextra;
   for (i=0; i<gfile.getSize(); i++) {
      if (gloc[i] == 3) {
         readExtraData(tempextra, gfile[i].c);
         extras.append(tempextra);
      }
   }
   for (i=0; i<lfile.getSize(); i++) {
      if (lloc[i] == 3) {
         readExtraData(tempextra, lfile[i].c);
         extras.append(tempextra);
      } else {
      }
   }

}



//////////////////////////////
//
// readExtraData -- read the external data records which will
//   be placed into the automatically generated Humdrum data 
//   read from the SCORE file.
//

void readExtraData(Extra& extra, const char* string) {
   if (strchr(string, '\t') == NULL) {
      cout << "Error in input for data extras: " << string << endl;
      exit(1);
   }
   extra.clear();
   char1024 c;
   strcpy(c.c, string);
   char* ptr1;
   char* ptr2;
   ptr1 = strtok(c.c, "\t");
   ptr2 = strtok(NULL, "\t");
   if (ptr2 == NULL || strlen(ptr2) == 0) {
      return;
   }

   int count;
   switch (tolower(ptr1[0])) {
      case 'a':
      case 'b':
         extra.precedence = tolower(ptr1[0]);
         extra.duration = 0.0;
         extra.duration = strtod(&ptr1[1], NULL);
         strcpy(extra.text, ptr2);
         break;

      case '=':
         if (strlen(ptr1) < 3) {
            return;
         }
         strcpy(extra.text, ptr2);
         extra.precedence = tolower(ptr1[1]);
         if (extra.precedence != 'a' && extra.precedence != 'b') {
            cout << "Error: invalid data extra line: " << string << endl;
            exit(1);
         }
         count = sscanf(&ptr1[2], "%d,%lf", &extra.measure, &extra.measurebeat);
         if (count != 2) {
            count = sscanf(&ptr1[2], "%d", &extra.measure);
            extra.measurebeat = 1.0;
            if (count != 1) {
               cout << "Error 2: invalid data extra line: " << string << endl;
               exit(1);
            }
         }
         break;
   }

}



//////////////////////////////
//
// printExtras -- 
//

void printExtras(Array<Thing>& things, Array<Extra>& extras, int index, 
      ostream& out) {

   double currentloc = things[index].absbeat;
   double nextloc = 0.0;
   double lastloc = 0.0;
   if (index < things.getSize()-1) {
      nextloc = things[index+1].absbeat;
   } else {
      nextloc = 999999.0;
   }
   if (index > 0) {
      lastloc = things[index-1].absbeat;
   } else {
      lastloc = -999999.0;
   }

   double lastdiff;
   double currdiff;

   int i;
   for (i=0; i<extras.getSize(); i++) {
      if (extras[i].mark != 0) {
         continue;
      }

      if (extras[i].duration >= 0.0) {
         currdiff = fabs(currentloc - extras[i].duration);
         lastdiff = fabs(lastloc - extras[i].duration);

         switch (extras[i].precedence) {
            case 'b':   // place before events
               if (extras[i].duration > lastloc && 
                   (extras[i].duration <= currentloc ||
                    currdiff < 0.01)) {
                   out << extras[i].text << "\n";
                   extras[i].mark = 1;
               }
               break;
   
            case 'a':   // place after events
               if (extras[i].duration < currentloc && 
                   (extras[i].duration >= lastloc ||
                    lastdiff < 0.01)) {
                   out << extras[i].text << "\n";
                   extras[i].mark = 1;
               }
               break;
         }
      } else {
         // measure anchored data
         switch (extras[i].precedence) {
            case 'b':
               if (things[index].type == P1_Barline &&
                  things[index].measure == extras[i].measure) {
                  out << extras[i].text << "\n";
                  extras[i].mark = 1;
               }
               break;
            case 'a':
               if (index > 0 && things[index-1].type == P1_Barline &&
                  things[index-1].measure == extras[i].measure) {
                  out << extras[i].text << "\n";
                  extras[i].mark = 1;
               }
               break;
         }
      }

   }
}



//////////////////////////////
//
// findDuration -- find the duration of the given measure in the music.
//

double findDuration(Array& things, int measure) {
   int i;
   double output = -1.0;
   for (i=0; i<things.getSize(); i++) {
      if (things[i].type == P1_Barline && things[i].measure == measure) {
         output = things[i].absbeat;   
         break;
      }
   }

   return output;
}



//////////////////////////////
//
// generateThruInfo -- find the correct thru command directive.
//    This function will probably have to be rewritten.
//

void generateThruInfo(Array& things, Array& thruinfo) {
   thruinfo.setSize(0);
   
   int back = 0;
   int i = 0;
   int done = 0;
   int ending = 1;
   Thru tempthru;
   while (!done) {
      if (things[i].segment) {
         switch (things[i].repeattype) {
            case 1:
               ending = 1;
sillytag:
               tempthru.seg = things[i].segment;
               tempthru.ending = things[i].ending;
               if (things[i].segment) {
                  thruinfo.append(tempthru);
               }
               back = i;
               break;
            case 2:
               if (ending == 1) {
                  i = back;
                  ending++;
                  goto sillytag;
               }
               ending++;
               if (ending != 1 && !things[i].finalbar) {
                  tempthru.seg = things[i].segment;
                  tempthru.ending = things[i].ending;
                  thruinfo.append(tempthru);
               }
               break;
            case 3:
               if (ending == 1) {
                  i = back;
                  ending++;
                  goto sillytag;
               } else {
                  tempthru.seg = things[i].segment;
                  tempthru.ending = things[i].ending;
                  thruinfo.append(tempthru);
                  back = i + 1;
                  ending = 1;
               }
               break;
            default:
               if (things[i].ending == ending) {
                  tempthru.seg = things[i].segment;
                  tempthru.ending = things[i].ending;
                  thruinfo.append(tempthru);
               } else if (things[i].ending == 0) {
                  tempthru.seg = things[i].segment;
                  tempthru.ending = things[i].ending;
                  thruinfo.append(tempthru);
               } 
         }
      }
      i++;
      if (i >= things.getSize()) {
         done = 1;
      }
   }

}


//////////////////////////////
//
// ostream<<(Thru) -- how to print out a thru marker
//

ostream& operator<<(ostream& out, Thru& thru) {
   out << (char)('A' + thru.seg - 1.0);
   if (thru.ending) {
      out << thru.ending;
   }
   return out;
}



//////////////////////////////
//
// printNoRep -- print the thru information taking only one repeat,
//   and all last endings.
//

void printNoRep(Array& thruinfo, ostream& out) {
   Array<Thru> copy;
   copy = thruinfo;

   int i;
   // get rid of second repeats
   for (i=1; i<copy.getSize(); i++) {
       if ((copy[i-1] == copy[i]) && (copy[i].ending == 0)) {
          copy[i].seg = 0;
          copy[i].ending = 0;
       }
   }

   // get rid of repeat of earlier endings
   for (i=0; i<copy.getSize()-3; i++) {
      if (copy[i].seg == 0) {
         // already deleted
         continue;
      }
      if (copy[i+1].seg == 0) {
         // already deleted
         continue;
      }
      if ( (copy[i] == copy[i+2]) && 
           (copy[i+1].seg == copy[i+3].seg) &&
           (copy[i+1].ending == copy[i+3].ending - 1) ) {
         copy[i].seg = 0;
         copy[i+1].seg = 0;
         copy[i].ending = 0;
         copy[i+1].ending = 0;
      }
   }

   Array<Thru> newdata;
   newdata.setSize(copy.getSize());
   newdata.setSize(0);

   for (i=0; i<copy.getSize(); i++) {
      if (copy[i].seg != 0) {
         newdata.append(copy[i]);
      }
   }

   if (newdata.getSize() == 0) {
      return;
   }

   out << "*>norep[";
   for (i=0; i<newdata.getSize(); i++) {
      out << newdata[i];
      if (i < newdata.getSize() - 1) {
         out << ",";
      }
   }
   out << "]\n";

}



// md5sum: b4159c3bc8ec1a20926e9b8bb58fb625 scrmono2hum.cpp [20050403]