//
// Programmer:    Craig Stuart Sapp <craig@ccrma.stanford.edu>
// Creation Date: Fri May 10 12:40:06 PDT 2002
// Last Modified: Fri May 10 12:40:09 PDT 2002
// Last Modified: Tue Aug 26 01:37:37 PDT 2008 (allow multiple input files)
// Filename:      ...sig/examples/all/pitchhist.cpp
// Web Address:   http://sig.sapp.org/examples/museinfo/humdrum/pitchhist.cpp
// Syntax:        C++; museinfo
//
// Description:   Generate pitch histograms of **kern data in Humdrum files.
//

#include "humdrum.h"

#include <string.h>


// function declarations
void    checkOptions(Options& opts, int argc, char* argv[]);
void    example(void);
void    usage(const char* command);
void    printHistogram(Array<double>& histogram);
void    getHistogram(Array<double>& histogram, HumdrumFile& infile, 
           double starttime, double stoptime);

// interface variables
Options       options;          // database for command-line arguments
double        starttime = 0.0;
double        stoptime = -1.0;
int           verboseQ = 0;     // used with -v option
int           pcQ = 1;          // used with -o option
int           restQ = 0;        // used with -r option
int           base40Q = 0;      // used with -b option
int           fillQ = 0;        // used with -f option
int           countQ = 0;       // used with -c option
int           normalizeQ = 0;   // used with -n option
int           fifthsQ = 0;      // used with -5 option
int           cstyleQ = 0;      // used with -C option

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

int main(int argc, char* argv[]) {
   HumdrumFile infile;

   // process the command-line options
   checkOptions(options, argc, argv);

   // figure out the number of input files to process
   int numinputs = options.getArgCount();

   Array<double> histogram(1001);
   Array<double> temphistogram(1001);
   histogram.zero();
   temphistogram.zero();
   for (int i=0; i<numinputs || i==0; i++) {
      infile.clear();

      // if no command-line arguments read data file from standard input
      if (numinputs < 1) {
         infile.read(cin);
      } else {
         infile.read(options.getArg(i+1));
      }
      infile.analyzeRhythm();

      // analyze the input file according to command-line options
      getHistogram(temphistogram, infile, starttime, stoptime);
      histogram += temphistogram;
   }

   double sum = 0.0;
   int i;


   if (normalizeQ) {
      for (i=0; i<1000; i++) {
         sum += histogram[i];
      }
      if (restQ) {
         sum += histogram[1000];
      }
      if (sum > 0.0) {
         for (i=0; i<1001; i++) {
            histogram[i] /= sum;
         }
      }
   }

   printHistogram(histogram);

   return 0;
}


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


//////////////////////////////
//
// printHistogram --
//

void printHistogram(Array& histogram) {
   int i;
   char buffer[256] = {0};
   int findex = 0;
   if (verboseQ) {
      for (i=0; i<histogram.getSize(); i++) {
         if (base40Q && pcQ && i >39 && i<1000) {
            continue;
         }
         if (!base40Q && pcQ && i >12 && i<1000) {
            continue;
         }
         if (!base40Q && i>127 && i<1000) {
            continue;
         }
         if (restQ == 0 && i >= 1000) {
            continue;
         }
         if (!fillQ && histogram[i] == 0.0) {
            continue;
         }
         if (base40Q) {
            if (i < 1000) {
               if (pcQ) {
                  if (fifthsQ) {
                     findex = (17 + i * 23) % 40;
                     Convert::base40ToKern(buffer, findex+4*40);
                     if (buffer[0] == '\0') {
                        continue;
                     }
                  } else {
                     Convert::base40ToKern(buffer, i+4*40);
                     if (buffer[0] == '\0') {
                        continue;
                     }
                  }
               } else {
                  if (histogram[i] == 0.0) {
                     continue;
                  }
                  Convert::base40ToKern(buffer, i);
               }
            } else {
               strcpy(buffer, "rest");
            }
            if (base40Q && pcQ && fifthsQ) {
               cout << buffer << ":\t" << histogram[findex] << endl;
            } else {
               cout << buffer << ":\t" << histogram[i] << endl;
            }
         } else {
            cout << i << ":\t" << histogram[i] << endl;
         }
      }
   } else {
      if (base40Q) {
      } else {
         if (cstyleQ) cout << "{";
         for (i=0; i<12; i++) {
            cout << histogram[i];
            if (i < 11) {
               if (cstyleQ) cout << ",";
               cout << " ";
            }
         }
         if (restQ) {
            if (cstyleQ) cout << ",";
            cout << " " << histogram[1000];
         }
         if (cstyleQ) cout << "};";
         cout << endl;
      }
   }
}



