//
// Programmer:    Craig Stuart Sapp <craig@ccrma.stanford.edu>
// Creation Date: Thu Mar 29 17:22:53 PST 2001
// Last Modified: Sun Feb 16 07:58:18 PST 2003 (convert to RootSpectrum class)
// Last Modified: Sat Jan 29 13:50:23 PST 2005
// Filename:      ...sig/examples/all/roottest.cpp
// Web Address:   http://sig.sapp.org/examples/museinfo/humdrum/roottest.cpp
// Syntax:        C++; museinfo
//
// Description:   determine the root of chords in music according
//                to the timespan of a preexisting root analysis spine.
// 

#include "humdrum.h"
#include "stdio.h"

class NoteData {
   public:
      int pitch;       // pitch of the note
      double dur;      // duration of the note
      double durin;    // duration of the note inside the chord region
      double beat;     // metric position in a meter
      double absbeat;  // absolute beat position of note in piece
      int inregion;    // 1 if attacked in region, 0 if attacked out of region
      int outregion;   // 1 if terminated in region, 0 if outside of region
      Array<NoteData>* preceding;  // note preceding this one
      Array<NoteData>* following;  // note following this one
     ~NoteData() {
         if (preceding != NULL) delete preceding;
         if (following != NULL) delete following;
      }
      NoteData(void) {
         preceding = NULL;
         following = NULL;
         clear();
      }
      NoteData(NoteData& aNote) {
         pitch        = aNote.pitch;
         dur          = aNote.dur;
         durin        = aNote.durin;
         beat         = aNote.beat;
         absbeat      = aNote.absbeat;
         inregion     = aNote.inregion;
         outregion    = aNote.outregion;
         deallocate();
         if (aNote.preceding != NULL) {
            preceding = new Array<NoteData>;
            *preceding = *(aNote.preceding);
         }
         if (aNote.following != NULL) {
            following = new Array<NoteData>;
            *following = *(aNote.following);
         }
      }
      void clear(void) {
         pitch     = -1;
         dur       = -1;
         durin     = -1;
         beat      = -1;
         absbeat   = -1;
         inregion  = -1;
         outregion = -1;
         deallocate();
      }
      void allocate(void) {
         if (preceding != NULL) delete preceding;
         if (following != NULL) delete following;
         preceding = new Array<NoteData>;
         following = new Array<NoteData>;
         preceding->setSize(0);
         following->setSize(0);
      }
      void deallocate(void) {
         if (preceding != NULL) delete preceding;
         if (following != NULL) delete following;
         preceding = NULL;
         following = NULL;
      }
      int hasPreceding(void) {
         if (preceding == NULL) return 0;
         if (preceding->getSize() == 0) return 0;
         return 1;
      }
      int hasFollowing(void) {
         if (following == NULL) return 0;
         if (following->getSize() == 0) return 0;
         return 1;
      }
      NoteData& operator=(NoteData& aNote) {
         if (this == &aNote) {
            return aNote;
         }
         pitch      = aNote.pitch;
         dur        = aNote.dur;
         durin      = aNote.durin;
         beat       = aNote.beat;
         absbeat    = aNote.absbeat;
         inregion   = aNote.inregion;
         outregion  = aNote.outregion;
         deallocate();
         if (aNote.preceding != NULL) {
            preceding  = new Array<NoteData>;
            *preceding = *(aNote.preceding);
         }
         if (aNote.following != NULL) {
            following  = new Array<NoteData>;
            *following = *(aNote.following);
         }
         return *this;
      }
};

#define RCASE_UNKNOWN 0
#define RCASE_135     1
#define RCASE_7       2
#define RCASE_2       3
#define RCASE_4       4
#define RCASE_6       5
#define RCASE_24      6
#define RCASE_26      7
#define RCASE_46      8
#define RCASE_246     9
#define RCASE_SIZE   10


// function declarations
void   checkOptions(Options& opts, int argc, char* argv[]);
void   example(void);
void   usage(const char* command);
void   generateAnalysis(HumdrumFile& hfile);
void   printAnalysis(Array<int>& newroots, 
                                 Array<int>& chordstartlines, 
                                 Array<int>& realroots, 
                                 Array<Array<NoteData> >& chordnotes, 
                                 HumdrumFile& infile,
                                 Array<RootSpectrum>& spectra);
void   getChordStartLines(Array<int>& chordstartlines, 
                                 Array<int>& realroot, HumdrumFile& infile);
void   printNoteInfo(NoteData& note);
void   getNoteDatum(Array<NoteData>& chordnotes,  
                                 HumdrumFile& infile, int line, int spine,
                                 int startline, int stopline, int datainit);
void   extractNoteInformation(Array<NoteData>& chordnotes, 
                                 HumdrumFile& infile, 
                                 int startline, int stopline);
void   checkForMelodicNotes(NoteData& note, HumdrumFile& infile, int line, 
                                 int spine, int startline, int stopline);
int    calculateRoot(Array<NoteData>& chordnotes,
                                 RootSpectrum& spectrum);
int    getStartingMeasure(HumdrumFile& infile);
double getDurationInsideChordRegion(HumdrumFile& infile, int line, int spine, 
                                 int startline, int stopline, double notedur);
int    getChordCase(int root, Array<NoteData>& chordnotes);
void   percent(int top, int bottom);
double checkMelodicContext(double value, int testroot, int notenum, 
                                 Array<NoteData>& chordnotes);


