//
// Programmer:    Craig Stuart Sapp <craig@ccrma.stanford.edu>
// Creation Date: Sat May 23 21:08:48 PDT 1998
// Last Modified: Fri Jul  3 14:18:04 PDT 1998
// Last Modified: Sat Oct 14 20:26:15 PDT 2000 revised for museinfo 1.0
// Last Modified: Wed Nov 29 12:14:41 PST 2000 use internal analysis
// Last Modified: Tue Apr 21 00:41:11 PDT 2009 fixed spine manip printing
// Last Modified: Thu May 14 20:25:17 PDT 2009 -U option added
// Last Modified: Mon Apr 26 06:21:58 PDT 2010 -n, -s options added
// Last Modified: Thu Mar 10 15:06:00 PST 2011 -i option added
// Last Modified: Wed Mar 16 14:16:01 PDT 2011 added --iv option
// Filename:      ...sig/examples/all/sonority2.cpp
// Web Address:   http://sig.sapp.org/examples/museinfo/humdrum/sonority2.cpp
// Syntax:        C++; museinfo
//
// Description:   Analyzes **kern data for timeslice chord qualities
//

#include "humdrum.h"
#include "Matrix.h"

#include <stdlib.h>
#include <string.h>

#ifndef OLDCPP
   #include <iostream>
#else
   #include <iostream.h>
#endif


// function declarations
void    checkOptions(Options& opts, int argc, char* argv[]);
void    example(void);
void    processRecords(HumdrumFile& infile);
void    usage(const char* command);
void    fillStringWithNotes(char* string, ChordQuality& quality, 
                             HumdrumFile& infile, int line);
int     identifyBassNote(SigCollection<int>& notes, 
                             HumdrumFile& infile, int line,
                             Array<int>& sounding);
int     transitionalSonority(ChordQuality& quality, HumdrumFile& infile, 
                             int line);
void    printTriadImage(HumdrumFile& infile, int rows, int cols);
void    printBarlines(HumdrumFile& infile, int numberheight, 
                             int numberwidth);
double  getMeasureSize(HumdrumFile& infile, int width);
void    printLegend(int legendheight, int legendwidth);
void    printAttackMarker(HumdrumFile& infile, int line);
void    printAttackMarker(HumdrumFile& infile, int line);

// global variables
Options      options;            // database for command-line arguments
char         unknown[256] = {0}; // space for unknown chord simplification
int          chordinit;          // for initializing chord detection function
int          explicitQ = 0;      // used with -U option
int          notesQ    = 0;      // used with -n option
int          suppressQ = 0;      // used with -s option
int          parenQ    = 1;      // used with -P option
int          ivQ       = 0;      // used with --iv option
int          forteQ    = 0;      // used with --forte option
int          tnQ       = 0;      // used with --tn option
int          tniQ      = 0;      // used with --tni option
int          attackQ   = 0;      // used with -x option
int          appendQ   = 0;      // used with -a option
int          imageQ    = 0;      // used with -I option
int          imagex    = 800;    // used with -I option
int          imagey    = 20;     // used with -I option
int          octaveVal = -100;   // used with -o option
int          barlinesQ = 0;      // used with -b option
int          legendQ   = 0;      // used with -l option
int          outlineQ  = 1;      // 
const char*  notesep   = " ";    // used with -N option
const char* colorindex[26];


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


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

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

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

   for (int i=0; i<numinputs || i==0; i++) {
      chordinit = 1;
      infile.clear();

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

      // analyze the input file according to command-line options
      if (imageQ) {
         printTriadImage(infile, imagey, imagex);
      } else {
         processRecords(infile);
      }
       
   }

   return 0;
}


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


//////////////////////////////
//
// printTriadImage --
//

