//
// Programmer:    Craig Stuart Sapp <craig@ccrma.stanford.edu>
// Creation Date: Mon May 20 14:21:40 PDT 2002
// Last Modified: Mon May 20 14:21:43 PDT 2002
// Filename:      ...sig/examples/all/kern2koto.cpp
// Web Address:   http://sig.sapp.org/examples/museinfo/humdrum/kern2koto.cpp
// Syntax:        C++; museinfo
//
// Description:   Convert **kern representation to **koto representation
// 
// Note: Not finished
// 

#include "humdrum.h"

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

// function declarations
void   checkOptions(Options& opts, int argc, char* argv[]);
void   example(void);
void   usage(const char* command);
void   convertKernToKoto(HumdrumFile& infile);
void   processKernData(HumdrumRecord& line);
void   processKernDatum(const char* string);
void   processInterpretation(HumdrumRecord& line);
void   storeTuning(const char* tunestring);
void   printRhythm(const char* string);
void   printPitch(const char* string);
void   printString(int stringno);
void   getKeyAndRange(HumdrumFile& infile, int& key, int& lownote, 
                              int& highnote, Array<double>& profile);
int    getTranspose(int key, int lownote, int highnote, 
                              Array<double>& profile);

// command line options:
Options    options;        // database for command-line arguments
int        debugQ = 0;     // for debugging options --debug
int        appendQ = 0;    // for use with -a option
int        automaticQ = 1; // for use with -A option

int transpose = 0;
int lownote = -1;
int highnote = -1;
int key = -1;
Array<double> profile(128);

Array<int> tuning;	   // for koto tuning

// Hira choshi:
//const char* defaultTuning = "*tune[d:G:A:A#:d:d#:g:a:a#:dd:dd#:gg:aa]";
// Synthetic Chinese tuning:
// const char* defaultTuning = "*tune[C#:E:F#:A:B:c#:e:f#:a:b:cc#:ee:ff#]";

const char* majorTuningC = "*tune[c:d:e:f:g:a:b:cc:dd:ee:ff:gg:aa]";
const char* majorTuningG = "*tune[c:d:e:f#:g:a:b:cc:dd:ee:ff#:gg:aa]";
const char* defaultTuning = majorTuningC;


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

int main(int argc, char* argv[]) {
   HumdrumFile infile;           // input Humdrum Format file
   tuning.setSize(13);
   tuning.allowGrowth(0);

   // 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++) {
      infile.clear();
      storeTuning(defaultTuning);
      // if no command-line arguments read data file from standard input
      if (numinputs < 1) {
         infile.read(cin);
      } else {
         infile.read(options.getArg(i+1));
      }
      if (automaticQ) {
         getKeyAndRange(infile, key, lownote, highnote, profile);
         if (debugQ) {
            cout << "Key is: " << key << endl;
            cout << "Low note is: " << lownote << endl;
            cout << "High note is: " << highnote << endl;
         }
         transpose = getTranspose(key, lownote, highnote, profile);
      }
      convertKernToKoto(infile);
   }

   return 0;
}


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


//////////////////////////////
//
// getTranspose --
//

int getTranspose(int key, int lownote, int highnote, Array& profile) {
   double center = 0.0;
   double norm = 0.0;
   int i;
   for (i=0; i<128; i++) {
      center += i * profile[i];
      norm += profile[i];
   }

   center = center / norm;
   int notecenter = (int)(center + 0.5);
   cout << "!! Key is: " << key << endl;
   cout << "!! Center pitch is: " << center << endl;
   cout << "!! Median center is: " << (highnote - lownote)/2.0 + lownote << endl;

   // find the relationship between the key and the note center
   int octave = notecenter / 12;
   int diff0 = (octave-1) * 12 + key - notecenter;
   int diff1 = octave * 12 + key - notecenter;
   int diff2 = (octave+1) * 12 + key - notecenter;
   int mode = 'G';

//   cout << "diff0 = " << diff0 << "\t"
//        << "diff1 = " << diff1 << "\t"
//        << "diff2 = " << diff2 << endl;

   if ((abs(diff0) <= abs(diff1)) && (abs(diff0) <= abs(diff2))) {
      octave = octave - 1;
      if (diff0 < 0) { // closest tonic below the center
         mode = 'C';
      } else { // closest tonic above the center
         mode = 'G';
      }
   } else if ((abs(diff1) <= abs(diff0)) && (abs(diff1) <= abs(diff2))) {
      if (diff1 < 0) { // closest tonic below the center
         mode = 'C';
      } else { // closest tonic above the center
         mode = 'G';
      }
   } else if ((abs(diff2) <= abs(diff0)) && (abs(diff2) <= abs(diff1))) {
      octave = octave + 1;
      if (diff2 < 0) { // closest tonic below the center
         mode = 'C';
      } else { // closest tonic above the center
         mode = 'G';
      }
   }

cout << "!! MODE = " << (char)mode << endl;

   int transpose = 0;
   if (mode == 'G') {
      storeTuning(majorTuningG);
      transpose = tuning[4] - (key + octave * 12);
      defaultTuning = majorTuningG;
   } else { 
      storeTuning(majorTuningC);
      transpose = tuning[7] - (key + octave * 12);
      defaultTuning = majorTuningC;
   }


//                       0 1 2 3  4 5 6 7  8  9  0  1  2
// majorTuningC = "*tune[c:d:e:f :g:a:b:cc:dd:ee:ff:gg:aa]";
// majorTuningG = "*tune[c:d:e:f#:g:a:b:cc:dd:ee:ff:gg:aa]";

   int lowerrors = 0;
   int hierrors = 0;
   for (i=0; i<profile.getSize(); i++) {
      if (profile[i] > 0) {
         if (i+transpose < tuning[0]) {
            lowerrors++;
         } else if (i+transpose > tuning[12]) {
            hierrors++;
         }
      }
   }
   if (lowerrors && mode == 'G') {
      mode = 'C';
      defaultTuning = majorTuningC;
      storeTuning(majorTuningC);
      transpose = tuning[7] - (key + octave * 12);
   }

cout << "!! Transpose = " << transpose << endl;

   return transpose;
}



