//
// Programmer:    Craig Stuart Sapp <craig@ccrma.stanford.edu>
// Creation Date: Sat Apr 28 04:46:58 PDT 2001
// Last Modified: Sat Apr 28 04:47:03 PDT 2001
// Filename:      ...sig/examples/all/kern2notelist.cpp
// Web Address:   http://sig.sapp.org/examples/museinfo/humdrum/kern2notelist.cpp
// Syntax:        C++; museinfo
//
// Description:   Converts kern data into the format used in 
//                Director Musices, a program that genrates
// 		  computer-based performances of musical scores.
// 

#include "humdrum.h"

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


// function declarations
void   checkOptions(Options& opts, int argc, char* argv[]);
void   example(void);
void   usage(const char* command);

void convertToDM(HumdrumFile& infile);
void processTrack(HumdrumFile& infile, int track);
void convertKernNoteToDM(HumdrumFile& infile, int line, int spine, int track);
void printNote(int base40);
void printFractionDuration(double duration);
int  parseInterpretation(HumdrumFile& infile, int line, int track);
void addExtraStuff(void);
void printPitchClass(int note);
void getNoteArray(Array<int>& notes, HumdrumFile& infile, int line, int spine);

// user interface variables
Options      options;            // database for command-line arguments

// other variables:
int key = -1;      // for storing the key  (base 40 number)
int nameinit = 0;
char name[128] = {0};
int bar = 0;       // bar number
int mode = -1;     // for storing the mode (0 = major, 1 = minor)
float metronome = -1.0;  // 
int metertop = -1;  // 
int meterbottom = -1; //
int start = -1;
int terminus = -1;

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

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

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

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

   Array<double> absbeat;
   Array<int> pitch;
   Array<int> testset;
   Array<double> duration;
   Array<double> level;
   Array<double> coef;

   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));
      }
      infile.analyzeRhythm();
      convertToDM(infile);
   }

   return 0;
}


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


//////////////////////////////
//
// convertToDM -- convert kern spines into Director musices format.
//

void convertToDM(HumdrumFile& infile) {
   for (int i=infile.getMaxTracks()-1; i>= 0; i--) {
      // set global state variables to uninitialized
      key = -1;
      nameinit = 0;
      name[0] = '\0';
      mode = -1;
      metronome = -1;
      metertop = -1;
      meterbottom = -1;
      bar = 0;

      processTrack(infile, i+1);
   }
}



//////////////////////////////
//
// processTrack -- print out the specified track
//

void processTrack(HumdrumFile& infile, int track) {
   int status;
   int i, j;
   for (i=0; i<infile.getNumLines(); i++) {
      if (start > i && track != infile.getMaxTracks()) {
         // skip over global comments after the first time:
         i = start;
      }
      if (terminus < i && terminus != -1 && track != 1) {
         // skip over global comments after the first time:
         break;
      }
      switch (infile[i].getType()) {
         case E_humrec_data:
            for (j=0; j<infile[i].getFieldCount(); j++) {
               if (infile[i].getPrimaryTrack(j) == track) {
                  convertKernNoteToDM(infile, i, j, track);
                  break;  // don't process second part of split track
               }
            }
            break;
         case E_humrec_bibliography:
            cout << ";;;" << (char*)&infile[i][0][3] << "\n";
            break;
         case E_humrec_global_comment:
            cout << ";;" << (char*)&infile[i][0][2] << "\n";
            break;
         case E_humrec_data_comment:
            for (j=0; j<infile[i].getFieldCount(); j++) {
               if (infile[i].getPrimaryTrack(j) == track) {
                  cout << ";" << (char*)&infile[i][j][1] << "\n";
                  break;  // don't process second part of split track
               }
            }
            break;
         case E_humrec_data_measure:
            break;
         case E_humrec_interpretation:
            status = parseInterpretation(infile, i, track);
            if (status == 0) {
               return;
            }
            if (status == 1) {
               cout << "mono-track\n";     // print the start marker of a track
            }
         default: ;
      }
   }
}



//////////////////////////////
//
// parseInterpretation --
//

int parseInterpretation(HumdrumFile& infile, int line, int track) {
   int i;
   int status = 2;
   int length = 0;
   if ((terminus == -1) && (strcmp(infile[line][0], "*-") == 0)) {
      terminus = line;
   }
   for (i=0; i<infile[line].getFieldCount(); i++) {
      if (infile[line].getPrimaryTrack(i) == track) {
         length = strlen(infile[line][i]);
         if (strncmp(infile[line][i], "**", 2) == 0) {
            if (start == -1) {
               start = line;
            }
            if (strcmp(infile[line][i], "**kern") != 0) {
               // don't translate non-musical data.
               return 2;
            } else {
               status = 1;
            }
         } else if (infile[line][i][0] == '*' && 
               infile[line][i][length-1] == ':') {
            key = Convert::kernToBase40(infile[line][i]);
            if (isupper(infile[line][i][1])) {
               mode = 0;
            } else if (islower(infile[line][i][1])) {
               mode = 1;
            } else {
               mode = -1;
            }
         } else if (strncmp(infile[line][i], "*I", 2) == 0) {
            if (islower(infile[line][i][2])) {
               strcpy(name, &infile[line][i][2]);
            }
         } else if (strncmp(infile[line][i], "*MM", 3) == 0) {
            sscanf(infile[line][i], "*MM%f", &metronome);
         } else if (strncmp(infile[line][i], "*M", 2) == 0) {
            sscanf(infile[line][i], "*M%d/%d", &metertop, &meterbottom);
         }
      }
   }
   return status;
}



//////////////////////////////
//
// convertKernNoteToDM --
//