void printTriadImage(HumdrumFile& infile, int rows, int cols) {

   Array<ChordQuality> cq;
   infile.analyzeSonorityQuality(cq);
   infile.analyzeRhythm("4");

   Array<Array<int> > triads;

   triads.setSize(3);

   triads[0].setSize(cols);
   triads[0].setAll(24);
   triads[1].setSize(cols);
   triads[1].setAll(24);
   triads[2].setSize(cols);
   triads[2].setAll(24);


   colorindex[0]  = "0 255 0";		// C major
   colorindex[1]  = "38 255 140";	// C-sharp major
   colorindex[2]  = "63 95 255";	// D major
   colorindex[3]  = "228 19 83";	// E-flat major
   colorindex[4]  = "255 0 0";		// E major
   colorindex[5]  = "255 255 0";	// F major
   colorindex[6]  = "192 255 0";	// F-sharp major
   colorindex[7]  = "93 211 255";	// G major
   colorindex[8]  = "129 50 255";	// A-flat major
   colorindex[9]  = "205 41 255";	// A major
   colorindex[10] = "255 160 0";	// B-flat major
   colorindex[11] = "255 110 10";	// B major
   colorindex[12] = "0 161 0";		// C minor
   colorindex[13] = "15 191 90";	// C-sharp minor
   colorindex[14] = "37 61 181";	// D minor
   colorindex[15] = "184 27 75";	// E-flat minor
   colorindex[16] = "175 0 0";		// E minor
   colorindex[17] = "220 200 0";	// F minor
   colorindex[18] = "140 200 0";	// F-sharp minor
   colorindex[19] = "65 163 181";	// G minor
   colorindex[20] = "100 28 181";	// G-sharp minor
   colorindex[21] = "136 13 181";	// A minor
   colorindex[22] = "181 93 20";	// B-flat minor
   colorindex[23] = "211 107 0";	// B minor
   colorindex[24] = "255 255 255";	// background
   colorindex[25] = "0 0 0";		// silence


   double start;
   double end;
   int    inversion;
   int    starti;
   int    endi;
   int    minQ;
   int    majQ;
   int    i, m, j, ii;
   int    rootindex;
   for (i=0; i<infile.getNumLines(); i++) {
      if (!infile[i].isData()) {
         continue;
      }
      if (strcmp(cq[i].getTypeName(), "min") == 0) {
         minQ = 1;
      } else {
         minQ = 0;
      }
      if (strcmp(cq[i].getTypeName(), "maj") == 0) {
         majQ = 1;
      } else {
         majQ = 0;
      }

      if (!(majQ || minQ)) {
cerr << cq[i] << "\t" << cq[i].getTypeName() << endl;
         continue;
      }
      start = infile[i].getAbsBeat();
      end   = start + infile[i].getDuration();
      starti = int(start / infile.getTotalDuration() * cols + 0.5);
      endi   = int(end   / infile.getTotalDuration() * cols + 0.5);
      if (starti < 0) { starti = 0; }
      if (endi   < 0) { endi   = 0; }
      if (starti >= cols) { starti = cols-1; }
      if (endi   >= cols) { endi   = cols-1; }
      rootindex = Convert::base40ToMidiNoteNumber(cq[i].getRoot());
      if (minQ) {
         rootindex += 12;
      }
      inversion = cq[i].getInversion();
      for (ii=starti; ii<=endi; ii++) {
         triads[inversion][ii] = rootindex;
      }
   }

   int barheight = 0;
   int barwidth  = 0;
   if (barlinesQ) {
      barheight = 11;
      barwidth  = cols;
   }

   int legendheight = 0;
   int legendwidth  = 0;
   if (legendQ) {
      legendheight = 100;
      legendwidth  = cols;
   }


   int value = 24;
   Matrix<int> image(rows*2, cols, value);

   for (i=triads.getSize()-1; i>=0; i--) {
      int start = int(i/2.0 * rows);
      for (m=0; m<rows; m++) {
         ii = (int)(m + start);
         if (ii >= image.getRowCount()) {
            ii = image.getRowCount() - 1;
         }
         for (j=0; j<triads[i].getSize(); j++) {
            if (triads[i][j] < 24) {
               image.cell(ii,j) = triads[i][j];
            }
            // cout << colorindex[triads[i][j]] << " ";
            //cout << triads[i][j] << " ";
         }
         //cout << "\n";
      }
   }

   // print Image:
   cout << "P3\n";
   cout << cols  << " " << rows*2 + barheight + legendheight << "\n";
   cout << "255\n";

   for (i=image.getRowCount()-1; i>=0; i--) {
      for (j=0; j<image.getColumnCount(); j++) {
         cout << colorindex[image.cell(i,j)] << ' ';
      }
      cout << "\n";
   }

   if (barlinesQ) {
      printBarlines(infile, barheight, barwidth);
   }

   if (legendQ) {
      printLegend(legendheight, legendwidth);
   }

}



