//
// Programmer:    Craig Stuart Sapp <craig@ccrma.stanford.edu>
// Creation Date: Thu Mar 29 17:22:53 PST 2001
// Last Modified: Sun Feb 16 07:58:18 PST 2003 (convert to RootSpectrum class)
// Last Modified: Thu Jun  3 13:39:25 PDT 2004 (adjusted statistics output)
// Filename:      ...sig/examples/all/rootcomp.cpp
// Web Address:   http://sig.sapp.org/examples/museinfo/humdrum/rootcomp.cpp
// Syntax:        C++; museinfo
//
// Description:   determine the root of chords in music according
//                to the timespan of a preexisting root analysis spine.
// 

#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& hfile);
void   analyzeDataLine(HumdrumFile& hfile, int line);

// global variables
Options      options;           // database for command-line arguments
int          debugQ     =  0;   // used with the --debug option
int          rawQ       =  0;   // used with the --raw option
int          appendQ    =  0;   // used with the -a option
double       rval       =  1.0; // used with the -r option
double       rlevel     =  1.0; // used with the -r option
int          octave     = -1;   // used with the -o option
int          weightsQ   =  0;   // used with the -w option
int          algorithm  =  0;   // used with the --algorithm option
int          countQ     =  0;   // used with the -c option
int          errorQ     =  0;   // used with the -e option
int          errorcount =  0;   // used with the -c option
double       dweight    =  0.25;// duration weight from -d option
double       lweight    =  0.25;// metric level weight from -l option
int          durationQ  =  1;   // used with -D option
int          levelQ     =  1;   // used with -M option
int          melodyQ    =  1;   // used with -H option
int          linearQ    =  1;   // used with -L option
double       scaleFactor=  1.0; // used with -s option
int          chordsum   =  0; 
int          errorsum   =  0; 
int          summaryQ   =  0;   // used with -S option
int          rateQ      =  0;   // used with --rate option
int          fullQ      =  0;   // display full input
int          chordcountQ = 0;   // used with --totalchords

IntervalWeight  iweights;
RootSpectrum    spectrum;

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

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

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

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

   Array<int>    rootanalysis;    // roots analysed at various times
   Array<double> rootbeat;        // absolute start beat of root
   Array<double> rootscores;      // score of best root

   for (int i=0; i<numinputs || i==0; i++) {
      hfile.clear();

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

      generateAnalysis(hfile);
   }

   if (rateQ) {
      cout << 100.0*errorsum/chordsum << endl;
   } else if (chordcountQ) {
      cout << chordsum << endl;
   } else if (summaryQ) {
      cout << "!! Total Number of Chords: " << chordsum << endl;
      cout << "!! Total Errors: " << errorsum << endl;
      cout << "!! Error Rate: " << 100.0*errorsum/chordsum << "%" << endl;
   }

   return 0;
}


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



//////////////////////////////
//
// generateAnalysis -- analyze the roots of the chords
//

void generateAnalysis(HumdrumFile& hfile) {
   hfile.analyzeRhythm(); 

   int i;

   for (i=0; i<hfile.getNumLines(); i++) {
      switch (hfile[i].getType()) {
         case E_humrec_none:
         case E_humrec_empty:
         case E_humrec_global_comment:
         case E_humrec_bibliography:
            if (fullQ) {
               cout << hfile[i].getLine() << endl;
            }
            break;
         case E_humrec_data_comment:
            if (fullQ) {
               cout << hfile[i].getLine() << "\t!" << endl;
            }
            break;
         case E_humrec_data_kern_measure:
            if (fullQ) {
               cout << hfile[i].getLine() << "\t" << hfile[i][0] << endl;
            }
            break;
         case E_humrec_interpretation:
            if (fullQ) {
               cout << hfile[i].getLine() << "\t*" << endl;
            }
            break;
         case E_humrec_data:
            analyzeDataLine(hfile, i);
            break;
         default:
            break;
      }
   }

   if (countQ && !rateQ) {
      cout << "!! Total root errors: " << errorcount << endl;
   } 
}