// global variables
Options  options;            // database for command-line arguments
int      debugQ       = 0;   // used with the --debug option
int      mdebugQ      = 0;   // used with the --melodic-debug option
int      informationQ = 0;   // used with -i option
int      appendQ      = 0;   // used with -a option
int      prependQ     = 0;   // used with -p option  
int      soloQ        = 1;   // used with -p and -a option
int      errorQ       = 0;   // used with -e option
int      printweightQ = 0;   // used with --print-weights option
double   theta1       = 0.0; // used with -t option
double   theta2       = 0.0; // used with -t option
int      twothetaQ    = 0;   // used with the --t2 option
const char* filename  = "";  // file currently being processsed
int      chordcountQ  = 0;   // used with -c option
int      shortQ       = 1;   // used with -L option
int      summaryQ     = 0;   // used with -s option
int      melodyQ      = 0;   // used with -m option
int      spectrumQ    = 0;   // used with --rs option
double   speclevel    = 2.0; // no input parameter yet.
double   mfactor      = 0.0; // used with --mf option
IntervalWeight  iweights;

Array<int>      casecount;
Array<int>      caseerror;

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

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

   casecount.setSize(RCASE_SIZE);
   caseerror.setSize(RCASE_SIZE);
   casecount.setAll(0);
   caseerror.setAll(0);
   casecount.allowGrowth(0);
   caseerror.allowGrowth(0);

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

   HumdrumFile hfile;
   if (twothetaQ) {
      iweights.setChromatic(theta1, theta2);
   } else {
      iweights.setChromatic(theta1);
   }

   if (printweightQ) {
      cout << iweights;
      exit(0);
   }

   Array<int> chordstartlines;
   Array<int> realroots;

   // figure out the number of input files to process
   int numinputs = options.getArgCount();

   int j;
   int chordsum = 0;

   for (int i=0; i<numinputs || i==0; i++) {
      hfile.clear();

      // if no command-line arguments read data file from standard input
      if (numinputs < 1) {
         hfile.read(cin);
      } else {
         filename = options.getArg(i+1);
         hfile.read(filename);
      }

      if (chordcountQ) {
         getChordStartLines(chordstartlines, realroots, hfile);
         for (j=0; j<realroots.getSize(); j++) {
            // char buffer[1024];
            if (realroots[j] >= 0) {
               // cout << "XXX " << realroots[j] << "\t"
               //      << Convert::base40ToKern(buffer, realroots[j])
               //      << endl;
               chordsum++;
            }
         }
      } else {
         generateAnalysis(hfile);
      }
   }

   if (chordcountQ) {
      cout << "!! Total Chord Segments: " << chordsum << endl;
   } else if (summaryQ) {
      cout << "!!" << endl;
      cout << "!! Chord Case Summary: " << endl;
      cout << "!!" << endl;
      int totalsum = 0;
      int errorsum = 0;
      for (int kk=1; kk<RCASE_SIZE; kk++) {
         totalsum += casecount[kk]; 
         errorsum += caseerror[kk]; 
      }
      cout << "!!        \terrors/total\terror-rate(%)" << endl;
      cout << "!! All cases:\t" << errorsum << "/" << totalsum;
      if (totalsum < 1000) {
         cout << "\t";
      }
      if (totalsum == 0) {
         cout << "\t\t NA";
      } else {
        cout << "\t" ; percent(errorsum,totalsum);
      }
      cout << endl;

      cout << "!! case135:\t" << caseerror[RCASE_135] 
           << "/" << casecount[RCASE_135];
      if (casecount[RCASE_135] == 0) {
         cout << "\t\t NA";
      } else {
        cout << "\t\t" ; percent(caseerror[RCASE_135],casecount[RCASE_135]);
      }
      cout << endl;

      cout << "!! case7:\t" << caseerror[RCASE_7] 
           << "/" << casecount[RCASE_7];
      if (casecount[RCASE_7] == 0) {
         cout << "\t\t NA";
      } else {
        cout << "\t\t" ; percent(caseerror[RCASE_7],casecount[RCASE_7]);
      }
      cout << endl;

      cout << "!! case2:\t" << caseerror[RCASE_2] 
           << "/" << casecount[RCASE_2];
      if (casecount[RCASE_2] == 0) {
         cout << "\t\t NA";
      } else {
        cout << "\t\t" ; percent(caseerror[RCASE_2],casecount[RCASE_2]);
      }
      cout << endl;

      cout << "!! case4:\t" << caseerror[RCASE_4] 
           << "/" << casecount[RCASE_4];
      if (casecount[RCASE_4] == 0) {
         cout << "\t\t NA";
      } else {
        cout << "\t\t" ; percent(caseerror[RCASE_4],casecount[RCASE_4]);
      }
      cout << endl;

      cout << "!! case6:\t" << caseerror[RCASE_6] 
           << "/" << casecount[RCASE_6];
      if (casecount[RCASE_6] == 0) {
         cout << "\t\t NA";
      } else {
        cout << "\t\t" ; percent(caseerror[RCASE_6],casecount[RCASE_6]);
      }
      cout << endl;

      cout << "!! case24:\t" << caseerror[RCASE_24] 
           << "/" << casecount[RCASE_24];
      if (casecount[RCASE_24] == 0) {
         cout << "\t\t NA";
      } else {
        cout << "\t\t" ; percent(caseerror[RCASE_24],casecount[RCASE_24]);
      }
      cout << endl;

      cout << "!! case26:\t" << caseerror[RCASE_26] 
           << "/" << casecount[RCASE_26];
      if (casecount[RCASE_26] == 0) {
         cout << "\t\t NA";
      } else {
        cout << "\t\t" ; percent(caseerror[RCASE_26],casecount[RCASE_26]);
      }
      cout << endl;

      cout << "!! case46:\t" << caseerror[RCASE_46] 
           << "/" << casecount[RCASE_46];
      if (casecount[RCASE_46] == 0) {
         cout << "\t\t NA";
      } else {
        cout << "\t\t" ; percent(caseerror[RCASE_46],casecount[RCASE_46]);
      }
      cout << endl;

      cout << "!! case246:\t" << caseerror[RCASE_246] 
           << "/" << casecount[RCASE_246];
      if (casecount[RCASE_246] == 0) {
         cout << "\t\t NA";
      } else {
        cout << "\t\t" ; percent(caseerror[RCASE_246],casecount[RCASE_246]);
      }
      cout << endl;
   }

   return 0;
}


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