//////////////////////////////
//
// getHistogram --
//

void getHistogram(Array<double>& histogram, HumdrumFile& infile, 
      double starttime, double stoptime) {
   if (stoptime <= starttime) {
      stoptime = infile.getTotalDuration();
   }

   histogram.zero();

   int i, j, k;
   double duration = 0.0;
   int tokencount = 0;
   int pitch = 0;
   char buffer[1024] = {0};
   for (i=0; i<infile.getNumLines(); i++) {
      if (infile[i].getAbsBeat() < starttime) {
         continue;
      } else if (infile[i].getAbsBeat() > stoptime) {
         break;
      }
      if (!infile[i].isData()) {
         continue;
      }
      for (j=0; j<infile[i].getFieldCount(); j++) {
         if (strcmp(infile[i].getExInterp(j), "**kern") != 0) {
            continue;
         }
         tokencount = infile[i].getTokenCount(j);
         for (k=0; k<tokencount; k++) {
            infile[i].getToken(buffer, j, k);
            if (strcmp(buffer, ".") == 0) {
               continue;
            }
            if (countQ && (strchr(buffer, '_') != NULL)) {
               continue;
            }
            if (countQ && (strchr(buffer, ']') != NULL)) {
               continue;
            }
            if (base40Q) {
               pitch    = Convert::kernToBase40(buffer);
            } else {
               pitch    = Convert::kernToMidiNoteNumber(buffer);
            }
            if (pitch < 0 || pitch == E_unknown) {
              continue;    // don't count weird notes
            } else if (base40Q && pcQ) {
               pitch = pitch % 40;
            } else if (pcQ) {
               pitch = pitch % 12;
            }
            duration = Convert::kernToDuration(buffer);
            if (countQ) {
               histogram[pitch] += 1;
            } else {
               histogram[pitch] += duration;
            }
         }
      }
   }
}



//////////////////////////////
//
// checkOptions -- validate and process command-line options.
//

void checkOptions(Options& opts, int argc, char* argv[]) {
   opts.define("v|verbose=b", "display output in verbose style");
   opts.define("o|octaves=b",  "keep octave information");
   opts.define("b|e|enharmonic|base40=b",   "keep enharmonic information");
   opts.define("r|rest=b",     "display rest information");
   opts.define("f|fill=b",     "diplay entries with 0 values");
   opts.define("c|count=b",    "count attacks rather than durations");
   opts.define("C|cstyle=b",   "output data in C-style array");
   opts.define("n|normalize=b","normalize to sum to 1.0");
   opts.define("5|fifths=b",   "display enharmonic pitch classes by fifths");

   opts.define("debug=b",     "determine bad input line num");
   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, May 2002" << endl;
      exit(0);
   } else if (opts.getBoolean("version")) {
      cout << argv[0] << ", version: 10 May 2002" << 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);
   }

   verboseQ   =  opts.getBoolean("verbose");
   pcQ        = !opts.getBoolean("octaves");
   base40Q    =  opts.getBoolean("base40");
   restQ      =  opts.getBoolean("rest");
   fillQ      =  opts.getBoolean("fill");
   countQ     =  opts.getBoolean("count");
   cstyleQ    =  opts.getBoolean("cstyle");
   normalizeQ =  opts.getBoolean("normalize");
   fifthsQ    =  opts.getBoolean("fifths");
}



//////////////////////////////
//
// example -- example usage of the pitchhist program
//

void example(void) {
   cout <<
   "                                                                         \n"
   "# example usage of the sample program.                                   \n"
   "                                                                         \n"
   << endl;
}



//////////////////////////////
//
// usage -- gives the usage statement for the pitchhist program
//

void usage(const char* command) {
   cout <<
   " To be written...                                                        \n"
   << endl;
}


// md5sum: 3a9ce567fdce91339b6a84aff70325f1 pitchhist.cpp [20101222]