//////////////////////////////
//
// analyzeDataLine --
//

void analyzeDataLine(HumdrumFile& hfile, int line) {
   Array<double> values;
   values.setSize(0);
   int startindex;
   int stopindex;
   int nextroot;
   int lastroot = -1;
   int most = 0;
   int i;
   int rooti = -1;
   for (i=0; i<hfile[line].getFieldCount(); i++) {
      if (strcmp(hfile[line].getExInterp(i), "**root") == 0) {
         rooti = i;
         break;
      }
   }
   if (rooti == -1) {
      cout << "Error: no **root spine" << endl;
      exit(1);
   }

   if (strcmp(hfile[line][rooti], ".") == 0) {
      if (fullQ) {
         cout << hfile[line].getLine() << "\t." << endl;
      }
      return;
   }

   if (strchr(hfile[line][rooti], 'r') != NULL) {
      // ignore rests
      if (fullQ) {
         cout << hfile[line].getLine() << "\t." << endl;
      }
      return;
   }
   
   double dur = Convert::kernToDuration(hfile[line][rooti]);
   char durstring[32] = {0};
   char pitchbuffer[32] = {0};
   Convert::durationToKernRhythm(durstring, dur);
   int pitch  = Convert::kernToBase40(hfile[line][rooti]);
   startindex = line;
   stopindex  = hfile.getStopIndex(hfile[line].getAbsBeat() + dur - 0.01);

   // where all of the work gets done:
   most = spectrum.calculate(iweights, hfile, startindex, stopindex, debugQ);
   chordsum++;

   if (most >= 0) {
      if (octave < 0) {
         nextroot = (most + 2) % 40 + 80;
         if (abs(nextroot + 40 - lastroot) < 13) {
            nextroot += 40;
         } else if (abs(nextroot - 40 - lastroot) < 13) {
            nextroot -= 40;
         }
      } else {
         nextroot = (most + 2) % 40 + 40 * octave;
      }
      Convert::base40ToKern(pitchbuffer, nextroot);
      lastroot = nextroot;
   } else {
      nextroot = 0;
      pitchbuffer[0] = 'r';
      pitchbuffer[1] = '\0';
   }

   if ((pitch % 40) != (nextroot % 40)) {
      errorcount++;
      errorsum++;
   }

   if (fullQ) {
      if (strchr(durstring, '-') != 0) {
         cout << hfile[line] << "\t" << pitchbuffer << endl;
      } else {
         cout << hfile[line] << "\t" << durstring << pitchbuffer << endl;
      }
   } else {
      if (errorQ && ((pitch % 40) != (nextroot % 40))) {
         Convert::base40ToKern(pitchbuffer, pitch);
         cout << "Wanted: " << pitchbuffer;
         Convert::base40ToKern(pitchbuffer, nextroot);
         cout << "\tbut got: " << pitchbuffer;
         cout << "\ton line: " << line+1 << endl;
      }
   }

}



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