//////////////////////////////
//
// percent -- 
//

void percent(int top, int bottom) {
   double value = 100.0 * top / bottom;
   value = ((int)(value * 100))/100.0;
   if (value < 100) {
      cout << " ";
   }
   if (value < 10) {
      cout << " ";
   }
   cout << value;
}



//////////////////////////////
//
// generateAnalysis -- 
//

void generateAnalysis(HumdrumFile& infile) {
   infile.analyzeRhythm(); 

   Array<int> chordstartlines;
   Array<int> realroots;
   getChordStartLines(chordstartlines, realroots, infile);

   Array<Array<NoteData> > chordnotes;

   chordnotes.setSize(chordstartlines.getSize());
   int i;
   for (i=0; i<chordstartlines.getSize(); i++) {
      chordnotes[i].setSize(100);
      chordnotes[i].setGrowth(1000);
      chordnotes[i].setSize(0);
      if (realroots[i] > 0) {
         extractNoteInformation(chordnotes[i], infile, chordstartlines[i],
               chordstartlines[i+1]);
      }
   }


   Array<int> newroots;
   newroots.setSize(realroots.getSize());
   newroots.setAll(-1);

   Array<RootSpectrum> spectra;
   spectra.setSize(chordstartlines.getSize());

   char buffer[128] = {0};

   for (i=0; i<chordstartlines.getSize(); i++) {
      if (realroots[i] >= 0) {
         newroots[i] = calculateRoot(chordnotes[i], spectra[i]);
      }

      if (debugQ) {
     
         cout << "ROOT = ";
         if (realroots[i] < 0) {
            cout << "r";
         } else {
            cout << Convert::base40ToKern(buffer, realroots[i]%40 + 120);
         }
         cout << " MEASURED: ";
         if (newroots[i] < 0) {
            cout << "r";
         } else {
            cout << Convert::base40ToKern(buffer, newroots[i] + 120);
         }
         cout << endl;
      }
   }

   printAnalysis(newroots, chordstartlines, realroots, chordnotes, 
         infile, spectra);
}



////////////////////////////////////////
//
// checkMelodicContext -- check if the given notenum is linkable to
//      a chord tone of the given testroot according to the preceding
//      and following notes in the chordnotes entry for the notenum.
//

