//
// Programmer:    Craig Stuart Sapp <craig@ccrma.stanford.edu> Creation Date:
// Mon Jan 26 08:53:14 PST 2004
// Last Modified: Fri Apr  9 19:02:18 PDT 2004 (rhythmic feature extraction)
// Last Modified: Mon Apr 12 14:05:52 PDT 2004 (adding more rhythm features)
// Last Modified: Fri Apr 16 18:01:03 PDT 2004 (added directory processing)
// Last Modified: Sun Aug  8 18:44:36 PDT 2004 (added more rhythm features)
// Last Modified: Thu Oct 30 10:38:19 PST 2008 (closedir fix for OSX)
// Last Modified: Wed Jul  1 16:11:07 PDT 2009 (added polyphonic extraction)
// Filename:      ...museinfo/examples/all/themebuilderx.cpp
// Web Address:   http://sig.sapp.org/examples/museinfo/humdrum/themebuilderx.cpp
// Syntax:        C++; museinfo
//
// Description:   Converts Humdrum files into themefinder search index records.
//
// Classical Thema index order:
// is: [Zz] { # : % } j J M
//
// Meaning of the tracer symbols:
//  [Zz] = major/minor key 
//  {    = 12-tone interval
//  #    = pitch refined contour
//  :    = pitch gross contour
//  %    = scale degree
//  }    = musical interval
//  j    = 12-tone pitch
//  J    = absolute pitch
//  M    = metric description
// Added rhythmic markers:
//  ~    = duration gross contour
//  ^    = duration refined contour
//  !    = duration (IOI)
//  &    = beat level
//  @    = metric gross contour
//  `    = metric refined contour
//  '    = metric level
//

#include "humdrum.h"

#ifndef OLDCPP
   #include <iostream>
   #include <fstream>
   using namespace std;
#else
   #include <iostream.h>
   #include <fstream.h>
#endif

// includes needed for file/directory processing:
#include <sys/types.h>
#include <dirent.h>
#include <sys/stat.h>
#include <string.h>

class ISTN {
   public:
      char *istn;
      char *filename;
      ISTN(void) { istn = NULL; filename = NULL; }
      ISTN(ISTN& anISTN) {
         int len;
         if (anISTN.istn == NULL) {
            istn = NULL;
         } else {
            len = strlen(anISTN.istn);
            istn = new char[len+1];
            strcpy(istn, anISTN.istn);
         }
         if (anISTN.filename == NULL) {
            filename = NULL;
         } else {
            len = strlen(anISTN.filename);
            filename = new char[len+1];
            strcpy(filename, anISTN.filename);
         }
      }

      void print(void) {
         if (istn != NULL) {
            cout << istn;
         } else {
            cout << ".";
         }
         cout << "\t";
         if (filename != NULL) {
            cout << filename;
         } else {
            cout << ".";
         }
         cout << "\n";
      }

      int is_valid(void) {
         if (filename == NULL) return 0;
         if (istn == NULL) return 0;
         return 1;
      }

      void setFilename(const char* string) {
         if (filename != NULL) {
            delete [] filename;
         }
         filename = new char[strlen(string)+1];
         strcpy(filename, string);
      }

      void setIstn(const char* string) {
         if (istn != NULL) {
            delete [] istn;
         }
         istn = new char[strlen(string)+1];
         strcpy(istn, string);
      }

      ISTN& operator=(ISTN& anISTN) {
         if (this == &anISTN) return *this;
         clear();
         int len;
         if (anISTN.istn == NULL) {
            istn = NULL;
         } else {
            len = strlen(anISTN.istn);
            istn = new char[len+1];
            strcpy(istn, anISTN.istn);
         }
         if (anISTN.filename == NULL) {
            filename = NULL;
         } else {
            len = strlen(anISTN.filename);
            filename = new char[len+1];
            strcpy(filename, anISTN.filename);
         }
         return *this;
      }
     ~ISTN() { clear(); }
      void clear(void) { 
         if (istn != NULL) delete [] istn;
         if (filename != NULL) delete [] filename;
         istn = NULL;
         filename = NULL;
      }
};
   

// function declarations:
void      checkOptions(Options& opts, int argc, char** argv);
void      example(void);
void      usage(const char* command);
void      createIndex(HumdrumFile& hfile, const char* filename);
void      createIndexEnding(HumdrumFile& hfile, int index);
void      extractPitchSequence(Array<int>& pitches, HumdrumFile& hfile,
                                  int track);
void      extractDurationSequence(Array<double>& durations, HumdrumFile& hfile,
                                  int track);
void      extractMetricSequence(Array<double>& metriclevel, 
                                  HumdrumFile& hfile, int track);
void      getKey(HumdrumFile& hfile, int& mode, int& tonic);
void      printKey(int mode, int tonic);
void      printMeter(HumdrumFile& hfile);

