//
// Programmer:    Craig Stuart Sapp <craig@ccrma.stanford.edu>
// Creation Date: Mon Nov  5 11:30:28 PST 2001
// Last Modified: Tue Nov  6 23:25:11 PST 2001
// Filename:      ...sig/examples/all/melstep/melstep.cpp
// Web Address:   http://sig.sapp.org/examples/museinfo/humdrum/melstep.cpp
// Syntax:        C++; museinfo
//
// Description:   Determines the melodic approach or departure of a note.  
//                0000 = 0  = not approached or left by a 2nd in track
//                0001 = 1  = left by a 2nd in track
//                0010 = 2  = approached by a 2nd in track
//                0011 = 3  = approached and left by a 2nd in track
//
// Not yet implemented:
//                0000 = 0  = note approached or left by a 2nd in another track
//                0100 = 4  = note left by a 2nd in another track
//                1000 = 8  = note approached by a 2nd in another track
//                1100 = 12 = note approached and left by a 2nd in another track
//

#include "humdrum.h"

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

typedef Array<int> ArrayInt;

// function declarations:
void checkOptions(Options& opts, int argc, char* argv[]);
void getDataLines(HumdrumFile& infile, Array<int>& datalines);
void generateAnalysis(HumdrumFile& infile, Array<ArrayInt>& mstates, 
                        Array<int>& datalines);
void printAnalysis(HumdrumFile& infile, Array<ArrayInt>& mstates, 
                        Array<int>& datalines);
void printState(int state, int style);
void printSummary(HumdrumFile& infile, Array<ArrayInt>& mstates, 
                        Array<int>& datalines);
void example(void);
void usage(const char* command);
template<class type> void switchvals(type& a, type& b);


// option variables:
Options      options;            // database for command-line arguments
int          debugQ = 0;         // for debugging
int          appendQ = 0;        // for appending analysis data to input data
int          binaryQ = 0;        // display analysis in binary format
int          summaryQ = 0;       // display transition statistics only
int          minstep = 4;        // minimum value for step (base40 interval)
int          maxstep = 8;        // maximum value for step (base40 interval)
int          ridQ = 0;           // rid plain **step data of non-data items

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

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

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

   // figure out the number of input files to process
   int numinputs = options.getArgCount();
 
   Array<ArrayInt> mstates;
   Array<int> datalines;
   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();

      getDataLines(infile, datalines);
      generateAnalysis(infile, mstates, datalines);
      printAnalysis(infile, mstates, datalines);
   }

   return 0;
}


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


//////////////////////////////
//
// getDataLines -- returns an array of the lines number in the Humdrum
//      File which contain data elements (notes and rests).
//

void getDataLines(HumdrumFile& infile, Array& datalines) {
   datalines.setSize(infile.getNumLines());
   datalines.setSize(0);
   datalines.allowGrowth();
   int i;
   for (i=0; i<infile.getNumLines(); i++) {
      if (infile[i].getType() == E_humrec_data) {
         datalines.append(i);
      }
   }
   datalines.allowGrowth(0);
}



//////////////////////////////
//
// generateAnalysis -- assign melodic state information for each note.
//