double checkMelodicContext(double value, int testroot, int notenum, 
       Array<NoteData>& chordnotes) {
   int tinterval;

   int note1;       // base-40 value of test root
   int note2;       // base-40 value of test chord tone
   int dianote1;    // diatonic value of input note
   int dianote2;    // diatonic value of test chord tone
   int diaroot;     // diatonic value of test root

   int rootdia1;    // diatonic interval above root for input note
   int rootdia2;    // diatonic interval above root for test chord tone

   diaroot = Convert::base40ToDiatonic(testroot) % 7;

   note1    = chordnotes[notenum].pitch;
   dianote1 = Convert::base40ToDiatonic(note1) % 7;
   rootdia1 = (dianote1 - diaroot + 700) % 7;

   char mbuffer[128] = {0};
   if (mdebugQ) {
      cout << "Checking if note " << Convert::base40ToKern(mbuffer, note1)
           << " can be non-harm. for test-root ";
      cout << Convert::base40ToKern(mbuffer, testroot+120);
      cout << " (Diatonic int. is: " << rootdia1 << ")" << endl;
   }

   // if test note cannot be interpreted as the 2, 4, or 6 scale
   // degree of the chord, then skip.  Note: this requirement should
   // be corrected so that enharmonic spelling is not important.
   if (!((rootdia1 == 1) || (rootdia1 == 3) || (rootdia1 == 5))) {
      return value;
   }

   if (mdebugQ) {
      cout << "YES IT IS POSSIBLE.  Now search for linkable chord-note" << endl;
   }


   // the test note is a possible non-harmonic tone candidate.
   // so check the preceding and following notes of the test note
   // to see if they are chord-tones.

   int i;
   if (chordnotes[notenum].preceding != NULL) {

      for (i=0; i<chordnotes[notenum].preceding->getSize(); i++) {

         note2 = (*chordnotes[notenum].preceding)[i].pitch;

         // if (debugQ) {

         // } 
         if (note1 == note2) {
            // ignore repeated notes
            continue;
         }
         dianote2 = Convert::base40ToDiatonic(note2) % 7;
         rootdia2 = (dianote2 - diaroot + 700) % 7;
         tinterval = note2 - note1;


         if (mdebugQ) {
            cout << "Comparing test note: " 
                 << Convert::base40ToKern(mbuffer, note1);
            cout << "\tto test chord note: " 
                 << Convert::base40ToKern(mbuffer, note2);
            cout << "\twhen root is: " 
                 << Convert::base40ToKern(mbuffer, testroot + 120);
            cout << endl;
            cout << "Diatonic interval between notes: " 
                 << dianote1 - dianote2 << endl;
            cout << "Diatonic interval between test root "
                 << "and test harmonic note: " << rootdia2 << endl;
            cout << "Diatonic interval between test root and "
                 << "test non-harmonic note: " << rootdia1 << endl;
         }



         if (abs(tinterval) < 9) {   // melodic note preeceding notenum
            // check to see notenum can be assigned a 2,4,6 position
            // in the chord (happens if note2 is in position 1,3,5)
            // but only above position or below position 5. 
            // Note: should correct rule by ignoring enharmonic spellings.
            
            if ((rootdia2 == 0) && (rootdia1 == 1)) {
              // note can attach to root as a second
              // return 0.0;
              // return iweights[(note2 - testroot + 4000)%40];
              return value * mfactor;
              // return value - mfactor;
            } else if ((rootdia2 == 2) && (rootdia1 == 1)) {
              // note can attach to third as a second
              // return 0.0;
              // return iweights[(note2 - testroot + 4000)%40];
              return value * mfactor;
              // return value - mfactor;
            } else if ((rootdia2 == 2) && (rootdia1 == 3)) {
              // note can attach to third as a fourth
              // return 0.0;
              // return iweights[(note2 - testroot + 4000)%40];
              return value * mfactor;
              // return value - mfactor;
            } else if ((rootdia2 == 4) && (rootdia1 == 3)) {
              // note can attach to fifth as a fourth
              // return 0.0;
              // return iweights[(note2 - testroot + 4000)%40];
              return value * mfactor;
              // return value - mfactor;
            } else if ((rootdia2 == 4) && (rootdia1 == 5)) {
              // note can attach to fifth as a sixth
              // return 0.0;
              // return iweights[(note2 - testroot + 4000)%40];
              return value * mfactor;
              // return value - mfactor;
            } else {
              // return value/2;
            }
      
         }
      }

   } else {
      if (mdebugQ) {
         cout << "No preceding notes in chord to link onto." << endl;
      }
   }

// cout << "Test note has not forward linkage" << endl;


   // do similar thing for following notes.

   if (chordnotes[notenum].following != NULL) {

      for (i=0; i<chordnotes[notenum].following->getSize(); i++) {
         note2 = (*chordnotes[notenum].following)[i].pitch;
         if (note1 == note2) {
            // ignore repeated notes
            continue;
         }
         dianote2 = Convert::base40ToDiatonic(note2) % 7;
         rootdia2 = (dianote2 - diaroot + 700) % 7;
         tinterval = note2 - note1;
         if (abs(tinterval) < 9) {   // melodic note preeceding notenum
            // check to see notenum can be assigned a 2,4,6 position
            // in the chord (happens if note2 is in position 1,3,5)
            // but only above position or below position 5. 
            // Note: should correct rule by ignoring enharmonic spellings.
            
            if ((rootdia2 == 0) && (rootdia1 == 1)) {
              // note can attach to root as a second
              // return 0.0;
              // return iweights[(note2 - testroot + 4000)%40];
              return value * mfactor;
              // return value - mfactor;
            } else if ((rootdia2 == 2) && (rootdia1 == 1)) {
              // note can attach to third as a second
              // return 0.0;
              // return iweights[(note2 - testroot + 4000)%40];
              return value * mfactor;
              // return value - mfactor;
            } else if ((rootdia2 == 2) && (rootdia1 == 3)) {
              // note can attach to third as a fourth
              // return 0.0;
              // return iweights[(note2 - testroot + 4000)%40];
              return value * mfactor;
              // return value - mfactor;
            } else if ((rootdia2 == 4) && (rootdia1 == 3)) {
              // note can attach to fifth as a fourth
              // return 0.0;
              // return iweights[(note2 - testroot + 4000)%40];
              return value * mfactor;
              // return value - mfactor;
            } else if ((rootdia2 == 4) && (rootdia1 == 5)) {
              // note can attach to fifth as a sixth
              // return 0.0;
              // return iweights[(note2 - testroot + 4000)%40];
              return value * mfactor;
              // return value - mfactor;
            } else {
              // return value/2;
            }
      
         }

      }
   } else {
      if (mdebugQ) {
         cout << "No preceding notes in chord to link onto." << endl;
      }
   }

// cout << "Test note has not backward linkage" << endl;

   // note could not be put into a non-harmonic melodic context,
   // so return the input value

   return value;
}



//////////////////////////////
//
// calculateRoot --  Do all of the harmonic analysis work.
//

int calculateRoot(Array& chordnotes, RootSpectrum& spectrum) {
   int testroot;
   int notenum;
   double minsum =  0.0;
   int minroot   = -1;
   double value;

   double sum = 0.0;
   for (testroot=0; testroot<40; testroot++) {
      sum = 0.0;
      for (notenum=0; notenum<chordnotes.getSize(); notenum++) {
         value = iweights[(chordnotes[notenum].pitch - (testroot) + 4000)%40];
         if (melodyQ) {
            value = checkMelodicContext(value, testroot, notenum, chordnotes);
         }

         // add rhythm weighting back at this point
         sum += value;
      }
      if (minroot < 0) {
         minroot = testroot; 
         minsum  = sum;
      } else if (sum < minsum) {
         minroot = testroot;
         minsum  = sum;
      }
      spectrum[testroot] = sum;
   }

   if (minroot >= 0) {
      minroot += 2;
      if (minroot > 40) {
         minroot -= 40;
      }
   }


   if (debugQ) {
      cout << "MEASURED ROOT: " << minroot << " SCORE = " << minsum << endl;
   }

   return minroot;
}




