//
// Programmer:    Craig Stuart Sapp <craig@ccrma.stanford.edu>
// Creation Date: Fri Jun 26 23:44:15 PDT 1998
// Last Modified: Fri Jun 26 23:44:19 PDT 1998
// Filename:      ...sig/examples/all/harmanal.cpp
// Web Address:   http://sig.sapp.org/examples/museinfo/humdrum/harmanal.cpp
// Syntax:        C++; museinfo
//
// Description:   Analyzes **kern data and generates a functional
//		  harmony analysis of timeslices (no melodic harmony).
//

#include "humdrum.h"

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

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

// function declarations
void         checkOptions(Options& opts, int argc, char* argv[]);
ChordQuality determineChordQuality(const HumdrumRecord& aRecord);
void         example(void);
void         processRecords(HumdrumFile& infile, HumdrumFile& outfile);
void         usage(const char* command);


// global variables
Options      options;             // database for command-line arguments
char         unknown[256] = {0};  // space for unknown chord simplification
int          chordinit;           // for initializing chord detection function
int          key = E_root_C;      // base pitch of key to analyze in
int          mode = E_mode_major; // mode of the key


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


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

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

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

   for (int i=0; i<numinputs || i==0; i++) {
      chordinit = 1;
      infile.clear();
      outfile.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));
      }

      // analyze the input file according to command-line options
      processRecords(infile, outfile);

      // check to see if only the analysis spine is required
      if (options.getBoolean("extract")) {
         outfile = outfile.extract(-1);
      }

      outfile.write(cout);
   }

   return 0;
}


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


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

void checkOptions(Options& opts, int argc, char* argv[]) {
   opts.define("r|riem|riemann=b");             // show **riem analysis
   opts.define("x|e|extract=b");                // display analysis only
   opts.define("f|format=s:t:i:r");             // control display style
   opts.define("u|unknown=s:X");                // control display of unknowns
   opts.define("d|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, June 1998" << endl;
      exit(0);
   } else if (opts.getBoolean("version")) {
      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);
   }

   ChordQuality::setDisplay(opts.getString("format"));
   Convert::chordType.setNullName(opts.getString("unknown"));
   Convert::chordInversion.setNullName(opts.getString("unknown"));
   Convert::chordRoot.setNullName(opts.getString("unknown"));

   strncpy(unknown, opts.getString("unknown"), 64);
   strcat(unknown, ":");
   strncat(unknown, opts.getString("unknown"), 64);
   strcat(unknown, ":");
   strncat(unknown, opts.getString("unknown"), 64);

}



//////////////////////////////
//
// determineChordQuality -- extracts notes from humdrum data record
//	and sends them to a chord identifier.  Notes from previous
//	records are remembered, and replace any null records in the
//	data.  Rests are represented by some negative value 
//	and will be ignored by the chord identifier.
//

ChordQuality determineChordQuality(const HumdrumRecord& aRecord) {
   static Collection<int> lastnotes;  // for keeping track of null record notes
   int i;
   if (chordinit) {
      chordinit = 0;
      lastnotes.setSize(aRecord.getFieldCount());
      for (i=0; i<aRecord.getFieldCount(); i++) {
         lastnotes[i] = E_root_rest;
      }
      // remove non-kern spines from note list
      for (i=0; i<aRecord.getFieldCount(); i++) {
         if (aRecord.getExInt(i) != E_KERN_EXINT) {
            lastnotes.setSize(lastnotes.getSize() - 1);
         }
      }
   }

   // determine chord notes and send to chord identifier
   Collection<int> currentNotes(lastnotes.getSize());
   int count = 0;
   for (i=0; i<aRecord.getFieldCount(); i++) {
      if (aRecord.getExInt(i) == E_KERN_EXINT) {
         if (strcmp(aRecord[i], ".") == 0) {
            currentNotes[count] = lastnotes[count];
         } else {
            currentNotes[count] = Convert::kernToBase40(aRecord[i]);
            lastnotes[count] = currentNotes[count];
         }
         count++;
      }
   }

   return Convert::noteSetToChordQuality(currentNotes); 
}


//////////////////////////////
//
// determineFunctionalAnalysis -- mode 0 = major, mode 1 = minor
//	default value: strlen = 32
//

