//
// Programmer:    Craig Stuart Sapp <craig@ccrma.stanford.edu>
// Creation Date: Sun Oct 31 04:21:10 PDT 2010
// Last Modified: Sun Oct 31 04:21:17 PDT 2010
// Filename:      ...sig/examples/all/motive.cpp
// Web Address:   http://sig.sapp.org/examples/museinfo/humdrum/motive.cpp
// Syntax:        C++; museinfo
//
// Description: Tool for motive identification in monophonic or single-voice
// 		polyphonic scores.
// 

#include "humdrum.h"

#ifndef OLDCPP
   using namespace std;
#endif
   
#define REST    -1000
#define INVALID -7000

class Coord {
   public: 
      int row;
      int col;
      Coord(void) { row = col = -1; }
};

// function declarations
void      checkOptions(Options& opts, int argc, char* argv[]);
void      example(void);
void      usage(const char* command);
void      doDiatonicIntervalAnalysis(HumdrumFile& infile);
void      getDiatonicIntervals(Array<Array<int> >& intervals, 
                               Array<Array<Coord> >& coords, 
                               HumdrumFile& infile);
void      printDiatonicIntervals(Array<Array<int> >& intervals);
void      printDiatonicData(HumdrumFile& infile, Array<Array<int> >& intervals, 
                               Array<Array<Coord> >& coords);
void      printDiatonicLine(HumdrumFile& infile, int i, 
                               Array<Array<int> >& intervals, 
                               const char* strang);


// global variables
Options   options;             // database for command-line arguments
int       debugQ = 0;          // used with --debug option
int       zeroQ  = 0;          // used with -z option
int       restQ  = 0;          // used with -r option
int       outputonlyQ = 0;     // used with -I option
int       printDiatonicIntervalsQ  = 1; // used with -D option


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

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();

   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();

      doDiatonicIntervalAnalysis(infile);
   }

   return 0;
}


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


//////////////////////////////
//
// doDiatonicIntervalAnalysis --
//


void doDiatonicIntervalAnalysis(HumdrumFile& infile) {
   Array<Array<int> > intervals;
   Array<Array<Coord> > coords;
   getDiatonicIntervals(intervals, coords, infile);
   // printDiatonicIntervals(intervals);
   if (printDiatonicIntervalsQ) {
      printDiatonicData(infile, intervals, coords);
      exit(0);
   }
}


//////////////////////////////
//
// printDiatonicData --
//

void printDiatonicData(HumdrumFile& infile, Array<Array<int> >& intervals, 
      Array<Array<Coord> >& coords) {

   int i;
   for (i=0; i<infile.getNumLines(); i++) {
      switch (infile[i].getType()) {
         case E_humrec_data_comment:
            printDiatonicLine(infile, i, intervals, "!");
            break;
         case E_humrec_data_kern_measure:
            printDiatonicLine(infile, i, intervals, infile[i][0]);
            break;
         case E_humrec_interpretation:
            if (strncmp(infile[i][0], "**", 2) == 0) {
               printDiatonicLine(infile, i, intervals, "**dint");
            } else if (strcmp(infile[i][0], "*-") == 0) {
               printDiatonicLine(infile, i, intervals, "*-");
            }
            break;
         case E_humrec_data:
            printDiatonicLine(infile, i, intervals, ".");
            break;
         case E_humrec_none:
         case E_humrec_empty:
         case E_humrec_global_comment:
         case E_humrec_bibliography:
         default:
            cout << infile[i] << endl;
      }
   }
}



//////////////////////////////
//
// printDiatonicLine --
//

void printDiatonicLine(HumdrumFile& infile, int i, 
      Array<Array<int> >& intervals, const char* strang) {
   int j;
   int tcounter = 0;
   int track;
   int track2;
   int value;
   for (j=0; j<infile[i].getFieldCount(); j++) {
      if (!outputonlyQ) {
         cout << infile[i][j];
      }
      if (strcmp(infile[i].getExInterp(j), "**kern") == 0) {
         track = infile[i].getPrimaryTrack(j);
         track2 = -10000;
         if (j < infile[i].getFieldCount()-1) {
            track2 = infile[i].getPrimaryTrack(j+1);
         }
         if (track != track2) {
            if (outputonlyQ && tcounter) {
               cout << "\t";
            } else if (!outputonlyQ) {
               cout << "\t";
            }
            tcounter++;
            if (intervals[i][j] >= REST) {
               value = intervals[i][j];
               if (restQ && (value == REST)) {
                  cout << "r";
               } else {
                  if (!zeroQ) {
                     if (value > 0) {
                        value++;
                     } else if (value < 0) {
                        value--;
                     }
                  }
                  if (value > 0) {
                     cout << "+";
                  }
                  cout << value;
               }
            } else {
               if ((strcmp(strang, "*") == 0) && 
                   (strncmp(infile[i][j], "*staff", strlen("*staff")) == 0)) {
                  cout << infile[i][j];
               } else {
                  cout << strang;
               }
            }
         }
      }
      if (!outputonlyQ) {
         if (j < infile[i].getFieldCount()-1) {
            cout << "\t";
         }
      }
   }
   cout << endl;
}



//////////////////////////////
//
// printDiatonicIntervals --
//

void printDiatonicIntervals(Array >& intervals) {
   int i, j;
   for (i=0; i<intervals.getSize(); i++) {
      for (j=0; j<intervals[i].getSize(); j++) {
         cout << intervals[i][j] << "\n";
      }
      if (i < intervals.getSize()-1) {
         cout << "\n";
      }
   }   

}



//////////////////////////////
//
// getDiatonicIntervals -- ignoring rests for now.
//