///////////////////////////////
//
// printBarlines -- print Barline numbers as lines underneath
//    the plot.
//

void printBarlines(HumdrumFile& infile, int numberheight, int numberwidth) {
   int i, j;
   Array<Array<int> > xaxis;
   xaxis.setSize(numberheight-1);
   xaxis.allowGrowth(0);
   for (i=0; i<xaxis.getSize(); i++) {
      xaxis[i].setSize(numberwidth);
      xaxis[i].allowGrowth(0);
      xaxis[i].setAll(24);
   }

   // bar counter keeps track of multiple repeats of the same
   // music.
   Array<int> barcount(10000);
   barcount.allowGrowth(0);
   barcount.setAll(0);

   double measuresize = getMeasureSize(infile, numberwidth);
   int size;
   int style = 0;
   int position;
   int number;
   for (i=0; i<infile.getNumLines(); i++) {
      if (!infile[i].isMeasure()) {
         continue;
      }
      size = 0;
      position = -1;
      if (sscanf(infile[i][0], "=%d", &number)) {
         if ((number >= 0) && (number < barcount.getSize())) {
            style = barcount[number];
            barcount[number]++;
         } else {
            style = 0;
         }
         position = int(numberwidth * infile[i].getAbsBeat() / 
               infile.getTotalDuration() + 0.5);
         if ((number % 100) == 0) {
            size = 10;
         } else if ((number % 50) == 0) {
            size = 8;
         } else if ((number % 10) == 0) {
            size = 6;
         } else if ((number % 5) == 0) {
            size = 4;
         } else {
            size = 2;
            if (measuresize < 3) {
               // don't display single measure ticks if they
               // are too closely spaced
               size = 0; 
            }
         }
      }

      int color;
      if ((position >= 0) && (size > 0)) {
         for (j=0; j<size; j++) {
            if (style == 0) {
               color = 25;
            } else if (style == 1) {
               color = 4;  // red (in default color)
            } else if (style == 2) {
               color = 2;  // blue (in default color)
            } else if (style == 3) {
               color = 0;  // green (in default color)
            } else {
               color = 11;    // orange (in default color)
            }
            xaxis[j][position] = color;
	    if (j==9) {
               if (position>0) {
                  xaxis[j][position-1] = color;
               }
               if (position<xaxis[0].getSize()-1) {
                  xaxis[j][position+1] = color;
               }
            }
         }
      } 
   }

   // print a empty line so that small measure markers can be seen
   for (i=0; i<xaxis[0].getSize(); i++) {
         cout << ' ' << colorindex[24];
   }
   cout << '\n';

   for (i=0; i<xaxis.getSize(); i++) {  
      for (j=0; j<xaxis[i].getSize(); j++) {  
         cout << ' ' << colorindex[xaxis[i][j]];
      }
      cout << '\n';
   }


}



//////////////////////////////
//
// getMeasureSize -- return the pixel size of a single measure.
//