void convertKernNoteToDM(HumdrumFile& infile, int line, int spine, int track) {
   const char* element = infile[line][spine];
   double duration;
   Array<int> notes;
   if (strcmp(element, ".") == 0) {
      return;
   }
   if (infile[line].getExInterpNum(spine) != E_KERN_EXINT) {
      return;
   }
   if (nameinit == 0 && name[0] == '\0') {
      cout << " :trackname \"track " << track << "\"\n";
      nameinit = 1;
   } else if (nameinit == 0) {
      cout << " :trackname \"" << name << "\"\n";
      nameinit = 1;
   }
   cout << " (";
   if (infile[line].getBeat() == 1.0) {
      bar++;
      cout << "bar " << bar << " ";
   }
   duration = Convert::kernToDuration(infile[line][spine]);
   if (strchr(element, 'r') != NULL) {
      cout << "n (nil ";
      printFractionDuration(duration);
      cout << ") rest t";
      addExtraStuff();
      cout << ")\n";
      return;
   }
   getNoteArray(notes, infile, line, spine);
   int notecount = notes.getSize();
   cout << "n (";
   if (notecount > 1) {
      cout << "(";
   } 
   for (int i=0; i<notecount; i++){
      printNote(notes[i]);
      if (i < notecount - 1) {
         cout << " ";
      }
   }
   if (notecount > 1) {
      cout << ")";
   } 
   cout << " ";
   printFractionDuration(duration);
   cout << ")";
   if (strchr(infile[line][spine], '[') != NULL) {
      cout << " tie t";
   } else if (strchr(infile[line][spine], '_') != NULL) {
      cout << " tie t";
   }
   addExtraStuff();
   cout << ")\n";
}



//////////////////////////////
//
// getNoteArray --
//

void getNoteArray(Array& notes, HumdrumFile& infile, int line, int spine){
   int notecount = infile[line].getTokenCount(spine);
   char buffer[128] = {0};
   notes.setSize(0);
   int note;
   notes.allowGrowth(1);
   for (int i=0; i<notecount; i++) {
      infile[line].getToken(buffer, spine, i, 128);
      note = Convert::kernToBase40(buffer);
      notes.append(note);
   }
   notes.allowGrowth(0);

}


//////////////////////////////
//
// addExtraStuff --
//

void addExtraStuff(void) {
   if (key != -1) {
      cout << " key \"";
      printPitchClass(key);
      cout << "\"";
   }
   if (mode == 0) {
      cout << " modus \"maj\"";
   } else if (mode == 1) {
      cout << " modus \"min\"";
   }

   if (metronome > 0) {
      cout << " mm " << metronome;
   }

   if (metertop != -1) {
      cout << " meter (" << metertop << " " << meterbottom << ")";
   }

   metertop = -1;
   meterbottom = -1;
   mode = -1;
   metronome = -1;
   key = -1;
}



//////////////////////////////
//
// printFractionDuration --
//

void printFractionDuration(double duration) {
   duration = duration/4.0;
   double tolerance = 0.001;
   double frac = duration - (int)duration;
   int whole = (int)(duration - frac);
   if (fabs(whole - duration) < tolerance) {
      cout << whole;
      return;
   }
   int top = 1;
   int bottom;
   while (top<257) {
      for (bottom=2; bottom<257; bottom++) {
         if (fabs((double)top/bottom - frac) < tolerance) {
            cout << top + whole * bottom;
            cout << "/";
            cout << bottom;
            return;
         }
      }
      top++;
   }
}



//////////////////////////////
//
// printPitchClass --
//

void printPitchClass(int base40) {
   switch ((base40 % 40) - 2) {	
      case -2:  cout << "Cbb";	break; case -1:  cout << "Cb";	break;
      case 0:   cout << "C";	break; case 1:   cout << "C#";	break;
      case 2:   cout << "C##";	break; case 3:   cout << "X";	break;
      case 4:   cout << "Dbb";	break; case 5:   cout << "Db";	break;
      case 6:   cout << "D";	break; case 7:   cout << "D#";	break;
      case 8:   cout << "D##";	break; case 9:   cout << "X";	break;
      case 10:  cout << "Ebb";	break; case 11:  cout << "Eb";	break;
      case 12:  cout << "E";	break; case 13:  cout << "E#";	break;
      case 14:  cout << "E##";	break; case 15:  cout << "Fbb";	break;
      case 16:  cout << "Fb";	break; case 17:  cout << "F";	break;
      case 18:  cout << "F#";	break; case 19:  cout << "F##";	break;
      case 20:  cout << "X";	break; case 21:  cout << "Gbb";	break;
      case 22:  cout << "Gb";	break; case 23:  cout << "G";	break;
      case 24:  cout << "G#";	break; case 25:  cout << "G##";	break;
      case 26:  cout << "X";	break; case 27:  cout << "Abb";	break;
      case 28:  cout << "Ab";	break; case 29:  cout << "A";	break;
      case 30:  cout << "A#";	break; case 31:  cout << "A##";	break;
      case 32:  cout << "X";	break; case 33:  cout << "Bbb";	break;
      case 34:  cout << "Bb";	break; case 35:  cout << "B";	break;
      case 36:  cout << "B#";	break; case 37:  cout << "B##";	break;
      default: cout << "X";
   }
}



//////////////////////////////
//
// printNote --
//

void printNote(int base40) {
   cout << "\"";
   printPitchClass(base40);
   cout << base40/40;   // the octave number
   cout << "\"";
}



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

void checkOptions(Options& opts, int argc, char* argv[]) {
   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, April 2001" << endl;
      exit(0);
   } else if (opts.getBoolean("version")) {
      cout << argv[0] << ", version: 28 April 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);
   }
}



//////////////////////////////
//
// example -- example usage of the kern2dm program
//

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



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

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


// md5sum: de24ce4347d8c7467a806db14105547b kern2dm.cpp [20050403]