//
// Programmer:    Craig Stuart Sapp <craig@ccrma.stanford.edu>
// Creation Date: Sat Jun  9 14:48:41 PDT 2001
// Last Modified: Mon Apr 18 08:58:17 PDT 2011
// Last Modified: Tue Jul 17 12:42:32 PDT 2012 marked all tied notes
// Filename:      ...museinfo/examples/all/parallel.cpp
// Web Address:   http://sig.sapp.org/examples/museinfo/humdrum/parallel.cpp
// Syntax:        C++; museinfo
//
// Description:   calculates the minimum timebase which is the least common
//                multiple of all rhythms in the file.
//
// Todo: some things to clean up in rhythmic representation due to conversions
// from ints to RationalNumber class for rhythms/durations...

#include "humdrum.h"
#include "SigString.h"
#include <stdlib.h>
#include "PerlRegularExpression.h"

class NoteNode {
   public:
      int b40;      // base-40 pitch number or 0 if a rest, negative if tied0.
      int b40next;  // not used yet
      int dia;      // base-40 pitch number or 0 if a rest, negative if tied0.
      int line;     // line number in original score of note
      int spine;    // spine number in original score of note
      int lastint;  // interval to last note (or 0 if no last or last is rest)
      int nextint;  // interval to next note (or 0 if no next or last is rest)
      int lastdint; // interval to last note (or 0 if no last or last is rest)
      int nextdint; // interval to next note (or 0 if no next or last is rest)
      int serial;   // notes serial number
      int measure;  // measure number of note
      NoteNode(void) { clear(); }
      void clear(void) { serial = b40next = b40 = dia = 0; line = spine = -1; 
                         lastdint = nextdint = lastint = nextint = 0; }
};

#define REST 0
#define RESTINT -1000000

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

// function declarations
void      checkOptions(Options& opts, int argc, char* argv[]);
void      example(void);
void      usage(const char* command);
void      processFile(HumdrumFile& infile, const char* filename);
void      getKernTracks(Array<int>& ktracks, HumdrumFile& infile);
void      calculateMelodicIntervals(Array<Array<NoteNode> >& notes);
void      markParallels(Array<Array<NoteNode> >& notes, 
                                HumdrumFile& infile, const char* filename, 
                                Array<Array<char> >& names, 
                                Array<int> reverselookup);
void      addMark(HumdrumFile& infile, int line, int spine, 
                                char marker);
void      markParallel(HumdrumFile& infile, 
                                Array<Array<NoteNode> >& notes, 
                                int hint, int i, int j, int k);
int       validateInterval(Array<Array<NoteNode> >& notes, 
                                int i, int j, int k);
void      markForward(HumdrumFile& infile, int line, int spine, 
                                char marker);
void      markBackward(HumdrumFile& infile, int line, int spine, 
                                char marker);
int       getNoteStart(HumdrumFile& infile, int line, int spine);

// global variables
Options     options;           // database for command-line arguments
int         debugQ   = 0;      // used with --debug
int         rawQ     = 0;      // used with -r option
int         locQ     = 0;      // used with -l option
int         raw2Q    = 0;      // used with --r2 option
int         fileQ    = 0;      // used with -f option
int         typeQ    = 0;      // used with -t option
int         dataQ    = 0;      // used with -i option
int         notiedQ  = 0;      // used with -T option (to be interfaced)
int         diatonicQ= 0;      // used with -d option
SigString   Filename;          // used if filename is "<STDIN>"


int OCTAVE = 0;
int FIFTH = 23;

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


int main(int argc, char** argv) {
   checkOptions(options, argc, argv);
   HumdrumFile infile;

   int i;
   // figure out the number of input files to process
   int numinputs = options.getArgCount();
   const char* filename = "";

   PerlRegularExpression pre;

   if (locQ) {
      // print start of data
      if (fileQ) {
         cout << "**file\t";
      }
      cout << "**bar\t";
      cout << "**description\t";
      cout << "**voice1\t";
      cout << "**voice2";
      if (typeQ) {
         cout << "\t**type";
      }
      if (dataQ) {
         cout << "\t**data";
      }
      cout << "\n";
   }

   for (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);
         filename = "<STDIN>";
      } else {
         filename = options.getArg(i+1);
         infile.read(filename);
         pre.search(filename, "([^/]+)(\\.[^\\.]+)$", "");
         filename = pre.getSubmatch(1);
         if (pre.search(filename, "^\\s*$")) {
            filename = options.getArg(i+1);
         }
      }
      processFile(infile, filename);
   }
 
   if (locQ) {
      if (fileQ) {
         cout << "*-\t";
      }
      cout << "*-\t*-\t*-\t*-";
      if (typeQ) {
         cout << "\t*-";
      }
      if (dataQ) {
         cout << "\t*-";
      }
      cout << "\n";
   }



}



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