double getMeasureSize(HumdrumFile& infile, int width) {
   int i;
   int bar = -100;
   int lastbar = -1000;
   int line = -100;
   int lastline = -1000;

   int number;

   for (i=0; i<infile.getNumLines(); i++) {
      if (!infile[i].isMeasure()) {
         continue;
      }
      if (sscanf(infile[i][0], "=%d", &number)) {
         bar = number;
         line = i;
         if (bar == lastbar + 1) {
            return 
            (infile[line].getAbsBeat() - infile[lastline].getAbsBeat()) 
            / infile.getTotalDuration() * width;
         } else {
            lastbar = bar;
            lastline = line;
         }
      }
      
   }

   return 5;
}



///////////////////////////////
//
// printLegend -- print key color mappings as a keyboard 
//

void printLegend(int legendheight, int legendwidth) {
   Array<Array<int> > legend;
   legend.setSize(legendheight);
   legend.allowGrowth(0);
   int i, j;
   for (i=0; i<legend.getSize(); i++) {
      legend[i].setSize(legendwidth);
      legend[i].allowGrowth(0);
      legend[i].setAll(24);  // set to background color
   }


   int startrow = legendheight / 5;
   int endrow   = legendheight - 1;
   int startcol = legendwidth / 5;
   int endcol   = legendwidth - startcol - 1;

   int dooutline = outlineQ;
   if (legendwidth < 100) {   // don't print outline on a small legend
      dooutline = 0;
   }

   Array<int> diatonic(8);
   diatonic.allowGrowth(0);
   diatonic[0] = 0;
   diatonic[1] = 2;
   diatonic[2] = 4;
   diatonic[3] = 5;
   diatonic[4] = 7;
   diatonic[5] = 9;
   diatonic[6] = 11;

   int v, lastv = -1;
   for (i=startrow; i<=endrow; i++) {
      for (j=startcol; j<=endcol; j++) {
         v = diatonic[int((double)(j-startcol)/(endcol-startcol+1)*7)]; 
         if (dooutline && ((v != lastv) || 
               (j == endcol) || (i==startrow) || (i==endrow))) {
            legend[i][j] = 25;
         } else if (i > (endrow + startrow)/2) {  // major keys
            legend[i][j] = v;
         } else {                          // minor keys
            legend[i][j] = v+12;
         }
         lastv = v;
      }
   }

   int blackkeyheight = 2 * (endrow - startrow) / 3;
   int blackend = startrow + blackkeyheight;

   int blackstartcol = startcol;
   int blackendcol   = endcol;
   blackstartcol = blackstartcol + (endcol - startcol) / 96;

   lastv = 0;
   for (i=startrow; i<=blackend; i++) {
      for (j=blackstartcol; j<=blackendcol; j++) {
	 v = int((double)(j-blackstartcol)/(blackendcol-blackstartcol+1)*12);
	 if ((v != lastv) || (i==startrow)) {
            if ((v != 0) && (v != 5)) {
               legend[i][j] = 25;
            }
	    lastv = v;
	    continue;
         }
	 if (!((v==1)||(v==3)||(v==6)||(v==8)||(v==10))) {
            continue;
         }
	 if (i==blackend) {
            legend[i][j] = 25;
         } else if (i > (blackend + startrow)/2) {  // major keys
            legend[i][j] = v;
         } else {                          // minor keys
            legend[i][j] = v+12;
         }
	 lastv = v;
      }
   }

   Array<const char*> transcolor;
   transcolor.setSize(26);
   transcolor.setAll(0);
   transcolor[24] = colorindex[24];
   transcolor[25] = colorindex[25];
   int transpose = 0;
   int rrotate   = 0;
   for (i=0; i<12; i++) {
      transcolor[i] = colorindex[(i+transpose+rrotate) % 12];
      transcolor[i+12] = colorindex[((i+transpose+rrotate) % 12)+12];
   }

   for (i=0; i<legend.getSize(); i++) {
      for (j=0; j<legend[i].getSize(); j++) {
         cout << ' ' << transcolor[legend[i][j]];
      }
      cout << "\n";
   }
}



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