// pitch sequence printing:
void      printPitch(Array<int>& pitches);
void      printGrossContour(Array<int>& pitches);
void      printRefinedContour(Array<int>& pitches);
void      print12toneInterval(Array<int>& pitches);
void      print12tonePitch(Array<int>& pitches);
void      printScaleDegree(Array<int>& pitches, int tonic);
void      printMusicalInterval(Array<int>& pitches);

// rhythm sequence printing:
void      printGrossContourRhythm(Array<double>& durations);
void      printRefinedContourRhythm(Array<double>& durations);
void      printMetricLevel(Array<double>& levels);
void      printMetricRefinedContour(Array<double>& levels);
void      printMetricGrossContour(Array<double>& levels);
void      printBeatLevel(Array<double>& levels);
void      printDuration(Array<double>& levels);

void      extractFeatureSet(const char* features);
int       is_directory           (const char* path);
int       is_file                (const char* path);
void      processArgument(const char* path);
void      fillIstnDatabase(Array<ISTN>& istndatabase, 
                                  const char* istnfile);
const char* getIstn(const char* filename);


// User interface variables:
Options     options;
int         debugQ  = 0;       // used with --debug option
int         polyQ   = 0;       // used with --poly option
int         rhythmQ = 0;       // used with -r option
int         pitchQ  = 1;       // used with -p option
int         extraQ  = 1;       // used with -E option
int         limitQ  = 0;       // used with -l option
int         limit   = 20;      // used with -l option
int         istnQ   = 0;       // used with --istn option
const char* istnfile= "";      // used with --istn option
Array<ISTN> istndatabase;      // used with --istn option

#define PSTATESIZE 128
int pstate[PSTATESIZE] = {0};   // true for printing of particular feature
#define pHumdrumFormat          0
//pitch printing states:
#define pPitch                  1
#define pGrossContour           2
#define pRefinedContour         3
#define p12toneInterval         4
#define p12tonePitch            5
#define pScaleDegree            6
#define pMusicalInterval        7
// rhythm printing states:
#define pDurationGrossContour   8
#define pDurationRefinedContour 9
#define pDuration              10
#define pBeat                  11
#define pMetricLevel           12
#define pMetricRefinedContour  13
#define pMetricGrossContour    14


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

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

   int i;
   int numinputs = options.getArgCount();
   HumdrumFile hfile;

   for (i=0; i<numinputs || i==0; i++) {
      hfile.clear();

      // if no command-line arguments read data file from standard input
      if (numinputs < 1) {
         hfile.read(cin);
         createIndex(hfile, "");
      } else {
         processArgument(options.getArg(i+1));
      }
   }

   return 0;
}


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


//////////////////////////////
//
// processArgument -- check if the argument is a file or a directory.
//    if a directory, then process all files/subdirectories in it.
//

void processArgument(const char* path) {
   HumdrumFile hfile;
   DIR* dir = NULL;
   char* fullname;
   struct dirent* entry;
   int namelen = 0;
   int valid = 0;

   if (is_file(path)) {
      namelen = strlen(path);
      valid = 0;
      if (strcmp(&(path[namelen-4]), ".thm") == 0) {
         valid = 1;
      } else if (strcmp(&(path[namelen-4]), ".krn") == 0) {
         valid = 1;
      } else if (strcmp(&(path[namelen-4]), ".THM") == 0) {
         valid = 1;
      } else if (strcmp(&(path[namelen-4]), ".KRN") == 0) {
         valid = 1;
      }
      if (!valid) {
         return;
      }
      hfile.read(path);
      createIndex(hfile, path);
   } else if (is_directory(path)) {
      dir = opendir(path);
      if (dir == NULL) {
         return;
      }
      entry = readdir(dir);
      while (entry != NULL) {
         if (strncmp(entry->d_name, ".", 1) == 0) {
            entry = readdir(dir);
            continue;
         }

         fullname = new char[strlen(path) + 1 + strlen(entry->d_name) + 1];
         strcpy(fullname, path);
         strcat(fullname, "/");
         strcat(fullname, entry->d_name);
         processArgument(fullname);
         entry = readdir(dir);
      }
   }

   if (dir != NULL) {
      closedir(dir);
   }
}


//////////////////////////////
//
// is_file -- returns true if the string is a file.
//

int is_file(const char* path) {
   struct stat filestat;
   stat(path, &filestat);
   return S_ISREG(filestat.st_mode);
}



//////////////////////////////
//
// is_directory -- returns true if the string is a directory.
//

int is_directory(const char* path) {
   struct stat filestat;
   stat(path, &filestat);
   return S_ISDIR(filestat.st_mode);
}



//////////////////////////////
//
// getIstn --
//

