//
// Programmer:    Craig Stuart Sapp <craig@ccrma.stanford.edu>
// Creation Date: Wed Dec 13 13:35:37 PST 2000
// Last Modified: Sun Oct  9 02:06:07 PDT 2005 (converted from kern2melisma)
// Last Modified: Tue Dec 13 22:16:14 PST 2005 (small fixes)
// Filename:      ...sig/examples/all/time2matlab.cpp
// Web Address:   http://sig.sapp.org/examples/museinfo/humdrum/time2matlab.cpp
// Syntax:        C++; museinfo
//
// Description:   Extracts performance times of Humdrum **kern data according
//                to information found in a **time spine on the line.
//

#include "humdrum.h"

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

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

// function declarations:
void      checkOptions(Options& opts, int argc, char** argv);
void      example(void);
void      preparePitch(char* buffer2, const char* buffer1);
void      printOutput(HumdrumFile& hfile);
void      usage(const char* command);
double    getEndTime(HumdrumFile& hfile, int startindex, 
                             double duration);
int       getMetricLevel(HumdrumFile& hfile, int index);
int       getMeasureNum(HumdrumFile& hfile, int index);
double    getTimeToEnd(HumdrumFile& infile, double starttime, 
                             int startindex);
void      printSaccid(ostream& out, const char* string);
void      comment(ostream& out, int count, int style);

// User interface variables:
Options   options;
int       debugQ    = 0;      // used with --debug option
int       barlinesQ = 1;      // used with -B option
int       midinoteQ = 0;      // used with -m option
int       classQ    = 0;      // used with -c option
int       auxdataQ  = 0;      // used with -a option
double    tdefault  = 60.0;   // used with -t option
int       humdrumQ  = 0;      // used with --humdrum option

char      humComment = '!';
char      matComment = '%';

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

int main(int argc, char** argv) {
   // process the command-line options
   checkOptions(options, argc, argv);

   HumdrumFile hfile(options.getArg(1));
   hfile.analyzeRhythm("4");
   printOutput(hfile);
   return 0;
}

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



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

void checkOptions(Options& opts, int argc, char* argv[]) {
   opts.define("a|auxiliary-data=b", "output extra notation info");
   opts.define("B|nobarlines=b", "don't display barlines");
   opts.define("m|midi|MIDI=b", "display pitches as MIDI note numbers");
   opts.define("c|class=b", "display pitches in pitch class notation");
   opts.define("t|tempo|default-tempo=d:60.0", "tempo if none specified");
   opts.define("humdrum=b", "print data in Humdrum file format");

   opts.define("debug=b", "Debugging flag");
   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 2005" << endl;
      exit(0);
   } else if (opts.getBoolean("version")) {
      cout << argv[0] << ", version: 8 Oct 2005" << 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");

   if (opts.getBoolean("nobarlines")) {
      barlinesQ = 0;
   } else {
      barlinesQ = 1;
   }

   if (opts.getBoolean("class")) {
      classQ = 1;
   } else {
      classQ = 0;
   }

   tdefault = opts.getDouble("default-tempo");
   if (tdefault == 0.0) {
      cout << "Error default tempo cannot be zero." << endl;
      exit(1);
   }
   if (tdefault < 0.0) {
      cerr << "Warning: converting tempo to be positive" << endl;
      tdefault = -tdefault;
   }
   auxdataQ = opts.getBoolean("auxiliary-data");
   humdrumQ = opts.getBoolean("humdrum");
}



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

void example(void) {
   // someone is lazy and has not put an example here.
}



//////////////////////////////
//
// comment --
//

void comment(ostream& out, int count, int style) {
   int i;
   for (i=0; i<count; i++) {
      switch (style) {
         case 0:  out << matComment; break;
         default: out << humComment;
      }
   }

}



//////////////////////////////
//
// printOutput -- 
//