void checkOptions(Options& opts, int argc, char* argv[]) {
   opts.define("t|type=b",          "show only chord type");
   opts.define("i|inversion=b",     "show only chord inversion");
   opts.define("r|root=b",          "show only chord root");
   opts.define("a|assemble|append=b", "append analysis to input data");
   opts.define("f|format=s:t:i:r",  "control display style");
   opts.define("u|unknown=s:X",     "control display of unknowns");
   opts.define("U|unknown-pcs=b",   "print pcs of unknown sonrities");
   opts.define("d|debug=b",         "determine bad input line num");
   opts.define("n|notes=b",         "display pitch classes in sonority");
   opts.define("iv=b",              "print interval vector");
   opts.define("x|sonor|suspension=b",    "print marker if not all start attacks");
   opts.define("forte=b",           "print forte interval vector set name");
   opts.define("Tn|tn=b",           "print forte set with subsets");
   opts.define("Tni|tni=b",         "print forte set with subsets/inversion");
   opts.define("s|suppress=b",      "suppress data if overlapping sonority");
   opts.define("I|image=s:800x20",  "display image of major/minor chords");
   opts.define("P|paren-off=b",     "suppress parentheses for overlapping");
   opts.define("N|separator=s: ",   "characters to separate pitch classes");
   opts.define("o|octave=i:4",      "characters to separate pitch classes");
   opts.define("b|barlines=b",      "display barlines at bottom of image");
   opts.define("l|legend=b",        "display color mapping");

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

   if (opts.getBoolean("root")) {
      ChordQuality::setDisplay("r");
   } else if (opts.getBoolean("type")) {
      ChordQuality::setDisplay("t");
   } else if (opts.getBoolean("inversion")) {
      ChordQuality::setDisplay("i");
   } else {
      ChordQuality::setDisplay(opts.getString("format"));
   }

   Convert::chordType.setNullName(opts.getString("unknown"));
   Convert::chordInversion.setNullName(opts.getString("unknown"));
   Convert::kernPitchClass.setNullName(opts.getString("unknown"));

   strncpy(unknown, opts.getString("unknown"), 64);
   strcat(unknown, ":");
   strncat(unknown, opts.getString("unknown"), 64);
   strcat(unknown, ":");
   strncat(unknown, opts.getString("unknown"), 64);

   explicitQ =  opts.getBoolean("unknown-pcs");
   imageQ    =  opts.getBoolean("image");
   if (imageQ) {
      imagex = 800;
      imagey = 20;
      sscanf(opts.getString("image"), "%dx%d", &imagex, &imagey);
   }

   ivQ       =  opts.getBoolean("iv");
   attackQ   =  opts.getBoolean("suspension");
   forteQ    =  opts.getBoolean("forte");
   tnQ       =  opts.getBoolean("Tn");
   if (tnQ) {
      forteQ = 1;
   }
   tniQ      =  opts.getBoolean("Tni");
   if (tniQ) {
      tnQ = 1;
      forteQ = 1;
   }
   notesQ    =  opts.getBoolean("notes");
   suppressQ =  opts.getBoolean("suppress");
   parenQ    = !opts.getBoolean("paren-off");
   appendQ   =  opts.getBoolean("append");
   barlinesQ =  opts.getBoolean("barlines");
   legendQ   =  opts.getBoolean("legend");
   if (opts.getBoolean("separator")) {
      notesep   =  opts.getString("separator");
   }
   if (opts.getBoolean("octave")) {
      octaveVal =  opts.getInteger("octave");
   }
}



//////////////////////////////
//
// example -- example usage of the sonority program
//

void example(void) {
   cout <<
   "                                                                         \n"
   "# example usage of the sonority program.                                 \n"
   "# analyze a Bach chorale for chord qualities:                            \n"
   "     sonority chor217.krn                                                \n"
   "                                                                         \n"
   "# display the chord analysis with original data:                         \n"
   "     sonority -a chor217.krn                                             \n"
   "                                                                         \n"
   "# display only the roots of chords:                                      \n"
   "     sonority -r chor217.krn                                             \n"
   "                                                                         \n"
   << endl;
}