const char* getIstn(const char* filename) {
   int i;
   const char* output = NULL;
   for (i=0; i<istndatabase.getSize(); i++) {
      if (strcmp(istndatabase[i].filename, filename) == 0) {
         output = istndatabase[i].istn;
         break;
      }
   }

   if (output == NULL) {
      output = filename;
   }

   return output;
}



//////////////////////////////
//
// createIndex -- The classical fixed order for the thema command
// is: [Zz] { # : % } j J M
//

void createIndex(HumdrumFile& hfile, const char* filename) {

   int i;
   int maxtracks = hfile.getMaxTracks();
   if (polyQ) {
      for (i=1; i<=maxtracks; i++) {
         if (strcmp("**kern", hfile.getTrackExInterp(i)) != 0) {
            continue;
         }
         if (istnQ) {
            cout << getIstn(filename);
         } else {
            cout << filename;
         }
         cout << "::" << i;
         if (strcmp("**kern", hfile.getTrackExInterp(i)) == 0) {
            createIndexEnding(hfile, i);
            cout << "\n";
         }
      }
   } else {
      if (istnQ) {
         cout << getIstn(filename);
      } else {
         cout << filename;
      }
      for (i=1; i<=maxtracks; i++) {
         if (strcmp("**kern", hfile.getTrackExInterp(i)) == 0) {
            createIndexEnding(hfile, i);
            cout << "\n";
            break;
	 }
      }
   }
}



//////////////////////////////
//
// createIndexEnding -- The classical fixed order for the thema command
// is: [Zz] { # : % } j J M
//


void createIndexEnding(HumdrumFile& hfile, int index) {
   Array<int>    pitches;
   Array<double> durations;
   Array<double> metriclevels;
   extractPitchSequence(pitches, hfile, index);

   int mode = 0;
   int tonic = 2;
   getKey(hfile, mode, tonic);

   if (extraQ) {
      cout << '\t';	printKey(mode, tonic);
   }

   if (pstate[p12toneInterval]) {
      cout << '\t';	print12toneInterval(pitches);
   }

   if (pstate[pRefinedContour]) {
      cout << '\t';	printRefinedContour(pitches);
   }

   if (pstate[pGrossContour]) {
      cout << '\t';	printGrossContour(pitches);
   } 

   if (pstate[pScaleDegree]) {
      cout << '\t';	printScaleDegree(pitches, tonic);
   }

   if (pstate[pMusicalInterval]) {
      cout << '\t';	printMusicalInterval(pitches);
   }

   if (pstate[p12tonePitch]) {
      cout << '\t';	print12tonePitch(pitches);
   }

   if (pstate[pPitch]) {
      cout << '\t';	printPitch(pitches);
   }

   if (extraQ) {
      cout << '\t';	printMeter(hfile);
   }


   if (rhythmQ) {
      extractDurationSequence(durations, hfile, index);
      extractMetricSequence(metriclevels, hfile, index);
      if (pstate[pDurationGrossContour]) {
         cout << '\t';	printGrossContourRhythm(durations);
      }
   
      if (pstate[pDurationRefinedContour]) {
         cout << '\t';	printRefinedContourRhythm(durations);
      }
   
      if (pstate[pDuration]) {
         cout << '\t';	printDuration(durations);
      }
   
      if (pstate[pBeat]) {
         cout << '\t';	printBeatLevel(metriclevels);
      }
   
      if (pstate[pMetricLevel]) {
         cout << '\t';	printMetricLevel(metriclevels);
      }
   
      if (pstate[pMetricRefinedContour]) {
         cout << '\t';	printMetricRefinedContour(metriclevels);
      }
   
      if (pstate[pMetricGrossContour]) {
         cout << '\t';	printMetricGrossContour(metriclevels);
      }
   }
}



//////////////////////////////
//
// printGrossContourRhythm --
//

void printGrossContourRhythm(Array& durations) {
   int i;
   cout << "~";
   for (i=1; i<durations.getSize(); i++) {
      if (durations[i] - durations[i-1] > 0) {
         cout << '>';
      } else if (durations[i] - durations[i-1] < 0) {
         cout << '<';
      } else {
         cout << '=';
      }
   }
}



//////////////////////////////
//
// printRefinedContourRhythm --
//

void printRefinedContourRhythm(Array& durations) {
   int i;
   cout << "^";
   double value;
   for (i=1; i<durations.getSize(); i++) {
      if (durations[i-1] == 0.0) {
         if (durations[i] == 0.0) {
            cout << "=";
         } else {
            cout << "]";
         }
      } else {
         value = durations[i]/durations[i-1];
         if (value > 2.0)       { cout << "]"; }
         else if (value > 1.0)  { cout << ">"; }
         else if (value == 1.0) { cout << "="; }
         else if (value >= 0.5) { cout << "<"; }
         else if (value < 0.5)  { cout << "["; }
         else                   { cout << "X"; }
      }
   }
}