//////////////////////////////
//
// getNames -- get the names of each column if they have one.
//

void getNames(Array<Array<char> >& names, Array<int>& reverselookup, 
      HumdrumFile& infile) {

   names.setSize(reverselookup.getSize()-1);
   names.allowGrowth(0);
   char buffer[1024] = {0};
   int value;
   PerlRegularExpression pre;
   int i;
   int j;
   int track;
 
   for (i=0; i<names.getSize(); i++) {
      value = reverselookup.getSize() - i;
      sprintf(buffer, "%d", value);
      names[i].setSize(strlen(buffer)+1);
      strcpy(names[i].getBase(), buffer);
   }

   for (i=0; i<infile.getNumLines(); i++) {
      if (infile[i].isData()) {
         // stop looking for instrument name after the first data line
         break;
      }
      if (!infile[i].isInterpretation()) {
         continue;
      }
      for (j=0; j<infile[i].getFieldCount(); j++) {
         if (!infile[i].isExInterp(j, "**kern")) {
            continue;
         }
         if (pre.search(infile[i][j], "^\\*I\"(.*)", "")) {
            track = infile[i].getPrimaryTrack(j);
            strcpy(buffer, pre.getSubmatch(1));
            names[reverselookup[track]].setSize(strlen(buffer)+1);
            strcpy(names[reverselookup[track]].getBase(), buffer);
         }
      }
   }

   if (debugQ) {
      for (i=0; i<names.getSize(); i++) {
         cout << i << ":\t" <<  names[i] << endl;
      }
   }

}



//////////////////////////////
//
// processFile --
//

void processFile(HumdrumFile& infile, const char* filename) {
   int i, j;

   Array<Array<NoteNode> > notes;
   Array<Array<char> >     names;
   Array<int>              ktracks;
   Array<int>              reverselookup;

   int ii;
   int jj;

   getKernTracks(ktracks, infile);
   notes.setSize(ktracks.getSize());
   reverselookup.setSize(infile.getMaxTracks()+1);
   reverselookup.setAll(-1);


   for (i=0; i<ktracks.getSize(); i++) {
      reverselookup[ktracks[i]] = i;
      notes[i].setSize(infile.getNumLines());
      notes[i].setSize(0);
   }

   getNames(names, reverselookup, infile);


   PerlRegularExpression pre;

   Array<NoteNode> current;
   current.setSize(ktracks.getSize());
   int sign;
   int track;
   int index;

   int snum = 0;
   int measurenumber = 0;
   
   for (i=0; i<infile.getNumLines(); i++) {
      if (debugQ) {
         cout << "PROCESSING LINE: " << i << "\t" << infile[i] << endl;
      }
      if (infile[i].isMeasure()) {
         if (pre.search(infile[i][0], "(\\d+)", "")) {
            measurenumber = atoi(pre.getSubmatch(1));
         }
      }
      for (j=0; j<current.getSize(); j++) {
         current[j].clear();
         current[j].measure = measurenumber;
      }

      if (infile[i].isMeasure() && (strstr(infile[i][0], "||") != NULL)) {
         // double barline (terminal for Josquin project), so add a row
         // of rests to prevent parallel interval identification between
         // adjacent notes in different sections.
         for (j=0; j<notes.getSize(); j++) {
            notes[j].append(current[j]);
         }
      }
      if (!infile[i].isData()) {
         continue;
      }
      for (j=0; j<infile[i].getFieldCount(); j++) {
         sign = 1;
         if (!infile[i].isExInterp(j, "**kern")) {
            continue;
         }
         track = infile[i].getPrimaryTrack(j);
         index = reverselookup[track];
         current[index].line  = i;
         current[index].spine = j;
         if (strcmp(infile[i][j], ".") == 0) {
            sign = -1;
            ii = infile[i].getDotLine(j);
            jj = infile[i].getDotSpine(j);
         } else {
            ii = i;
            jj = j;
         }
         if (strchr(infile[ii][jj], 'r') != NULL) {
            current[index].b40 = 0;
            current[index].dia = 0;
            current[index].serial = ++snum;
            continue;
         }
         if (strcmp(infile[ii][jj], ".") == 0) {
            current[index].b40 = 0;
            current[index].dia = 0;
            current[index].serial = snum;
         }
         current[index].b40 = Convert::kernToBase40(infile[ii][jj]);
         current[index].dia = Convert::base40ToDiatonic(current[index].b40);
         if (strchr(infile[ii][jj], '_') != NULL) {
            sign = -1;
            current[index].serial = snum;
         }
         if (strchr(infile[ii][jj], ']') != NULL) {
            sign = -1;
            current[index].serial = snum;
         }
         current[index].b40 *= sign;
         current[index].dia *= sign;
         if (sign > 0) {
            current[index].serial = ++snum;
         }
      }
      if (debugQ) {
         cout << "NOTES:";
      }
      for (j=0; j<notes.getSize(); j++) {
         notes[j].append(current[j]);
         if (debugQ) {
            cout << "\t" << current[j].b40 << "(" << current[j].dia << ")";
         }
      }
      if (debugQ) {
         cout << endl;
      }
   }

   int nextone = 0;
   for (i=0; i<notes.getSize(); i++) {
      notes[i].last().b40next = nextone;
      for (j=notes[i].getSize()-1; j>= 0; j--) {
         notes[i][j].b40next = nextone;
         if (notes[i][j].b40 >= 0) {
            nextone = notes[i][j].b40;
         }
      }
   }

   if (rawQ) {
      for (i=0; i<notes[0].getSize(); i++) {
         for (j=0; j<notes.getSize(); j++) {
            cout << notes[j][i].b40;
            cout << " to " << notes[j][i].b40next;
            if (j < notes.getSize()-1) {
               cout << "\t";
            }
         }
         cout << endl;
      }
      exit(0);
   }

   calculateMelodicIntervals(notes);

   if (raw2Q) {
      for (i=0; i<notes[0].getSize(); i++) {
         for (j=0; j<notes.getSize(); j++) {
            cout << notes[j][i].b40;
            cout << ",";
            cout << notes[j][i].b40next;
            cout << "(";
            cout << notes[j][i].lastint;
            cout << ";";
            cout << notes[j][i].nextint;
            cout << ")";
            if (j < notes.getSize()-1) {
               cout << "\t";
            }
         }
         cout << endl;
      }
      exit(0);
   }

   markParallels(notes, infile, filename, names, reverselookup);

}



