//
// Programmer:    Craig Stuart Sapp <craig@ccrma.stanford.edu>
// Creation Date: Wed Jun 16 20:02:43 PDT 2004
// Last Modified: Wed Jun 16 20:02:46 PDT 2004
// Filename:      ...sig/examples/all/transfix.cpp
// Web Address:   http://sig.sapp.org/examples/museinfo/humdrum/transfix.cpp
// Syntax:        C++; museinfo
//
// Description:   Identify and transpose **kern musical data in parts
//                which need to be transposed.  Test algorithm for future
//                application to identify MuseData parts that need
//                transposition.
//

#include "humdrum.h"

#include <string.h>
#include <stdio.h>

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


// function declarations:
void      checkOptions(Options& opts, int argc, char** argv);
void      example(void);
void      usage(const char* command);
void      printFile(HumdrumFile& infile);
int       generateFieldList(Array<int>& fieldlist,const char* fieldstring);
void      processFile(HumdrumFile& infile);
void      printNewKernString(const char* string);
void      printHumdrumKernToken(HumdrumRecord& record, int index);
void      printHumdrumDataRecord(HumdrumRecord& record, 
                                 Array<int>& spineprocess);
int       getBase40ValueFromInterval(const char* string);
void      printAnalysis(HumdrumFile& infile, Array<int>& transamount);
void      determineTransposition(Array<int>& transamount, 
                                 Array<Array<int> >& keyarray);
void      analyzeKeys(HumdrumFile& infile, 
                                 Array<Array<int> >& keyarray);
void      analyzeKeyPart(HumdrumFile& infile, Array<int>& keyarray, 
                                 int tracknum, double hop, double window);
int       findStartBeatIndex(HumdrumFile& infile, double startbeat);
int       findStopBeatIndex(HumdrumFile& infile, double endbeat);

// User interface variables:
Options   options;
int       debugQ = 0;           // used with --debug option
int       transval = 0;         // used with -b option
const char* fieldstring = "";   // used with -f option
int       fieldQ = 0;           // used with -f option
double    hop    = 16.0;        // used with -h option
double    window = 16.0;        // used with -w option


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

int main(int argc, char** argv) {
   // process the command-line options
   checkOptions(options, argc, argv);

   HumdrumFile infile;
   infile.read(options.getArg(1));
   infile.analyzeRhythm("4");
   Array<Array<int> > keyarray;
   analyzeKeys(infile, keyarray);
   Array<int> transamount;
   determineTransposition(transamount, keyarray);
   printAnalysis(infile, transamount);

   return 0;
}

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



//////////////////////////////
//
// analyzeKeys -- check the key of the music at various regular intervals.
//

void analyzeKeys(HumdrumFile& infile, Array >& keyarray) { 
   int count = infile.getMaxTracks();
   if (count <= 0) {
      cout << "Error: not enough spines in Humdrum file: " << count << endl;
      exit(1);
   }
   keyarray.setSize(count);
   int i;
   for (i=0; i<count; i++) {
      analyzeKeyPart(infile, keyarray[i], i+1, hop, window);
   }
}



//////////////////////////////
//
// analyzeKeyPart -- extract the key analysis for one part
// -1 keyanalysis means not enough music in the region to analyze the key.
// 0-11 = Major keys 0=C 1=C#/Db 2=D, etc.
// 12-13 = Minor keys on same tonic: 0=c 1=c#/db, 2=d, etc.
//

void analyzeKeyPart(HumdrumFile& infile, Array<int>& keyarray, int tracknum,
   double hop, double window) {
   int framecount = (int)(infile[infile.getNumLines()-1].getAbsBeat() / hop);
   if (debugQ) {
      cout << "!!! Frame count = " << framecount << endl;
   }
   keyarray.setSize(framecount);
   keyarray.setSize(0);
   int keyanalysis = 0;
   Array<double> scores(24);
   scores.setAll(0);
   int startindex;
   int stopindex;
   double startbeat;
   double endbeat;
   int i;
   for (i=0; i<framecount; i++) {
     startbeat   = i * hop;
     endbeat     = startbeat + window;
     startindex  = findStartBeatIndex(infile, startbeat);
     stopindex   = findStartBeatIndex(infile, endbeat);
     keyanalysis = infile.analyzeKeyKS(scores, startindex, stopindex, 
                      1, 0, tracknum);
     keyarray.append(keyanalysis);
   }
}



//////////////////////////////
//
// findStartBeatIndex -- figure out which index into the Humdrum file
//  starts at the given absolute beat position  (or the first line after
//  that beat position).  Very simple algorithm with is not efficient
//  at all.
//