//////////////////////////////
//
// extractNoteInformation --
//

void extractNoteInformation(Array<NoteData>& chordnotes, HumdrumFile& infile, 
      int startline, int stopline) {
   int i, j;

   chordnotes.setSize(0);
   int datainit = 0;
   
   for (i=startline; i<stopline; i++) {
      if (infile[i].getType() != E_humrec_data) {
         continue;
      }
      for (j=0; j<infile[i].getFieldCount(); j++) {
         if (strcmp(infile[i].getExInterp(j), "**kern") != 0) {
            continue;
         }
         if (strcmp(infile[i][j], ".") == 0) {
            // in the middle of a note being played.
            if (datainit == 0) {
               getNoteDatum(chordnotes, infile, i, j, startline, stopline, 
                     datainit);
            } else {
               continue;
            }
         } else {
            getNoteDatum(chordnotes, infile, i, j, startline, stopline, 
                  datainit);
         }
      }
      datainit = 1;
   }
}



//////////////////////////////
//
// getNoteDatum -- get note information
//

void getNoteDatum(Array<NoteData>& chordnotes, HumdrumFile& infile, int line, 
      int spine, int startline, int stopline, int datainit) {
   NoteData note;
   if (strchr(infile[line][spine], 'r') != NULL) {
      return;
   }

   int i;
   int tokencount;
   char buffer[1024] = {0}; 
   note.clear();

   if (strcmp(infile[line][spine], ".") == 0) {
      int lastline  = 0;
      int lastspine = 0;
      lastline = infile.getLastDatumLine(lastspine, line, spine);
      note.inregion = 0;
      tokencount = infile[line].getTokenCount(spine);
      for (i=0; i<tokencount; i++) {
         infile[lastline].getToken(buffer, lastspine, i);
         if (buffer[0] == '\0') {
            continue;
         }
         if (strchr(buffer, 'r') != NULL) {
            continue;
         }
         note.pitch   = Convert::kernToBase40(buffer);
         if (strchr(buffer, '[') != NULL) {
            note.dur = infile.getTiedDuration(line, spine, i);
         } else if (strchr(buffer, '_') != NULL) {
            note.dur = infile.getTiedDuration(line, spine, i);
         } else if (strchr(buffer, ']') != NULL) {
            note.dur = infile.getTiedDuration(line, spine, i);
         } else {
            note.dur = Convert::kernToDuration(buffer);
         }
         note.beat    = infile.getBeat(lastline);
         note.absbeat = infile.getAbsBeat(lastline);
         if (note.absbeat + note.dur <= infile.getAbsBeat(stopline) + 0.001) {
            note.outregion = 1;
         } else {
            note.outregion = 0;
         }
         checkForMelodicNotes(note, infile, line, spine, startline, stopline);
         note.durin = getDurationInsideChordRegion(infile, line, spine, 
            startline, stopline, note.dur);
         chordnotes.append(note);
      }

   } else {
      note.beat     = infile.getBeat(line);
      note.absbeat  = infile.getAbsBeat(line);
      note.inregion = 1;
      tokencount = infile[line].getTokenCount(spine);
      for (i=0; i<tokencount; i++) {
         infile[line].getToken(buffer, spine, i);
         if (buffer[0] == '\0') {
            continue;
         }
         if (strchr(buffer, 'r') != NULL) {
            continue;
         }
         note.pitch   = Convert::kernToBase40(buffer);
         if (strchr(buffer, '[') != NULL) {
            note.dur = infile.getTiedDuration(line, spine, i);
         } else if (strchr(buffer, '_') != NULL) {
            if (datainit == 0) {
               note.dur = infile.getTiedDuration(line, spine, i);
            } else {
               // ignore tie continuation notes
               continue;
            }
         } else if (strchr(buffer, ']') != NULL) {
            if (datainit == 0) {
               note.dur = infile.getTiedDuration(line, spine, i);
            } else {
               // ignore tie terminating notes
               continue;
            }
         } else {
            note.dur = Convert::kernToDuration(buffer);
         }
         if (note.absbeat + note.dur <= infile.getAbsBeat(stopline) + 0.001) {
            note.outregion = 1;
         } else {
            note.outregion = 0;
         }

         // check for note coming before or after this one in the music.
         checkForMelodicNotes(note, infile, line, spine, startline, stopline);

         note.durin = getDurationInsideChordRegion(infile, line, spine, 
            startline, stopline, note.dur);

         chordnotes.append(note);

      }
   }
}



//////////////////////////////
//
// getDurationInsideChordRegion --  does not handle tied chords yet.
//