//////////////////////////////
//
// markParallels -- search for parts which are parallel fifth or octaves 
//    
// 0(REST;REST)		134(-5;REST)	162(11;-11)	174(6;28)
// 111(REST;-6)		-134(-5;REST)	-162(11;-11)	202(28;-5)
// -111(REST;-6)	-134(-5;REST)	151(-11;6)	-202(28;-5)
// 105(-6;6)		-134(-5;REST)	157(6;5)	197(-5;-6)
// 111(6;23)		-134(-5;REST)	162(5;-5)	191(-6;-6)
// 134(23;REST)		-134(-5;REST)	157(-5;REST)	185(-6;REST)
//

void markParallels(Array<Array<NoteNode> >& notes, HumdrumFile& infile,
      const char* filename, Array<Array<char> >& names, 
      Array<int> reverselookup) {
   int i;
   int j;
   int k;
   int hint;
   int ii;
   int jj;
   int track;
   int start1;
   int start2;
   int interval;
   int dint;

   int hasoctavemarks = 0;
   int has5thmarks    = 0;

   char topstate = '?';
   char bottomstate = '?';

   int onote1;
   int onote2;
   int otherhint = 0;
   int otherdint = 0;
   int nextlower = 0;
   int nextupper = 0;
   int nextdlower = 0;
   int nextdupper = 0;

   for (j=0; j<notes[0].getSize(); j++) {
      for (i=0; i<notes.getSize(); i++) {
         if (notes[i][j].b40 == REST) {
            // ignore rests
            continue;
         }
         if (notes[i][j].nextint == RESTINT) {
            // ignore rest intervals
            continue;
         }
         if (notes[i][j].nextint == 0) {
            // ignore repeated notes
            continue;
         }
         for (k=i+1; k<notes.getSize(); k++) {
            if (debugQ) {
               cout << "comparing " << notes[i][j].b40
                    << " to " << notes[k][j].b40 << endl;
            }
            if (notes[k][j].nextint == RESTINT) {
               if (debugQ) {
                  cout << "SECOND NOTE, next note is rest, "
                       << " int is " << notes[k][j].nextint << endl;
               }
               // ignore rest intervals
               continue;
            }
            if (notes[k][j].b40 == 0) {
               if (debugQ) {
                  cout << "SECOND NOTE is a rest" << endl;
               }
               // ignore rests
               continue;
            }
            // disallow interval check from elided notes for now
            //if (notes[i][j].b40 < 0) {
            //   continue;
            //}
            //if (notes[k][j].b40 < 0) {
            //   continue;
            //}
            
            if ((notes[k][j].b40 < 0) && (notes[i][j].b40 < 0)) {
               if (debugQ) {
                  cout << "Both notes are not attacks " << endl;
               }
               continue;
            }

            
char buf[1024];
            if (abs(notes[k][j].b40) > abs(notes[i][j].b40)) {
               hint = (abs(notes[k][j].b40) - abs(notes[i][j].b40) + 400) % 40;
               dint = (abs(notes[k][j].dia) - abs(notes[i][j].dia) + 700) % 7;
               onote1 = notes[k][j].b40next;
               onote2 = notes[i][j].b40next;

if (debugQ) {
cout << "!! >>> " << Convert::base40ToKern(buf, abs(notes[k][j].b40));
cout << " is higher than " << Convert::base40ToKern(buf, abs(notes[i][j].b40));
cout << " INTERVAL " << hint << endl;
}

               nextlower = notes[k][j].nextint;
               nextupper = notes[i][j].nextint;
               nextdupper = Convert::base40IntervalToDiatonic(nextupper);
               if (nextdupper < 0) {
                  nextdupper--;
               } else {
                  nextdupper++;
               }
               nextdlower = Convert::base40IntervalToDiatonic(nextlower);
               if (nextdlower < 0) {
                  nextdlower--;
               } else {
                  nextdlower++;
               }
               otherhint = hint + (nextlower - nextupper);
if (debugQ) {
cout << "!! " << Convert::base40ToKern(buf, onote1);
cout << " and " << Convert::base40ToKern(buf, onote2);
cout << " == OTHER HINT = " << otherhint << endl;
cout << "!!    OR1 : " << hint + (nextupper - nextlower) << endl;
}
               otherdint = Convert::base40IntervalToDiatonic(otherhint);
               if (notes[k][j].b40 > 0) {
                  topstate = 'x';
               } else {
                  topstate = 's';
               }
               if (notes[i][j].b40 > 0) {
                  bottomstate = 'x';
               } else {
                  bottomstate = 's';
               }
            } else {
               hint = (abs(notes[i][j].b40) - abs(notes[k][j].b40) + 400) % 40;
               dint = (abs(notes[i][j].dia) - abs(notes[k][j].dia) + 700) % 7;
               onote1 = notes[i][j].b40next;
               onote2 = notes[k][j].b40next;
if (debugQ) {
cout << "!! >>X " << Convert::base40ToKern(buf, abs(notes[k][j].b40));
cout << " is lower than " << Convert::base40ToKern(buf, abs(notes[i][j].b40));
cout << " INTERVAL " << hint << endl;
}
               nextupper = notes[i][j].nextint;
               nextlower = notes[k][j].nextint;
               nextdupper = Convert::base40IntervalToDiatonic(nextupper);
               if (nextdupper < 0) {
                  nextdupper--;
               } else {
                  nextdupper++;
               }
               nextdlower = Convert::base40IntervalToDiatonic(nextlower);
               if (nextdlower < 0) {
                  nextdlower--;
               } else {
                  nextdlower++;
               }
               otherhint = hint + (nextupper - nextlower);
if (debugQ) {
cout << "!! " << Convert::base40ToKern(buf, onote1);
cout << " and " << Convert::base40ToKern(buf, onote2);
cout << " == OTHER HINT = " << otherhint << endl;
cout << "!!    OR2 : " << hint + (nextlower - nextupper) << endl;
}
               otherdint = Convert::base40IntervalToDiatonic(otherhint);
               if (notes[i][j].b40 > 0) {
                  topstate = 'x';
               } else {
                  topstate = 's';
               }
               if (notes[k][j].b40 > 0) {
                  bottomstate = 'x';
               } else {
                  bottomstate = 's';
               }
            }
            if (debugQ) {
               cout << "comparing " << notes[i][j].b40
                    << " to " << notes[k][j].b40
                    << "\tHINT = " << hint
                    << "\tDINT = " << dint << endl;
            }
            if (diatonicQ) {
               interval = dint;
            } else {
               interval = hint;
            }
            if (!((interval == OCTAVE) || (interval == FIFTH))) {
               // not a fifth or an octave
               continue;
            }
            if (diatonicQ) {
               if (notes[k][j].nextdint != notes[i][j].nextdint) {
                  // note are not creating a parallel motion
                  continue;
               }
               if ((abs(notes[k][j].nextint) < 3) || (abs(notes[i][j].nextint) < 3)) {
                  // non-perfect unison/octave, ignore
                  continue;
               }
            } else {
               if (notes[k][j].nextint != notes[i][j].nextint) {
                  // note are not creating a parallel motion
                  continue;
               }
            }

            //cout << "Parallel " << hint << endl;
            if (validateInterval(notes, i, j, k)) {

               if (locQ) {

                  if (interval == OCTAVE) {
                     if (fileQ) {
                        if (strlen(Filename.getBase()) > 0) {
                           cout << Filename;
                        } else {
                           cout << filename;
                        }
                        cout << "\t";
                     }
                     cout << notes[i][j].measure;
                     cout << "\t" << "parallel octave/unison";
                     cout << "\t";
                     ii = notes[i][j].line;
                     jj = notes[i][j].spine;
                     track = infile[ii].getPrimaryTrack(jj);
                     cout << names[reverselookup[track]];
                     cout << "\t";
                     ii = notes[k][j].line;
                     jj = notes[k][j].spine;
                     track = infile[ii].getPrimaryTrack(jj);
                     cout << names[reverselookup[track]];
                     if (typeQ) {
                        start1 = getNoteStart(infile, notes[i][j].line,
                                       notes[i][j].spine);
                        start2 = getNoteStart(infile, notes[k][j].line,
                                       notes[k][j].spine);
                        if (start1 == start2) {
                           cout << "\t" << "hard";
                        } else {
                           cout << "\t" << "soft";
                        }
                     }
                     if (dataQ) {
                        cout << "\t" << topstate << bottomstate << "," 
                                     << hint << "(" << dint+1 << ")"
                                     << ";xx," << otherhint << "(" << otherdint+1<< ")"
                                     << ";" << nextupper << "(" << nextdupper << ")"
                                     << ";" << nextlower << "(" << nextdlower << ")";
                     }
                     cout << endl;

                  } else if (interval == FIFTH) {
                     if (fileQ) {
                        if (strlen(Filename.getBase()) > 0) {
                           cout << Filename;
                        } else {
                           cout << filename;
                        }
                        cout << "\t";
                     }
                     cout << notes[i][j].measure;
                     cout << "\t" << "parallel fifth";
                     cout << "\t";
                     ii = notes[i][j].line;
                     jj = notes[i][j].spine;
                     track = infile[ii].getPrimaryTrack(jj);
                     cout << names[reverselookup[track]];
                     cout << "\t";
                     ii = notes[k][j].line;
                     jj = notes[k][j].spine;
                     track = infile[ii].getPrimaryTrack(jj);
                     cout << names[reverselookup[track]];
                     if (typeQ) {
                        start1 = getNoteStart(infile, notes[i][j].line,
                                       notes[i][j].spine);
                        start2 = getNoteStart(infile, notes[k][j].line,
                                       notes[k][j].spine);
                        if (start1 == start2) {
                           cout << "\t" << "hard";
                        } else {
                           cout << "\t" << "soft";
                        }
                     }
                     if (dataQ) {
                        cout << "\t" << topstate << bottomstate << "," 
                                     << hint << "(" << dint+1 << ")"
                                     << ";xx," << otherhint << "(" << otherdint+1 << ")"
                                     << ";" << nextupper << "(" << nextdupper << ")"
                                     << ";" << nextlower << "(" << nextdlower << ")";
                     }
                     cout << endl;

                  }


               }

               markParallel(infile, notes, interval, i, j, k);
               if (interval == OCTAVE)  { hasoctavemarks = 1; }
               if (interval == FIFTH)   { has5thmarks    = 1; }
            }
         }
      }
   }

/*   // mark the ending notes in the parallel motion
   for (i=0; i<notes.getSize(); i++) {
      for (j=0; j<notes[i].getSize(); j++) {
         if (notes[i][j].b40 == REST) {
            // ignore rests
            continue;
         }
         if (notes[i][j].lastint == RESTINT) {
            // ignore rest intervals
            continue;
         }
         if (notes[i][j].lastint == 0) {
            // ignore repeated notes
            continue;
         }
         for (k=i+1; k<notes.getSize(); k++) {
            if (notes[i][k].lastint == RESTINT) {
               // ignore rest intervals
               continue;
            }
            if (notes[i][k].b40 == 0) {
               // ignore rests
               continue;
            }
            // disallow interval check from elided notes for now
            if (notes[i][j].b40 < 0) {
               continue;
            }
            if (notes[k][j].b40 < 0) {
               continue;
            }

            if (abs(notes[k][j].b40) > abs(notes[i][j].b40)) {
               hint = (abs(notes[k][j].b40) - abs(notes[i][j].b40) + 400) % 40;
            } else {
               hint = (abs(notes[i][j].b40) - abs(notes[k][j].b40) + 400) % 40;
            }

            if (!((hint == 0) || (hint == 23))) {
               // not a fifth or an octave
               continue;
            }
            if (notes[k][j].lastint != notes[i][j].lastint) {
               // notes are not creating a parallel motion
               continue;
            }
            // cout << "Parallel2 " << hint << endl;
            //if (validateInterval(notes, i, j, k)) {
               markParallel(infile, notes, hint, i, j, k);
               if (hint == 0)  { hasoctavemarks = 1; }
               if (hint == 23) { has5thmarks    = 1; }
            //}
         }
      }
   }
*/

   if (!locQ) {
      cout << infile;
      if (hasoctavemarks) {
         cout << "!!!RDF**kern: < = marked note, color=\"#00ff00\", ";
         cout << "parallel octave" << endl;
      }
      if (has5thmarks) {
         cout << "!!!RDF**kern: > = marked note, color=\"#ff0000\", ";
         cout << "parallel fifth" << endl;
      }
   }

}



