//
// Programmer:    Craig Stuart Sapp <craig@ccrma.stanford.edu>
// Creation Date: Thu Mar 31 21:51:34 PST 2005
// Last Modified: Fri Apr  1 15:12:06 PST 2005
// Filename:      ...sig/examples/all/range.cpp
// Web Address:   http://sig.sapp.org/examples/museinfo/humdrum/range.cpp
// Syntax:        C++; museinfo
//
// Description:   Calculate pitch histograms in a score of **kern data.
// 

#include "humdrum.h"

#include <string.h>
#include <math.h>

// function declarations
void   checkOptions(Options& opts, int argc, char* argv[]);
void   example(void);
void   usage(const char* command);
void   generateAnalysis(HumdrumFile& infile, 
                                 Array<double>& rdata);
void   printAnalysis(Array<double>& rdata);
void   printPercentile(Array<double>& midibins, double percentile);
double countNotesInRange(Array<double>& midibins, int low, int high);
void   getRange(int& rangeL, int& rangeH, 
                                 const char* rangestring);

// global variables
Options      options;            // database for command-line arguments
int          durationQ  = 0;     // used with the -d option
int          debugQ     = 0;     // used with the --debug option
int          percentileQ= 0;     // used with the -p option
int          addfractionQ = 0;   // used with the -f option
double       percentile = 0.0;   // used with the -p option
int          rangeQ     = 0;     // used with the -r option
int          rangeL     = 0;     // used with the -r option
int          rangeH     = 0;     // used with the -r 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> values;
   values.setSize(0);

   Array<double> midibins;
   midibins.setSize(128);
   midibins.setAll(0);
   midibins.allowGrowth(0);

   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));
      }

      generateAnalysis(infile, midibins);
   }

   printAnalysis(midibins);

   return 0;
}


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

//////////////////////////////
//
// printAnalysis --
//

void printAnalysis(Array& midibins) {
   if (percentileQ) {
      printPercentile(midibins, percentile);
      return;
   }  else if (rangeQ) {
      double notesinrange = countNotesInRange(midibins, rangeL, rangeH);
      cout << notesinrange << endl;
      return;
   }

   // print the pitch histogram

   double fracL = 0.0;
   double fracH = 0.0;
   double fracA = 0.0;
   double sum = midibins.sum();
   double runningtotal = 0.0;
   cout << "**keyno\t**kern\t**count";
   if (addfractionQ) {
      cout << "\t**fracL";
      cout << "\t**fracA";
      cout << "\t**fracH";
   }
   cout << "\n";

   char buffer[1024] = {0};
   int i;
   for (i=0; i<midibins.getSize(); i++) {
      if (midibins[i] <= 0.0) {
         continue;
      }      
      cout << i << "\t";
      cout << Convert::base12ToKern(buffer, i);
      cout << "\t";
      cout << midibins[i];
      fracL = runningtotal/sum;
      runningtotal += midibins[i];
      fracH = runningtotal/sum;
      fracA = (fracH + fracL)/2.0;
      fracL = (int)(fracL * 10000.0 + 0.5)/10000.0;
      fracH = (int)(fracH * 10000.0 + 0.5)/10000.0;
      fracA = (int)(fracA * 10000.0 + 0.5)/10000.0;
      if (addfractionQ) {
         cout << "\t" << fracL;
         cout << "\t" << fracA;
         cout << "\t" << fracH;
      }
      cout << "\n";
   }
   cout << "*-\t*-\t*-";
   if (addfractionQ) {
      cout << "\t*-";
      cout << "\t*-";
      cout << "\t*-";
   }
   cout << "\n";

}


//////////////////////////////
//
// countNotesInRange --
//

double countNotesInRange(Array& midibins, int low, int high) {
   int i;
   double sum = 0;
   for (i=low; i<=high; i++) {
      sum += midibins[i];
   }
   return sum;
}




//////////////////////////////
//
// printPercentile --
//

void printPercentile(Array& midibins, double percentile) {
   double sum = midibins.sum();
   double runningtotal = 0.0;
   int i;
   for (i=0; i<midibins.getSize(); i++) {
      if (midibins[i] <= 0) {
         continue;
      }
      runningtotal += midibins[i] / sum;
      if (runningtotal >= percentile) {
         cout << i << endl;
         return;
      }
   }

   cout << "unknown" << endl;
}



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