double getDurationInsideChordRegion(HumdrumFile& infile, int line, int spine, 
   int startline, int stopline, double notedur) {
   double startbeat = infile[startline].getAbsBeat();
   double stopbeat  = infile[stopline].getAbsBeat();

   double notestart = 0.0;
   double notestop  = 0.0;

   int lastline  = -1;
   int lastspine = -1;
   if (strcmp(infile[line][spine], ".") == 0) {
      // find the last note token that applies to this null token
      lastline = infile.getLastDatumLine(lastspine, line, spine);
      line = lastline;
      spine = lastspine;
   }

   if ((strchr(infile[line][spine], '_') != NULL) ||
          (strchr(infile[line][spine], ']') != NULL) ) {
      // found the middle/end of a tie, search for the 
      // beginning point of the tie in the music.
      notestart = infile.getTiedStartBeat(line, spine);
   } else {
      // either the start of a tied note or a regular note
      notestart = infile[line].getAbsBeat();
   }

   notestop = notestart + notedur;

   double inregion = notedur;
   if (startbeat - notestart > 0) {
      inregion = inregion - (startbeat - notestart);
   }
   if (notestop - stopbeat > 0) {
      inregion = inregion - (notestop - stopbeat);
   }

   return inregion;
}



///////////////////////////////
//
// checkForMelodicNotes -- look for melodic notes attached to the
//    current note which is inside the chord region.
//

void checkForMelodicNotes(NoteData& note, HumdrumFile& infile, int line, 
      int spine, int startline, int stopline) {

   int preline;
   int prespine;
   int postline;
   int postspine;

   if (startline == line) {
      preline = line;
      prespine = spine;
   } else {
      preline  = infile.getLastDatumLine(prespine, line, spine);
   }

   if (stopline-1 == line) {
      postline = line;
      postspine = spine;
   } else {
      postline = infile.getNextDatumLine(postspine, line, spine);
   }


   int k;
   char buffer[128] = {0};
   int tokencount;
   NoteData melnote;

   if ((preline >= startline) && (preline < line)) {
      // some previous notes to aquire;
      // cout << "Prenote data: " << infile[preline][prespine] << endl;
      note.allocate();

      tokencount = infile[preline].getTokenCount(prespine);
      for (k=0; k<tokencount; k++) {
         infile[preline].getToken(buffer, prespine, k);
         if (strchr(buffer, 'r') != NULL) {
            continue;
         }
         melnote.pitch = Convert::kernToBase40(buffer);
         note.preceding->append(melnote);
      }

   }

   if ((postline > 0) && (postline < stopline) && (line != postline)) {
      // some following notes to aquire
      // cout << "Postnote data: " << infile[postline][postspine] << endl;

      if (note.following == NULL) {
         note.allocate();
      }
      tokencount = infile[postline].getTokenCount(postspine);
      for (k=0; k<tokencount; k++) {
         infile[postline].getToken(buffer, postspine, k);
         if (strchr(buffer, 'r') != NULL) {
            continue;
         }
         melnote.pitch = Convert::kernToBase40(buffer);
         note.following->append(melnote);
      }

   }



}



//////////////////////////////
//
// getStartingMeasure --
//

int getStartingMeasure(HumdrumFile& infile) {
   int output = 0;
   int datastart = 0;
   int i;
   
   for (i=0; i<infile.getNumLines(); i++) {
      if (infile[i].getType() == E_humrec_data) {
         datastart = 1;
      } else if (infile[i].getType() == E_humrec_data_measure) {
         int count = sscanf(infile[i][0], "=%d", &output);
         if (count != 1) {
            output = 0;
         } else {
            output = output - 1;
         }
         break;
      }
   }

   return output;
}



//////////////////////////////
//
// printRootIntervals(int root, Array<Array<NoteData> >& chordnotes) {
//

void printRootIntervals(int root, Array& chordnotes) {
   cout << "{";
   int i;
   int value = 0;
   char buffer[128] = {0};
   for (i=0; i<chordnotes.getSize(); i++) {
      value = (chordnotes[i].pitch + 400 - root) % 40;
      cout << Convert::base40ToIntervalAbbr(buffer, value);
      if (i<chordnotes.getSize()-1) {
         cout << " ";
      }
   }
   cout << "}";

}



//////////////////////////////
//
// printChordCase --
//

void printChordCase(int rootcase) {

   switch (rootcase) {
      case RCASE_135:  cout << "case135";  break;
      case RCASE_7:    cout << "case7";    break;
      case RCASE_2:    cout << "case2";    break;
      case RCASE_4:    cout << "case4";    break;
      case RCASE_6:    cout << "case6";    break;
      case RCASE_24:   cout << "case24";   break;
      case RCASE_26:   cout << "case26";   break;
      case RCASE_46:   cout << "case46";   break;
      case RCASE_246:  cout << "case246";  break;
      default:            cout << "caseX";    break;
   }
}


//////////////////////////////
//
// getChordCase -- 
//

int getChordCase(int root, Array& chordnotes) {
   Array<int> tones(7);
   tones.zero();
   tones.allowGrowth(0);

   int i;
   int value = 0;
   for (i=0; i<chordnotes.getSize(); i++) {
      value = (chordnotes[i].pitch + 400 - root) % 40;
      tones[Convert::base40ToDiatonic((value+2) + 120) % 7] += 1;
   }

   int rootcase = RCASE_UNKNOWN;
   if (tones[1] && tones[3] && tones[5]) {
      rootcase = RCASE_246;
   } else if (!tones[1] && tones[3] && tones[5]) {
      rootcase = RCASE_46;
   } else if (tones[1] && !tones[3] && tones[5]) {
      rootcase = RCASE_26;
   } else if (tones[1] && tones[3] && !tones[5]) {
      rootcase = RCASE_24;
   } else if (!tones[1] && !tones[3] && tones[5]) {
      rootcase = RCASE_6;
   } else if (!tones[1] && tones[3]  && !tones[5]) {
      rootcase = RCASE_4;
   } else if (tones[1]  && !tones[3] && !tones[5]) {
      rootcase = RCASE_2;
   } else if (tones[6]) {
      rootcase = RCASE_7;
   } else if (tones[0] || tones[2] || tones[4]) {
      rootcase = RCASE_135;
   }

   return rootcase;
}