//////////////////////////////
//
// getNoteStart --
//

int getNoteStart(HumdrumFile& infile, int line, int spine) {
   int isdot = 0;
   if ((strchr(infile[line][spine], ']') != NULL) || 
       (strchr(infile[line][spine], '_') != NULL) ) {
      // do nothing 
   } else if (strcmp(infile[line][spine], ".") == 0) {
      isdot = 1;
   } else {
      return line;
   }

   int track, ptrack;
   int i, j;
   // search for starting note of tied note group
   track = infile[line].getPrimaryTrack(spine);
   for (i=line-1; i>0; i--) {
      if (!infile[i].isData()) {
         continue;
      }
      for (j=0; j<infile[i].getFieldCount(); j++) {
         ptrack = infile[i].getPrimaryTrack(j);
         if (ptrack != track) {
            continue;
         }
         if (strcmp(infile[i][j], ".") == 0) {
            continue;
         }
         if (strchr(infile[i][j], '_') != NULL) {
            continue;
         }
         if (strchr(infile[i][j], '[') == NULL) {
            // something probably went wrong: no opening to tie
            if (isdot) {
               return i;
            } else {
               // something probably went wrong: no opening to tie
               return -1;
            }
         } else {
            return i;
         }
      }
   }

   // something probably went wrong: no opening to tie
   return -1;
}