//////////////////////////////
//
// getKeyAndRange --
//

void getKeyAndRange(HumdrumFile& infile, int& key, int& lownote, int& highnote,
      Array<double>& profile) {

   Array<int> pitches;
   pitches.setSize(infile.getNumLines()*2);
   pitches.setSize(0);
   Array<double> durations;
   durations.setSize(infile.getNumLines()*2);
   durations.setSize(0);

   profile.setSize(128);
   profile.setAll(0.0);
   int tokencount;
   char buffer[1024] = {0};
   int pitch = 0;
   double duration = 0.0;
   lownote = 127;
   highnote = 0;
   key = 0;
   int i, j;
   for (i=0; i<infile.getNumLines(); i++) {
      if (!infile[i].isData()) {
         continue;
      }
      if (strcmp(infile[i][0], ".") == 0) {
         continue;
      }
      if (strchr(infile[i][0], '_') != NULL) {
         continue;
      }
      if (strchr(infile[i][0], ']') != NULL) {
         continue;
      }
      tokencount = infile[i].getTokenCount(0);
      for (j=0; j<tokencount; j++) {
         infile[i].getToken(buffer, 0, j);
         pitch = Convert::kernToMidiNoteNumber(buffer);
         if (pitch < 0) {
            continue;
         }
         pitches.append(pitch);
         duration = Convert::kernToDuration(buffer);
         durations.append(duration);
         profile[pitch] += duration;
         if (pitch < lownote) lownote = pitch;
         if (pitch > highnote) highnote = pitch;
      }
   }

   Array<double> scores(24);
   Array<double> distribution(12);

   key = analyzeKeyKS(scores.getBase(), distribution.getBase(),
      pitches.getBase(), durations.getBase(), pitches.getSize(), 1, 1);

   // don't distinguish between major and minor for now:
   if (key >= 12) key = (key + 3) % 12; 
}



//////////////////////////////
//
// convertKernToKoto --
//

void convertKernToKoto(HumdrumFile& infile) {
   int i;
   for (i=0; i<infile.getNumLines(); i++) {
      switch (infile[i].getType()) {
         case E_humrec_none:
         case E_humrec_empty:
         case E_humrec_global_comment:
         case E_humrec_bibliography:
            cout << infile[i] << "\n";
            break;
         case E_humrec_data_comment:
            if (appendQ) {
               cout << infile[i] << "\t!\n";
            } else {
               cout << infile[i][0] << "\n";
            }
            break;
         case E_humrec_data_kern_measure:
            if (appendQ) {
               cout << infile[i] << "\t" << infile[i][0] << "\n";
            } else {
               cout << infile[i][0] << "\n";
            }
            break;
         case E_humrec_interpretation:
            processInterpretation(infile[i]);
            break;
         case E_humrec_data:
            processKernData(infile[i]);
            break;
         default:
            cout << "!!" << infile[i] << "\n";
            break;
      }
   }

}



//////////////////////////////
//
// processInterpretation --
//

void processInterpretation(HumdrumRecord& line) {
   if (appendQ) {
      cout << line << "\t";
   }

   if (strncmp(line[0], "**", 2) == 0) {
      cout << "**koto\n";
      cout << defaultTuning << endl;
      return;
   }

   if (strcmp(line[0], "*-") == 0) {
      cout << "*-\n";
      return;
   }

   if (strncmp(line[0], "*M", 2) == 0) {
      int top, bottom;
      int count = sscanf(line[0], "*M%d/%d", &top, &bottom);
      if (count == 2) {
         cout << line[0] << "\n";
      } else {
         cout << "*\n";
      }
      return;
   }

   if (strncmp(line[0], "*tune[", 6) == 0) {
      storeTuning(line[0]);
      if (appendQ) {
         cout << line[0] << "\n";
      }
      return;
   }

   if (appendQ) {
      cout << line[0] << "\n";
   }

}



///////////////////////////////
//
// storeTuning --
//