//////////////////////////////
//
// printAnalysis --
//

void printAnalysis(Array<int>& newroots, Array<int>& chordstartlines, 
      Array<int>& realroots, Array<Array<NoteData> >& chordnotes, 
      HumdrumFile& infile, Array<RootSpectrum>& spectra) {
   int i, j;
   int ii = 0;
   char buffer[128] = {0};
   const char* pstring = "*";
   int printroot = 0;
   int measure = getStartingMeasure(infile);
   int rootcase;

   for (i=0; i<infile.getNumLines(); i++) {
      if (informationQ) {
         if ((ii < chordstartlines.getSize()-1) && (i == chordstartlines[ii])) {
            cout << "!!ROOT_SEGMENT: "; 
            if (realroots[ii] < 0) {
               cout << "r";
            } else {
               cout << Convert::base40ToKern(buffer, (realroots[ii]%40) + 120);
            }
            cout << "\t=========================================";
            cout << "\n";
            for (j=0; j<chordnotes[ii].getSize(); j++) {
               if (chordnotes[ii][j].pitch >= 0) {
                  printNoteInfo(chordnotes[ii][j]);
               }
            }
         }
      }
      printroot = 0;
      if ((ii < chordstartlines.getSize()-1) && (i == chordstartlines[ii])) {
         printroot = 1;
      }
      if (infile[i].getType() == E_humrec_data_measure) {
         int tempnum = 0;
         int tcount = sscanf(infile[i][0], "=%d", &tempnum);
         if (tcount == 1) {
            measure = tempnum;
         }
      }

      if ((ii < chordstartlines.getSize()-1) && (i == chordstartlines[ii])) {

         rootcase = getChordCase(realroots[ii]%40, chordnotes[ii]);
         casecount[rootcase]++;
         if (newroots[ii]%40 != realroots[ii]%40) {
            caseerror[rootcase]++;
         }

         if (errorQ) {
            if (newroots[ii]%40 != realroots[ii]%40) {
               cout << "!!ROOT_ERROR: ";
               if (newroots[ii] < 0) {
                  cout << "r";
               } else {
                  cout << Convert::base40ToKern(buffer, 
                                                newroots[ii] % 40 + 120);
               }
               cout << " instead of ";
               if (realroots[ii] < 0) {
                  cout << "r";
               } else {
                  cout << Convert::base40ToKern(buffer, 
                                                realroots[ii] % 40 + 120);
               }
               cout << " at absbeat:" << infile[i].getAbsBeat();
               cout << " in measure:" << measure;
               cout << " beat:" << infile[i].getBeat();
               if (filename[0] != '\0') {
                  const char* ptr = strrchr(filename, '/');
                  if ((ptr != NULL) && shortQ) {
                     cout << " filename:" << ptr+1;
                  } else {
                     cout << " filename:" << filename;
                  }
               }
               cout << " ";
               printChordCase(rootcase);
               cout << ":";
               printRootIntervals(realroots[ii]%40, chordnotes[ii]);
               cout << endl;
            }
         }

         if (spectrumQ) {
            char classbuffer[128] = {0};
            int nminroot = spectra[ii].bestIndex();
            cout << "!!ROOT_SPECTRUM:{";
            for (int kk=0; kk<40; kk++) {
               if (spectra[ii][kk] > spectra[ii][nminroot]*speclevel) {
                 continue;
               }
               cout << Convert::base40ToKern(classbuffer, kk + 120 + 2) 
                    << ":" << spectra[ii][kk];
               cout << ", ";
            }
            cout << "}\n";
         }
      
      }

      switch(infile[i].getType()) {
         case E_humrec_data_comment:
            if (prependQ) {
               cout << "!\t";
            } else if (appendQ) {
               cout << infile[i] << "\t!\n";
            }
            break;
         case E_humrec_data_kern_measure:
            if (prependQ) {
               cout << infile[i][0] << "\t" << infile[i] << "\n";
            } else if (soloQ) {
               cout << infile[i][0] << "\n";
            } else if (appendQ) {
               cout << infile[i] << "\t" << infile[i][0] << "\n";
            }
            break;
         case E_humrec_interpretation:
            if (strncmp(infile[i][0], "**", 2) == 0) {
               pstring = "**root";
            } else if (strcmp(infile[i][0], "*-") == 0) {
               pstring = "*-";
            } else if (infile[i].equalFieldsQ()) {
               if (strcmp(infile[i][0], "*x") == 0) {
                  pstring = "*";
               } else if (strcmp(infile[i][0], "*v") == 0) {
                  pstring = "*";
               } else if (strcmp(infile[i][0], "*^") == 0) {
                  pstring = "*";
               } else if (strcmp(infile[i][0], "*+") == 0) {
                  pstring = "*";
               } else {
                  pstring = infile[i][0];
               }
            } else {
               pstring = "*";
            }
            if (prependQ) {
               cout << pstring << "\t" << infile[i] << "\n";
            } else if (soloQ) {
               cout << pstring << "\n";
            } else if (appendQ) {
               cout << infile[i] << "\t" << pstring << "\n";
            }            
            break;
         case E_humrec_data:
            if (printroot) {
               Convert::base40ToKern(buffer, newroots[ii] + 120);
            } else {
               strcpy(buffer, ".");
            }
            if (prependQ) {
               cout << buffer << "\t" << infile[i] << "\n";
            } else if (soloQ) {
               cout << buffer << "\n";
            } else if (appendQ) {
               cout << infile[i] << "\t" << buffer << "\n";
            }            
            break;
         case E_humrec_none:
         case E_humrec_empty:
         case E_humrec_bibliography:
         case E_humrec_global_comment:
         default:
            cout << infile[i] << "\n"; 
            break;
      }

      if (printroot) {  
         ii++;
         printroot = 0;
      }

   }
}