/* Old definition(a ratio between adjacent notes)

void printRefinedContourRhythm(Array& durations) {
   int i;
   cout << "^";
   double value;
   int ivalue;
   for (i=1; i<durations.getSize(); i++) {
      if (durations[i-1] != 0.0) {
         value = durations[i]/durations[i-1];
         ivalue = (int)(value * 1000.0 + 0.5);
      } else {
         ivalue = 100000;
      }
      cout << ivalue << " ";
   }
}

*/


//////////////////////////////
//
// printDuration --
//

void printDuration(Array& durations) {
   int i;
   int j;
   int k;
   int len;
   char buffer[128] = {0};
   cout << "!";
   int count;
   for (i=0; i<durations.getSize(); i++) {
      Convert::durationToKernRhythm(buffer, durations[i]);
      if (durations[i] > 0 && (buffer[0] == 'q')) {
         count = (int)durations[i];
         for (j=0; j<count; j++) {
            cout << "4";
         }
         if (durations[i] - count > 0) {
            Convert::durationToKernRhythm(buffer, durations[i]-count);
         }
	 len = strlen(buffer);
	 for (k=0; k<len; k++) {
            if (buffer[k] == '.') {
               cout << "d";
            } else {
               cout << buffer[k];
            }
         }
         cout << " ";
         
      } else {
	 len = strlen(buffer);
	 for (k=0; k<len; k++) {
            if (buffer[k] == '.') {
               cout << "d";
            } else {
               cout << buffer[k];
            }
         }
         cout << " ";
      }
   }
}



//////////////////////////////
//
// printMetricLevel --
//

void printMetricLevel(Array& levels) {
   int i;
   cout << "\'";
   double value;
   int ivalue;
   for (i=0; i<levels.getSize(); i++) {
      value = levels[i];
      ivalue = (int)value;
      if (ivalue > 0) {
         cout << "p";
      } else if (ivalue < 0) {
         cout << "m";
         ivalue = -ivalue;
      }
      cout << ivalue << " ";
   }
}



//////////////////////////////
//
// printMetricRefinedContour --
//

void printMetricRefinedContour(Array& levels) {
   int i;
   cout << "`";
   double value;
   int ivalue;
   double bvalue;
   int bivalue;
   int zvalue;
   for (i=1; i<levels.getSize(); i++) {
      value = levels[i];
      ivalue = (int)value;
      bvalue = levels[i-1];
      bivalue = (int)bvalue;
      zvalue = ivalue - bivalue;
      if (zvalue > 1) {
         cout << "H";
      } else if (zvalue == 1) {
         cout << "h";
      } else if (zvalue == 0) {
         cout << "S";
      } else if (zvalue == -1) {
         cout << "w";
      } else if (zvalue < -1) {
         cout << "W";
      } else {
         cout << "x";
      }
   }
}



//////////////////////////////
//
// printMetricGrossContour --
//

void printMetricGrossContour(Array& levels) {
   int i;
   cout << "@";
   double value;
   int ivalue;
   double bvalue;
   int bivalue;
   int zvalue;
   for (i=1; i<levels.getSize(); i++) {
      value = levels[i];
      ivalue = (int)value;
      bvalue = levels[i-1];
      bivalue = (int)bvalue;
      zvalue = ivalue - bivalue;
      if (zvalue > 0) {
         cout << "H";
      } else if (zvalue < 0) {
         cout << "W";
      } else {
         cout << "S";
      }
   }
}



//////////////////////////////
//
// printBeatLevel --
//

void printBeatLevel(Array& levels) {
   int i;
   cout << "&";
   double value;
   int ivalue;
   for (i=0; i<levels.getSize(); i++) {
      value = levels[i];
      ivalue = (int)value;
      if (ivalue >= 0) {
         cout << 1;
      } else {
         cout << 0;
      }
   }
}




//////////////////////////////
//
// printMusicalInterval --
//