//////////////////////////////
//
// processRecords -- looks at humdrum records and determines chord
//	sonority quality;
//

void processRecords(HumdrumFile& infile) {
   Array<ChordQuality> cq;
   infile.analyzeSonorityQuality(cq);
   ChordQuality quality;

   int foundstart = 0;
   char aString[512] = {0};

   for (int i=0; i<infile.getNumLines(); i++) {
      if (options.getBoolean("debug")) {
         cout << "processing line " << (i+1) << " of input ..." << endl;
	 cout << "LINE IS: " << infile[i] << endl;
      }
      switch (infile[i].getType()) {
         case E_humrec_none:
         case E_humrec_empty:
         case E_humrec_bibliography:
         case E_humrec_global_comment:
            cout << infile[i] << endl;
            break;
         case E_humrec_data_comment:
            if (appendQ) {
	       cout << infile[i] << "\t";
            } 
            if (infile[i].equalFieldsQ("**kern")) {
               cout << infile[i][0];
            } else {
               cout << "!";
            }
            cout << endl;
            break;
         case E_humrec_data_interpretation:
            if (appendQ) {
               cout << infile[i] << "\t";
            }
            if (!foundstart && infile[i].hasExclusiveQ()) {
               foundstart = 1;
               if (tniQ) {
                  cout << "**Tni";
               } else if (tnQ) {
                  cout << "**Tn";
               } else if (forteQ) {
                  cout << "**forte";
               } else {
                  cout << "**qual";
               }
            } else {
               if (infile[i].equalFieldsQ("**kern") && 
                   (!infile[i].isSpineManipulator(0))) {
                  cout << infile[i][0];
               } else {
                  cout << "*";
               }
            }
	    cout << endl;
            break;
         case E_humrec_data_kern_measure:
            if (appendQ) {
               cout << infile[i] << "\t";
            }
            cout << infile[i][0];
            cout << endl;
            break;
         case E_humrec_data:
            if (appendQ) {
               cout << infile[i] << "\t";
            }
            // handle null fields
            if (infile[i].equalFieldsQ("**kern", ".")) {
	       cout << "." << endl;
               break;
            }
            if (ivQ) {
               Array<int> iv;
               infile.getIntervalVector(iv, i);
               cout << "<";
               for (int ii=0; ii<iv.getSize(); ii++) {
                  if (iv[ii] < 9) {
                     cout << iv[ii];
                     continue;
                  }
                  if (iv[ii] < 36) {
                     cout << char(iv[ii]-10+'A');
                     continue;
                  }
                  if (ii > 0) {
                     cout << ",";
                  }
                  cout << iv[ii];
                  if (ii < 5) {
                     cout << ",";
                  }
               }
               cout << ">" << endl;

            } else if (forteQ) {
               const char* name = infile.getForteSetName(i);
               cout << name;
               if (tnQ) {
                  if (strcmp(name, "3-11") == 0) {
                     if (strcmp(cq[i].getTypeName(), "min") == 0) {
                        cout << "A";
                     } else if (strcmp(cq[i].getTypeName(), "maj") == 0) {
                        cout << "B";
                     }
                  }
               }
               if (tniQ) {
                  int inversion = -1;
                  if (strcmp(name, "3-11") == 0) {
                     if ((strcmp(cq[i].getTypeName(), "min") == 0) || 
                        (strcmp(cq[i].getTypeName(), "maj") == 0)) {
                        inversion = cq[i].getInversion();
                     }
                     if (inversion >= 0) {
                        cout << char('a'+inversion);
                     }
                  }
               }
               if (attackQ) {
                  printAttackMarker(infile, i);
               }
               cout << endl;

            } else if (notesQ == 0) {
               quality = cq[i];
               quality.makeString(aString, explicitQ);
               if (strcmp(aString, "") != 0) {
                  if (strcmp(aString, unknown) == 0 || (
                        quality.getType()==E_chord_note && 
                              options.getBoolean("root"))
                        ) {
                     char tempbuffer[128] = {0};
	             strcpy(tempbuffer, aString);
                     strcpy(aString, options.getString("unknown"));
	             strcat(aString, tempbuffer);
                  }
		  if (suppressQ && transitionalSonority(quality, infile, i)) {
                     strcpy(aString, ".");
                  }
               } else {
                  strcpy(aString, "rest");
               }
	       cout << aString << endl;
            } else {
               quality = cq[i];
               fillStringWithNotes(aString, quality, infile, i);
	       cout << aString << endl;
            }

            break;
         default:
            cerr << "Error on line " << (i+1) << " of input" << endl;
            cerr << "record type = " << infile[i].getType() << endl;
            exit(1);
      }
   }

}



