//
// Programmer:    Craig Stuart Sapp <craig@ccrma.stanford.edu>
// Creation Date: Wed Oct 24 10:08:24 PDT 2001
// Last Modified: Fri Mar 29 09:37:12 PST 2002 (fixed bib recs at end of data)
// Last Modified: Wed Jun 24 15:37:46 PDT 2009 (updated for GCC 4.4)
// Filename:      ...sig/examples/all/hum2gmn.cpp
// Web Address:   http://sig.sapp.org/examples/museinfo/humdrum/hum2gmn.cpp
// Syntax:        C++; museinfo
//
// Description:   Converts a Humdrum file into Guido Music Notation.
//
// Reference:     http://www.salieri.org/GUIDO
// Reference:     http://guidolib.sourceforge.net
//
// Status:        Initial stages of development
//
// Things todo:   beams
//                articulations (except staccatos)
//                ties
//                lyrics
//                multiple voices on a staff
//                stem direction markers
//                double barline encodings?
//                tempo markings?
//                etc.
//
// Things done:   systems, notes, rhythms, slurs, clefs, time sigs, key sigs,
//		  grace notes, staccatos.
//
//

#include "humdrum.h"
#include <string.h>
#include <ctype.h>
#include <stdio.h>

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

#ifndef VISUAL
   #include <time.h>
#endif

// function declarations:
void      checkOptions(Options& opts, int argc, char** argv);
void      example(void);
void      convertToGMN(HumdrumFile& hfile);
void      convertPartToGMN(HumdrumFile& hfile, int part, int startline);
void      convertMeasureToGMN(HumdrumFile& hfile, int part, int line);
int       convertInterpretationToGMN(HumdrumFile& hfile, int part, int line);
void      convertNoteDataToGMN(HumdrumFile& hfile, int part, int line);
void      convertNoteToGMN(const char* note, int graceQ);
void      convertGlobalComment(HumdrumRecord& globalcomment);
void      convertBibliography(HumdrumRecord& bibrec);
void      processHeader(HumdrumFile& hfile);
void      processFooter(HumdrumFile& hfile);
void      print(const char* data);
void      usage(const char* command);
int       getPartSpine(HumdrumFile& hfile, int part, int line);
void      resetGlobals(void);
void      printTitleComposer(HumdrumFile& hfile);


// User interface variables:
Options   options;
int       debugQ = 0;          // used with the --debug option
int       cautionaryQ = 1;     // used with the --nocaution option
int       reverseQ = 0;        // reverse the ordering of the parts
int       quietQ = 0;          // don't add extra information

// Global state information
int       Goctave = -99;       // for displaying octave after a note
double    Gduration = -99.0;   // for sticky rhythms
int       Gnoteinit = 0;       // for marking when the first note is written
char      lastchar = '\0';     // last printed character in print stream
int       Gline  =0;           // for error warnings
int       Gpartinit = 0;       // for identifying the first part in the file.

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

int main(int argc, char** argv) {
   checkOptions(options, argc, argv);  // process the command-line options
   HumdrumFile hfile(options.getArg(1));
   hfile.analyzeRhythm();
   convertToGMN(hfile);
   return 0;
}

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


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

void checkOptions(Options& opts, int argc, char* argv[]) {
   opts.define("debug=b",  "print debug information"); 
   opts.define("nocaution=b",  "print cautionary accidentals"); 
   opts.define("r|reverse=b",  "reverse the order of the parts"); 

   opts.define("author=b",  "author of program"); 
   opts.define("version=b", "compilation info");
   opts.define("example=b", "example usages");   
   opts.define("h|help=b",  "short description");
   opts.process(argc, argv);
   
   // handle basic options:
   if (opts.getBoolean("author")) {
      cout << "Written by Craig Stuart Sapp, "
           << "craig@ccrma.stanford.edu, Oct 2001" << endl;
      exit(0);
   } else if (opts.getBoolean("version")) {
      cout << argv[0] << ", version: 24 Oct 2001" << endl;
      cout << "compiled: " << __DATE__ << endl;
      cout << MUSEINFO_VERSION << endl;
      exit(0);
   } else if (opts.getBoolean("help")) {
      usage(opts.getCommand());
      exit(0);
   } else if (opts.getBoolean("example")) {
      example();
      exit(0);
   }
   
   debugQ      = opts.getBoolean("debug");
   cautionaryQ = !opts.getBoolean("nocaution");
   reverseQ    = opts.getBoolean("reverse");
}