void getDiatonicIntervals(Array<Array<int> >& intervals, 
      Array<Array<Coord> >& coords, HumdrumFile& infile) {
   int maxtrack = infile.getMaxTracks();
   int track;
   int pitch;
   Coord coord;
   intervals.setSize(infile.getNumLines());
   coords.setSize(maxtrack);
   int i, j;
   // pre-allocate storage space for data:
   for (i=0; i<maxtrack; i++) {
      coords[i].setSize(infile.getNumLines());
      coords[i].setSize(0);
   }
   for (i=0; i<infile.getNumLines(); i++) {
      intervals[i].setSize(infile[i].getFieldCount());
      intervals[i].allowGrowth(0);
      intervals[i].setAll(INVALID);
   }

   for (i=0; i<infile.getNumLines(); i++) {
      if (debugQ) {
         cout << "Processing input line " << i + 1 << "..." << endl;
      }
      if (!infile[i].isData()) {
         continue;
      }
      for (j=0; j<infile[i].getFieldCount(); j++) {
         if (strcmp(infile[i].getExInterp(j), "**kern") != 0) {
            continue;
         }
         track = infile[i].getPrimaryTrack(j)-1;
         if (strchr(infile[i][j], 'r') != NULL) {
            if (restQ) {
               pitch = REST;
               intervals[i][j] = pitch;
               coord.row = i;
               coord.col = j;
               coords[track].append(coord);
            }
            continue;
         }
         if (strcmp(infile[i][j], ".") == 0) {
            continue;
         }
         if (strchr(infile[i][j], '_') != NULL) {
            // middle tie note
            continue;
         }
         if (strchr(infile[i][j], ']') != NULL) {
            // ending tied note
            continue;
         }
         track = infile[i].getPrimaryTrack(j) - 1;
         pitch = Convert::kernToBase40(infile[i][j]);
         pitch = pitch/40 * 7 + Convert::base40ToDiatonic(pitch) % 7;
         coord.row = i;
         coord.col = j;
         intervals[i][j] = pitch;
         coords[track].append(coord);
      }
   }

   if (debugQ) {
      cout << "FINISHED EXTRACTING DATA FROM FILE" << endl;
   }

   Array<Array<Coord> >& c = coords;
   int lasti, lastj;

   // convert absolute diatonic pitch into diatonic interval.
   if (restQ) {
      for (i=0; i<c.getSize(); i++) {
         for (j=0; j<c[i].getSize()-1; j++) {
            if (intervals[c[i][j].row][c[i][j].col] == REST) {
               continue;
            }
            if (intervals[c[i][j+1].row][c[i][j+1].col] == REST) {
               intervals[c[i][j].row][c[i][j].col] = INVALID;
               continue;
            }
            intervals[c[i][j].row][c[i][j].col] = 
                  intervals[c[i][j+1].row][c[i][j+1].col] 
                  - intervals[c[i][j].row][c[i][j].col];
         }
         lasti = c[i][c[i].getSize()-1].row;
         lastj = c[i][c[i].getSize()-1].col;
         intervals[lasti][lastj] = INVALID;
         c[i].setSize(c[i].getSize()-1);
      }
   } else {
      for (i=0; i<c.getSize(); i++) {
         for (j=0; j<c[i].getSize()-1; j++) {
            intervals[c[i][j].row][c[i][j].col] = 
                  intervals[c[i][j+1].row][c[i][j+1].col] 
                  - intervals[c[i][j].row][c[i][j].col];
         }
         lasti = c[i][c[i].getSize()-1].row;
         lastj = c[i][c[i].getSize()-1].col;
         intervals[lasti][lastj] = INVALID;
         c[i].setSize(c[i].getSize()-1);
      }
   }

   int value;
   int writepos = 0;
   if (restQ) {
      // remove invalid intervals from list
      for (i=0; i<c.getSize(); i++) {
         if (c[i].getSize() == 0) {
            continue;
         }
         writepos = 0;
         for (j=0; j<c[i].getSize(); j++) {
            value = intervals[c[i][j].row][c[i][j].col];
            if (value == INVALID) {
               continue;
            } else {
               c[i][writepos].row = c[i][j].row;
               c[i][writepos].col = c[i][j].col;
               writepos++;
            }
         }
         c[i].setSize(writepos);
      }
   }

   if (debugQ) {
      cout << "FINISHED CREATING INTERVALS" << endl;
   }
}



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

void checkOptions(Options& opts, int argc, char* argv[]) {
   opts.define("I=b", "display output without echoing input data");  
   opts.define("z|zero=b", "display diatonic intervals zero-offset");  
   opts.define("r|rest=b", "do not cross rests");  

   opts.define("debug=b");              // determine bad input line num
   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, Oct 2010" << endl;
      exit(0);
   } else if (opts.getBoolean("version")) {
      cout << argv[0] << ", version: 31 Oct 2010" << 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");
   zeroQ  = opts.getBoolean("zero");
   restQ  = opts.getBoolean("rest");
   outputonlyQ = opts.getBoolean("I");

}



//////////////////////////////
//
// example -- example usage of the quality program
//

void example(void) {
   cout <<
   "                                                                         \n"
   "# example usage of the meter program.                                    \n"
   "# analyze a Bach chorale for meter position:                             \n"
   "     meter chor217.krn                                                   \n"
   "                                                                         \n"
   "# display the metrical location spine with original data:                \n"
   "     meter -a chor217.krn                                                \n"
   "                                                                         \n"
   << endl;
}



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

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



// md5sum: 915382eb3b12ff8e7cc66b07c64fd8c5 motive.cpp [20101226]