//////////////////////////////
//
// validateInterval -- Currently only allow intervals where both notes
// in the second interval start at the same time.
//

int validateInterval(Array >& notes, int i, int j, int k) {
   int nij = -1;
   int nkj = -1;

   int m;


   for (m=j+1; m<notes[i].getSize(); m++) {
      if (notes[i][m].b40 >= 0) {
         nij = m;
         break;
      }
   }

   for (m=j+1; m<notes[k].getSize(); m++) {
      if (notes[k][m].b40 >= 0) {
         nkj = m;
         break;
      }
   }

   if (nij == nkj) {
      return 1;
   }
   if (nij < 0) {   // strange case if it occurs, but just mark as good
      return 1;
   }
   if (nkj < 0) {   // strage case if it occurs, but just mark as good
      return 1;
   }

   return 0;
}




//////////////////////////////
//
// markParallel --
//

void markParallel(HumdrumFile& infile, Array<Array<NoteNode> >& notes, 
      int interval, int i, int j, int k) {

   char marker = '<';  // parallel octaves/unisons
   if (interval == FIFTH) {
      marker = '>';    // parallel fifths
   }

   int ii;
   int jj;


   // mark first note in parallel motion
   ii = notes[i][j].line;
   jj = notes[i][j].spine;

   if (strcmp(infile[ii][jj], ".") == 0) {
      int ti;
      int tj;
      ti = infile[ii].getDotLine(jj);
      tj = infile[ii].getDotSpine(jj);
      ii = ti;
      jj = tj;
   }

   if (strcmp(infile[ii][jj], ".") != 0) {
      // if (strchr(infile[ii][jj], marker) == NULL) {
         addMark(infile, ii, jj, marker);
      // }
   }

   // mark second note in parallel motion
   ii = notes[k][j].line;
   jj = notes[k][j].spine;

   if (strcmp(infile[ii][jj], ".") == 0) {
      int ti;
      int tj;
      ti = infile[ii].getDotLine(jj);
      tj = infile[ii].getDotSpine(jj);
      ii = ti;
      jj = tj;
   }

   if (strcmp(infile[ii][jj], ".") != 0) {
      // if (strchr(infile[ii][jj], marker) == NULL) {
         addMark(infile, ii, jj, marker);
      // }
   }

   int m;

   // mark third note in parallel motion
   for (m=j+1; m<notes[i].getSize(); m++) {
      if (notes[i][m].b40 < 0) {
         continue;
      }
      ii = notes[i][m].line;
      jj = notes[i][m].spine;
      if (strcmp(infile[ii][jj], ".") == 0) {
         int ti;
         int tj;
         ti = infile[ii].getDotLine(jj);
         tj = infile[ii].getDotSpine(jj);
         ii = ti;
         jj = tj;
      }
      if (strcmp(infile[ii][jj], ".") != 0) {
         // if (strchr(infile[ii][jj], marker) == NULL) {
            addMark(infile, ii, jj, marker);
         // }
      }
      break;
   }

   // mark third note in parallel motion
   for (m=j+1; m<notes[k].getSize(); m++) {
      if (notes[i][m].b40 < 0) {
         continue;
      }
      ii = notes[k][m].line;
      jj = notes[k][m].spine;
      if (strcmp(infile[ii][jj], ".") == 0) {
         int ti;
         int tj;
         ti = infile[ii].getDotLine(jj);
         tj = infile[ii].getDotSpine(jj);
         ii = ti;
         jj = tj;
      }
      if (strcmp(infile[ii][jj], ".") != 0) {
         // if (strchr(infile[ii][jj], marker) == NULL) {
            addMark(infile, ii, jj, marker);
         // }
      }
      break;
   }

}



