//
// Programmer:    Craig Stuart Sapp <craig@ccrma.stanford.edu>
// Creation Date: Sat Jun  9 14:48:41 PDT 2001
// Last Modified: Wed Apr 27 22:40:12 PDT 2011 (adapted from parallel.cpp)
// Filename:      ...museinfo/examples/all/dissonant.cpp
// Web Address:   http://sig.sapp.org/examples/museinfo/humdrum/dissonant.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 <stdlib.h>
#include "PerlRegularExpression.h"

#ifndef OLDCPP
   #include <iostream>
   #include <fstream>
   #include <sstream>
   #define SSTREAM stringstream
   #define CSTRING str().c_str()
   using namespace std;
#else
   #include <iostream.h>
   #include <fstream.h>
   #ifdef VISUAL
      #include <strstrea.h>
   #else
      #include <strstream.h>
   #endif
   #define SSTREAM strstream
   #define CSTRING str()
#endif

class NoteNode {
   public:
      int b40;      // 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 serial;   // notes serial number
      int measure;  // measure number of note
      Array<int> hint;   // harmonic intervals to other voices
      Array<int> hvoice; // voice number of harmonic interval
      NoteNode(void) { clear(); }
      void clear(void) { serial = b40 = 0; line = spine = -1; 
                         lastint = nextint = 0; 
                         hvoice.setSize(0);
                         hint.setSize(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      markDissonances(Array<Array<NoteNode> >& notes, 
                                HumdrumFile& infile, const char* filename, 
                                Array<Array<char> >& names, 
                                Array<int> reverselookup);
void      printDissonanceOutput(HumdrumFile& infile, 
                                Array<Array<NoteNode> >& notes, 
                                Array<Array<char> >& names);
void      printIntervalInfo(HumdrumFile& infile, int line, int spine, 
                                Array<Array<NoteNode> >& notes, int noteline, 
                                int noteindex, Array<Array<char> >& abbr);
void      getAbbreviations(Array<Array<char> >& abbreviations, 
                                Array<Array<char> >& names);
void      getAbbreviation(Array<char>& abbr, Array<char>& name);
void      markNote(HumdrumFile& infile, char mark, int line, 
                                int spine);
int       checkForFourth(Array<Array<NoteNode> >& notes, int j, 
                                HumdrumFile& infile);

// global variables
Options   options;             // database for command-line arguments
int       debugQ = 0;          // used with --debug
int       labelQ = 1;          // used with -l option
int       rawQ   = 0;          // used with -r option
int       raw2Q  = 0;          // used with --r2 option
int       fileQ  = 0;          // used with -f option


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


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;

   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);
      }
      processFile(infile, filename);
   }
 

}



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



//////////////////////////////
//
// 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 dissonant 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].serial = ++snum;
            continue;
         }
         if (strcmp(infile[ii][jj], ".") == 0) {
            current[index].b40 = 0;
            current[index].serial = snum;
         }
         current[index].b40 = Convert::kernToBase40(infile[ii][jj]);
         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;
         if (sign > 0) {
            current[index].serial = ++snum;
         }
      }
      for (j=0; j<notes.getSize(); j++) {
         notes[j].append(current[j]);
      }
   }

   if (rawQ) {
      for (i=0; i<notes[0].getSize(); i++) {
         for (j=0; j<notes.getSize(); j++) {
            cout << notes[j][i].b40;
            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].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);
   markDissonances(notes, infile, filename, names, reverselookup);

}