//////////////////////////////
//
// printAttackMarker -- print an "x" if the there is any note at
//    the start of the region which is not attacked (i.e., suspended
//    from a previous sonority.
//

void printAttackMarker(HumdrumFile& infile, int line) {
   int j, ii, jj;
   int& i = line;
   int dotQ;
   for (j=0; j<infile[i].getFieldCount(); j++) {
      if (!infile[i].isExInterp(j, "**kern")) {
         continue;
      }
      if (strcmp(infile[i][j], ".") == 0) {
        ii = infile[i].getDotLine(j);
        jj = infile[i].getDotSpine(j);
        dotQ = 1;
      } else {
         ii = i;
         jj = j;
         dotQ = 0;
      }
      if (strchr(infile[ii][jj], 'r') != NULL) {
         continue;
      } else if (dotQ) {
         cout << "x";
         return;
      }
      if (strchr(infile[ii][jj], '_') != NULL) {
         cout << "x";
         return;
      }
      if (strchr(infile[ii][jj], ']') != NULL) {
         cout << "x";
         return;
      }
   }

}



//////////////////////////////
//
// transitionalSonority --
//

int transitionalSonority(ChordQuality& quality, HumdrumFile& infile, int line) {
   SigCollection<int> notes;
   quality.getNotesInChord(notes);

   Array<int> octave;
   octave.setSize(notes.getSize());
   octave.allowGrowth(0);
   octave.setAll(0);

   Array<int> sounding;
   sounding.setSize(notes.getSize());
   sounding.allowGrowth(0);
   sounding.setAll(0);

   // int bassindex = identifyBassNote(notes, infile, line, sounding);

   int i;
   for (i=0; i<sounding.getSize(); i++) {
      if (sounding[i] == 0) {
         return 1;
      }
   }

   return 0;
}



//////////////////////////////
//
// fillStringWithNotes --
//

void fillStringWithNotes(char* string, ChordQuality& quality, 
      HumdrumFile& infile, int line) {

   string[0] = '\0';

   SigCollection<int> notes;
   quality.getNotesInChord(notes);

   Array<int> octave;
   octave.setSize(notes.getSize());
   octave.allowGrowth(0);
   octave.setAll(4);

   Array<int> sounding;
   sounding.setSize(notes.getSize());
   sounding.allowGrowth(0);
   sounding.setAll(0);

   int bassindex = identifyBassNote(notes, infile, line, sounding);
   if (bassindex >= 0) {
      octave[bassindex] = 3;
      //if (notes[bassindex] >= 40) {
      //   octave[bassindex] += -2;
      //}
   }

   int i;
   if (suppressQ) {
      for (i=0; i<sounding.getSize(); i++) {
         if (sounding[i] == 0) {
            strcpy(string, ".");
            return;
         }
      }
   }

   string[0] = '\0';
   char buffer[32] = {0};
   for (i=0; i<notes.getSize(); i++) {
      //if (octaveVal >= 0) {
      //   Convert::base40ToKern(buffer, (notes[i]%40) + octaveVal * 40);
      //} else {
      //   Convert::base40ToKern(buffer, notes[i] + ((octave[i]+4) * 40));
      //}
      Convert::base40ToKern(buffer, notes[i]%40 + (octave[i] * 40));
      if (parenQ && (sounding[i] == 0)) {
         strcat(string, "(");
      }
      strcat(string, buffer);
      if (parenQ && (sounding[i] == 0)) {
         strcat(string, ")");
      }
      if (i < notes.getSize() - 1) {
         strcat(string, notesep);
      }
   }

}