void printMusicalInterval(Array& pitches) {
   cout << '}';  // print the musical interval identification marker
   int octave;
   int interval;
   int degree;
   int direction;
   int i;
   for (i=1; i<pitches.getSize(); i++) {
      interval = pitches[i] - pitches[i-1];
      if (interval < 0) {
         direction = -1;
         interval = -interval;
      } else {
         direction = +1;
      }
      octave   = interval / 40;
      degree   = Convert::base40ToDiatonic(interval+2)+1 + octave * 7;
      if (degree != 1) {
         if (direction < 0) {
            cout << 'x';
         } else {
            cout << 'X';
         }
      }

      int accidental = Convert::base40ToAccidental(interval+2);
      switch ((degree-1) % 7) {
         case 0:   // 1st
            switch (direction * abs(accidental)) {
               case -2:  cout << "dd"; break;
               case -1:  cout << "d";  break;
               case  0:  cout << "P";  break;
               case +1:  cout << "A";  break;
               case +2:  cout << "AA"; break;
            }
            break;
         case 3:   // 4th
         case 4:   // 5th
            switch (accidental) {
               case -2:  cout << "dd"; break;
               case -1:  cout << "d";  break;
               case  0:  cout << "P";  break;
               case +1:  cout << "A";  break;
               case +2:  cout << "AA"; break;
            }
            break;
         case 1:   // 2nd
         case 2:   // 3rd
         case 5:   // 6th
         case 6:   // 7th
            switch (accidental) {
               case -3:  cout << "dd"; break;
               case -2:  cout << "d";  break;
               case -1:  cout << "m";  break;
               case  0:  cout << "M";  break;
               case +1:  cout << "A";  break;
               case +2:  cout << "AA"; break;
            }
      }
      cout << degree;
   }
}



//////////////////////////////
//
// printScaleDegree --
//

void printScaleDegree(Array<int>& pitches, int tonic) {
   cout << '%';  // print the scale-degree identification marker
   int i;
   for (i=0; i<pitches.getSize(); i++) {
      cout << Convert::base40ToDiatonic(pitches[i]-tonic+2+40)+1;
   }
}



//////////////////////////////
//
// printMeter -- 
//

void printMeter(HumdrumFile& hfile) {
   int i;
   int top;
   int bottom;
   int count = 0;
   cout << "M";
   for (i=0; i<hfile.getNumLines(); i++) {
      if (hfile[i].isInterpretation()) {
         if (hfile[i][0][1] != 'M') {
            continue;
         }
         if (!isdigit(hfile[i][0][2])) {
            if (strcmp("*MX", hfile[i][0]) == 0) {
               cout << "irregular";
               return;
            }
            continue;
         }
         count = sscanf(hfile[i][0], "*M%d/%d", &top, &bottom);
         if (count != 2) {
            continue;
         }
         cout << &(hfile[i][0][2]);
         switch (top) {
            case 4:
            case 12:
               cout << "quadruple";
               break;
            case 3:
            case 9:
               cout << "triple";
               break;
            case 2:
            case 6:    
               cout << "duple";       
               break;
            default:   cout << "irregular";
         }
         switch (top) {
            case 6:
            case 9:
            case 12:
            case 16:
               cout << "compound";
               break;
            case 1:
            case 2:
            case 3:
            case 4:
            case 5:
               cout << "simple";
               break;
         }
         break;
      }
   }
}



//////////////////////////////
//
// printKey --
//

void printKey(int mode, int tonic) {
   char buffer[128] = {0};

   if(tonic < 0) {
      // unknown key
      cout << "ZX=";
      return;
   }

   if(mode) {
      cout << 'z';   // minor
   } else {
      cout << 'Z';   // major
   }

   cout << Convert::base40ToKern(buffer, tonic + 3*40);
   cout << '=';
}



//////////////////////////////
//
// getKey --
//

void getKey(HumdrumFile& hfile, int& mode, int& tonic) {

   tonic = 2;  // C
   mode  = 0;  // major

   int i;
   int length;
   for (i=0; i<hfile.getNumLines(); i++) {
      if (hfile[i].isInterpretation()) {
         length = strlen(hfile[i][0]);
         if (length < 2 || length > 5) {
            continue;
         }
         if (hfile[i][0][length-1] != ':') {
            continue;
         }
         if (!isalpha(hfile[i][0][1])) {
            continue;
         }
         if (islower(hfile[i][0][1])) {
            mode = 1;  // minor
         } else {
            mode = 0;  // major
         }
         tonic = Convert::kernToBase40(&(hfile[i][0][1]));
         if (tonic >= 0) {
            tonic = tonic % 40;
         }
         if (hfile[i][0][1] == 'x' || hfile[i][0][1] == 'X') {
            tonic = -1;   // unknown key
         }
         break;

      }
   }
}



//////////////////////////////
//
// print12tonePitch --
//

void print12tonePitch(Array<int>& pitches) {
   cout << 'j';  // print the 12-tone pitch class identification marker
   int i;
   int midi;
   for (i=0; i<pitches.getSize(); i++) {
      midi = Convert::base40ToMidiNoteNumber(pitches[i]) % 12;
      if (midi < 10) {
         cout << midi;
      } else if (midi == 10) {
         cout << 'A';
      } else if (midi == 11) {
         cout << 'B';
      } else {
         cout << 'X';
      }
   }
}



//////////////////////////////
//
// print12toneInterval --
//