//////////////////////////////
//
// example --
//

void example(void) {


}



//////////////////////////////
//
// convertToGMN -- 
//

void convertToGMN(HumdrumFile& hfile) {

   // find the start of the data and count how many parts there are
   int i, j;
   int parts = 0;
   Array<int> partspines;
   partspines.setSize(100);
   partspines.setSize(0);
   partspines.allowGrowth();
   int startline = 0;
   for (i=0; i<hfile.getNumLines(); i++) {
      if (strncmp(hfile[i][0], "**", 2) == 0) {
         startline = i;
         for (j=0; j<hfile[i].getFieldCount(); j++) {
            if (strcmp(hfile[i].getExInterp(j), "**kern") == 0) {
               parts++;
               partspines.append(j);
            }
         }
         break;
      }
   }

   
   processHeader(hfile);
   print("{ ");    // print a system start
  

   for (i=0; i<partspines.getSize(); i++) {
      resetGlobals();
      if (!reverseQ) {
         convertPartToGMN(hfile, partspines[partspines.getSize()-1-i], startline);
      } else {
         convertPartToGMN(hfile, partspines[i], startline);
      }
      if (i < partspines.getSize() - 1) {
         print(", ");
      }
   }

   print("} \n");    // print a system end
   processFooter(hfile);

   if (quietQ == 0) {
      print("\n\n");
      print("% Converted with hum2gmn v1.0.1 on ");
      #ifndef VISUAL
         time_t rawtime;
         struct tm *timeinfo;
         time(&rawtime);
         timeinfo = localtime(&rawtime);
         print(asctime(timeinfo));
      #else
         print("<time not available>");
      #endif
      print("\n");
      
   }
}



//////////////////////////////
//
// convertPartToGMN --
//

void convertPartToGMN(HumdrumFile& hfile, int part, int startline) {
   static int staffno = 1;
   static char buffer[1024] = {0};
   print("[ ");     // beginning of part marker

   sprintf(buffer, "\\staff<%d> ", staffno++);
   print(buffer);
   if (Gpartinit == 0) {
      printTitleComposer(hfile);
      print("\n\t\\systemFormat<dx=1.0cm> ");
      print("\n\t\\barFormat<style=\"system\"> ");
      print("\n\t\\set<autoEndBar=\"off\"> ");
   }

   int i;
   int done = 0;
   for (i=startline+1; i<hfile.getNumLines(); i++) {
      Gline = i+1;
      switch (hfile[i].getType()) {
         case E_humrec_none:
         case E_humrec_empty:
            break;
         case E_humrec_global_comment:
            convertGlobalComment(hfile[i]);
            break;
         case E_humrec_bibliography:
            if (Gpartinit == 0) {
               convertBibliography(hfile[i]);
            }
            break;
         case E_humrec_data_comment:
            break;
         case E_humrec_data_kern_measure:
            convertMeasureToGMN(hfile, part, i);
            break;
         case E_humrec_interpretation:
            done = convertInterpretationToGMN(hfile, part, i);
            break;
         case E_humrec_data:
            if (Gnoteinit == 0) {
               print("\n\t\t");
               Gnoteinit = 1;
            }
            convertNoteDataToGMN(hfile, part, i);
            break;
         default:
            break;
      }
      if (done) break;
   }


   print("\n] ");    // end of part marker

   Gpartinit = 1;    // set to 1 after first voice has been processed
}



//////////////////////////////
//
// printTitleComposer -- print the title and composer if available
//