//////////////////////////////
//
// identifyBassnote --
//

int identifyBassNote(SigCollection<int>& notes, HumdrumFile& infile, 
      int line, Array<int>& sounding) {
   int j, k;
   int output = -1;
   int minval = 1000000;
   int value;
   int tcount;
   char buffer[128] = {0};

   Array<int> soundQ(40);
   soundQ.setAll(0);

   sounding.setSize(notes.getSize());
   sounding.setAll(0);

   int pline;
   int pspine;

   int dotQ = 0;

   if (notes.getSize() == 0) {
      return -1;
   }

   for (j=0; j<infile[line].getFieldCount(); j++) {
      if (!infile[line].isExInterp(j, "**kern")) {
         continue;
      }
      dotQ = 0;
      if (strcmp(infile[line][j], ".") == 0) {
         pline  = infile[line].getDotLine(j);
         pspine = infile[line].getDotSpine(j);
         dotQ = 1;
      } else {
         pline = line;
         pspine = j;
      }
      tcount = infile[pline].getTokenCount(pspine);
      for (k=0; k<tcount; k++) {
         infile[pline].getToken(buffer, pspine, k);
         if (strchr(buffer, 'r') != NULL) {
            continue;
         }
	 if (strcmp(buffer, ".") == 0) {
            // shouldn't get here...
            continue;
         }
	 value = Convert::kernToMidiNoteNumber(buffer);
         if (value < 0) {
            continue;
         }
         if (value < minval) {
            minval = value;
         }
         if (dotQ) {
            continue;
         }
         if (strchr(buffer, '_') != NULL) {
            continue;
         }
         if (strchr(buffer, ']') != NULL) {
            continue;
         }
	 value = Convert::kernToBase40(buffer);
         if (value < 0) {
            continue;
         }
         soundQ[value % 40] = 1;
      }
   }

   if (minval > 100000) {
      return -1;
   }

   minval = minval % 12;
   int i;
   int tval;
   for (i=0; i<notes.getSize(); i++) {
      if (notes[i] >= 0) {
         if (soundQ[notes[i]%40]) {
            sounding[i] = 1;
         }
      }
      tval = Convert::base40ToMidiNoteNumber(notes[i]);
      if (tval < 0) {
         continue;
      }
      tval = tval % 12;
      if (tval == minval) {
         output = i;
         // break;  need to supress this because of sounding tests
      }
   }
   return output;
}



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

void usage(const char* command) {
   cout <<
   "                                                                         \n"
   "Analyzes **kern data and generates a **qual spine which gives the chord  \n"
   "quality of the **kern spines.  Currently, input spines cannot split or   \n"
   "join.                                                                    \n"
   "                                                                         \n"
   "Usage: " << command << " [-a][-i|-r|-t] [input1 [input2 ...]]            \n"
   "                                                                         \n"
   "Options:                                                                 \n"
   "   -a = assemble the **qual analysis spine with input data               \n"
   "   -i = displays the **qual chord inversion only                         \n"
   "   -r = displays the **qual chord root only                              \n"
   "   -t = displays the **qual chord type only                              \n"
   "   --options = list of all options, aliases and default values           \n"
   "                                                                         \n"
   << endl;
}



// md5sum: 45968f7aea2c30cf0f9608748e2b3ed8 sonority.cpp [20130420]