///////////////////////////////
//
// printNoteInfo --
//

void printNoteInfo(NoteData& note) {
   char buffer[128] = {0};

   cout << "!!\tNote: " << Convert::base40ToKern(buffer, note.pitch);
   cout << " \tdur:"   << note.dur;
   cout << "\tbt:"   << note.beat;
   cout << "\tatk:"  << note.inregion << ":" << note.outregion;
   int i;

   if (note.hasPreceding()) {
      cout << "\tpre:";
      for (i=0; i<note.preceding->getSize(); i++) {
         cout << Convert::base40ToKern(buffer, (*(note.preceding))[i].pitch);
         if (i < note.preceding->getSize() - 1) {
            cout << " ";
         }
      }
      
   }

   if (note.hasFollowing()) {
      cout << "\tpost:";
      for (i=0; i<note.following->getSize(); i++) {
         cout << Convert::base40ToKern(buffer, (*(note.following))[i].pitch);
         if (i < note.following->getSize() - 1) {
            cout << " ";
         }
      }
      
   }

   cout << "\n";

}



//////////////////////////////
//
// getChordStartLines -- 
//

void getChordStartLines(Array<int>& chordstartlines, Array<int>& realroot, 
      HumdrumFile& infile) {
   chordstartlines.setSize(infile.getNumLines());
   chordstartlines.setSize(0);
   realroot.setSize(infile.getNumLines());
   realroot.setSize(0);
   int root;
   int i, j;
   for (i=0; i<infile.getNumLines(); i++) {
      if (infile[i].getType() != E_humrec_data) {
         continue;
      }
      for (j=0; j<infile[i].getFieldCount(); j++) {
         if (strcmp(infile[i].getExInterp(j), "**root") == 0) {
            if (strcmp(infile[i][j], ".") == 0) {
               break;
            } else if (strchr(infile[i][j], 'r') != NULL) {
               root = -1;
               chordstartlines.append(i);
               realroot.append(root);
               break;
            } else {
               chordstartlines.append(i);
               root = Convert::kernToBase40(infile[i][j]);
               realroot.append(root);
               break;
            }
         }
      }
   }
   i = infile.getNumLines() - 1;
   root = -1;
   realroot.append(root);
   chordstartlines.append(i);
   chordstartlines.allowGrowth(0);
}



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

void checkOptions(Options& opts, int argc, char* argv[]) {
   opts.define("i|info|segments=b",  "display note extraction information");   
   opts.define("pw|print-weights=b", "print interval weights then exit");   
   opts.define("a|append=b",         "append analysis to input data");
   opts.define("p|prepend=b",        "prepend analysis to input data");
   opts.define("e|error=b",          "display error markers");
   opts.define("c|count=b",          "count chord regions then exit");
   opts.define("s|summary=b",        "summarize chord case accuracies");
   opts.define("rs|root-spectrum=b", "print root spectrum for each segment");
   opts.define("m|melodic=b",        "add melodic context analysis");
   opts.define("P|full-pathes=b",    "print full pathname of files");
   opts.define("t|t1|theta1=d:0.0",  "chromatic angle (single angle)");
   opts.define("t2|theta2=d:0.0",    "chromatic angle (double angle)");
   opts.define("mdebug|melodic-debug=b",  "trace input parsing for melody");   
   opts.define("mf|melodic-factor=d:0.0", "melodic scaling factor");   

   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, Dec 2000" << endl;
      exit(0);
   } else if (opts.getBoolean("version")) {
      cout << argv[0] << ", version: 2 Dec 2004" << endl;
      cout << "compiled: " << __DATE__ << endl;
      cout << MUSEINFO_VERSION << endl;
      exit(0);
   } else if (opts.getBoolean("help")) {
      usage(opts.getCommand());
      exit(0);
   } else if (opts.getBoolean("example")) {
      example();
      exit(0);
   }

   informationQ = opts.getBoolean("info");
   printweightQ = opts.getBoolean("print-weights");
   debugQ       = opts.getBoolean("debug");
   mdebugQ      = opts.getBoolean("melodic-debug");
   appendQ      = opts.getBoolean("append");
   prependQ     = opts.getBoolean("prepend");
   errorQ       = opts.getBoolean("error");
   chordcountQ  = opts.getBoolean("count");
   theta1       = opts.getDouble("theta1");
   theta2       = opts.getDouble("theta2");
   twothetaQ    = opts.getBoolean("theta2");
   shortQ       =!opts.getBoolean("full-pathes");
   summaryQ     = opts.getBoolean("summary");
   melodyQ      = opts.getBoolean("melodic");
   spectrumQ    = opts.getBoolean("root-spectrum");
   mfactor      = opts.getDouble("melodic-factor");

   if (appendQ) {
      prependQ = 0;
   }

   soloQ = !(prependQ || appendQ);

}



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

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



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

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



// md5sum: 58d8389193529505423e87ff84b675e9 roottest.cpp [20101226]