//////////////////////////////
//
// markDissonances -- search for parts which are dissonant 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 markDissonances(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 hval;
   int dhint; // diatonic harmonic interval

   int highest;
   int lowest;
   int has2nd = 0;
   int has7th = 0;
   int has4th = 0;

   for (j=0; j<notes[0].getSize(); j++) {
      has4th |= checkForFourth(notes, j, infile);
   }

   for (i=0; i<notes.getSize(); i++) {
      for (j=0; j<notes[i].getSize(); j++) {
         if (notes[i][j].b40 == REST) {
            // ignore rests
            continue;
         }

         for (k=i+1; k<notes.getSize(); k++) {
            if (notes[k][j].b40 == 0) {
               // ignore rests
               continue;
            }

            if ((notes[k][j].b40 < 0) && (notes[i][j].b40 < 0)) {
               // ignore notes if both are tied.
               continue;
            }

            // measure the interval from highest to lowest
            if (abs(notes[k][j].b40) > abs(notes[i][j].b40)) {
               highest = k;
               lowest  = i;
            } else {
               highest = i;
               lowest  = k;
            }

            hint  = abs(notes[highest][j].b40) - abs(notes[lowest][j].b40);
            dhint = Convert::base40IntervalToDiatonic(hint) % 7;

            // store interval as positive for bottom voice's note and 
            // negative for top voice's note.
            if (notes[lowest][j].b40 > 0) {
               hval = hint % 40;
               if (hval != 17) {  // suppress 4ths since handled elsewhere
                  notes[lowest][j].hvoice.append(highest);
                  notes[lowest][j].hint.append(hval);
               }
            }
            if (notes[highest][j].b40 > 0) {
               hval = hint % 40;
               if (hval != 17) { // suppress 4ths since handled elsewhere
                  hval = -hval;
                  notes[highest][j].hvoice.append(lowest);
                  notes[highest][j].hint.append(hval);
               }
            }

            // markInterval(infile, notes, hint, highest, j, lowest);
            if (dhint == 1) { 
               has2nd = 1; 
               if (notes[highest][j].b40 > 0) {
                  markNote(infile, '<', notes[highest][j].line, 
                        notes[highest][j].spine);
               }
               if (notes[lowest][j].b40 > 0) {
                  markNote(infile, '<', notes[lowest][j].line, 
                        notes[lowest][j].spine);
               }
            }
            if (dhint == 6) { 
               has7th = 1; 
               if (notes[highest][j].b40 > 0) {
                  markNote(infile, '>', notes[highest][j].line, 
                        notes[highest][j].spine);
               }
               if (notes[lowest][j].b40 > 0) {
                  markNote(infile, '>', notes[lowest][j].line, 
                        notes[lowest][j].spine);
               }
            }

         }
      }
   }

   if (labelQ) {
      printDissonanceOutput(infile, notes, names);
   } else {
      cout << infile;
   }

   if (has4th) {
      cout << "!!!RDF**kern: | = marked note, color=\"#ff0000\", ";
      cout << "fourth interval above lowest note" << endl;
   }

   if (has2nd) {
      cout << "!!!RDF**kern: < = marked note, color=\"#00ff00\", ";
      cout << "second interval" << endl;
   }

   if (has7th) {
      cout << "!!!RDF**kern: > = marked note, color=\"#00ff00\", ";
      cout << "seventh interval" << endl;
   }

}



//////////////////////////////
//
// checkForFourth -- returns true if a 4th above the lowest note was found.
//

int checkForFourth(Array >& notes, int j, HumdrumFile& infile) {
   int i;
   int lowest = 100000;
   int lowestindex = -1;
   int notecount = 0;
   int output = 0;
   int hint;

   for (i=0; i<notes.getSize(); i++) {
      if (notes[i][j].b40 == REST) {
         continue;
      }
      notecount++;
      if (notecount == 1) {
         lowest = abs(notes[i][j].b40);
         lowestindex = i;
         continue;
      }
      if (abs(notes[i][j].b40) < lowest) {
         lowest = abs(notes[i][j].b40);
         lowestindex = i;
      }
   }
   if (notecount == 0) {
      return output;
   }
   if (lowestindex < 0) {
      return output;
   }

   int value;
   for (i=0; i<notes.getSize(); i++) {
      if (notes[i][j].b40 == REST) {
         continue;
      }
      hint = abs(notes[i][j].b40) - abs(notes[lowestindex][j].b40);
      if ((hint % 40) == 17) { // 17 = base-40 value for a perfect fourth
         output = 1;  
         // found a fourth against the lowest note in the sonority.
         value = -hint;
         if (notes[i][j].b40 > 0) {
            notes[i][j].hint.append(value);
            notes[i][j].hvoice.append(lowestindex);
            markNote(infile, '|', notes[i][j].line, 
                  notes[i][j].spine);
         }
         value = hint;
         if (notes[lowestindex][j].b40 > 0) {
            notes[lowestindex][j].hint.append(value);
            notes[lowestindex][j].hvoice.append(i);
            markNote(infile, '|', notes[lowestindex][j].line, 
                  notes[lowestindex][j].spine);
         }

      }
   }

   return output;
}