void generateAnalysis(HumdrumFile& infile, Array<ArrayInt>& mstates, 
      Array<int>& datalines) {
   mstates.setSize(datalines.getSize());
   static char buffer[1024] = {0};
   static char bufferN[1024] = {0};
   static char bufferL[1024] = {0};
   Array<double> lastspines;
   Array<double> nextspines;
   int i, j;
   int m;
   int lindex, nindex;
   int lspine, nspine;
   int ltokens, ntokens;
   int analysis = 0;
   for (i=0; i<datalines.getSize(); i++) {
      mstates[i].setSize(20);
      mstates[i].setSize(0);
      mstates[i].allowGrowth(0);
      for (j=0; j<infile[datalines[i]].getFieldCount(); j++) {
         if (infile[datalines[i]].getExInterpNum(j) != E_KERN_EXINT) {
            continue;
         }
         infile[datalines[i]].getToken(buffer, j, 0);
         if (strchr(buffer, ']') != NULL) {   // ignore ending tied notes
            continue;
         }
         if (strchr(buffer, '_') != NULL) {   // ignore continuing tied notes
            continue;
         }
         if (strchr(buffer, 'r') != NULL) {   // ignore rests
            continue;
         }
         if (strcmp(buffer, ".") == 0) {      // ignore null tokens
            continue;
         }

         if (debugQ) {
            cout << "Line=" << datalines[i] << "\t" 
                 << "Track=" << infile[datalines[i]].getTrack(j) << "\t"
                 << infile[datalines[i]][j] << "\t"
                 << "Last=" << infile.getLastDatum(datalines[i], j) << "\t"
                 << "Next=" << infile.getNextDatum(datalines[i], j) << "\t"
                 << endl;
         }

         lindex = infile.getLastDatumLine(lspine, datalines[i], j);
         nindex = infile.getNextDatumLine(nspine, datalines[i], j);

         // check if there is a split or join between the last or next
         // datafields.

//       getLastSpines(lastspines, lspine, infile, lines, datalines[i], spine);
//       getNextSpines(nextspines, nspine, infile, lines, datalines[i], spine);
   
         int ndiff = 0;
         int ldiff = 0;
         int tokencount = infile[datalines[i]].getTokenCount(j);
         for (m=0; m<tokencount; m++) {
            infile[datalines[i]].getToken(buffer, j, m);
            ltokens = -1;
            if (lindex >= 0 && lspine >= 0) {
               ltokens = infile[lindex].getTokenCount(lspine);
            }
            ntokens = -1;
            if (nindex >= 0 && nspine >= 0) {
               ntokens = infile[nindex].getTokenCount(nspine);
            }

            if (m < ltokens && lindex >= 0 && lspine >= 0) {
               infile[lindex].getToken(bufferL, lspine, m);
            } else {
               bufferL[0] = '\0';
            }
            if (m < ntokens && nindex >= 0 && nspine >= 0) {
               infile[nindex].getToken(bufferN, nspine, m);
            } else {
               bufferN[0] = '\0';
            }

            ndiff = 0;
            if (bufferN[0] != '\0' && (strchr(bufferN, 'r') == NULL)) {
               ndiff = abs(Convert::kernToBase40(bufferN) -
                               Convert::kernToBase40(buffer));
            }


            ldiff = 0;
            if (bufferL[0] != '\0' && (strchr(bufferL, 'r') == NULL)) {
               ldiff = abs(Convert::kernToBase40(bufferL) -
                               Convert::kernToBase40(buffer));
            }

            if (ndiff == 0 && ntokens != tokencount) {
               if (ntokens - m - 1 >= 0 && nindex >= 0 && nspine >= 0) {
                  infile[nindex].getToken(bufferN, nspine, ntokens-m-1);
               } else {
                  bufferN[0] = '\0';
               }
               if (bufferN[0] != '\0' && (strchr(bufferN, 'r') == NULL)) {
                  ndiff = abs(Convert::kernToBase40(bufferN) -
                                  Convert::kernToBase40(buffer));
               }
            }

            if (ldiff == 0 && ltokens != tokencount) {
               if (ltokens - m - 1 >= 0 && lindex >= 0 && lspine >= 0) {
                  infile[lindex].getToken(bufferL, lspine, ltokens-m-1);
               } else {
                  bufferL[0] = '\0';
               }
               if (bufferL[0] != '\0' && (strchr(bufferL, 'r') == NULL)) {
                  ldiff = abs(Convert::kernToBase40(bufferL) -
                                  Convert::kernToBase40(buffer));
               }
            }
  

            analysis = 0;
            if (ldiff >= minstep && ldiff <= maxstep) {
               analysis |= 0x02;
            }
            if (ndiff >= minstep && ndiff <= maxstep) {
               analysis |= 0x01;
            }
            mstates[i].append(analysis);

         } /* token loop */
      } /* spine loop */
   } /* line loop */
}



//////////////////////////////
//
// getLastSpines -- check for spine changes
//

/*void getLastSpines(Array<double>& lastspines, int lindex, int lspine, 
      HumdrumFile& infile, int curindex, int curspine) {

}
*/