//////////////////////////////
//
// addMark --
//

void addMark(HumdrumFile& infile, int line, int spine, char marker) {
   char buffer[1024]; 
   char mstring[2] = {0};
   mstring[0] = marker;
   strcpy(buffer, infile[line][spine]);
   strcat(buffer, mstring);
   infile[line].changeField(spine, buffer);

   // mark all notes which are tied to this note.
   if (notiedQ) {
      return;
   }

   if (strchr(buffer, '[') != NULL) {
      markForward(infile, line, spine, marker);
   } else if (strchr(buffer, ']') != NULL) {
      markBackward(infile, line, spine, marker);
   } else {
      markForward(infile, line, spine, marker);
      markBackward(infile, line, spine, marker);
   }
}



//////////////////////////////
//
// markForward --
//

void markForward(HumdrumFile& infile, int line, int spine, char marker) {
   int track = infile[line].getPrimaryTrack(spine);
   int i, j;
 
   char buffer[1024];
   char mstring[2] = {0};
   int ptrack;
   mstring[0] = marker;

   for (i=line+1; i<infile.getNumLines(); i++) {
      if (!infile[i].isData()) {
         continue;
      }
      for (j=0; j<infile[i].getFieldCount(); j++) {
         ptrack = infile[i].getPrimaryTrack(j);
         if (ptrack != track) {
            continue;
         }
         if (strcmp(infile[i][j], ".") == 0) {
            continue;
         }
         if (strchr(infile[i][j], ']') != NULL) {
            strcpy(buffer, infile[i][j]);
            strcat(buffer, mstring);
            infile[i].changeField(j, buffer);
            return;
         } else if (strchr(infile[i][j], '_') != NULL) {
            strcpy(buffer, infile[i][j]);
            strcat(buffer, mstring);
            infile[i].changeField(j, buffer);
            markForward(infile, i, j, marker);
         } else {
            // unterminated tie?
            return;
         }
      }
   }
}