void print12toneInterval(Array<int>& pitches) {
   Array<int> midi(pitches.getSize());
   cout << '{';  // print the refined-contour identification marker
   int i;
   for (i=0; i<pitches.getSize(); i++) {
      midi[i] = Convert::base40ToMidiNoteNumber(pitches[i]);
   }

   for (i=1; i<midi.getSize(); i++) {
      if (midi[i] > midi[i-1]) {
         cout << 'p' << midi[i] - midi[i-1];
      } else if (midi[i] < midi[i-1]) {
         cout << 'm' << midi[i-1] - midi[i];
      } else {
         cout << "p0";
      }
   }
}



//////////////////////////////
//
// printRefinedContour -- augmented second is assigned to be a step
//

void printRefinedContour(Array& pitches) {
   int i;
   cout << '#';  // print the refined-contour identification marker

   for (i=1; i<pitches.getSize(); i++) {
      if (pitches[i] < pitches[i-1]) {
         if (pitches[i-1] - pitches[i] < 9) {
            cout << 'd';
         } else {
            cout << 'D';
         }
      } else if (pitches[i] > pitches[i-1]) {
         if (pitches[i] - pitches[i-1] < 9) {
            cout << 'u';
         } else {
            cout << 'U';
         }
      } else {
         cout << 's';
      }
   }
}



//////////////////////////////
//
// printGrossContour --
//

void printGrossContour(Array& pitches) {
   int i;
   cout << ':';  // print the gross-contour identification marker

   for (i=1; i<pitches.getSize(); i++) {
      if (pitches[i] < pitches[i-1]) {
         cout << 'D';
      } else if (pitches[i] > pitches[i-1]) {
         cout << 'U';
      } else {
         cout << 'S';
      }
   }
}



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

void printPitch(Array& pitches) {
   int i;
   int j;
   char buffer[128] = {0};
   cout << 'J';  // print the pitch identification marker
   for (i=0; i<pitches.getSize(); i++) {
      Convert::base40ToKern(buffer, (pitches[i] % 40) + 3 * 40);
      j = 0;
      while (buffer[j] != '\0') {
         if (buffer[j] == '-') {
            cout << 'b';
         } else {
            cout << buffer[j];
         }
         j++;
      }
      cout << " ";
      // when not printing a terminal " ":
      //if (i < pitches.getSize()-1) {
      //   cout << " ";
      //}
   }
}



//////////////////////////////
//
// extractMetricSequence --
//

void extractMetricSequence(Array<double>& metriclevels, HumdrumFile& hfile,
      int track) {
   Array<int> metlev;
   hfile.analyzeMetricLevel(metlev);

   int i, j;
   metriclevels.setSize(metlev.getSize());

   metriclevels.setSize(1000);
   metriclevels.setSize(0);
   metriclevels.allowGrowth();

   double level;
   for (i=0; i<hfile.getNumLines(); i++) {
      switch (hfile[i].getType()) {
         case E_humrec_data:
            for (j=0; j<hfile[i].getFieldCount(); j++) {
               if (hfile[i].getPrimaryTrack(j) != track) {
                  continue;
               }
               if (strcmp(hfile[i][j], ".") == 0) {
                  // ignore null tokens
                  break;
               }
               if (strchr(hfile[i][j], '_') != NULL) {
                  // ignore continuing ties
                  break;
               }
               if (strchr(hfile[i][j], ']') != NULL) {
                  // ignore ending ties
                  break;
               }
               if (strchr(hfile[i][j], 'r') != NULL) {
                  // ignore rests
                  break;
               }
               level = -(double)metlev[i];
               metriclevels.append(level);
               break;
            }
            break;
      }
   }

}



//////////////////////////////
//
// extractDurationSequence --
//

void extractDurationSequence(Array<double>& durations, HumdrumFile& hfile, 
      int track) {
   durations.setSize(10000);
   durations.setSize(0);
   durations.allowGrowth();
   double dur = 0;
   int i, j;
   int pitch;

   for (i=0; i<hfile.getNumLines(); i++) {
      switch (hfile[i].getType()) {
         case E_humrec_none:
         case E_humrec_empty:
         case E_humrec_bibliography:
         case E_humrec_global_comment:
         case E_humrec_data_comment:
         case E_humrec_interpretation:
         case E_humrec_data_kern_measure:
            break;
         case E_humrec_data:
            for (j=0; j<hfile[i].getFieldCount(); j++) {
               if (hfile[i].getPrimaryTrack(j) != track) {
                  continue;
               }
               if (strcmp(hfile[i][j], ".") == 0) {
                  // ignore null tokens
                  break;
               }
               if (strchr(hfile[i][j], '_') != NULL) {
                  // ignore continuing ties
                  break;
               }
               if (strchr(hfile[i][j], ']') != NULL) {
                  // ignore ending ties
                  break;
               }
               if (strchr(hfile[i][j], 'r') != NULL) {
                  // ignore rests
                  break;
               }
               pitch = Convert::kernToBase40(hfile[i][j]);
               if ((pitch < 0) || (pitch > 10000)) {
                  // ignore rests and other strange things
                  break;
               }
               dur = hfile.getTiedDuration(i, j);
               durations.append(dur); 
               if (limitQ) {
                  if (durations.getSize() >= limit) {
                     return;
                  }
               }
               break;
            }
            break;
         default:
            break;
      }
   }
}