//////////////////////////////
//
// getNextSpines -- check for spine changes
//

/*void getNextSpines(nextspines, nspine, infile, lines, datalines[i], spine) {

}
*/



//////////////////////////////
//
// printAnalysis -- print melodic state information for each note.
//

void printAnalysis(HumdrumFile& infile, Array<ArrayInt>& mstates, 
      Array<int>& datalines) {
   int i, j, k;

   if (summaryQ) {
      printSummary(infile, mstates, datalines);
      return;
   }

   if (!appendQ) {

      k = 0;
      for (i=0; i<infile.getNumLines(); i++) {
         switch (infile[i].getType()) {
         case E_humrec_global_comment:
         case E_humrec_bibliography:
         case E_humrec_none:
         case E_humrec_empty:
            if (!ridQ) {
               cout << infile[i].getLine() << "\n";
            }
            break;
         case E_humrec_data:
            for (j=0; j<mstates[k].getSize(); j++) {
               printState(mstates[k][j], binaryQ);
               if (j<mstates[k].getSize()-1) {
                  cout << " ";
               }
            }
            cout << "\n";
            k++;
            break;
         case E_humrec_data_comment:
            if (!ridQ) {
               if (infile[i].equalFieldsQ("**kern")) {
                  cout << infile[i][0] << "\n";
               } else {
                  cout << infile[i].getLine() << "\t!\n";
               }
            }
            break;
         case E_humrec_data_measure:
            if (!ridQ) {
               if (infile[i].equalFieldsQ("**kern")) {
                  cout << infile[i][0] << "\n";
               } else {
                  cout << infile[i].getLine() << "\t=\n";
               }
            }
            break;
         case E_humrec_data_interpretation:
            if (!ridQ) {
               if (strncmp(infile[i][0], "**", 2) == 0) {
                  cout << "**step" << "\n";
               } else if (infile[i].equalFieldsQ("**kern") &&
                     (strcmp(infile[i][0], "*+") != 0) &&
                     (strcmp(infile[i][0], "*^") != 0) &&
                     (strcmp(infile[i][0], "*v") != 0) &&
                     (strcmp(infile[i][0], "*x") != 0) ) {
                  cout << infile[i][0] << "\n";
               } else {
                  cout << "*\n";
               }
            }
            break;
         }
      }
      return;
   }

   k = 0;
   for (i=0; i<infile.getNumLines(); i++) {
      switch (infile[i].getType()) {
      case E_humrec_global_comment:
      case E_humrec_bibliography:
      case E_humrec_none:
      case E_humrec_empty:
         cout << infile[i].getLine() << "\n";
         break;
      case E_humrec_data:
         cout << infile[i].getLine() << "\t";
         for (j=0; j<mstates[k].getSize(); j++) {
            printState(mstates[k][j], binaryQ);
            if (j<mstates[k].getSize()-1) {
               cout << " ";
            }
         }
         cout << "\n";
         k++;
         break;
      case E_humrec_data_comment:
         if (infile[i].equalFieldsQ("**kern")) {
            cout << infile[i].getLine() << "\t"
                 << infile[i][0] << "\n";
         } else {
            cout << infile[i].getLine() << "\t!\n";
         }
         break;
      case E_humrec_data_measure:
         if (infile[i].equalFieldsQ("**kern")) {
            cout << infile[i].getLine() << "\t"
                 << infile[i][0] << "\n";
         } else {
            cout << infile[i].getLine() << "\t=\n";
         }
         break;
      case E_humrec_data_interpretation:
         if (strncmp(infile[i][0], "**", 2) == 0) {
            cout << infile[i].getLine() << "\t";
            cout << "**step" << "\n";
         } else if (infile[i].equalFieldsQ("**kern") &&
               (strcmp(infile[i][0], "*+") != 0) &&
               (strcmp(infile[i][0], "*^") != 0) &&
               (strcmp(infile[i][0], "*v") != 0) &&
               (strcmp(infile[i][0], "*x") != 0) ) {
            cout << infile[i].getLine() << "\t"
                 << infile[i][0] << "\n";
         } else {
            cout << infile[i].getLine() << "\t*\n";
         }
         break;
      }
   }

}



