//
// Programmer:    Craig Stuart Sapp <craig@ccrma.stanford.edu>
// Creation Date: Mon Jun 24 20:05:15 PDT 2002
// Last Modified: Thu Jul 11 14:48:23  2002
// Filename:      ...sig/examples/all/chordset.cpp
// Web Address:   http://sig.sapp.org/examples/museinfo/humdrum/chordset.cpp
// Syntax:        C++; museinfo
//
// Description:   Count and collect chords from **kern data using a
//                **root interpretation indicating the segmentation of
//                the chords.
// 

#include "humdrum.h"

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

template<class type> int arrayEqual(Array<type>& a, Array<type>& b);

class ChordEntry {
   public:
                 ChordEntry(void) { clear(); }
                ~ChordEntry()     { clear(); }
      void       clear(void)      { count = root = 0; pitches.setSize(0); }

      int        count;
      int        root;
      Array<int> pitches;
};

class ChordData : public SigCollection<ChordEntry> {
   public:
            ChordData(void) { clear(); }
           ~ChordData()     { clear(); }
      void  clear(void)     { setSize(10000); setGrowth(10000); setSize(0); }
      void  addData(int root, Array<int>& pitches);
      void  addDataNoRoot(Array<int>& pitches);
};

void ChordData::addData(int root, Array<int>& pitches) {
   ChordData& data = *this;
   int i;
   for (i=0; i<getSize(); i++) {
      if (arrayEqual(data[i].pitches, pitches) && data[i].root == root) {
         data[i].count++;
         return;
      }
   }
   
   // chord configuration not found -- add the data
   ChordEntry ce;
   ce.root = root;
   ce.pitches = pitches;
   ce.count = 1;
   append(ce);
}



void ChordData::addDataNoRoot(Array<int>& pitches) {
   ChordData& data = *this;
   int i;
   for (i=0; i<getSize(); i++) {
      if (arrayEqual(data[i].pitches, pitches)) {
         data[i].count++;
         return;
      }
   }
   
   // chord configuration not found -- add the data
   ChordEntry ce;
   ce.root = 0;
   ce.pitches = pitches;
   ce.count = 1;
   append(ce);
}



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

// function declarations
void   checkOptions(Options& opts, int argc, char* argv[]);
void   example(void);
void   usage(const char* command);
void   addToTable(ChordData& chordData, HumdrumFile& infile);
int    rootcompare(const void* a, const void* b);
void   extractChord(ChordData& chordData, HumdrumFile& infile, 
                                 int startline, int stopline, int root);
void   addToTable(ChordData& chordData, HumdrumFile& infile);
void   printTable(ChordData& chordData);
void   sortByCount(ChordData& chordData);
void   sortByRoot(ChordData& chordData);
void   sortByPitch(ChordData& chordData);
int    countcompare(const void* a, const void* b);
int    root2compare(const void* a, const void* b);
int    pitchcompare(const void* a, const void* b);

// global variables
Options      options;            // database for command-line arguments
int          debugQ     = 0;     // used with --debug option
int          sortType   = 0;     // used with -s option
int          rawQ       = 0;     // used with -r option
int          transposeQ = 0;     // used with -t option
int          intervalQ  = 0;     // used with -i option
int          uniqQ      = 0;     // used with -u option
int          beatQ      = 0;     // used with -b option
int          rootlessQ  = 0;     // used with -R option


const char* CurrentFile = ".";

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

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

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

   if (rawQ) {
      if (rootlessQ) {
         cout << "**file\t**line\t**root\t**kern\n";
      } else {
         cout << "**file\t**line\t**kern\n";
      }
   }

   // build up the chord table from the input file(s):
   int i;
   for (i=1; i<=options.getArgCount() || options.getArgCount()==0; i++) {
      infile.clear();

      // if no command-line arguments read data file from standard input
      if (options.getArgCount() < 1) {
         infile.read(cin);
      } else {
         infile.read(options.getArg(i));
         CurrentFile = options.getArg(i);
         if (strrchr(options.getArg(i), '/') != NULL) {
            CurrentFile = strrchr(options.getArg(i), '/') + 1;
         }
      }
     
      if (beatQ) {
         infile.analyzeRhythm();
      }
      addToTable(chordData, infile);
   }

   switch (sortType) {
      case 'c':   sortByCount(chordData); break;
      case 'r':   sortByRoot(chordData);  break;
      case 'p':   sortByPitch(chordData); break;
   }

   if (rawQ) {
      cout << "*-\t*-\t*-\t*-\n";
   } else {
      printTable(chordData);
   }
   return 0;
}


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




//////////////////////////////
//
// printTable --
//