void determineFunctionalAnalysis(char* output, const ChordQuality& aQuality, 
   int aKey, int aMode, int strlen = 32) {

   if (aQuality.getType() == E_unknown) {
      strncpy(output, "X", strlen);
      return;
   } else if (aQuality.getType() == E_chord_note) {
      strncpy(output, "X", strlen);
      return;
   }

   int rootInKeyQ = 0;
   int rightKeyQualityQ = 1;
   int degreeLocation = 0;
   Collection<int> chordNotes;
   Collection<int> scaleDegrees;

   chordNotes = Convert::chordQualityToNoteSet(aQuality);
   scaleDegrees = Convert::keyToScaleDegrees(aKey, aMode);

   // question: is root in key?
   int i;
   for (i=0; i<scaleDegrees.getSize(); i++) {
      if (chordNotes[0] == scaleDegrees[i]) {
         rootInKeyQ = 1;
         degreeLocation = i;
         break;
      }
   }

   // question: are chord notes in the key?
   for (i=0; i<chordNotes.getSize(); i++) {
      if (chordNotes[i] != scaleDegrees[(2*i+degreeLocation) %
            scaleDegrees.getSize()] ) {
         rightKeyQualityQ = 0;
         break;
      }
   }
 

   int chordThird = 0;
   int chordFifth = 0;
   if (rightKeyQualityQ) {
      if (chordNotes.getSize() > 1) {
         if (chordNotes[0] < chordNotes[1]) {
            chordThird = chordNotes[1] - chordNotes[0];
         } else {
            chordThird = chordNotes[0] - chordNotes[1];
         }
      } else {
         strncpy(output, "X", strlen);
         return;
      }
      if (chordNotes.getSize() > 2) {
         if (chordNotes[0] < chordNotes[2]) {
            chordFifth = chordNotes[2] - chordNotes[0];
         } else {
            chordFifth = chordNotes[0] - chordNotes[2];
         }
      }
      
      // can now build a chord in the key
      switch (degreeLocation) {
         case 0:
            strcpy(output, "I");
            break;
         case 1:
            strcpy(output, "II");
            break;
         case 2:
            strcpy(output, "III");
            break;
         case 3:
            strcpy(output, "IV");
            break;
         case 4:
            strcpy(output, "V");
            break;
         case 5:
            strcpy(output, "VI");
            break;
         case 6:
            strcpy(output, "VII");
            break;
         default:
            strncpy(output, "X", strlen);
            return;
      }
    
      // adjust for major/minor third
      i = 0;
      while (output[i] != '\0') {
         output[i] = tolower(output[i]);
      }
   
      if (chordFifth == E_base40_dim5 || (chordFifth == 0 &&
           aQuality.getType() == E_chord_incmin)) {
        strcat(output, "o");
      } else if (chordFifth == E_base40_aug5) {
        strcat(output, "+");
      }

      if (chordNotes.getSize() == 4) {
         if (degreeLocation == 6) {
            strcat(output, "0");
         }
         strcat(output, "7");
      }

      switch (aQuality.getInversion()) {
         case 0:
            break;
         case 1:
            strcat(output, "a");
            break;
         case 2:
            strcat(output, "b");
            break;
         case 3:
            strcat(output, "c");
            break;
         case 4:
            strcat(output, "d");
            break;
         default:
            strcat(output, "x");
      }
      return;
   }

   else {
      strncpy(output, "X", strlen);
      return;
   }

}



//////////////////////////////
//
// example -- example usage of the quality program
//

void example(void) {
   cout <<
   "                                                                         \n"
   "# example usage of the quality program.                                  \n"
   "# analyze a Bach chorale for chord qualities:                            \n"
   "     quality chor217.krn                                                 \n"
   "                                                                         \n"
   "# display only the chord analysis:                                       \n"
   "     quality -x chor217.krn                                              \n"
   "                                                                         \n"
   "# display only the roots of chords:                                      \n"
   "     quality -r chor217.krn                                              \n"
   "                                                                         \n"
   << endl;
}



//////////////////////////////
//
// processRecords -- looks at humdrum records and determines chord
//	quality
//

void processRecords(HumdrumFile& infile, HumdrumFile& outfile) {
   ChordQuality quality;

   char aString[256] = {0};
   HumdrumRecord currRecord;
   for (int i=0; i<infile.getNumLines(); i++) {
      if (options.getBoolean("debug")) {
         cout << "processing line " << (i+1) << " of input ..." << endl;
      }
      currRecord = infile[i]; 
      switch (currRecord.getType()) {
         case E_humrec_none:
         case E_humrec_empty:
         case E_humrec_comment:
         case E_humrec_global_comment:
            outfile.appendLine(currRecord);
            break;
         case E_humrec_data_comment:
            if (currRecord.equalFieldsQ("**kern")) {
               currRecord.appendField(currRecord[0]);
            } else {
               currRecord.appendField("!");
            }
            outfile.appendLine(currRecord);
            break;
         case E_humrec_data_tandem:
            if (currRecord.equalFieldsQ("**kern")) {
               currRecord.appendField(currRecord[0]);
            } else {
               currRecord.appendField("*");
            }
            outfile.appendLine(currRecord);
            break;
         case E_humrec_data_kern_measure:
            if (currRecord.equalFieldsQ("**kern")) {
               currRecord.appendField(currRecord[0]);
            } else {
               currRecord.appendField("=");
            }
            outfile.appendLine(currRecord);
            break;
         case E_humrec_data_interpretation:
            currRecord.appendField("**qual");
            outfile.appendLine(currRecord);
            break;
         case E_humrec_data:
            quality = determineChordQuality(infile[i]);
            determineFunctionalAnalysis(aString, quality, key, mode);
            currRecord.appendField(aString);
            outfile.appendLine(currRecord);
            break;
         default:
            cerr << "Error on line " << (i+1) << " of input" << endl;
            exit(1);
      }
   }
}



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

void usage(const char* command) {
   cout <<
   "                                                                         \n"
   "Places a **qual spine into humdrum data which analyzes the chord quality \n"
   "of **kern spines.  Currently, input spines cannot split or join.         \n"
   "                                                                         \n"
   "                                                                         \n"
   "Usage: " << command << "[-x][-t|-r|-i] [input1 [input2 ...]][output]     \n"
   "                                                                         \n"
   "Options:                                                                 \n"
   "   -x = extract the **qual analysis spine to output only                 \n"
   "   --options = list of all options, aliases and default values           \n"
   "                                                                         \n"
   << endl;
}



// md5sum: 759127396ae981f3d7fa1c0b51899cf8 harmanal.cpp [20050403]