//////////////////////////////
//
// switchvals --
//

template<class type>
void switchvals(type& a, type& b) {
   type temp = a;
   a = b;
   b = temp;
}
   


/////////////////////////////
//
// printSummary -- print analysis summary.
//

void printSummary(HumdrumFile& infile, Array<ArrayInt>& mstates, 
      Array<int>& datalines) {
   int c[4] = {0};
   int i, j, m;
   for (i=0; i<mstates.getSize(); i++) {
      for (j=0; j<mstates[i].getSize(); j++) {
         c[mstates[i][j] & 0x3]++;
      }
   }
   double sum = c[0] + c[1] + c[2] + c[3];

   cout << "Transition Summary:\n\n";
   cout << "type\tcount\tfraction (total=" << sum << ")\n";
   cout << "-------------------------------------\n";
   for (m=0; m<4; m++) {
      printState(m, binaryQ);
      cout << "\t" << c[m] << "\t" << c[m] / sum << "\n";
   }

   Array<double> metpos;
   metpos.setSize(500);
   metpos.setSize(0);
   metpos.allowGrowth();
   double pos = 0;
   int pindex = 0;

   Array<double> metposb;
   metposb.setSize(500);
   metposb.setSize(0);
   metposb.allowGrowth();
   double posb = 0;
   int pindexb = 0;

   Array<ArrayInt> counts(4);
   Array<ArrayInt> countsb(4);
   counts.allowGrowth(0);
   countsb.allowGrowth(0);
   for (m=0; m<4; m++) {
      counts[m].setSize(500);
      counts[m].setSize(0);
      counts[0].allowGrowth();
      countsb[m].setSize(500);
      countsb[m].setSize(0);
      countsb[0].allowGrowth();
   }
   int zero = 0;

   int pfound = 0;
   int pfoundb = 0;
   for (i=0; i<datalines.getSize(); i++) {
      pos = infile[datalines[i]].getBeat(); 
      pfound = 0;

      for (m=0; m<metpos.getSize(); m++) {
         if (fabs(metpos[m] - pos) < 0.001) {
            pfound = 1;
            pindex = m;
            break;
         }
      }
      if (!pfound) {
         metpos.append(pos);
         for (m=0; m<4; m++) {
            counts[m].append(zero);
         }
         pindex = metpos.getSize() - 1;
      }

      posb = pos - (int)pos;
      pfoundb = 0;
      for (m=0; m<metposb.getSize(); m++) {
         if (fabs(metposb[m] - posb) < 0.001) {
            pfoundb = 1;
            pindexb = m;
            break;
         }
      }
      if (!pfoundb) {
         metposb.append(posb);
         for (m=0; m<4; m++) {
            countsb[m].append(zero);
         }
         pindexb = metposb.getSize() - 1;
      }

      for (j=0; j<mstates[i].getSize(); j++) {
         counts[mstates[i][j] & 0x3][pindex]++;
         countsb[mstates[i][j] & 0x3][pindexb]++;
      }
   }

   // sort the summary by beat position
   for (i=0; i<metpos.getSize(); i++) {
      for (j=i+1; j<metpos.getSize(); j++) {
         if (metpos[i] > metpos[j]) {
            switchvals(metpos[i], metpos[j]);
            for (m=0; m<4; m++) {
               switchvals(counts[m][i], counts[m][j]);
            }
         }
      }
   }

   for (i=0; i<metposb.getSize(); i++) {
      for (j=i+1; j<metposb.getSize(); j++) {
         if (metposb[i] > metposb[j]) {
            switchvals(metposb[i], metposb[j]);
            for (m=0; m<4; m++) {
               switchvals(countsb[m][i], countsb[m][j]);
            }
         }
      }
   }

   // print the metric data summary:

   cout << "\nTransition counts for each metric position:\n\n";
   cout << "\ttransitions:\n";
   cout << "beat\t";
   printState(0, binaryQ);
   cout << "\t";
   printState(1, binaryQ);
   cout << "\t";
   printState(2, binaryQ);
   cout << "\t";
   printState(3, binaryQ);
   cout << "\tsum\tfraction\n";
   cout << "-----------------------------------------------------------\n";

   for (i=0; i<metpos.getSize(); i++) {
      cout << metpos[i] << "\t" 
           << counts[0][i] << "\t"
           << counts[1][i] << "\t"
           << counts[2][i] << "\t"
           << counts[3][i] << "\t"
           << counts[0][i] + counts[1][i] + counts[2][i] + counts[3][i] << "\t"
           << (counts[0][i] + counts[1][i] + counts[2][i] + counts[3][i])/sum
           << endl;
   }

   cout << "\nTransition counts for beat position:\n\n";
   cout << "\ttransitions:\n";
   cout << "subbeat\t";
   printState(0, binaryQ);
   cout << "\t";
   printState(1, binaryQ);
   cout << "\t";
   printState(2, binaryQ);
   cout << "\t";
   printState(3, binaryQ);
   cout << "\tsum\tfraction\n";
   cout << "-----------------------------------------------------------\n";

   for (i=0; i<metposb.getSize(); i++) {
      cout << metposb[i] << "\t" 
           << countsb[0][i] << "\t"
           << countsb[1][i] << "\t"
           << countsb[2][i] << "\t"
           << countsb[3][i] << "\t"
           << countsb[0][i] + countsb[1][i] + countsb[2][i] + countsb[3][i] << "\t"
           << (countsb[0][i] + countsb[1][i] + countsb[2][i] + countsb[3][i])/sum
           << endl;
   }

}



