//
// Programmer:    Craig Stuart Sapp <craig@ccrma.stanford.edu>
// Creation Date: Tue Jun 18 16:02:28 PDT 2002
// Last Modified: Tue Jun 18 16:02:31 PDT 2002
// Filename:      ...sig/examples/all/diaroot.cpp
// Web Address:   http://sig.sapp.org/examples/museinfo/humdrum/diaroot.cpp
// Syntax:        C++; museinfo
//
// Description:   Determine the root weightings for every possible
//                root using a diatonic metric.
// 

#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   printScores(Array<double>& rootscores);
int    analyzeRootProbability1(Array<double>& rootscores, HumdrumFile& infile,
                                 int startline, int stopline, int debugQ = 0);
double base40ToBase7(int pitch);
void   findErrors(HumdrumFile& infile, int debugQ);
char   base7pcToName(int value);

// global variables
Options      options;            // database for command-line arguments
int          debugQ     = 0;     // used with the --debug option
int          appendQ    = 0;     // used with the -a option
int          rootQ      = 0;     // used with the -R option


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

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

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

   Array<double> rootscores;
   rootscores.setSize(40);
   rootscores.setSize(0);

   int i;
   for (i=1; i<=options.getArgCount() || options.getArgCount()==0; i++) {
      infile.clear();

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

      if (rootQ == 0) {
         analyzeRootProbability1(rootscores, infile, 0, 0, debugQ);
         printScores(rootscores);
      } else {
         findErrors(infile, debugQ);
      }
   }

   return 0;
}


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


//////////////////////////////
//
// findErrors -- compare root scores value to root in data.
//

void findErrors(HumdrumFile& infile, int debugQ) {
   int oldline = 0;
   int init = 0;
   int best;
   int correct = 0;
   int i;
   int j;
   Array<double> rootscores(7);
   rootscores.setAll(0);
   for (i=0; i<infile.getNumLines(); i++) {
      if (!infile[i].isData()) {
         continue;
      }
      for (j=0; j<infile[i].getFieldCount(); j++) {
         if (strcmp(infile[i].getExInterp(j), "**root") == 0) {
            if (strcmp(infile[i][j], ".") == 0) {
               break;
            } 
            if (strchr(infile[i][j], '_') != NULL) {  // cont. ties
               break;
            }
            if (strchr(infile[i][j], ']') != NULL) {  // tie ends
               break;
            }
            if (init == 0) {
               init = 1;
               oldline = i;
               correct = (int)base40ToBase7(
                     Convert::kernToBase40(infile[i][j])) % 7;
               correct = (correct + 70) % 7;
               break;
            }
            best=analyzeRootProbability1(rootscores,infile,oldline,i-1,debugQ);
            best = ((int)base40ToBase7(best) + 70) % 7;
            if (best != correct) {
               cout << "Error on line " << oldline + 1
                    << "\tcorrect=" << base7pcToName(correct)
                    << "\tvalue=" << base7pcToName(best) 
                    << endl;
            }
            oldline = i;
            correct = (int)base40ToBase7(Convert::kernToBase40(infile[i][j]))%7;
            correct = (correct + 70) % 7;
            break;
         }
      }
      
   }
   best=analyzeRootProbability1(rootscores,infile,oldline,i-1,debugQ);
   if (best != correct) {
      cout << "Error on line " << oldline + 1
           << "\tcorrect=" << base7pcToName(correct)
           << "\tvalue=" << base7pcToName(best) << endl;
   }

}



//////////////////////////////
//
// analyzeRootProbability1 --
//

int analyzeRootProbability1(Array<double>& rootscores, 
      HumdrumFile& infile, int startline, int stopline, int debugQ) {
   // extract note data

   Array<double> absbeat;
   Array<int>    pitches;
   Array<double> durations;
   Array<double> levels;
   infile.getNoteArray(absbeat, pitches, durations, levels, startline,
      stopline);

   int i;
   if (debugQ) {
      cout << "====================" << endl;
      for (i=startline; i<=stopline; i++) {
         cout << infile[i] << "\n";
      }
      cout << "--------------------" << endl;
      cout << "Start line=" << startline+1 
           << "\tEnd line=" << stopline+1 << endl;
      for (i=0; i<pitches.getSize(); i++) {
         cout << "pitch = " << (int)base40ToBase7(pitches[i]) % 7 << endl;
      }
   }

   int baseindex = 0;
   int diatonic = 0;
   rootscores.setSize(40);
   rootscores.setAll(0.0);
   int j;
   for (i=0; i<rootscores.getSize(); i++) {
      baseindex = (int)base40ToBase7(i);
      for (j=0; j<pitches.getSize(); j++) {
         diatonic = (int) (base40ToBase7(pitches[j]) - baseindex + 70) % 7;
         if (diatonic % 2 == 1) {
            diatonic += 7;
         }
         rootscores[i] += diatonic / 2;
      }
   }


   int min = 0;
   for (i=1; i<rootscores.getSize(); i++) {
      if (rootscores[i] < rootscores[min]) {
         min = i;
      }
   }

   if (debugQ) {
      cout << "BEST ROOT = " << min << endl;
   }

   return min;
}