//////////////////////////////
//
// markBackward --
//

void markBackward(HumdrumFile& infile, int line, int spine, char marker) {
   int track = infile[line].getPrimaryTrack(spine);
   int i, j;
 
   char buffer[1024];
   char mstring[2] = {0};
   int ptrack;
   mstring[0] = marker;

   for (i=line-1; i>0; i--) {
      if (!infile[i].isData()) {
         continue;
      }
      for (j=0; j<infile[i].getFieldCount(); j++) {
         ptrack = infile[i].getPrimaryTrack(j);
         if (ptrack != track) {
            continue;
         }
         if (strcmp(infile[i][j], ".") == 0) {
            continue;
         }
         if (strchr(infile[i][j], '[') != NULL) {
            strcpy(buffer, infile[i][j]);
            strcat(buffer, mstring);
            infile[i].changeField(j, buffer);
            return;
         } else if (strchr(infile[i][j], '_') != NULL) {
            strcpy(buffer, infile[i][j]);
            strcat(buffer, mstring);
            infile[i].changeField(j, buffer);
            markBackward(infile, i, j, marker);
         } else {
            // unterminated tie?
            return;
         }
      }
   }
}



//////////////////////////////
//
// calculateMelodicIntervals --
//

void calculateMelodicIntervals(Array >& notes) {
   int i, j;
   int lastint = RESTINT;
   int lastdint = RESTINT;
   int lastnote = 0;
   int lastdnote = 0;

   // caluculate the intervals to previous notes:
   for (i=0; i<notes.getSize(); i++) {
      lastint = RESTINT;
      lastdint = RESTINT;
      lastnote = 0;
      lastdnote = 0;
      for (j=0; j<notes[i].getSize(); j++) {
         if (notes[i][j].b40 < 0) {
            // continuing note, just repeat previously calculate interval
            notes[i][j].lastint = lastint;
            notes[i][j].lastdint = lastdint;
            continue;
         }
         if (notes[i][j].b40 == 0) {
            // current note is a rest, to make the interval to the last
            // note a RESTINT.
            lastnote = 0;
            lastdnote = 0;
            notes[i][j].lastint = RESTINT;
            notes[i][j].lastdint = RESTINT;
            lastint = RESTINT;
            lastdint = RESTINT;
            continue;
         }
         if (lastnote == 0) {
            // if the last note was a rest, then the interval to it is a RESTINT
            lastnote = abs(notes[i][j].b40); // update last note
            lastdnote = abs(notes[i][j].dia); // update last note
            notes[i][j].lastint = RESTINT;
            notes[i][j].lastdint = RESTINT;
            lastint = RESTINT;
            lastdint = RESTINT;
            continue;
         }
         // have a note attack which does note follow a rest
         lastint = notes[i][j].b40 - lastnote;
         lastdint = notes[i][j].dia - lastdnote;
         lastnote = notes[i][j].b40;
         lastdnote = notes[i][j].dia;
         notes[i][j].lastint = lastint;
         notes[i][j].lastdint = lastdint;
      }
   }


   // caluculate the intervales in the other direction
   //128(-6)	-174(0)		-185(5)		-197(-5)
   //122(-6)	-174(0)		-185(5)		191(-6)
   //128(6)	168(-6)		180(-5)		-191(-6)
   //-128(6)	162(-6)		-180(-5)	-191(-6)
   //105(-23)	157(-5)		168(-12)	185(-6)
   
   int nextint = RESTINT;
   int nextdint = RESTINT;
   int nextnote = 0;
   // caluculate the intervals to next notes:
   for (i=0; i<notes.getSize(); i++) {
      nextint = RESTINT;
      nextdint = RESTINT;
      nextnote = 0;
      for (j=notes[i].getSize()-1; j>=0; j--) {
         notes[i][j].nextint = nextint;
         notes[i][j].nextdint = nextdint;
         if (notes[i][j].b40 >= 0) {
            nextint = notes[i][j].lastint;
            nextdint = notes[i][j].lastdint;
         }
      }
   }

}




