//
// Programmer:    Craig Stuart Sapp <craig@ccrma.stanford.edu>
// Creation Date: Tue Dec 18 11:01:22 PST 2001
// Last Modified: Wed Aug 20 22:37:23 PDT 2003 (add field option)
// Last Modified: Thu Aug 21 19:20:02 PDT 2003 (added **kotov conversion)
// Last Modified: Tue Aug 26 22:54:00 PDT 2003 (added spine manipulators)
// Filename:      ...sig/examples/all/koto2kern.cpp
// Web Address:   http://sig.sapp.org/examples/museinfo/humdrum/koto2kern.cpp
// Syntax:        C++; museinfo
//
// Description:   Convert **koto representation to **kern 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   convertKoto(HumdrumFile& infile);
void   processKotoData(HumdrumRecord& line);
void   processKotoDatum(const char* string, HumdrumRecord& line);
void   processInterpretation(HumdrumRecord& line);
void   storeTuning(const char* tunestring);
void   printRhythm(const char* string, double scaling = 1.0);
void   printPitch(const char* string, HumdrumRecord& line);
void   printKernNotes(const char* string, HumdrumRecord& line);

// command line options:
Options    options;         // database for command-line arguments
int        debugQ    = 0;   // for debugging options --debug
int        skeletonQ = 0;   // convert only basic musical ornaments
int        appendQ   = 0;   // for use with the -a option
int        field     = 0;   // for use with the -f option


Array<int> tuning;	   // for koto tuning


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

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();
      // if no command-line arguments read data file from standard input
      if (numinputs < 1) {
         infile.read(cin);
      } else {
         infile.read(options.getArg(i+1));
      }
      convertKoto(infile);
   }

   return 0;
}


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



//////////////////////////////
//
// convertKoto -- 
//

void convertKoto(HumdrumFile& infile) {
   int i;
   int spine;
   int scount;
   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"; 
            }
            scount = 0;
            for (spine=0; spine<infile[i].getFieldCount(); spine++) {
               if (infile[i].getPrimaryTrack(spine)-1 != field) {
                  continue;
               }
               scount++;
               if (scount > 1) {
                  cout << "\t";
               }
               cout << infile[i][spine];
            }
            cout << "\n";

            break;
         case E_humrec_interpretation:
            processInterpretation(infile[i]);
            break;
         case E_humrec_data:
            processKotoData(infile[i]);
            break;
         default:
            cout << "!!" << infile[i] << "\n";
            break;
      }
   }

}



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

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

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


   int spine;
   int scount = 0;
   for (spine=0; spine<line.getFieldCount(); spine++) {
      if (line.getPrimaryTrack(spine)-1 != field) {
         continue;
      }
      scount++;
      
      if (scount > 1) {
         cout << "\t";
      }

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

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

      if (strcmp(line[spine], "*free") == 0) {
         cout << "*free";
         continue;
      }

      if (strcmp(line[spine], "*strict") == 0) {
         cout << "*strict";
         continue;
      }

      cout << line[spine];
   }
   cout << "\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]);
         }
      }
   }

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

}