void printTitleComposer(HumdrumFile& hfile) {
   int i;
   const char *cptr;
   char buffer[128] = {0};
   int titleQ = 0;
   for (i=0; i<hfile.getNumLines(); i++) {
      if (hfile[i].getType() == E_humrec_bibliography) {

         if (!titleQ && strncmp(hfile[i][0], "!!!OTL", 6) == 0) {
            titleQ = 1;
            cptr = strchr(hfile[i][0], ':');
            if (cptr == NULL) {
               continue;
            }
            cptr++;
            while (cptr[0] == ' ') {
               cptr++;
            }
            if (cptr[0] != '\0') {
               print("\n\t\\title<\"");
            } else {
               continue;
            }
            while (cptr[0] != '\0') {
               if (cptr[0] == '\"') {
                  print("\\\'");
               } else {
                  buffer[0] = cptr[0];
                  buffer[1] = '\0';
                  print(buffer);
               }
               cptr++;
            }
            print("\">");
         }

         if (strncmp(hfile[i][0], "!!!COM", 6) == 0) {
            cptr = strchr(hfile[i][0], ',');
            if (cptr == NULL) {
               cptr = strchr(hfile[i][0], ':');
            }
            if (cptr == NULL) {
               continue;
            }
            cptr++;
            while (cptr[0] == ' ') {
               cptr++;
            }
            if (cptr[0] != '\0') {
               print("\n\t\\composer<\"");
            } else {
               continue;
            }
            while (cptr[0] != '\0') {
               if (cptr[0] == '\"') {
                  print("\\\'");
               } else {
                  buffer[0] = cptr[0];
                  buffer[1] = '\0';
                  print(buffer);
               }
               cptr++;
            }
            cptr = strchr(hfile[i][0], ':');
            if (cptr != NULL) {
               cptr++;
               while (cptr[0] == ' ') {
                  cptr++;
               }
               if (cptr[0] == '\0') {
                  print("\">");
                  continue;
               }
               print(" ");
               while (cptr[0] != ',' && cptr[0] != '\0') {
                  if (cptr[0] == '\"') {
                     print("\\\'");
                  } else {
                     buffer[0] = cptr[0];
                     buffer[1] = '\0';
                     print(buffer);
                  }
                  cptr++;
               }

            }
            print("\">");
         }

      }
   }
}


//////////////////////////////
//
// getPartSpine -- get the spine number for the first
//    occurance of the part in the given line.
//

int getPartSpine(HumdrumFile& hfile, int part, int line) {
   int i;
   int output = -1;
   for (i=0; i<hfile[line].getFieldCount(); i++) {
      if (part+1 == hfile[line].getPrimaryTrack(i)) {
         output = i;
         break;
      } 
   }

   return output;
}



//////////////////////////////
//
// convertMeasureToGMN -- convert **kern measure to GMN values.
//

void convertMeasureToGMN(HumdrumFile& hfile, int part, int line) {
   char buffer[1024] = {0};
   int spine = getPartSpine(hfile, part, line);
   if (spine < 0) {
      cerr << "Warning: invalid humdrum syntax at line " << line+1 << endl;
   }
   int length = strlen(hfile[line][spine]);
   int repeatQ = 0;
   int startrep = 0;
   int endrep = 0;

   if (strchr(hfile[line][spine], '-') != NULL) {
      // don't display hidden measures
      return;
   }

   if (strchr(hfile[line][spine], ':') != NULL) {
      repeatQ = 1;
   }

   // process repeat signs
   if (repeatQ) {
      int i;
      for (i=1; i<length; i++) {
         if (hfile[line][spine][i] == ':') {
            if (hfile[line][spine][i-1] == '|' || hfile[line][spine][i-1]=='!') {
               startrep = 1;
            } else {
               endrep = 1;
            }
         }
      }
   
      if (endrep) {
         print("\\repeatEnd ");
      }
   }

   print("\n\t");

   int measurenum = 0;
   int measureQ = 0;
   int count = sscanf(hfile[line][spine], "=%d", &measurenum);
   if (count == 1) {
      measureQ = 1;
   }
   int doubleQ = 0;
   if (strncmp(hfile[line][spine], "==", 2) == 0) {
      doubleQ = 1;
   }

   if (!repeatQ) { 
      if (doubleQ) {
         print("\\endBar");
      } else {
         print("\\bar");
      }
      if (measureQ) {
         sprintf(buffer, "<%d> ", measurenum);
         print(buffer);
      } else {
         print(" ");
      }
   }

   if (startrep) {
      print("\\repeatBegin ");
   }

}



//////////////////////////////
//
// convertInterpretationToGMN -- convert **kern interpretation to GMN values.
//