//////////////////////////////
//
// markNote --
//

void markNote(HumdrumFile& infile, char mark, int line, int spine) {
   Array<char> token;
   token.setSize(strlen(infile[line][spine])+2);
   strcpy(token.getBase(), infile[line][spine]);
   char buffer[2] = {0};
   buffer[0] = mark;
   strcat(token.getBase(), buffer);
   infile[line].changeField(spine, token.getBase());
}



//////////////////////////////
//
// getAbbreviations --
//

void getAbbreviations(Array<Array<char> >& abbreviations, 
      Array<Array<char> >& names) {


   abbreviations.setSize(names.getSize());
   int i;
   for (i=0; i<names.getSize(); i++) {
      getAbbreviation(abbreviations[i], names[i]);     
   }
}



//////////////////////////////
//
// getAbbreviation --
//

void getAbbreviation(Array& abbr, Array& name) {
   PerlRegularExpression pre;
   abbr.setSize(strlen(name.getBase())+1);
   strcpy(abbr.getBase(), name.getBase());
   pre.sar(abbr, "(?<=[a-zA-Z])[a-zA-Z]*", "", "");
   pre.tr(abbr, "123456789", "abcdefghi");
}



//////////////////////////////
//
// printDissonanceOutput --
//

void printDissonanceOutput(HumdrumFile& infile, 
      Array<Array<NoteNode> >& notes, Array<Array<char> >& names) {
   Array<int> linemap;
   linemap.setSize(infile.getNumLines());
   linemap.setAll(-1);
   int i, k;
   for (k=0; k<notes.getSize(); k++) {
      for (i=0; i<notes[k].getSize(); i++) {
         if (notes[k][i].line >= 0) {
            linemap[notes[k][i].line] = i;
         }
      }
   }

   Array<Array<char> > abbreviations;
   getAbbreviations(abbreviations, names);

   int j;
   int n;
   int value;
   int track;
   int nexttrack;
   int noteindex;
   PerlRegularExpression pre;
   PerlRegularExpression pre2;

   for (i=0; i<infile.getNumLines(); i++) {
      if (debugQ) {
         cout << "LINE = " << i << ":\t" << infile[i] << endl;
      }
      if (infile[i].isMeasure() || infile[i].isData()
          || infile[i].isInterpretation() || infile[i].isLocalComment()) {
         noteindex = 0;
         nexttrack = 0;
         track = 0;

         for (j=0; j<infile[i].getFieldCount(); j++) {
            if (!infile[i].isExInterp(j, "**kern")) {
               cout << infile[i][j];
               if (j < infile[i].getFieldCount() - 1) {
                  cout << '\t';
               }
               continue;
            }

            // first print the **kern-related token:
            cout << infile[i][j];

            // then print extra data spines for interval descriptions
            // if the **kern token is at the end of an adjacent subspine group:
            if (j < infile[i].getFieldCount()-1) {
               track = infile[i].getPrimaryTrack(j);
               nexttrack = infile[i].getPrimaryTrack(j+1);
            } else {
               track = infile[i].getPrimaryTrack(j);
               nexttrack = -1;
            }
            if (track != nexttrack) {
               // the next track is different from the current one
               // so print interval data spine here.
               cout << "\t"; 
               if (infile[i].isMeasure()) {
                  cout << infile[i][0];
               } else if (infile[i].isLocalComment()) {
                  cout << "!";
               } else if (infile[i].isInterpretation()) {
                  if (strncmp(infile[i][0], "**", 2) == 0) {
                     cout << "**text";
                  } else if (strcmp(infile[i][0], "*-") == 0) {
                     cout << "*-";
                  } else {
                     cout << "*";
                  }
               } else if (infile[i].isData()) {
                  printIntervalInfo(infile, i, j, notes, linemap[i], noteindex,
                        abbreviations);
               }
            } else {
               noteindex--;
            }
            if (j < infile[i].getFieldCount() - 1) {
               cout << '\t';
            }
            noteindex++;
         }
         cout << '\n';
      } else {
         if (pre.search(infile[i][0], "^(!+muse2ps.*)v([\\d,]+)(.*)", "")) {
            cout << pre.getSubmatch(1);
            cout << "v";
            Array<Array<char> > tokens;
            pre2.getTokens(tokens, ",", pre.getSubmatch(2));
            for (n=0; n<tokens.getSize(); n++) {
               value = atoi(tokens[n].getBase());            
               value += 10;
               cout << value;
               if (n < tokens.getSize()-1) {
                  cout << ",";
               }
            }
            cout << pre.getSubmatch(3);
            cout << "\n";

         } else {
            cout << infile[i] << endl;
         }
      }
   }
}