//////////////////////////////
//
// extractPitchSequence --
// restrictions:
//   (1) **kern data expected is track being searched
//   (2) chords will be ignored, only first note in chord will be processed.
//

void extractPitchSequence(Array& pitches, HumdrumFile& hfile, int track) {
   pitches.setSize(10000);
   pitches.setGrowth(10000);
   pitches.setSize(0);
   pitches.allowGrowth();
   int pitch = 0;
   int i, j;

   for (i=0; i<hfile.getNumLines(); i++) {
      switch (hfile[i].getType()) {
         case E_humrec_none:
         case E_humrec_empty:
         case E_humrec_bibliography:
         case E_humrec_global_comment:
         case E_humrec_data_comment:
         case E_humrec_interpretation:
         case E_humrec_data_kern_measure:
            break;
         case E_humrec_data:
            for (j=0; j<hfile[i].getFieldCount(); j++) {
               if (hfile[i].getPrimaryTrack(j) != track) {
                  continue;
               }
		           
               if (strcmp(hfile[i][j], ".") == 0) {
                  // ignore null tokens
                  break;
               }
               if (strchr(hfile[i][j], '_') != NULL) {
                  // ignore continuing ties
                  break;
               }
               if (strchr(hfile[i][j], ']') != NULL) {
                  // ignore ending ties
                  break;
               }
               if (strchr(hfile[i][j], 'r') != NULL) {
                  // ignore rests
                  break;
               }
               pitch = Convert::kernToBase40(hfile[i][j]);
               if ((pitch < 0) || (pitch > 10000)) {
                  // ignore rests and other strange things
                  break;
               }
               pitches.append(pitch); 
               if (limitQ) {
                  if (pitches.getSize() >= limit) {
                     return;
                  }
               }
               break;
            }
            break;
         default:
            break;
      }
   }

}



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

void checkOptions(Options& opts, int argc, char* argv[]) {
   opts.define("debug=b",        "print debug information"); 
   opts.define("poly=b",         "create poliphonic"); 
   opts.define("E|no-extra=b",   "do nto print extra information");
   opts.define("r|rhythm=b",     "extract rhythm information"); 
   opts.define("p|pitch=b",      "extract pitch information"); 
   opts.define("P|not-pitch=b",  "do not extract pitch information"); 
   opts.define("a|all=b",        "extract all possible musical features");
   opts.define("H|humdrum=b",    "format output as a humdrum file");
   opts.define("f|features=s",   "extract the list of features");
   opts.define("t|istn|translate=s", "translation file which contains istn values");
   opts.define("l|limit=i:20",   "limit the number of extracted features");

   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, Jan 2004" << endl;
      exit(0);
   } else if (opts.getBoolean("version")) {
      cout << argv[0] << ", version: 26 Jan 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);
   }
   
   debugQ      = opts.getBoolean("debug");
   polyQ       = opts.getBoolean("poly");
   rhythmQ     = opts.getBoolean("rhythm");
   pitchQ      = 1;
   extraQ      = !opts.getBoolean("no-extra");
   limitQ      = opts.getBoolean("limit");
   limit       = opts.getInteger("limit");
   istnQ       = opts.getBoolean("istn");
   istnfile    = opts.getString("istn");

   if (istnQ) {
      fillIstnDatabase(istndatabase, istnfile);
   }

   if (rhythmQ) {
      pitchQ = opts.getBoolean("pitch");
   }

   if (opts.getBoolean("all")) {
      rhythmQ = 1;
      pitchQ  = 1;
   }

   if (options.getBoolean("not-pitch")) {
      pitchQ = 0;
   }

   if (!opts.getBoolean("humdrum")) {
      pstate[pHumdrumFormat] = 1;
   }

   if (opts.getBoolean("features")) {
      extractFeatureSet(opts.getString("features"));
   } else {
      // set up the printing options.
      pstate[pGrossContour]           = pitchQ;
      pstate[pRefinedContour]         = pitchQ;
      pstate[p12toneInterval]         = pitchQ;
      pstate[p12tonePitch]            = pitchQ;
      pstate[pScaleDegree]            = pitchQ;
      pstate[pMusicalInterval]        = pitchQ;
      pstate[pPitch]                  = pitchQ;
      pstate[pDurationGrossContour]   = rhythmQ;
      pstate[pDurationRefinedContour] = rhythmQ;
      pstate[pDuration]               = rhythmQ;
      pstate[pBeat]                   = rhythmQ;
      pstate[pMetricLevel]            = rhythmQ;
      pstate[pMetricRefinedContour]   = rhythmQ;
      pstate[pMetricGrossContour]     = rhythmQ;
   }
}