//////////////////////////////
//
// getKernTracks -- return a list of track number for **kern spines.
//

void getKernTracks(Array& ktracks, HumdrumFile& infile) {
   int i, j;
   ktracks.setSize(infile.getMaxTracks());
   ktracks.setSize(0);
   int track;
   for (i=0; i<infile.getNumLines(); i++) {
      if (!infile[i].isInterpretation()) {
         continue;
      }
      for (j=0; j<infile[i].getFieldCount(); j++) {
         if (infile[i].isExInterp(j, "**kern")) {
            track = infile[i].getPrimaryTrack(j);
            ktracks.append(track);
         }
      }
      break;
   }
}



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

void checkOptions(Options& opts, int argc, char* argv[]) {
   opts.define("d|diatonic=b", "do diatonic analysis");
   opts.define("i|intervals|data=b", "display interval data");
   opts.define("f|file=b", "display filenames with -l option");
   opts.define("F|filename=s", "filenames to store with -f option (for stdin)");
   opts.define("l|location|loc=b", "print locations of parallels");
   opts.define("r|raw=b", "print raw data before analysis is done");
   opts.define("t|type=b", "print type of interval (hard/soft)");
   opts.define("raw2|r2=b", "print raw data 2 before analysis is done");
   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, April 2011" << endl;
      exit(0);
   } else if (opts.getBoolean("version")) {
      cout << argv[0] << ", version: 22 April 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");
   rawQ      = opts.getBoolean("raw");
   locQ      = opts.getBoolean("location");
   raw2Q     = opts.getBoolean("raw2");
   fileQ     = opts.getBoolean("file");
   typeQ     = opts.getBoolean("type");
   dataQ     = opts.getBoolean("data");
   diatonicQ = opts.getBoolean("diatonic");

   if (diatonicQ) {
      FIFTH = 4;
   }

   PerlRegularExpression pre;
   pre.search(opts.getString("filename"), "([^/]+)(\\.[^\\.]+)$", "");
   Filename = pre.getSubmatch(1);
   if (pre.search(Filename.getBase(), "^\\s*$")) {
      Filename = opts.getString("filename");
   }
}



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

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



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

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


// md5sum: 5714fd1abdf996f86687f03e1c0d1221 parallel.cpp [20120903]