int findStartBeatIndex(HumdrumFile& infile, double startbeat) {
   int i;
   int output = -1;
   for (i=0; i<infile.getNumLines(); i++) { 
      // compensate for any rounding errors
      if ((infile[i].getAbsBeat()+0.0001) >= startbeat) {
         output = i;
         break; 
      }   
   }

   if (output == -1) {
      return infile.getNumLines()-1;
   } else {
      return output;
   }
}



//////////////////////////////
//
// findStopBeatIndex -- figure out which index into the Humdrum file
//  stops at the given absolute beat position.  Very simple algorithm 
//  which is not efficient at all.
//

int findStopBeatIndex(HumdrumFile& infile, double endbeat) {
   int i;
   int output = -1;
   for (i=infile.getNumLines()-1; i>=0; i++) { 
      // compensate for any rounding errors
      if ((infile[i].getAbsBeat()+0.0001) < endbeat) {
         output = i+1;
         break; 
      }   
   }

   if (output >= infile.getNumLines()) {
      output = infile.getNumLines()-1;
   }

   if (output == -1) {
      return infile.getNumLines()-1;
   } else {
      return output;
   }
}



//////////////////////////////
//
// determineTransposition --
//

void determineTransposition(Array<int>& transamount, 
      Array<Array<int> >& keyarray) { 
   cout << "GOT TO DETERMINE_TRANSPOSITION" << endl;

   transamount.setSize(keyarray.getSize());
   transamount.setAll(0);
   int i;
   int j;
   Array<int> histogram(48);
   histogram.setAll(0);
   int value = 0;
   if (debugQ) {
      cout << "!!! Key analyses for each part:\n";
      for (j=0; j<keyarray[0].getSize(); j++) {
         for (i=0; i<keyarray.getSize(); i++) {
            cout << keyarray[i][j];
            if (i < keyarray.getSize()-1) {
               cout << "\t";
            } else if (keyarray.getSize() >= 2) {
               cout << "\t";
               value = keyarray[0][j] - keyarray[1][j];
               cout << value;
               histogram[value+24]++;
            }
         }
         cout << "\n";
      }

      cout << "Histogram of difference between parts in spines 1 and 2:" <<endl;
      int max = 24;
      for (i=0; i<histogram.getSize(); i++) {
         cout << "delta " << i - 24 << "\t=\t" << histogram[i] << endl;
         // search for max in reasonable range
         if (i >= 12 && i <= 32) {
             if (histogram[i] > histogram[max]) {
                max = i;
             }
         }
      }
      cout << "Most likely transposition between parts is: " << max-24 << endl;
   }

}



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

void printAnalysis(HumdrumFile& infile, Array& transamount) { 
   // cout << "GOT TO PRINTANALYSIS" << endl;

}



//////////////////////////////
//
// processFile --  From old transposition function
//

void processFile(HumdrumFile& infile) {
   int i;
   Array<int> fieldlist;
   Array<int> spineprocess(infile.getMaxTracks());
   if (fieldQ) {
      spineprocess.setAll(0);
      generateFieldList(fieldlist, fieldstring);
      for (i=0; i<fieldlist.getSize(); i++) {
         if (fieldlist[i] < 1) {
            continue;
         }
         if (fieldlist[i]-1 < spineprocess.getSize()) {
            spineprocess[fieldlist[i]-1] = 1;
         }
      }
   } else {
      spineprocess.setAll(1);
   }

   for (i=0; i<infile.getNumLines(); i++) {
      switch (infile[i].getType()) {
         case E_humrec_data:
            printHumdrumDataRecord(infile[i], spineprocess);
            cout << "\n";
            break;
         case E_humrec_interpretation:
            // adjust *k[] values here.
         case E_humrec_none:
         case E_humrec_empty:
         case E_humrec_global_comment:
         case E_humrec_bibliography:
         case E_humrec_data_comment:
         case E_humrec_data_kern_measure:
         default:
            cout << infile[i] << "\n";
      }
   }
}



//////////////////////////////
//
// printHumdrumDataRecord --
//

void printHumdrumDataRecord(HumdrumRecord& record, Array& spineprocess) {
   int i;
   for (i=0; i<record.getFieldCount(); i++) {
      if (!spineprocess[record.getPrimaryTrack(i)-1]) {
         // don't try to transpose spines which were not indicated.
         cout << record[i];
         if (i<record.getFieldCount()-1) {
            cout << "\t";
         }
         continue;
      }
      if (record.getExInterpNum(i) != E_KERN_EXINT) {
         // don't try to transpose non-kern spines
         cout << record[i];
         if (i<record.getFieldCount()-1) {
            cout << "\t";
         }
         continue;
      }

      printHumdrumKernToken(record, i);
      if (i<record.getFieldCount()-1) {
         cout << "\t";
      }
      continue;
   }
   
}