int convertInterpretationToGMN(HumdrumFile& hfile, int part, int line) {
   static char buffer[1024] = {0};
   int spine = getPartSpine(hfile, part, line);
   if (spine < 0) {
      cerr << "Warning: invalid humdrum syntax at line " << line+1 << endl;
      return 0;
   }
   int length = strlen(hfile[line][spine]);

   if (strcmp(hfile[line][spine], "*-") == 0) {
      return 1;
   }

   // proces a clef
   if (strncmp(hfile[line][spine], "*clef", 5) == 0) {
      if (Gnoteinit == 0) {
         print("\n\t"); 
      }
      char clef = 'x';
      int  ctype = -99;
      if (length >= 5) {
         clef = hfile[line][spine][5];
      }
      if (length >= 6 && isdigit(hfile[line][spine][6])) {
         ctype = hfile[line][spine][6] - '0'; 
      }
      clef = tolower(clef);
      if (ctype != 99) {
         sprintf(buffer, "\\clef<\"%c%d\">", clef, ctype);
      } else {
         sprintf(buffer, "\\clef<\"%c\">", clef);
      }
      print(buffer);
      print(" ");
      return 0;
   }

   // proces a key signature
   if ((strncmp(hfile[line][spine], "*k[", 3) == 0)
         && (hfile[line][spine][length-1] == ']')) {
      if (Gnoteinit == 0) {
         print("\n\t"); 
      }
      int pitch = 0;
      if (length > 4) {
         pitch = Convert::kernToBase40(&hfile[line][spine][length-3]);
         pitch = pitch % 40;
      } else {
         pitch = E_muse_c;
      }
      int keyinfo = 0;
      switch (pitch) {
         case E_muse_c:   keyinfo = 0;   break;
         case E_muse_fs:  keyinfo = 1;   break;
         case E_muse_cs:  keyinfo = 2;   break;
         case E_muse_gs:  keyinfo = 3;   break;
         case E_muse_ds:  keyinfo = 4;   break;
         case E_muse_as:  keyinfo = 5;   break;
         case E_muse_es:  keyinfo = 6;   break;
         case E_muse_bs:  keyinfo = 7;   break;
         case E_muse_bf:  keyinfo = -1;  break;
         case E_muse_ef:  keyinfo = -2;  break;
         case E_muse_af:  keyinfo = -3;  break;
         case E_muse_df:  keyinfo = -4;  break;
         case E_muse_cf:  keyinfo = -5;  break;
         case E_muse_gf:  keyinfo = -6;  break;
         case E_muse_ff:  keyinfo = -7;  break;
      }

      sprintf(buffer, "\\key<%d>", keyinfo);
      print(buffer);
      print(" ");

      return 0;
   }

   // process time signatures
   if (strncmp(hfile[line][spine], "*M", 2) == 0 &&
         strchr(hfile[line][spine], '/') != NULL) {
      int top = 0;
      int bottom = 0;

      int flag = sscanf(hfile[line][spine], "*M%d/%d", &top, &bottom);
      if (flag == 2) {
         if (Gnoteinit == 0) {
            print("\n\t"); 
         }
         sprintf(buffer, "\\meter<\"%d/%d\">", top, bottom);
         print(buffer);
         print(" ");
      }
      return 0;
   } 


   return 0;
}



//////////////////////////////
//
// convertNoteDataToGMN -- Convert note data to GMN values.
//

void convertNoteDataToGMN(HumdrumFile& hfile, int part, int line) {
   int i;
   char kbuffer[1024] = {0};
   int spine = getPartSpine(hfile, part, line);
   int chordnotes = hfile[line].getTokenCount(spine);

   // check to see if just a null token
   if (strcmp(hfile[line][spine], ".") == 0) {
      return;
   }

   // check for the start of a slur
   if (strchr(hfile[line][spine], '(') != NULL) {
      print("\\slur( ");
   }

   // check for the start of a tie
   if (strchr(hfile[line][spine], '[') != NULL) {
      print("\\tieBegin ");
   }

   // check to see if this is a grace note/chord
   int graceQ = 0;
   if (strchr(hfile[line][spine], 'q') != NULL) {
      print("\\grace( ");
      graceQ = 1;
   }

   // check for accidentals
   int staccatoQ = 0;
   if (strchr(hfile[line][spine], '\'') != NULL) {
      print("\\stacc( ");
      staccatoQ = 1;
   }


   if (chordnotes > 1) {
      print("{");
   }

   for (i=0; i<chordnotes; i++) {
      hfile[line].getToken(kbuffer, spine, i);
      convertNoteToGMN(kbuffer, graceQ);
      if (chordnotes > 1) {
         if (i < chordnotes - 1) {
            print(",");
         }
      } else {
         print(" ");
      }
   }

   if (chordnotes > 1) {
      print("} ");
   }

   if (staccatoQ) {
      print(") ");
   }

   if (graceQ) {
      print(") ");
   }

   // check for the end of a tie
   if (strchr(hfile[line][spine], ']') != NULL) {
      print("\\tieEnd ");
   }

   // check for the end of a slur
   if (strchr(hfile[line][spine], ')') != NULL) {
      print(") ");
   }
}