//////////////////////////////
//
// printIntervalInfo --
//

void printIntervalInfo(HumdrumFile& infile, int line, int spine, 
      Array<Array<NoteNode> >& notes, int noteline, int noteindex,
      Array<Array<char> >& abbr) {


   // [20120323] fixed spine split problem?
   if (noteindex >= notes.getSize()) {
      return;
   } 


   if (notes[noteindex][noteline].hint.getSize() == 0) {
      cout << ".";
      return;
   }

   SSTREAM out;


   int m;
   int dhint;
   int msize = notes[noteindex][noteline].hint.getSize();
   int hint;
   int pcount = 0;
   for (m=0; m<msize; m++) {
      hint = notes[noteindex][noteline].hint[m];
      dhint = Convert::base40IntervalToDiatonic(abs(hint)) % 7 + 1;

      if (dhint == 2) {
         pcount++;
         out << abbr[notes[noteindex][noteline].hvoice[m]];
         if (hint < 0) {
            out << "-";
         }
         out << "2";
         out << ",";
      }

      if (dhint == 7) {
         pcount++;
         out << abbr[notes[noteindex][noteline].hvoice[m]];
         if (hint < 0) {
            out << "-";
         }
         out << "7";
         out << ",";
      }

      if (abs(hint) % 40 == 17) {
         pcount++;
         out << abbr[notes[noteindex][noteline].hvoice[m]];
         if (hint < 0) {
            out << "-";
         }
         out << "4";
         out << ",";
      }

   }
   if (pcount == 0) {
      out << ".";
   }

   out << ends;
   Array<char> output;
   output.setSize(strlen(out.CSTRING)+1);
   strcpy(output.getBase(), out.CSTRING);
   PerlRegularExpression pre;
   pre.sar(output, ",$", "", "");
   cout << output;

}



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

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

   char marker = '<';  // dissonant octaves/unisons
   if (hint == 23) {
      marker = '>';    // dissonant fifths
   }

   int ii;
   int jj;


   // mark first note in dissonant 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 dissonant 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 dissonant 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 dissonant 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);
}



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

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

   // caluculate the intervals to previous notes:
   for (i=0; i<notes.getSize(); i++) {
      lastint = RESTINT;
      lastnote = 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;
            continue;
         }
         if (notes[i][j].b40 == 0) {
            // current note is a rest, to make the interval to the last
            // note a RESTINT.
            lastnote = 0;
            notes[i][j].lastint = RESTINT;
            lastint = 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
            notes[i][j].lastint = RESTINT;
            lastint = RESTINT;
            continue;
         }
         // have a note attack which does note follow a rest
         lastint = notes[i][j].b40 - lastnote;
         lastnote = notes[i][j].b40;
         notes[i][j].lastint = lastint;
      }
   }


   // 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 nextnote = 0;
   // caluculate the intervals to next notes:
   for (i=0; i<notes.getSize(); i++) {
      nextint = RESTINT;
      nextnote = 0;
      for (j=notes[i].getSize()-1; j>=0; j--) {
         notes[i][j].nextint = nextint;
         if (notes[i][j].b40 >= 0) {
            nextint = notes[i][j].lastint;
         }
      }
   }

}




//////////////////////////////
//
// 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("f|file=b", "display filenames with -l option");
   //opts.define("l|location|loc=b", "print locations of dissonants");
   opts.define("label=b", "print text labels");
   opts.define("r|raw=b", "print raw data before analysis is done");
   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");
   labelQ =!opts.getBoolean("label");
   raw2Q  = opts.getBoolean("raw2");
   fileQ  = opts.getBoolean("file");
}



//////////////////////////////
//
// 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: 6dbbf454c8efaef3af52821368f51178 dissonant.cpp [20120404]