//////////////////////////////
//
// processKotoData --
//

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

   int spine;
   int i;
   int tokencount;
   int scount = 0;
   for (spine=0; spine<line.getFieldCount(); spine++) {
      if (line.getPrimaryTrack(spine) - 1 != field) {
         continue;
      }
      scount++;
      if (scount > 1) {
         cout << "\t";
      }

      if (strncmp(line[spine], "-", 1) == 0) {
         cout << ".";
         continue;
      }

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

   

//////////////////////////////
//
// processKotoDatum --
//

void processKotoDatum(const char* string, HumdrumRecord& line) {

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

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

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

   printKernNotes(string, line);

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

}



//////////////////////////////
//
// printKernNotes --
//

void printKernNotes(const char* string, HumdrumRecord& line) {
   printPitch(string, line);
}



//////////////////////////////
//
// printRhythm --
//

void printRhythm(const char* string, double scaling) {
   int pluscount = 0;
   int dotcount = 0;
   int pipecount = 0;

   int i;
   int length = strlen(string);

   // print a rhythm only if there is a string number
   // also allowed:  W, Z, and z
   int stringQ = 0;
   for (i=0; i<length; i++) {
      if (isdigit(string[i])) {
         stringQ = 1;
         break;
      } else if (string[i] == 'A' || string[i] == 'B' || string[i] == 'C'
            || string[i] == 'D' || string[i] == 'E' || 
            string[i] == 'W' || string[i] == 'Z' || string[i] == 'z') {
         stringQ = 1;
         break;
      }
   }
   
   if (stringQ == 0) {
      return;
   }


   for (i=0; i<length; i++) {
      if (string[i] == '+') {
         pluscount++;
      }
      if (string[i] == '|') {
         pipecount++;
      }
      if (string[i] == '.') {
         dotcount++;
      }
   }

   if (pluscount == 0 && pipecount == 0) {
      cout << (int)(4 / scaling);
   } else if (pluscount > 0) {
      if (pluscount == 2) {
         cout << "2.";
      } else {
         cout << (int)(4 / scaling) / (pluscount+1);
      }
   } else if (pipecount > 0) {
      cout << (int)(4 / scaling) * pow(2.0, pipecount);
   } else {
      cout << "RHYTHM_ERROR";
   }

   for (i=0; i<dotcount; i++) {
      cout << ".";
   }

}



//////////////////////////////
//
// printPitch -- print the pitch related to this line.
//   Added sha arpeggio Wed Aug  7 15:03:33 PDT 2002
//   Added wa glissando Tue Aug 26 18:54:23 PDT 2003
//

void printPitch(const char* string, HumdrumRecord& line) {
   double scaling = 1;  // rhythmic scaling for oshi tome hanashi, etc.
   int i, j;
   int pvalue = 0;
   int length = strlen(string);
   int index = 0;
   char buffer[32] = {0};
   int sharpcount = 0;
   int sharpcount2 = 0;
   for (i=0; i<length; i++) {
      if (string[i] == '#') {
         sharpcount++;
      }
   }

   // check for higher octave in vocal part
   int octave = 0;
   if (strchr(string, '^') != NULL) {
      octave = 1;
   }


   if (!skeletonQ) {
      if (strchr(string, 'o')) {   // oshi tome is present
         scaling = 0.5;
      }
   }

   printRhythm(string, scaling);


   for (i=0; i<length; i++) {
      if (isxdigit(string[i])) {
         if (string[i] == 'a' || string[i] == 'b' || string[i] == 'c'
              || string[i] == 'd' || string[i] == 'e' || string[i] == 'f' ||
              string[i] == 'F') {
            continue;
         } else {
            if (isdigit(string[i])) {
               index = string[i] - '0' - 1;
            } else {
               index = string[i] - 'A' - 1 + 10;
            }
            if (index < 0) {
               cout << "r";
            } else {
               pvalue = tuning[index];
               Convert::base40ToKern(buffer, pvalue + octave * 40);
               length = strlen(buffer);
               for (j=0; j<length; j++) {
                  if (buffer[j] == '#') {
                     sharpcount2++;
                  }
               }
               if (sharpcount == 1 && sharpcount2 == 1) {
                  pvalue = pvalue - sharpcount2 + 6;
               } else if (sharpcount == 2 && sharpcount2 == 1) {
                  pvalue = pvalue - sharpcount2 + 7;
               } else if (sharpcount == 3 && sharpcount2 == 0) {
                  pvalue = pvalue + 11;
               } else if (sharpcount == 1 && sharpcount2 == 0) {
                  pvalue = pvalue + 1;
               } else if (sharpcount == 2 && sharpcount2 == 0) {
                  pvalue = pvalue + 6;
               } else if (sharpcount + sharpcount2 > 3) {
                  cout << "PERROR";
               }
// cout << ">" << sharpcount << "," << sharpcount2 << "<";
               cout << Convert::base40ToKern(buffer, pvalue + octave * 40);
               if (!skeletonQ) {
                  if (strchr(string, 's') != NULL) {
                     // print sha ornament
                     cout << ": ";
                     if (index + 1 < tuning.getSize()) {
                        pvalue = tuning[index + 1];
                     }
                     printRhythm(string, scaling);
                     cout << Convert::base40ToKern(buffer, pvalue + octave * 40);
                     cout << ":";
                  } else if (strchr(string, 'o') != NULL) {
                     // oshi tome: increase the current tone by a whole step
                     cout << "H\n"; // beginning of glissando
                     if (appendQ) {
                        for (i=0; i<line.getFieldCount(); i++) {
                           cout << ".\t";
                        }
                     }
                     printRhythm(string, scaling);
                     cout << Convert::base40ToKern(buffer, pvalue+6 + octave * 40);
                     cout << "h";  // end of glissando
                  }
               } // end !skeletonQ
            }
            return;
            
         }
      }
   } 

   // check for ura-ren (wa) arpeggio on strings 1,2,3,4:
   if (strchr(string, 'W') != 0) {
      cout << Convert::base40ToKern(buffer, tuning[0]);
      cout << ": ";
      printRhythm(string, scaling);
      cout << Convert::base40ToKern(buffer, tuning[1]);
      cout << ": ";
      printRhythm(string, scaling);
      cout << Convert::base40ToKern(buffer, tuning[2]);
      cout << ": ";
      printRhythm(string, scaling);
      cout << Convert::base40ToKern(buffer, tuning[3]);
      cout << ":";
      return;
   }

   // nothing, so the previous note must be continuing
   if (strcmp(string, ".") == 0) {
      cout << ".";
   } else {
      cout << "x";   // don't know or missing a string number
   }
}



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

void checkOptions(Options& opts, int argc, char* argv[]) {
   opts.define("a|append=b", "append conversion to input data");
   opts.define("s|skeleton|b|basic=b", "convert only basic ornaments");
   opts.define("f|field=i:1", "field to convert");

   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 2001" << endl;
      exit(0);
   } else if (opts.getBoolean("version")) {
      cout << argv[0] << ", version: Dec 2001" << 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");
   skeletonQ = opts.getBoolean("skeleton");
   debugQ    = opts.getBoolean("debug");
   field     = opts.getInteger("field") - 1;
   if (field < 0) {
      field = 0;
   }
}



//////////////////////////////
//
// 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: 129d8e3b3ef4ff13e5fd4c904b890d9a koto2kern.cpp [20050403]