//////////////////////////////
//
// convertNoteToGMN -- convert a note to GMN format.
//

void convertNoteToGMN(const char* note, int graceQ) {
   static char buffer[1024] = {0};

   int octave = 0;
   int base40 = 0;
   int restQ = 0;
   char name = 'x';
   int length = strlen(note);

   // get the accidentals
   int accidental = 0;
   int i;
   for (i=0; i<length; i++) {
      if (note[i] == '#') {
         accidental++;
      } else if (note[i] == '-') {
         accidental--;
      }
   }
 
   // process pitch
   if (strchr(note, 'r') != NULL) {
      restQ = 1;
      name = '_';
   } else {   // process a real note
      base40 = Convert::kernToBase40(note);
      octave = (base40 / 40) - 3;           // Middle C = C1 in Guido
      Convert::base40ToKern(buffer, base40);
      name = tolower(buffer[0]);
   }

   if (restQ) {
      print("_");
   } else {
      sprintf(buffer, "%c", name);
      print(buffer);
      if (accidental > 0) {
         for (i=0; i<accidental; i++) {
            print("#");
         }
      } else if (accidental < 0) {
         for (i=0; i<-accidental; i++) {
            print("&");
         }
      }
      if (octave != Goctave) {
         sprintf(buffer, "%d", octave);
         print(buffer);
         Goctave = octave;
      }
   }


   // process duration
   double duration = Convert::kernToDuration(note);
   Convert::durationToKernRhythm(buffer, duration);
   if (duration == Gduration) {
      // don't print duration, since it is alreay active.
   } else {
      Gduration = duration;
      print("/");
      print(buffer);
   }
   
}


//////////////////////////////
//
// processFooter --
//

void processFooter(HumdrumFile& hfile) {
   int i;
   int start = 0;
   for (i=hfile.getNumLines()-1; i>=0; i--) {
      if (strcmp(hfile[i][0], "*-") == 0) {
         start = i;
         break;
      }
   }

   if (start == 0) {
      // already printed the header
      return;
   }

   print("\n");
   for (i=start; i<hfile.getNumLines(); i++) {
      switch(hfile[i].getType()) {
         case E_humrec_global_comment:
            convertGlobalComment(hfile[i]);
            break;
         case E_humrec_bibliography:
            convertBibliography(hfile[i]);
            break;
      }
   }
}



/////////////////////////////
//
// processHeader -- print the header information in the file
//

void processHeader(HumdrumFile& hfile) {
   int i;
   int done = 0;
   for (i=0; i<hfile.getNumLines(); i++) {
      switch(hfile[i].getType()) {
         case E_humrec_global_comment:
            convertGlobalComment(hfile[i]);
            break;
         case E_humrec_bibliography:
            convertBibliography(hfile[i]);
            break;
         case E_humrec_interpretation:
            done = 1; 
            break;
      }
      if (done) break;
   }
   print("\n");
}



//////////////////////////////
//
// convertBibliography --  print a bibliographic record (!!! -> %%%) 
//

void convertBibliography(HumdrumRecord& bibrec) {
   if (lastchar != '\n') {
      print("\n");
   }
   print("%%%");
   print(&(bibrec.getLine()[3])); 
   print("\n");
}



//////////////////////////////
//
// convertBibliography --  print a bibliographic record (!!! -> %%%) 
//

void convertGlobalComment(HumdrumRecord& globalcomment) {
   if (lastchar != '\n') {
      print("\n");
   }
   print("%%");
   print(&(globalcomment.getLine()[2]));
   print("\n");
}




//////////////////////////////
//
// print -- print the data
//

void print(const char* data) {
  int length = strlen(data);
  lastchar = data[length-1];
  cout << data;
}



//////////////////////////////
//
// usage --
//

void usage(const char* command) {

}


//////////////////////////////
//
// resetGlobals -- global states for parsing into Guido notation
//

void resetGlobals(void) {
   Goctave   = -99;
   Gduration = -99.0;
   Gnoteinit = 0;
}






// md5sum: 86ef2895d810769328d903dd55105db3 hum2gmn.cpp [20090626]