//////////////////////////////
//
// printState -- print the analysis in decimal or binary form.
//

void printState(int state, int style) {
   if (style) {
      switch (state) {
         case 0:  cout << "00";   break;
         case 1:  cout << "01";   break;
         case 2:  cout << "10";   break;
         case 3:  cout << "11";   break;
         default: cout << state;  break;
      }
   } else {
      cout << state;
   }
}



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

void checkOptions(Options& opts, int argc, char* argv[]) {
   opts.define("a|append=b",     "append analysis to data in output");   
   opts.define("s|summary=b",    "summary of analysis only");   
   opts.define("b|binary=b",     "analysis displayed in binary format");   
   opts.define("r|range=s:4-8",  "base40 interval range for defining a step");   
   opts.define("n|min=i:4",      "minimum base40 interval for defining a step");
   opts.define("x|max=i:8",      "maximum base40 interval for defining a step");
   opts.define("i|interval=i:2", "interval defined as a step");
   opts.define("R|rid=b",        "rid data of non-data lines");   

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

   debugQ   = opts.getBoolean("debug");
   appendQ  = opts.getBoolean("append");
   summaryQ = opts.getBoolean("summary");
   binaryQ  = opts.getBoolean("binary");
   ridQ     = opts.getBoolean("rid");

   sscanf(opts.getString("range"), "%d-%d", &minstep, &maxstep);
   if (opts.getBoolean("min")) {
      minstep = opts.getInteger("min");
   }
   if (opts.getBoolean("max")) {
      maxstep = opts.getInteger("max");
   }
   if (opts.getBoolean("interval")) {
      int interval = opts.getInteger("interval");
      switch (interval) {
         case 1:   minstep = 0; maxstep = 2; break;      // unisons
         case 2:   minstep = 4; maxstep = 8; break;      // seconds
         case 3:   minstep = 10; maxstep = 14; break;    // thirds
         case 4:   minstep = 15; maxstep = 19; break;    // fourths
         case 5:   minstep = 21; maxstep = 25; break;    // fifths
         case 6:   minstep = 27; maxstep = 31; break;    // sixths
         case 7:   minstep = 33; maxstep = 37; break;    // sevenths
         default:  cout << "Error: invalid interval: " << interval << endl;
                   exit(1);
      }
   }

   if (minstep > maxstep) {
      int temp = minstep;
      minstep = maxstep;
      maxstep = temp;
   }
}



//////////////////////////////
//
// example -- example usage of the melstep program
//

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



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

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


// md5sum: dae85b0de4324139794b0d7376b9d1a2 melstep.cpp [20050403]