//////////////////////////////
//
// printHumdrumKernToken --
//

void printHumdrumKernToken(HumdrumRecord& record, int index) {
   if (strcmp(record[index], ".") == 0) {
      // null record element (no pitch).
      cout << ".";
      return;
   }

   int k;
   static char buffer[1024] = {0};
   int tokencount = record.getTokenCount(index);

   for (k=0; k<tokencount; k++) {
      record.getToken(buffer, index, k);
      printNewKernString(buffer);
      if (k<tokencount-1) {
         cout << " ";
      }         
   }
}



//////////////////////////////
//
// printNewKernString --
//

void printNewKernString(const char* string) {
   if (strchr(string, 'r') != NULL) {
      cout << string;
      return;
   }

   char buffer[1024] = {0};
   char buffer2[1024] = {0};
   strcpy(buffer, string);
   int base40 = Convert::kernToBase40(string);
   char* ptr1 = strtok(buffer, "ABCDEFGabcdefg#-n");
   char* ptr2 = strtok(NULL, "ABCDEFGabcdefg#-n");
   char* ptr3 = strtok(NULL, "ABCDEFGabcdefg#-n");
   
   if (ptr3 != NULL) {
      cout << "Error in **kern pitch token: " << string << endl;
      exit(1);
   }

   if (ptr1 != NULL) {
      cout << ptr1;
   }
   cout << Convert::base40ToKern(buffer2, base40 + transval);
   if (ptr2 != NULL) {
      cout << ptr2;
   }
}



//////////////////////////////
//
// checkOptions -- 
//

void checkOptions(Options& opts, int argc, char* argv[]) {
   opts.define("b|base40=i:0", "The base-40 transposition value");
   opts.define("o|octave=i:0", "The octave addition to tranpose value");
   opts.define("f|field=s", "The base-40 transposition value");
   opts.define("t|transpose=s", "musical interval transposition value");

   opts.define("h|hop=d:16", "The window hop size for key analysis in beats");
   opts.define("w|window=d:16", "The window size for key analysis in beats");

   opts.define("debug=b",   "print debugging statements"); 
   opts.define("author=b",  "author of program"); 
   opts.define("version=b", "compilation info");
   opts.define("example=b", "example usages");   
   opts.define("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, 16 June 2004" << endl;
      exit(0);
   } else if (opts.getBoolean("version")) {
      cout << argv[0] << ", version: 16 June 2004" << 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);
   }

   transval    = opts.getInteger("base40");
   fieldstring = opts.getString("field");
   fieldQ      = opts.getBoolean("field");
   hop         = opts.getDouble("hop");
   window      = opts.getDouble("window");
   debugQ      = opts.getBoolean("debug");

   if (opts.getBoolean("transpose")) {
      transval = getBase40ValueFromInterval(opts.getString("transpose"));
   }

   transval += 40 * opts.getInteger("octave");
}



//////////////////////////////
//
// getBase40ValueFromInterval -- note: only ninth interval range allowed
//