//////////////////////////////
//
// printScores --
//

void printScores(Array& rootscores) {
   // int i;
   // for (i=0; i<rootscores.getSize(); i++) {
   //    cout << i << "\t" << rootscores[i] << endl;
   // }

   cout << "C\t" << rootscores[2] << endl;
   cout << "E\t" << rootscores[14] << endl;
   cout << "G\t" << rootscores[25] << endl;
   cout << "B\t" << rootscores[37] << endl;
   cout << "D\t" << rootscores[8] << endl;
   cout << "F\t" << rootscores[19] << endl;
   cout << "A\t" << rootscores[31] << endl;
}



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

void checkOptions(Options& opts, int argc, char* argv[]) {
   opts.define("a|append=b",    "append analysis to data in output");   
   opts.define("R|no-root=b",   "analyze by **root events");   

   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, June 2002" << endl;
      exit(0);
   } else if (opts.getBoolean("version")) {
      cout << argv[0] << ", version: 18 June 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);
   }

   debugQ  =  opts.getBoolean("debug");
   rootQ   = !opts.getBoolean("no-root");
   appendQ =  opts.getBoolean("append");

}



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



//////////////////////////////
//
// base40ToBase7 -- Convert pitch to diatonic pitch
//   .0 = natural
//   .1 = sharp
//   .2 = double sharp
//   .9 = flat
//   .8 = double flat
//

double base40ToBase7(int pitch) {
   int octave = pitch / 40;
   if (octave < 0) {
      // rest
      return -1;
   }

   switch (pitch % 40) {
      case  0:  return octave * 7 + 0.9;     // C--
      case  1:  return octave * 7 + 0.8;     // C-
      case  2:  return octave * 7 + 0.0;     // C
      case  3:  return octave * 7 + 0.1;     // C#
      case  4:  return octave * 7 + 0.2;     // C##
      case  5:  return -1;                   // X
      case  6:  return octave * 7 + 1.9;     // D--
      case  7:  return octave * 7 + 1.8;     // D-
      case  8:  return octave * 7 + 1.0;     // D
      case  9:  return octave * 7 + 1.1;     // D#
      case 10:  return octave * 7 + 1.2;     // D##
      case 11:  return -1;                   // X
      case 12:  return octave * 7 + 2.9;     // E--
      case 13:  return octave * 7 + 2.8;     // E-
      case 14:  return octave * 7 + 2.0;     // E
      case 15:  return octave * 7 + 2.1;     // E#
      case 16:  return octave * 7 + 2.2;     // E##
      case 17:  return octave * 7 + 3.9;     // F--
      case 18:  return octave * 7 + 3.8;     // F-
      case 19:  return octave * 7 + 3.0;     // F
      case 20:  return octave * 7 + 3.1;     // F#
      case 21:  return octave * 7 + 3.2;     // F##
      case 22:  return -1;                   // X
      case 23:  return octave * 7 + 4.9;     // G--
      case 24:  return octave * 7 + 4.8;     // G-
      case 25:  return octave * 7 + 4.0;     // G
      case 26:  return octave * 7 + 4.1;     // G#
      case 27:  return octave * 7 + 4.2;     // G##
      case 28:  return -1;                   // X
      case 29:  return octave * 7 + 5.9;     // A--
      case 30:  return octave * 7 + 5.8;     // A-
      case 31:  return octave * 7 + 5.0;     // A
      case 32:  return octave * 7 + 5.1;     // A#
      case 33:  return octave * 7 + 5.2;     // A##
      case 34:  return -1;                   // X
      case 35:  return octave * 7 + 6.9;     // B--
      case 36:  return octave * 7 + 6.8;     // B-
      case 37:  return octave * 7 + 6.0;     // B
      case 38:  return octave * 7 + 6.1;     // B#
      case 39:  return octave * 7 + 6.2;     // B##
   }

   return -1;

}



//////////////////////////////
//
// base7pcToName -- base 7 pitch class name: C D E F G A B
//

char base7pcToName(int value) {
   value = (value + 70) % 7;
   switch (value) {
      case 0:  return 'C';
      case 1:  return 'D';
      case 2:  return 'E';
      case 3:  return 'F';
      case 4:  return 'G';
      case 5:  return 'A';
      case 6:  return 'B';
      default: return 'X';
   }
   return 'X';
}




// md5sum: 6450d27e4540504ca83f83f62bef75ec diaroot.cpp [20050403]