void printOutput(HumdrumFile& hfile) {
   Array<double> tempo;
   hfile.analyzeTempoMarkings(tempo, tdefault);

   double currentmillisecond = 0.0;
   double lastduration = 0.0;

   double linestarttime = 0.0;
   double lineendtime = 0.0;

   int founddata = 0;

   int metlev;
   int i, j, k;
   char buffer1[1024] = {0};
   char buffer2[1024] = {0};
   double duration;
   for (i=0; i<hfile.getNumLines(); i++) {
      currentmillisecond = currentmillisecond + 
            lastduration * 60000.0 / tempo[i];
      lastduration = hfile[i].getDuration();

      if (hfile[i].getType() == E_humrec_global_comment) {
         cout << "%% " << &(hfile[i].getLine()[3]) << endl;
      }

      if (hfile[i].getType() == E_humrec_bibliography) {
         cout << "%%%" << &(hfile[i].getLine()[3]) << endl;
      }


      if (hfile[i].getType() != E_humrec_data) {
         continue;
      }

      if (debugQ) {
         cout << "RECORD: " << hfile[i] << endl;
      }

      if (founddata == 0) {
         founddata = 1;

         comment(cout, 2, humdrumQ);
         cout << " Data column information:\n";
         comment(cout, 3, humdrumQ);
         cout << "col01: abstime\t" << 
                 "(average absolute time in milliseconds of human "
              << "beats)\n";
         comment(cout, 3, humdrumQ);
         cout << "col02: duration\t" <<
                 "(expected duration in ms based "
              << "on score duration)\n";
         comment(cout, 3, humdrumQ);
         cout << "col03: note\t\t" << 
                 "(MIDI note number of pitch)\n";
         comment(cout, 3, humdrumQ);
         cout << "col04: metlev\t" << 
                 "(metric level: 1 = downbeat; "
              <<  "0 = beat; -1 = offbeat)\n";
         comment(cout, 3, humdrumQ);
         cout << "col05: measure\t" <<
                  "(measure number in which note occurs)\n";
         comment(cout, 3, humdrumQ);
         cout << "col06: absbeat\t" <<
                 "(absolute beat from starting beat at 0)\n";
         comment(cout, 3, humdrumQ);
         cout << "col07: trackno\t" << 
                 "(hand which plays the note: 1 = left; 2=right)\n";
	 if (!auxdataQ) {
            comment(cout, 3, humdrumQ);
            cout << "col08: mintime\t" << 
                    "(minimum absolute time of human beat for this note)\n";
            comment(cout, 3, humdrumQ);
            cout << "col09: maxtime\t" << 
                    "(maximum absolute time of human beat for this note)\n";
            comment(cout, 3, humdrumQ);
            cout << "col10: sd\t\t" << 
                    "(standard deviation of human beat time in ms.)\n";
	 } else {
            //comment(cout, 3, humdrumQ);
            //cout << "col08: paccid\t" <<
	    //	    "(printed accidental 0=none, -1=flat, +1=sharp, 10=nat)\n";
            comment(cout, 3, humdrumQ);
            cout << "col08: saccid\t" <<
		    "(sounding accidental 0=natural, -1=flat, +1=sharp)\n";
         }
		   
         if (humdrumQ) {
            cout << "**start\t**dur\t**key\t**metr\t**meas\t**absb\t**track\t**saccid\n";
         }
      }
	        

      linestarttime = -1.0;
      // find current time value and save 
      for (j=0; j<hfile[i].getFieldCount(); j++) {
         if (strcmp(hfile[i].getExInterp(j), "**time") == 0) {
            sscanf(hfile[i][j], "%lf", &linestarttime);
            break;
         }
      }

      for (j=0; j<hfile[i].getFieldCount(); j++) {
         if (hfile[i].getExInterpNum(j) != E_KERN_EXINT) {
            continue;
         }

         for (k=0; k<hfile[i].getTokenCount(j); k++) {
            if (strcmp(hfile[i][j], ".") == 0) {
               continue;
            }
            hfile[i].getToken(buffer1, j, k);
            if (strchr(buffer1, '_') != NULL) {
               // ignore notes which are tied
               continue;
            }
            if (strchr(buffer1, ']') != NULL) {
               // ignore notes which are tied
               continue;
            }

            preparePitch(buffer2, buffer1);
            duration = hfile.getTiedDuration(i, j, k);
            lineendtime = getEndTime(hfile, i, duration);

            int note = Convert::kernToMidiNoteNumber(buffer2);
            if (classQ) {
               note = note % 12;
            }
            if (note < 0) {
               // don't display rests.
               continue;
            }
            if (note < 0 || note > 127) {
               cerr << "Error reading MIDI pitch number from string: " 
                    << buffer2 << endl;
               exit(1);
            }

            //cout << "Note\t";
            // cout << hfile.getAbsBeat(i) << "\t";
            // cout << (int)(currentmillisecond+0.5) << "\t";
            cout << linestarttime << "\t";

            if (debugQ && (lineendtime - linestarttime < 0)) {
               cerr << "Error duration of note on line: " << hfile[i] << endl;
               cerr << "Starttime: " << linestarttime << endl;
               cerr << "Endtime:   " << lineendtime << endl;
               cerr << "Line Index:     " << i << endl;
               exit(1);
            } 
            if (lineendtime != -1) {
               cout << lineendtime - linestarttime << "\t";
            } else {
               cout << (int)(getTimeToEnd(hfile, linestarttime, i) + 0.5) 
                    << "\t";
            }

            cout << note;

            cout << "\t";
            
            metlev = getMetricLevel(hfile, i);
            cout << metlev;

            cout << "\t" << getMeasureNum(hfile, i);

            cout << "\t" << hfile[i].getAbsBeat();

            // you must make sure that the spine order is
            // correct or this data will be bad
            cout << "\t" << hfile[i].getPrimaryTrack(j)-1;
   
            cout << "\t"; 
	    printSaccid(cout, buffer2);
 
            cout << "\n";
         }
      }
   }

   if (humdrumQ) {
      cout << "*-\t*-\t*-\t*-\t*-\t*-\t*-\t*-\n";
   }
}