void checkOptions(Options& opts, int argc, char* argv[]) {
   opts.define("n|norm=b",         "normalize root scores so that best is 1.0");
   opts.define("t|theta=d:55.0",   "theta1 for interval weight calculations");
   opts.define("F|no-functions=b", "do not display MMA functions for plotting");
   opts.define("wf|weightfile=s",      "interval weight file");
   opts.define("W|display-weights=b",  "display interval weights and exit");
   opts.define("H|no-non-harmonic=b",  "don't do non-harmonic note id");
   opts.define("I|no-invert=b",        "don't invert root scores");
   opts.define("L|rhythm-log=b",       "calculate rhythm in log scale");
   opts.define("d|duration-weight=d:0.25",   "duration weighting factor");
   opts.define("D|duration-weighting-off=b", "turn off duration weighting");
   opts.define("m|metric-weight=d:0.25",     "metric level weighting factor");
   opts.define("M|metric-weighting-off=b", "turn off metric weighting");
   opts.define("R|rhythm-weighting-off=b",   "turn off duration and metric weighting");
   opts.define("rate=b", "display only the root error rate in percentage");
   opts.define("range|lines=s:-1:-1", "range of lines offset from 1 to analyze in file");
   opts.define("data=b",              "trace input parsing");   

   opts.define("a|append=b",      "append analysis to data in output");   
   opts.define("summary=b",       "summarize error rate of all files");   
   opts.define("raw=b",           "raw display of output");   
   opts.define("c|count=b",       "count the numbers of errors only");   
   opts.define("e|error=b",       "display pitch errors");   
   opts.define("r|rhythm=i:4",    "rhythm to measure root at");   
   opts.define("s|non-harmonic-scale-factor=d:1.0", "scale factor for non-harmonic tones");
   opts.define("S|non-resolution-scaling=d:1.0", "scale factor for non-harmonic notes which do not resolve");
   opts.define("o|octave=i:-1",   "octave number of bass line");
   opts.define("w|weights=b",     "display parallel spine of scores next to roots");
   opts.define("algorithm=i:0",   "analysis algorithm to use");
   opts.define("totalcount=b",   "display a count of all chords");

   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, Dec 2000" << endl;
      exit(0);
   } else if (opts.getBoolean("version")) {
      cout << argv[0] << ", version: 16 Feb 2003" << 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);
   }

   lweight    =  opts.getDouble("metric-weight");
   dweight    =  opts.getDouble("duration-weight");
   durationQ  = !opts.getBoolean("duration-weighting-off");
   levelQ     = !opts.getBoolean("metric-weighting-off");
   linearQ    = !opts.getBoolean("rhythm-log");
   melodyQ    = !opts.getBoolean("no-non-harmonic");
   scaleFactor=  opts.getDouble("non-harmonic-scale-factor");
   if (opts.getBoolean("rhythm-weighting-off")) {
      durationQ = 0;
      levelQ    = 0;
   }

   // set the spectrum analysis values
   spectrum.setResolutionFactor(opts.getDouble("non-resolution-scaling"));
   spectrum.setDuration(durationQ);
   spectrum.setMetricLevel(levelQ);
   spectrum.setMelodyScaleFactor(scaleFactor);
   if (linearQ) {
      spectrum.rhythmLinear();
      spectrum.setDurationWeight(dweight);
      spectrum.setMetricLevelWeight(lweight);
   } else {
      spectrum.rhythmLog();
      spectrum.setDurationBias(dweight);
      spectrum.setMeterBias(lweight);
   }
   if (melodyQ) {
      spectrum.melodyOn();     // do non-harmonic tone estimation
   } else {
      spectrum.melodyOff();    // do not do non-harmonic tone estimation
   }

   if (opts.getBoolean("weightfile")) {
      iweights.setFromFile(opts.getString("weightfile"));
   } else {
      iweights.setChromatic1(opts.getDouble("theta"));
   }
   
   if (opts.getBoolean("display-weights")) {
      cout << iweights;
      exit(0);
   }

   debugQ      = opts.getBoolean("debug");
   appendQ     = opts.getBoolean("append");
   rawQ        = opts.getBoolean("raw");
   countQ      = opts.getBoolean("count");
   chordcountQ = opts.getBoolean("totalcount");
   errorQ      = opts.getBoolean("error");
   if (errorQ) {
      countQ = 1;
   }

   rateQ     = opts.getBoolean("rate");

   octave    = opts.getInteger("octave");
   weightsQ  = opts.getBoolean("weights");
   summaryQ  = opts.getBoolean("summary");

   if (rateQ) {
      fullQ = 0;
   }

   if (countQ) {
      fullQ = 0;
   }

   if (chordcountQ) {
      fullQ = 0;
   }

}



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


// md5sum: ff3f93a54ba66f1647a9921317a30828 rootcomp.cpp [20050403]