int getBase40ValueFromInterval(const char* string) {
   int sign = 1;
   if (strchr(string, '-') != NULL) {
      sign = -1;
   }

   int length = strlen(string);
   char* buffer = new char[length+1];
   strcpy(buffer, string);
   int i;
   for (i=0; i<length; i++) {
      if (buffer[i] == 'p') { buffer[i] = 'P'; }
      if (buffer[i] == 'a') { buffer[i] = 'A'; }
      if (buffer[i] == 'D') { buffer[i] = 'd'; }
   }

   int output = 0;

   if (strstr(buffer, "dd1") != NULL)      { output = -2; }
   else if (strstr(buffer, "d1") != NULL)  { output = -1; }
   else if (strstr(buffer, "P1") != NULL)  { output =  0; }
   else if (strstr(buffer, "AA1") != NULL) { output =  2; }
   else if (strstr(buffer, "A1") != NULL)  { output =  1; }

   else if (strstr(buffer, "dd2") != NULL) { output =  3; }
   else if (strstr(buffer, "d2") != NULL)  { output =  4; }
   else if (strstr(buffer, "m2") != NULL)  { output =  5; }
   else if (strstr(buffer, "M2") != NULL)  { output =  6; }
   else if (strstr(buffer, "AA2") != NULL) { output =  8; }
   else if (strstr(buffer, "A2") != NULL)  { output =  7; }

   else if (strstr(buffer, "dd3") != NULL) { output =  9; }
   else if (strstr(buffer, "d3") != NULL)  { output = 10; }
   else if (strstr(buffer, "m3") != NULL)  { output = 11; }
   else if (strstr(buffer, "M3") != NULL)  { output = 12; }
   else if (strstr(buffer, "AA3") != NULL) { output = 14; }
   else if (strstr(buffer, "A3") != NULL)  { output = 13; }

   else if (strstr(buffer, "dd4") != NULL) { output = 15; }
   else if (strstr(buffer, "d4") != NULL)  { output = 16; }
   else if (strstr(buffer, "P4") != NULL)  { output = 17; }
   else if (strstr(buffer, "AA4") != NULL) { output = 19; }
   else if (strstr(buffer, "A4") != NULL)  { output = 18; }

   else if (strstr(buffer, "dd5") != NULL) { output = 21; }
   else if (strstr(buffer, "d5") != NULL)  { output = 22; }
   else if (strstr(buffer, "P5") != NULL)  { output = 23; }
   else if (strstr(buffer, "AA5") != NULL) { output = 25; }
   else if (strstr(buffer, "A5") != NULL)  { output = 24; }

   else if (strstr(buffer, "dd6") != NULL) { output = 26; }
   else if (strstr(buffer, "d6") != NULL)  { output = 27; }
   else if (strstr(buffer, "m6") != NULL)  { output = 28; }
   else if (strstr(buffer, "M6") != NULL)  { output = 29; }
   else if (strstr(buffer, "AA6") != NULL) { output = 31; }
   else if (strstr(buffer, "A6") != NULL)  { output = 30; }

   else if (strstr(buffer, "dd7") != NULL) { output = 32; }
   else if (strstr(buffer, "d7") != NULL)  { output = 33; }
   else if (strstr(buffer, "m7") != NULL)  { output = 34; }
   else if (strstr(buffer, "M7") != NULL)  { output = 35; }
   else if (strstr(buffer, "AA7") != NULL) { output = 37; }
   else if (strstr(buffer, "A7") != NULL)  { output = 36; }

   else if (strstr(buffer, "dd8") != NULL) { output = 38; }
   else if (strstr(buffer, "d8") != NULL)  { output = 39; }
   else if (strstr(buffer, "P8") != NULL)  { output = 40; }
   else if (strstr(buffer, "AA8") != NULL) { output = 42; }
   else if (strstr(buffer, "A8") != NULL)  { output = 41; }

   else if (strstr(buffer, "dd9") != NULL) { output = 43; }
   else if (strstr(buffer, "d9") != NULL)  { output = 44; }
   else if (strstr(buffer, "m9") != NULL)  { output = 45; }
   else if (strstr(buffer, "M9") != NULL)  { output = 46; }
   else if (strstr(buffer, "AA9") != NULL) { output = 48; }
   else if (strstr(buffer, "A9") != NULL)  { output = 47; }

   return output * sign;
}



//////////////////////////////
//
// example --
//

void example(void) {


}



//////////////////////////////
//
// usage --
//

void usage(const char* command) {

}




//////////////////////////////
//
// generateFieldList --
//

int generateFieldList(Array& fieldlist, const char* fieldstring) {
cout << "ENTERING GENERATEFIELD LIST" << endl;
   int maxfield = 0;
   int length = strlen(fieldstring);
   char* buffer = new char[length+1];
   strcpy(buffer, fieldstring);
   int starti, stopi;
   int temp;
   int num;
   char* ptr = strtok(buffer, " ,;:");
   while (ptr != NULL) {
      if (strchr(ptr, '-') != NULL) {
         sscanf(ptr, "%d-%d", &starti, &stopi);
         if (starti > stopi) {
            temp = starti;
            starti=stopi;
            stopi = temp;
         } 
         for (num=starti; num<=stopi; num++) {
            fieldlist.append(num);
         }
         if (num > maxfield) {
            maxfield = num;
         }
      } else {
         sscanf(ptr, "%d", &num);
         fieldlist.append(num);
         if (num > maxfield) {
            maxfield = num;
         }
      }
      ptr = strtok(NULL, " ,;:");
   }
cout << "LEAVING GENERATEFIELD LIST" << endl;
   return maxfield;
}



// md5sum: 45db19294a1d4f87b78ec94dcfa937c8 transfix.cpp [20050403]