//////////////////////////////
//
// printSaccid -- print the souding accidental of the note.
//    0 = natural without an accidental sign, 10 = natural with
//    a natural sign, -1 is flat, -2 double flat, +1 = sharp, 
//    +2 = double sharp.  10 is added (subtracted) to sharps (flats)
//    values if the sharp or flat is required to be printed as
//    a cautionary sign.
//

void printSaccid(ostream& out, const char* string) {
   if (strchr(string, 'n') != NULL) {
      out << 10;
      return;
   }

   int i;
   int accid = 0;
   int length = strlen(string);
   int literal = 0;
   for (i=0; i<length; i++) {
      if (string[i] == '-') {
         accid--;
      }
      if (string[i] == '#') {
         accid++;
      }
      if (string[i] == 'X') {
         literal = 1;
      }
   }
   if (literal) {
      if (accid > 0) {
         accid = accid+10;
      } else if (accid < 0) {
         accid = accid-10;
      }
   }
   out << accid;
   return;
}



//////////////////////////////
//
// getTimeToEnd --
//

double getTimeToEnd(HumdrumFile& infile, double starttime, int startindex) {


   double ctime = starttime;
   double cbeat = infile[startindex].getAbsBeat();
   double nbeat = infile[infile.getNumLines()-1].getAbsBeat();

   int preindex = -1;

   int i;
   for (i=startindex-1; i>=0; i--) {
      if (infile[i].getType() != E_humrec_data) {
         continue;
      }

      preindex = i;
      break;
   }

   if (preindex < 0) {
       return -1;
   }

   double pbeat = infile[preindex].getAbsBeat();
   double ptime = -1.0;

   for (i=0; i<infile[preindex].getFieldCount(); i++) {
      if (strcmp("**time", infile[preindex].getExInterp(i)) != 0) {
         continue;
      }

      sscanf(infile[preindex][i], "%lf", &ptime);
      break;
   }

   if (ptime < 0.0) {
      return -1;
   }

   double db2 = nbeat - cbeat;
   double db1 = cbeat - pbeat;
   double dt1 = ctime - ptime;

   // cout << "<< DB1 = " << db1 << " >> ";
   // cout << "<< DB2 = " << db2 << " >> ";
   // cout << "<< DT1 = " << dt1 << " >> ";
   
   return db2 * dt1 / db1;
}



//////////////////////////////
//
// getMeasureNum --
//

int getMeasureNum(HumdrumFile& hfile, int index) {
   int output = -1;

   int i;
   for (i=index; i>=0; i--) {
      if (hfile[i][0][0] == '=' && isdigit(hfile[i][0][1])) {
         sscanf(hfile[i][0], "=%d", &output);
         break;
      }
   }

   if (i <= 0) {
      output = 0;
   }


   return output;
}



//////////////////////////////
//
// getEndTime -- return the **time value at the given duration point after
//     the given index.
//

double getEndTime(HumdrumFile& hfile, int startindex, double duration) {
   double stopbeat = duration + hfile[startindex].getAbsBeat();
   int i, j;
   double output = -1.0;

   for (i=startindex+1; i<hfile.getNumLines(); i++) {
      if (hfile[i].getType() != E_humrec_data) {
         continue;
      }
      if (hfile[i].getAbsBeat() >= (stopbeat-0.0002)) {
         for (j=0; j<hfile[i].getFieldCount(); j++) {
            if (strcmp(hfile[i].getExInterp(j), "**time") == 0) {
               sscanf(hfile[i][j], "%lf", &output);
               break;
            } 
         }
         break;
      }
   }

   return output;
}



//////////////////////////////
//
// preparePitch --
//

void preparePitch(char* buffer2, const char* buffer1) {
   strcpy(buffer2, buffer1);
}


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

void usage(const char* command) {


}



//////////////////////////////
//
// getMetricLevel --  only checked with quarter-note beats.
//

int getMetricLevel(HumdrumFile& hfile, int index) {
   double fraction = hfile[index].getAbsBeat();
   fraction = fraction - (int)fraction;
   if (fraction < 0.0001 || fraction > 0.9999) {
      fraction = hfile[index].getAbsBeat() / 3.0;
      fraction = fraction - (int)fraction;
      if (fraction < 0.0001 || fraction > 0.9999) {
         return 1; // on a measure downbeat
      } else {
         return 0; // on the (quarter note) beat
      }
   } else {
      return -1; // on an offbeat
   }
}




// md5sum: edad163cf9797b125c2fd80500199c0f time2matlab.cpp [20090406]