void printTable(ChordData& chordData) {
   int i, j;
   char buffer[1024] = {0};
   int totalchords = 0;

   for (i=0; i<chordData.getSize(); i++) {
      totalchords += chordData[i].count;
   }

   cout << "!! Total Chords: " << totalchords << "\n";
   cout << "!! Unique Chords: " << chordData.getSize() << "\n";

   if (rootlessQ) {
      cout << "**count\t";
   } else {
      cout << "**count\t**root\t";
   }

   if (intervalQ) {
      cout << "**rooti\n";
   } else {
      cout << "**kern\n";
   }

   for (i=0; i<chordData.getSize(); i++) {
      cout << chordData[i].count << "\t";
      if (!rootlessQ) {
         cout << Convert::base40ToKern(buffer, chordData[i].root+3*40) << "\t";
      }
      if (chordData[i].pitches.getSize() == 0) {
         cout << ".";
      }
      for (j=0; j<chordData[i].pitches.getSize(); j++) {
         if (intervalQ) {
            cout << Convert::base40ToIntervalAbbr(buffer, (chordData[i].pitches[j] -2 + 400) %40);
         } else {
            cout << Convert::base40ToKern(buffer, chordData[i].pitches[j] + 4*40);
         }
         if (j < chordData[i].pitches.getSize()-1) {
            cout << " ";
         }
      }
      cout << "\n";
   }
   cout << "*-\t*-\t*-\n";
}



//////////////////////////////
//
// addToTable -- add Humdrum file chords to the chord table.
//

void addToTable(ChordData& chordData, HumdrumFile& infile) {
   int oldline = 0;
   int init = 0;
   int i, j;
   int root = -1;

   // go though the file and extract notes sets according to the
   // **root segmentation of the music:
   for (i=0; i<infile.getNumLines(); i++) {
      if (!infile[i].isData()) {
         continue;
      }
      for (j=0; j<infile[i].getFieldCount(); j++) {
         if (strcmp(infile[i].getExInterp(j), "**root") == 0) {
            if (strcmp(infile[i][j], ".") == 0) {
               break;
            } 
            if (strchr(infile[i][j], '_') != NULL) {  // cont. ties
               break;
            }
            if (strchr(infile[i][j], ']') != NULL) {  // tie ends
               break;
            }
            if (init == 0) {
               init = 1;
               oldline = i;
               root = Convert::kernToBase40(infile[i][j]) % 40;
               break;
            }
            extractChord(chordData, infile, oldline, i-1, root);
            oldline = i;
            root = Convert::kernToBase40(infile[i][j]) % 40;
            break;
         }
      }
      
   }
   extractChord(chordData, infile, oldline, i-1, root);
}



//////////////////////////////
//
// extractChord -- extract the chord region from the file and store in
//    the chordData structure.
//

void extractChord(ChordData& chordData, HumdrumFile& infile, 
      int startline, int stopline, int root) {

   // ignore roots which are rests
   if (root < 0) {
      return;
   }

   // extract note data from Humdrum file
   Array<double> absbeat;
   Array<int>    pitches;
   Array<double> durations;
   Array<double> levels;
   infile.getNoteArray(absbeat, pitches, durations, levels, startline,
      stopline);

   // extract just pitch class info, and ignore rests
   Array<int> npitches;
   npitches.setSize(pitches.getSize());
   npitches.setSize(0);
   int value = 0;

   int i;
   for (i=0; i<pitches.getSize(); i++) {
      if (pitches[i] < 0) {
         continue;
      }      
      value = pitches[i] % 40;
      npitches.append(value);
   }
   // ignore empty pitch sets
   if (npitches.getSize() == 0) {
      return;
   }

   if (transposeQ) {
      for (i=0; i<npitches.getSize(); i++) {
         npitches[i] = (npitches[i] - root + 2 + 400) % 40;
      }
   }

   // sort and store the chord entry
   qsort(npitches.getBase(), npitches.getSize(), sizeof(int), rootcompare);

   Array<int> nnpitches(npitches.getSize());
   if (uniqQ == 0) {
      if (rootlessQ) {
         chordData.addDataNoRoot(npitches);
      } else {
         chordData.addData(root, npitches);
      }
      nnpitches = npitches;
   } else {
      nnpitches.setSize(0);
      nnpitches.append(npitches[0]);
      for (i=1; i<npitches.getSize(); i++) {
         if (npitches[i] == npitches[i-1]) {
            continue;
         }
         nnpitches.append(npitches[i]);
      }
      if (rootlessQ) {
         chordData.addDataNoRoot(nnpitches);
      } else {
         chordData.addData(root, nnpitches);
      }
   }

   char buffer[128] = {0};

   if (rawQ) {
      cout << CurrentFile << "\t";
      if (beatQ) {
         cout << infile.getAbsBeat(startline) << "\t";
      } else {
         cout << startline+1 << "\t";
      }
      cout << Convert::base40ToKern(buffer, root+3*40) << "\t";
      for (i=0; i<nnpitches.getSize(); i++) {
         // cout << Convert::base40ToKern(buffer, nnpitches[i]+4*40);
         if (intervalQ) {
            cout << Convert::base40ToIntervalAbbr(buffer, (nnpitches[i] -2 + 400) %40);
         } else {
            cout << Convert::base40ToKern(buffer, nnpitches[i] + 4*40);
         }

         if (i < nnpitches.getSize() - 1) {
            cout << " ";
         }
      }
      cout << "\n";
   }

}



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