void storeTuning(const char* tunestring) {
   int length = strlen(tunestring);
   int i;
   int stringnum = 0;
   Array<int> info(13);
   for (i=0; i<13; i++) {
      info[i] = -1;
   }
   for (i=6; i<length-1; i++) {
      if (tunestring[i] == ':') {
         stringnum++;
      } else {
         if (info[stringnum] < 0) {
            // info[stringnum] = Convert::kernToBase40(&tunestring[i]);
            info[stringnum] = Convert::kernToMidiNoteNumber(&tunestring[i]);
         }
      }
   }

   for (i=0; i<13; i++) {
      if (info[i] > 0) {
         tuning[i] = info[i];
      }
   }

}



//////////////////////////////
//
// processKernData --
//

void processKernData(HumdrumRecord& line) {
   static char buffer[1024] = {0};
   if (appendQ) {
      cout << line << "\t";
   } 

   if (strncmp(line[0], "-", 1) == 0) {
      cout << ".\n";
      return;
   }

   int tokencount = line.getTokenCount(0);
   int i;
   for (i=0; i<tokencount; i++) {
      line.getToken(buffer, 0, i);
      processKernDatum(buffer);
      if (i < tokencount - 1) {
         cout << " ";
      }
   }
   cout << "\n";
}
   

   

//////////////////////////////
//
// processKernDatum --
//

void processKernDatum(const char* string) {

   if (strchr(string, '{') != NULL) {
      cout << '{';
   }

   if (strchr(string, '(') != NULL) {
      cout << '(';
   }

   if (strchr(string, '[') != NULL) {
      cout << '[';
   }

   printPitch(string);
   printRhythm(string);
   int duration = (int)Convert::kernToDuration(string);
   int i;
   for (i=1; i<duration; i++) {
      cout << endl << "-";
   }

   if (strchr(string, 'q') != NULL) {
      cout << 'q';
   }

   if (strchr(string, ':') != NULL) {
      cout << ':';
   }

   if (strchr(string, ';') != NULL) {
      cout << ';';
   }

   if (strchr(string, ']') != NULL) {
      cout << ']';
   }

   if (strchr(string, ')') != NULL) {
      cout << ')';
   }

   if (strchr(string, '}') != NULL) {
      cout << '}';
   }

}



//////////////////////////////
//
// printRhythm -- given a **kern rhythm, convert to **koto rhythm
//    not finalized, but good enough for now.
//

void printRhythm(const char* string) {
   int dotcount = 0;
   int i;
   int length = strlen(string);
   for (i=0; i<length; i++) {
      if (string[i] == '.') {
         dotcount++;
      }
   }
   double duration = Convert::kernToDuration(string);
   if (duration < 1.0) { 
      cout << "|";
   }
   if (duration < 0.5) { 
      cout << "|";
   }
   if (duration < 0.25) { 
      cout << "|";
   }
   if (duration < 0.125) { 
      cout << "|";
   }
   if (duration >= 2.0) {
      cout << "+";
   }
   if (duration >= 3.0) {
      cout << "+";
   }
   if (duration >= 4.0) {
      cout << "+";
   }
   for (i=0; i<dotcount; i++) {
      cout << ".";
   }
}



//////////////////////////////
//
// printPitch --
//

void printPitch(const char* string) {
   int pitch = Convert::kernToMidiNoteNumber(string);
   if (pitch < 0) {
      printString(0);
      return;
   }
   pitch += transpose;
   Array<int> difference(13);
   difference.setAll(0);
   int i;
   for (i=0; i<13; i++) {
      difference[i] = pitch - tuning[i];
      if (difference[i] == 0) {
         printString(i+1);
         return;
      }
   }

   for (i=0; i<13; i++) {
      if (difference[i] == 1) {
         printString(i+1);
         cout << "#";
         return;
      }
   }

   for (i=0; i<13; i++) {
      if (difference[i] == 2) {
         printString(i+1);
         cout << "##";
         return;
      }
   }

   for (i=0; i<13; i++) {
      if (difference[i] == 3) {
         printString(i+1);
         cout << "###";
         return;
      }
   }

   cout << "X";

}



//////////////////////////////
//
// printString --
//

void printString(int stringno) {
   if (stringno < 10) {
      cout << stringno;
      return;
   }
   if (stringno == 10) {
      cout << "A";
      return;
   }
   if (stringno == 11) {
      cout << "B";
      return;
   }
   if (stringno == 12) {
      cout << "C";
      return;
   }
   if (stringno == 13) {
      cout << "D";
      return;
   }
   
   cout << "X";
   
}



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

void checkOptions(Options& opts, int argc, char* argv[]) {
   opts.define("a|append=b", "append analysis");
   opts.define("A|no-automatic=b", "no automatic transposition");
   opts.define("t|tuning=s:", "tuning to use");

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

   appendQ    = opts.getBoolean("append");
   debugQ     = opts.getBoolean("debug");
   automaticQ = !opts.getBoolean("no-automatic");

   if (opts.getBoolean("tuning")) {
      defaultTuning = opts.getString("tuning");
   }
}



//////////////////////////////
//
// example -- example usage of the scrub program
//

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



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

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



// md5sum: 17930717fabc5d166decd3cf27940fea kern2koto.cpp [20050403]