void checkOptions(Options& opts, int argc, char* argv[]) {
   opts.define("c|range|count=s:60-71", "count notes in a particular range");
   opts.define("d|duration=b",      "weight pitches by duration");   
   opts.define("f|fraction=b",      "display histogram fractions");   
   opts.define("p|percentile=d:0.0","display the xth percentile pitch");   

   opts.define("debug=b",       "trace input parsing");   
   opts.define("author=b",      "author of the program");   
   opts.define("version=b",     "compilation information"); 
   opts.define("example=b",     "example usage"); 
   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, Mar 2005" << endl;
      exit(0);
   } else if (opts.getBoolean("version")) {
      cout << argv[0] << ", version: 31 Mar 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");
   durationQ    = opts.getBoolean("duration");
   percentileQ  = opts.getBoolean("percentile");
   rangeQ       = opts.getBoolean("range");
   getRange(rangeL, rangeH, opts.getString("range"));
   addfractionQ = opts.getBoolean("fraction");
   percentile   = opts.getDouble("percentile");

   // the percentile is a fraction from 0.0 to 1.0.
   // if the percentile is above 1.0, then it is assumed
   // to be a percentage, in which case the value will be
   // divided by 100 to get it in the range from 0 to 1.
   if (percentile > 1) {
      percentile = percentile / 100.0;
   }

}



//////////////////////////////
//
// getRange --
//

void getRange(int& rangeL, int& rangeH, const char* rangestring) {
   rangeL = 0; rangeH = 0;
   if (rangestring == NULL) {
      return;
   }
   if (rangestring[0] == '\0') {
      return;
   }
   int length = strlen(rangestring);
   char* buffer = new char[length+1];
   strcpy(buffer, rangestring);
   char* ptr;
   if (isdigit(buffer[0])) {
      ptr = strtok(buffer, " \t\n:-");
      sscanf(ptr, "%d", &rangeL);
      ptr = strtok(NULL, " \t\n:-");
      if (ptr != NULL) {
         sscanf(ptr, "%d", &rangeH);
      }
   } else {
      ptr = strtok(buffer, " :");
      if (ptr != NULL) {
         rangeL = Convert::kernToMidiNoteNumber(ptr);
         ptr = strtok(NULL, " :");
         if (ptr != NULL) {
            rangeH = Convert::kernToMidiNoteNumber(ptr);
         }
      }
   }

   if (rangeL <   0) { rangeL =   0; }
   if (rangeH <   0) { rangeH =   0; }
   if (rangeL > 127) { rangeL = 127; }
   if (rangeH > 127) { rangeH = 127; }
   if (rangeL > rangeH) {
      int temp = rangeL;
      rangeL = rangeH;
      rangeH = temp;
   }

}



//////////////////////////////
//
// example -- example usage of the maxent program
//

void example(void) {
   cout <<
   "                                                                        \n"
   << endl;
}




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

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



//////////////////////////////
//
// generateAnalysis --
//

void generateAnalysis(HumdrumFile& infile, Array& midibins) {
   int i, j, k;

   char buffer[1024] = {0};
   int tokencount;
   int keynum;
   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 (strcmp(infile[i].getExInterp(j), "**kern") != 0) {
            continue;
         }
         if (strcmp(infile[i][j], ".") == 0) {  // ignore null tokens
            continue;
         }
         tokencount = infile[i].getTokenCount(j);
         for (k=0; k<tokencount; k++) {
            infile[i].getToken(buffer, j, k);         
            if (strcmp(buffer, ".") == 0) {    // ignore strange null tokens
               continue;
            }
            if (strchr(buffer, 'r') != NULL) { // ignore rests
               continue;
            }
            keynum = Convert::kernToMidiNoteNumber(buffer);
            if (keynum > 127) {
               cout << "ERROR: Funny pitch: " << keynum 
                    << " = " << buffer << endl;
            } else if (durationQ) {
               duration = Convert::kernToDuration(buffer);
               midibins[keynum] += duration;
            } else {
               midibins[keynum] += 1.0;
            }
         }
      }
   }
}



// md5sum: 7c466010a853f0407670d0baa6f74bdb prange.cpp [20050403]