void checkOptions(Options& opts, int argc, char* argv[]) {
   opts.define("s|sort=s:n",      "sorting method");   
   opts.define("r|raw=b",         "raw chord data only");   
   opts.define("b|beat=b",        "display beat position instead of line no."); 
   opts.define("t|transpose=b",   "transpose pitch class root to c");   
   opts.define("R|rootless=b",    "do not display chord root spine");   
   opts.define("i|interval=b",    "display root inteval abbreviations");   
   opts.define("u|unique|uniq=b", "unique pitch class only");   

   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, July 2002" << endl;
      exit(0);
   } else if (opts.getBoolean("version")) {
      cout << argv[0] << ", version: Dec 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");
   sortType   = tolower(opts.getString("sort")[0]);
   uniqQ      = opts.getBoolean("unique");
   beatQ      = opts.getBoolean("beat");
   rawQ       = opts.getBoolean("raw");
   transposeQ = opts.getBoolean("transpose");
   intervalQ  = opts.getBoolean("interval");
   rootlessQ  = opts.getBoolean("rootless");
   if (intervalQ) {
      transposeQ = 1;
   }
}



//////////////////////////////
//
// example -- example usage of the maxent program
//

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



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

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



//////////////////////////////
//
// arrayEqual -- return true all of the array elements are equal 
//     in the same positions in each array.
//

template<class type>
int arrayEqual(Array& a, Array& b) {
   if (a.getSize() != b.getSize()) {
      return 0;
   }
   int i;
   for (i=0; i<a.getSize(); i++) {
      if (a[i] != b[i]) {
         return 0;
      }
   }
   return 1;
}



//////////////////////////////
//
// sortByCount -- sort the chord data by occurance count.
//

void sortByCount(ChordData& chordData) {
   qsort(chordData.getBase(), chordData.getSize(), sizeof(ChordEntry), 
         countcompare);
}



//////////////////////////////
//
// sortByRoot -- sort the chord data by root
//

void sortByRoot(ChordData& chordData) {
   qsort(chordData.getBase(), chordData.getSize(), sizeof(ChordEntry), 
         root2compare);
}



//////////////////////////////
//
// sortByPitch -- sort the chord data by pitch class
//

void sortByPitch(ChordData& chordData) {
   qsort(chordData.getBase(), chordData.getSize(), sizeof(ChordEntry), 
         pitchcompare);
}



//////////////////////////////
//
// root2compare -- 
//

int root2compare(const void* a, const void* b) {
   ChordEntry& A = *((ChordEntry*)a);
   ChordEntry& B = *((ChordEntry*)b);

   if (A.root < B.root) {
      return -1;
   } else if (A.root > B.root) {
      return 1;
   } else {
      return 0;
   }
}



//////////////////////////////
//
// countcompare -- 
//

int countcompare(const void* a, const void* b) {
   ChordEntry& A = *((ChordEntry*)a);
   ChordEntry& B = *((ChordEntry*)b);

   if (A.count < B.count) {
      return 1;
   } else if (A.count > B.count) {
      return -1;
   } else {
      return 0;
   }
}



//////////////////////////////
//
// picthcompare -- 
//

int pitchcompare(const void* a, const void* b) {
   ChordEntry& A = *((ChordEntry*)a);
   ChordEntry& B = *((ChordEntry*)b);

   Array<int> x(A.pitches.getSize());
   Array<int> y(A.pitches.getSize());
   x.setSize(0);
   y.setSize(0);
   int i;

   x.append(A.pitches[0]);
   for (i=1; i<A.pitches.getSize(); i++) {
      if (A.pitches[i] != A.pitches[i-1]) {
         x.append(A.pitches[i]);
      }
   }

   y.append(B.pitches[0]);
   for (i=1; i<B.pitches.getSize(); i++) {
      if (B.pitches[i] != B.pitches[i-1]) {
         y.append(B.pitches[i]);
      }
   }

   int size = 0;
   if (x.getSize() < y.getSize()) {
      size = x.getSize();
   } else if (y.getSize() < x.getSize()) {
      size = y.getSize();
   } else {
      size = x.getSize();  // equal sizes
   }

   for (i=0; i<size; i++) {
      if (x[i] < y[i]) {
         return -1;
      } else if (x[i] > y[i]) {
         return 1;
      }
   }

   if (x.getSize() < y.getSize()) {
      return -1;
   } else if (x.getSize() > y.getSize()) {
      return 1;
   } else {
      return 0;
   }

}



//////////////////////////////
//
// rootcompare -- compare two roots for ordering
//

int rootcompare(const void* a, const void* b) {
   if (*((int*)a) < *((int*)b)) {
      return -1;
   } else if (*((int*)a) > *((int*)b)) {
      return 1;
   } else {
      return 0;
   }
}
// md5sum: 6150564e474ef5f95c45d7468adb491a chordset.cpp [20090626]