//////////////////////////////
//
// extractFeatureSet --
//
// Pitch Abbreviations
//    P        = Pitch
//    GC, PGC  = GrossContour
//    RC, PRC  = RefinedContour
//    12I      = 12toneInterval
//    12P      = 12tonePitch
//    SD, S, D = ScaleDegree
//    MI, I    = MusicalInterval
// Rhythm Abbreviations:
//    RGC      = RhythmGrossContour
//    RRC      = RhythmRefinedContour
//    IOI      = Duration
//    BLV      = BeatLevel
//    MLV      = Metric Level
//    MLI      = Metric Interval
//

void extractFeatureSet(const char* features) {
   char *buffer;
   int length = strlen(features);
   buffer = new char[length+1];
   int i;
   for (i=0; i<length; i++) {
      buffer[i] = toupper(features[i]);
   }
   buffer[length] = '\0';

   char* ptr = strtok(buffer, " :;-\n\t");
   while (ptr != NULL) {
      if (strcmp(ptr, "GC") == 0)       { pstate[pGrossContour]           = 1; }
      else if (strcmp(ptr, "PGC") == 0) { pstate[pGrossContour]           = 1; }
      else if (strcmp(ptr, "RC" ) == 0) { pstate[pRefinedContour]         = 1; }
      else if (strcmp(ptr, "PRC") == 0) { pstate[pRefinedContour]         = 1; }
      else if (strcmp(ptr, "12I") == 0) { pstate[p12toneInterval]         = 1; }
      else if (strcmp(ptr, "12P") == 0) { pstate[p12tonePitch]            = 1; }
      else if (strcmp(ptr, "SD" ) == 0) { pstate[pScaleDegree]            = 1; }
      else if (strcmp(ptr, "S"  ) == 0) { pstate[pScaleDegree]            = 1; }
      else if (strcmp(ptr, "D"  ) == 0) { pstate[pScaleDegree]            = 1; }
      else if (strcmp(ptr, "MI" ) == 0) { pstate[pMusicalInterval]        = 1; }
      else if (strcmp(ptr, "I"  ) == 0) { pstate[pMusicalInterval]        = 1; }
      else if (strcmp(ptr, "RGC") == 0) { pstate[pDurationGrossContour]   = 1; }
      else if (strcmp(ptr, "RRC") == 0) { pstate[pDurationRefinedContour] = 1; }
      else if (strcmp(ptr, "IOI") == 0) { pstate[pDuration]               = 1; }
      else if (strcmp(ptr, "DUR") == 0) { pstate[pDuration]               = 1; }
      else if (strcmp(ptr, "BLV") == 0) { pstate[pBeat]                   = 1; }
      else if (strcmp(ptr, "MLV") == 0) { pstate[pMetricLevel]            = 1; }
      else if (strcmp(ptr, "MLI") == 0) { pstate[pMetricRefinedContour]   = 1; }
      else if (strcmp(ptr, "MGC") == 0) { pstate[pMetricGrossContour]     = 1; }
      else if (strcmp(ptr, "P"  ) == 0) { pstate[pPitch]                  = 1; }

      ptr = strtok(NULL, " :;-\n\t");
   }
}



//////////////////////////////
//
// fillIstnDatabase --
//

void fillIstnDatabase(Array& istndatabase, const char* istnfile) {
   HumdrumFile infile;
   infile.read(istnfile);
   int i, j;
   ISTN entry;
   istndatabase.setSize(infile.getNumLines());
   istndatabase.setSize(0);
   for (i=0; i<infile.getNumLines(); i++) {
      if (infile[i].getType() != E_humrec_data) {
         continue;
      }
      entry.clear();
      for (j=0; j<infile[i].getFieldCount(); j++) {
         if (strcmp(infile[i].getExInterp(j), "**theme") == 0) {
            entry.setFilename(infile[i][j]);
         } else if (strcmp(infile[i].getExInterp(j), "**istn") == 0) {
            entry.setIstn(infile[i][j]);
         }
      }
      if (entry.is_valid()) {
         istndatabase.append(entry);
      }
   }

   istndatabase.allowGrowth(0);
}



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

void example(void) {


}



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

void usage(const char* command) {

}



// md5sum: 25731f3e277fe117c81cb4d43ef94b10 themebuilderx.cpp [20090525]