//
// Programmer:    Craig Stuart Sapp <craig@ccrma.stanford.edu>
// Creation Date: Sun Dec 26 03:28:25 PST 2010
// Last Modified: Mon Feb  7 06:22:42 PST 2011 (added beam directions)
// Filename:      ...sig/examples/all/autostem.cpp 
// Web Address:   http://sig.sapp.org/examples/museinfo/humdrum/autostem.cpp
// Syntax:        C++; museinfo
//
// Description:   Converts Humdrum files into MuseData files.
//

#include <math.h>

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

#include "string.h"

#include "humdrum.h"
#include "PerlRegularExpression.h"
#include "MuseData.h"

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

class Coord {
   public:
      Coord(void) { clear(); }
      void clear(void) { i = j = -1; }
      int i;
      int j;
};


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

// function declarations:
void      checkOptions(Options& opts, int argc, char** argv);
void      example(void);
void      usage(const char* command);
void      autostem(HumdrumFile& infile);
void      getClefInfo(Array<Array<int> >& baseline, 
                                HumdrumFile& infile);
void      addStem(char* output, const char* input, 
                                const char* piece);
void      processKernTokenStemsSimpleModel(HumdrumFile& infile, 
                                Array<Array<int> >& baseline, 
                                int row, int col);
void      removeStems(ostream& out, HumdrumFile& infile);
void      removeStem2(HumdrumFile& infile, int row, int col);
int       getVoice(HumdrumFile& infile, int row, int col);
void      getNotePositions(Array<Array<Array<int> > >& notepos, 
                                Array<Array<int> >& baseline, 
                                HumdrumFile& infile);
void      printNotePositions(HumdrumFile& infile, 
                                Array<Array<Array<int> > >& notepos);
void      getVoiceInfo(Array<Array<int> >& voice, HumdrumFile& infile);
void      printVoiceInfo(HumdrumFile& infile, Array<Array<int> >& voice);
void      processKernTokenStems(HumdrumFile& infile, 
                                Array<Array<int> >& baseline, int row, int col);
void      getMaxLayers(Array<int>& maxlayer, Array<Array<int> >& voice,
                                HumdrumFile& infile);
void      assignStemDirections(Array<Array<int> >& stemdir, 
                                Array<Array<int> > & voice, 
                                Array<Array<Array<int> > >& notepos, 
                                HumdrumFile& infile);
void      assignBasicStemDirections(Array<Array<int> >& stemdir, 
                                Array<Array<int> >& voice, 
                                Array<Array<Array<int> > >& notepos, 
                                HumdrumFile& infile);
int       determineChordStem(Array<Array<int> >& voice, 
                                Array<Array<Array<int> > >& notepos, 
                                HumdrumFile& infile, int row, int col);
void      insertStems(HumdrumFile& infile, 
                                Array<Array<int> >& stemdir);
void      setStemDirection(HumdrumFile& infile, int row, int col, 
                                int direction);
void      getBeamState(Array<Array<Array<char> > >& beams, 
                                HumdrumFile& infile);
void      countBeamStuff(const char* token, int& start, int& stop, 
                                int& flagr, int& flagl);
void      getBeamSegments(Array<Array<Coord> >& beamednotes, 
                                Array<Array<Array<char> > >& beamstates, 
                                HumdrumFile& infile, Array<int> maxlayer);
int       getBeamDirection(Array<Coord>& coords,  
                                Array<Array<int> >& voice, 
                                Array<Array<Array<int> > >& notepos);
void      setBeamDirection(Array<Array<int> >& stemdir, 
                                Array<Coord>& bnote, int direction);


// User interface variables:
Options options;
int    debugQ        = 0;             // used with --debug option
int    removeQ       = 0;             // used with -r option
int    noteposQ      = 0;             // used with -p option
int    voiceQ        = 0;             // used with --voice option
int    removeallQ    = 0;             // used with -R option
int    overwriteQ    = 0;             // used with -o option
int    overwriteallQ = 0;             // used with -O option
int    Middle        = 4;             // used with -u option


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

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

   // initial processing of the command-line options
   checkOptions(options, argc, argv);

   if (options.getArgCount() < 1) {
      infile.read(cin);
   } else {
      infile.read(options.getArg(1));
   }

   if (removeQ || overwriteQ) {
      SSTREAM tempstream;
      removeStems(tempstream, infile);
      infile.clear();
      infile.read(tempstream);
      if (removeQ) {
         cout << infile;
         exit(0);
      }
   } 

   autostem(infile);
   cout << infile;

   return 0;
}


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



//////////////////////////////
//
// removeStems --
//

void removeStems(ostream& out, HumdrumFile& infile) {
   int i, j;
   PerlRegularExpression pre;
   Array<char> buffer;
   buffer.setSize(1024);

   for (i=0; i<infile.getNumLines(); i++) {
      if (!infile[i].isData()) {
         out << infile[i] << "\n";
         continue;
      }
      for (j=0; j<infile[i].getFieldCount(); j++) {
         if (!infile[i].isExInterp(j, "**kern")) {
            out << infile[i][j];
            if (j < infile[i].getFieldCount()-1) {
               out << "\t";
            }
            continue;
         }
         int len = strlen(infile[i][j]);
         buffer.setSize(len+1);
         strcpy(buffer.getBase(), infile[i][j]);
         if (removeallQ || overwriteallQ) {
            pre.sar(buffer, "[\\\\/]x(?!x)", "", "g");
            pre.sar(buffer, "[\\\\/](?!x)", "", "g");
         } else {
            pre.sar(buffer, "[\\\\/](?!x)", "", "g");
         }
         out << buffer;

         if (j < infile[i].getFieldCount()-1) {
            out << "\t";
         }
      }
      out << "\n";
   }
}



//////////////////////////////
//
// autostem -- add an up/down stem on notes in **kern data which do not
//     already have stem information.
//

void autostem(HumdrumFile& infile) {
   Array<Array<int> > baseline;
   getClefInfo(baseline, infile);
   
   Array<Array<Array<int> > > notepos;     // staff line position of all notes
   getNotePositions(notepos, baseline, infile);

   if (noteposQ) {
      printNotePositions(infile, notepos);
      exit(0);
   }

   Array<Array<int> > voice;     // voice/layer number in track
   getVoiceInfo(voice, infile);

   if (voiceQ) {
      printVoiceInfo(infile, voice);
      exit(0);
   }

   Array<Array<int> > stemdir;   // stem directions;
   stemdir.setSize(infile.getNumLines());
   for (int i=0; i<stemdir.getSize(); i++) {
      stemdir[i].setSize(infile[i].getFieldCount());
      stemdir[i].setAll(0); // 0 = undefined;
   }
   // later add stems presently in data.
   assignStemDirections(stemdir, voice, notepos, infile);

   insertStems(infile, stemdir);

/*   // simple model which only considers notes in isolation from any
   // beaming to other notes...
   int i, j;
   for (i=0; i<infile.getNumLines(); i++) {
      if (!infile[i].isData()) {
         continue;
      }
      for (j=0; j<infile[i].getFieldCount(); j++) {
         if (!infile[i].isExInterp(j, "**kern")) {
            continue;
         } 
         if (strcmp(infile[i][j], ".") == 0) {
            continue;
         }
         processKernTokenStems(infile, stemdir, i, j);
      }
   }
*/

}



//////////////////////////////
//
// insertStems -- put stem directions into the data.
//

void insertStems(HumdrumFile& infile, Array >& stemdir) {
   int i, j;

   for (i=0; i<infile.getNumLines(); i++) {
      if (!infile[i].isData()) {
         continue;
      }
      for (j=0; j<infile[i].getFieldCount(); j++) {
         if (!infile[i].isExInterp(j, "**kern")) {
            continue;
         }
         setStemDirection(infile, i, j, stemdir[i][j]);
      }
   }
}



//////////////////////////////
//
// setStemDirection -- don't change stem direction if there is already
//    a stem on the note.
//

void setStemDirection(HumdrumFile& infile, int row, int col, int direction) {
   int& i = row;
   int& j = col;
   int k;

   if (strcmp(infile[i][j], ".") == 0) {
      return;
   }
   if (strchr(infile[i][j], 'r') != NULL) {
      return;
   }

   int tokencount = infile[i].getTokenCount(j);
   char buffer[128] = {0};
   char buffer2[128] = {0};
   int len = strlen(infile[i][j]);
   char output[len*2+tokencount];
   output[0] = '\0';

   for (k=0; k<tokencount; k++) {
      infile[i].getToken(buffer, j, k, 30);
      if ((strchr(buffer, '/') == NULL) & (strchr(buffer, '\\') == NULL)) {
         if (direction > 0) {
            addStem(buffer2, buffer, "/");
         } else if (direction < 0) {
            addStem(buffer2, buffer, "\\");
         } else {
            strcpy(buffer2, buffer);
         }
      } else {
         strcpy(buffer2, buffer);
      }
      strcat(output, buffer2);
      if (k < tokencount-1) {
         strcat(output, " ");
      }
   }
   infile[i].setToken(j, output);
}



//////////////////////////////
//
// assignStemDirections --
//

void assignStemDirections(Array<Array<int> >& stemdir, 
      Array<Array<int> > & voice, 
      Array<Array<Array<int> > >& notepos, HumdrumFile& infile) {

   Array<int> maxlayer;
   getMaxLayers(maxlayer, voice, infile);

   assignBasicStemDirections(stemdir, voice, notepos, infile);

   Array<Array<Array<char> > > beamstates;
   getBeamState(beamstates, infile);

   Array<Array<Coord> > beamednotes;
   getBeamSegments(beamednotes, beamstates, infile, maxlayer);

   // print notes which are beamed together for debugging:

   int i, j;
   if (debugQ) {
      for (i=0; i<beamednotes.getSize(); i++) {
         cout << "!! ";
         for (j=0; j<beamednotes[i].getSize(); j++) {
            cout << infile[beamednotes[i][j].i][beamednotes[i][j].j] << "\t";
         }
         cout << "\n";
      }
   }

   int direction;
   for (i=0; i<beamednotes.getSize(); i++) {
      direction = getBeamDirection(beamednotes[i], voice, notepos);
      setBeamDirection(stemdir, beamednotes[i], direction);
   }

}



//////////////////////////////
//
// setBeamDirection --
//

void setBeamDirection(Array<Array<int> >& stemdir, Array<Coord>& bnote, 
      int direction) {
   int x;
   int i, j;
   for (x=0; x<bnote.getSize(); x++) {
      i = bnote[x].i;
      j = bnote[x].j;
      stemdir[i][j] = direction;
   }
}




//////////////////////////////
//
// getBeamDirection -- return a consensus stem direction for beamed notes.
//

int getBeamDirection(Array<Coord>& coords, Array<Array<int> >& voice, 
      Array<Array<Array<int> > >& notepos) {

   // voice values are presumbed to be 0 at the moment.

   int minn = 1000;
   int maxx = -1000;

   int x;
   int i, j, k;
   for (x=0; x<coords.getSize(); x++) {
      i = coords[x].i;
      j = coords[x].j;
      if (voice[i][j] == 1) {
         return +1;
      }
      if (voice[i][j] == 2) {
         return -1;
      }
      for (k=0; k<notepos[i][j].getSize(); k++) {
         if (minn > notepos[i][j][k]) {
            minn = notepos[i][j][k];
         }
         if (maxx < notepos[i][j][k]) {
            maxx = notepos[i][j][k];
         }
      }
   }

   if (maxx < 0) {
      // both minn and maxx are less than zero, so place stems up
      return +1;
   }
   if (minn > 0) {
      // both minn and maxx are greater than zero, so place stems down
      return -1;
   }

   if (abs(maxx) > abs(minn)) {
      // highest note is higher than lower note is lower, so place
      // stems down
      return -1;
   }
   if (abs(maxx) > abs(minn)) {
      // highest note is lower than lower note is lower, so place
      // stems up
      return +1;
   }

   // its a draw, so place stem up.
   return +1;
}



//////////////////////////////
//
// getBeamSegments -- arrange the beamed notes into a long list with
//    each entry being a list of notes containing one beam.  Each
//    beamed note set should have their beams all pointing in the same 
//    direction.
//

void getBeamSegments(Array<Array<Coord> >& beamednotes, 
      Array<Array<Array<char> > >& beamstates, HumdrumFile& infile, 
      Array<int> maxlayer) {

   beamednotes.setSize(infile.getNumLines() * infile.getMaxTracks() / 2);
   beamednotes.setGrowth(1000000);
   beamednotes.setSize(0);

   Array<Array<Array<Coord> > > beambuffer;
   beambuffer.setSize(infile.getMaxTracks() + 1);
   int i, j;
   for (i=0; i<beambuffer.getSize(); i++) {
      beambuffer[i].setSize(10); // layer  max 10, but allow growth
      for (j=0; j<beambuffer[i].getSize(); j++) {
         beambuffer[i][j].setSize(100); // 100 notes in a beam expected as max
         beambuffer[i][j].setGrowth(1000); // but grow by increments of 1000
         beambuffer[i][j].setSize(0); 
      }
   }

   Coord tcoord;
   char beamchar;
   int track, oldtrack, layer;
   for (i=0; i<infile.getNumLines(); i++) {
      if (!infile[i].isData()) {
         continue;
      }
      oldtrack = 0;
      layer = 0;
      for (j=0; j<infile[i].getFieldCount(); j++) {
         if (!infile[i].isExInterp(j, "**kern")) {
            continue;
         }
         track = infile[i].getPrimaryTrack(j);
         if (track == oldtrack) {
            layer++;
         } else {
            layer = 0;
         }
         oldtrack = track;
         if (strcmp(infile[i][j], ".") == 0) {
            continue;
         }
         if (strchr(infile[i][j], 'r') != NULL) {
            continue;
         }
         beamchar = beamstates[i][j][0];
         if (beamchar == '\0') {
            beambuffer[track][layer].setSize(0);  // possible unter. beam
            continue;
         }
         if ((beamchar == '[') || (beamchar == '=')) {
            // add a beam to the buffer and wait for more
            tcoord.i = i;
            tcoord.j = j;
            beambuffer[track][layer].append(tcoord);
            continue;
         }
         if (beamchar == ']') {
            // ending of a beam so store in permanent storage
            tcoord.i = i;
            tcoord.j = j;
            beambuffer[track][layer].append(tcoord);
            beamednotes.append(beambuffer[track][layer]);
            beambuffer[track][layer].setSize(0);
         }
      }
   }
}



//////////////////////////////
//
// getMaxLayers --
//

void getMaxLayers(Array<int>& maxlayer, Array<Array<int> >& voice, 
      HumdrumFile& infile) {

   int track;
   maxlayer.setSize(infile.getMaxTracks() + 1);
   maxlayer.setAll(0);
   int i, j;
   for (i=0; i<infile.getNumLines(); i++) {
      if (!infile[i].isData()) {
         continue;
      }
      for (j=0; j<infile[i].getFieldCount(); j++) {
         if (!infile[i].isExInterp(j, "**kern")) {
            continue;
         }
         if (strcmp(infile[i][j], ".") == 0) {
            continue;
         }
         if (strchr(infile[i][j], 'r') != NULL) {
            continue;
         }
         track = infile[i].getPrimaryTrack(j);
         if (voice[i][j] + 1 > maxlayer[track]) {
            maxlayer[track] = voice[i][j] + 1;
         }
      }
   }
}



//////////////////////////////
//
// getVoiceInfo -- 0 = only voice in track, 1 = layer 1, 2 = layer 2, etc.
//
// 0 voices will be stemmed up or down based on vertical positions of notes
// 1 voices will be stemmed up always
// 2 voices will be stemmed down always.
// 3 and higher are still to be determined.
//
// Future enhancement of this algorithm: if one voice contains an invisible 
// rest, then it will be ignored in the voice calculation.
//

void getVoiceInfo(Array >& voice, HumdrumFile& infile) {

   voice.setSize(infile.getNumLines());

   int i, j, v;
   for (i=0; i<infile.getNumLines(); i++) {
      if (!infile[i].isData()) {
         continue;
      }
      voice[i].setSize(infile[i].getFieldCount());
      voice[i].setAll(-1);
      for (j=0; j<infile[i].getFieldCount(); j++) {
         if (!infile[i].isExInterp(j, "**kern")) {
            continue;
         } 
         if (strcmp(infile[i][j], ".") == 0) {
            continue;
         }
         v = getVoice(infile, i, j);
         voice[i][j] = v;
      }
   }
}



//////////////////////////////
//
// printvoiceInfo --
//

void printVoiceInfo(HumdrumFile& infile, Array >& voice) {
   PerlRegularExpression pre;
   Array<char> data;
   SSTREAM *pstream;


   int i, j;
   for (i=0; i<infile.getNumLines(); i++) {
      if (!(infile[i].isData() || infile[i].isLocalComment() ||
            infile[i].isMeasure() || infile[i].isInterpretation())) {
         cout << infile[i] << "\n";
         continue;
      }
      cout << infile[i] << "\t";
      if (infile[i].isMeasure()) {
         cout << infile[i][0] << "\n";
         continue;
      }
      if (infile[i].isLocalComment()) {
         cout << "!\n";
         continue;
      }
      if (infile[i].isInterpretation()) { 
         if (strncmp(infile[i][0], "**", 2) == 0) {
            cout << "**voice";
         } else if (strcmp(infile[i][0], "*-") == 0) {
            cout << "*-";
         } else {
            cout << "*";
         }
         cout << "\n";
         continue;
      }
      pstream = new SSTREAM;
      for (j=0; j<infile[i].getFieldCount(); j++) {
         if (!infile[i].isExInterp(j, "**kern")) {
            continue;
         }
         if (strcmp(infile[i][j], ".") == 0) {
            // ignore null tokens
            continue;
         }
         if (strchr(infile[i][j], 'r') != NULL) {
            // ignore rests
            continue;
         }
         (*pstream) << voice[i][j];
         (*pstream) << " ";
      }
      (*pstream) << ends;
      data.setSize(strlen(pstream->CSTRING) + 1);
      strcpy(data.getBase(), pstream->CSTRING);
      pre.sar(data, "\\s+$", "", "");
      if (strlen(data.getBase()) == 0) {
         pre.sar(data, "^\\s*$", ".");
      }
      cout << data.getBase();
      cout << "\n";
      delete pstream;
      pstream = NULL;
   }
}




//////////////////////////////
//
// printNotePositions -- prints the vertical position of notes on the
//    staves.  Mostly for debugging purposes.  A spine at the end of the
//    data will be added containing all positions for notes on the line
//    in the sequence in which the notes occur from left to right.
//
//    The middle line of a 5-line staff is the zero position, and
//    position values are diatonic steps above or below that level:
//
//    ===== +4
//          +3
//    ===== +2
//          +1
//    =====  0
//          -1
//    ===== -2
//          -3
//    ===== -4
//

void printNotePositions(HumdrumFile& infile, 
      Array<Array<Array<int> > >& notepos) {

   PerlRegularExpression pre;
   Array<char> data;
   SSTREAM *pstream;


   int i, j, k;
   for (i=0; i<infile.getNumLines(); i++) {
      if (!(infile[i].isData() || infile[i].isLocalComment() ||
            infile[i].isMeasure() || infile[i].isInterpretation())) {
         cout << infile[i] << "\n";
         continue;
      }
      cout << infile[i] << "\t";
      if (infile[i].isMeasure()) {
         cout << infile[i][0] << "\n";
         continue;
      }
      if (infile[i].isLocalComment()) {
         cout << "!\n";
         continue;
      }
      if (infile[i].isInterpretation()) { 
         if (strncmp(infile[i][0], "**", 2) == 0) {
            cout << "**vpos";
         } else if (strcmp(infile[i][0], "*-") == 0) {
            cout << "*-";
         } else {
            cout << "*";
         }
         cout << "\n";
         continue;
      }
      pstream = new SSTREAM;
      for (j=0; j<infile[i].getFieldCount(); j++) {
         if (!infile[i].isExInterp(j, "**kern")) {
            continue;
         }
         if (strcmp(infile[i][j], ".") == 0) {
            // ignore null tokens
            continue;
         }
         if (strchr(infile[i][j], 'r') != NULL) {
            // ignore rests
            continue;
         }
         for (k=0; k<notepos[i][j].getSize(); k++) {
            (*pstream) << notepos[i][j][k];
            if (k < notepos[i][j].getSize()-1) {
               (*pstream) << " ";
            }
         }
         (*pstream) << " ";
      }
      (*pstream) << ends;
      data.setSize(strlen(pstream->CSTRING) + 1);
      strcpy(data.getBase(), pstream->CSTRING);
      pre.sar(data, "\\s+$", "", "");
      if (strlen(data.getBase()) == 0) {
         pre.sar(data, "^\\s*$", ".");
      }
      cout << data.getBase();
      cout << "\n";
      delete pstream;
      pstream = NULL;
   }
}



//////////////////////////////
//
// getNotePositions -- Extract the vertical position of the notes
// on the staves, with the centerline of the staff being the 0 position
// and each diatonic step equal to 1, so that lines of 5-lined staff are
// at positions from bottom to top: -4, -2, 0, +2, +4.
//

void getNotePositions(Array<Array<Array<int> > >& notepos, 
      Array<Array<int> >& baseline, HumdrumFile& infile) {

   notepos.setSize(infile.getNumLines());

   int location;
   char buffer[128] = {0};
   int i, j, k;
   int tokencount;

   for (i=0; i<infile.getNumLines(); i++) {
      if (!infile[i].isData()) {
         continue;
      }
      notepos[i].setSize(infile[i].getFieldCount());
      for (j=0; j<infile[i].getFieldCount(); j++) {
         if (!infile[i].isExInterp(j, "**kern")) {
            continue;
         } 
         if (strcmp(infile[i][j], ".") == 0) {
            // ignore null-tokens
            continue;
         }
         if (strchr(infile[i][j], 'r') != NULL) {
            // ignore rests
            continue;
         }

         tokencount = infile[i].getTokenCount(j);
         notepos[i][j].setSize(tokencount);
         for (k=0; k<tokencount; k++) {
            infile[i].getToken(buffer, j, k, 30);
            location = Convert::kernToDiatonicPitch(buffer) -
                  baseline[i][j] - 4;
            notepos[i][j][k] = location;
         }
      }
   }
}



//////////////////////////////
//
// processKernTokenStems --
//

void processKernTokenStems(HumdrumFile& infile, 
      Array<Array<int> >& baseline, int row, int col) {

   exit(1);


}



//////////////////////////////
//
// assignBasicStemDirections -- don't take beams into consideration.
//

void assignBasicStemDirections(Array<Array<int> >& stemdir, 
      Array<Array<int> >& voice, Array<Array<Array<int> > >& notepos, 
      HumdrumFile& infile) {

   int i, j;
   for (i=0; i<infile.getNumLines(); i++) {
      if (!infile[i].isData()) {
         continue;
      }
      for (j=0; j<infile[i].getFieldCount(); j++) {
         if (!infile[i].isExInterp(j, "**kern")) {
            continue;
         }
         if (strcmp(infile[i][j], ".") == 0) {
            continue;
         }
         if (strchr(infile[i][j], 'r') != NULL) {
            continue;
         }
         if (removeQ) {
            removeStem2(infile, i, j);
         }

         if (strchr(infile[i][j], '/') != NULL) {
            stemdir[i][j] = +1;
         } else if (strchr(infile[i][j], '\\') != NULL) {
            stemdir[i][j] = -1;
         } else {
            stemdir[i][j] = determineChordStem(voice, notepos, infile, i, j);
         }
      }
   }
}



//////////////////////////////
//
// determineChordStem --
//

int determineChordStem(Array<Array<int> >& voice, 
      Array<Array<Array<int> > >& notepos, HumdrumFile& infile, int row, 
      int col) {

   if (notepos[row][col].getSize() == 0) {
      return 0;
   }

   if (voice[row][col] == 1) {
      return +1;
   }
   if (voice[row][col] == 2) {
      return -1;
   }
   if (voice[row][col] == 3) {
      return +1;
   }
   // voice == 0 means determine by vertical position


   if (notepos[row][col].getSize() == 1) {
      int location = notepos[row][col][0];
      if (location >= 0) {
         return -1;
      } else {
         return +1;
      }
   }

   // chord with more than one note so choose the extreme note as the 
   // one which decides the direction
 
   int i;
   int minn = notepos[row][col][0];
   int maxx = notepos[row][col][0];
   for (i=1; i<notepos[row][col].getSize(); i++) {
      if (minn > notepos[row][col][i]) {
         minn = notepos[row][col][i];
      } 
      if (maxx < notepos[row][col][i]) {
         maxx = notepos[row][col][i];
      }
   }

   if (maxx < 0) {
      // all stems want to point upwards:
      return +1;
   }
   if (minn > 0) {
      // all stems want to point downwards:
      return -1;
   }

   if (abs(maxx) > abs(minn)) {
      return -1;
   }
   if (abs(minn) > abs(maxx)) {
      return +1;
   }

   return +1;
}



//////////////////////////////
//
// processKernTokenStemsSimpleModel --
//

void processKernTokenStemsSimpleModel(HumdrumFile& infile, 
      Array<Array<int> >& baseline, int row, int col) {

   int k;
   int& i = row;
   int& j = col;
   int tokencount = infile[i].getTokenCount(j);

   RationalNumber duration;
   if (tokencount == 1) {
      duration = Convert::kernToDurationR(infile[i][j]);
      if (duration >= 4) {
         // whole note or larger for note/chord, to not append a stem
         return;
      } 
      if ((strchr(infile[i][j], '/') != NULL) || 
             (strchr(infile[i][j], '/') != NULL)) {
         if (removeallQ || overwriteallQ) {
            if (strstr(infile[i][j], "/x") != NULL) {
               if (strstr(infile[i][j], "/xx") == NULL) {
                  return;
               }
            } else if (strstr(infile[i][j], "\\x") != NULL) {
               if (strstr(infile[i][j], "\\xx") == NULL) {
                  return;
               }
            }
         } else if (removeallQ || overwriteallQ) {
            removeStem2(infile, i, j);
         } else {
            // nothing to do
            return;
         }
      }
      if (strchr(infile[i][j], 'r') != NULL) {
         // rest which does not have a stem
         return;
      }
   }

   if (removeQ) {
      removeStem2(infile, i, j);
   }

   int voice = getVoice(infile, row, col);
   
   int len = strlen(infile[i][j]);
   char buffer[128] = {0};
   char buffer2[128] = {0};
   int location;
   char output[len*2+tokencount];
   output[0] = '\0';
   for (k=0; k<tokencount; k++) {
      infile[i].getToken(buffer, j, k, 30);
      if (i == 0) {
         duration = Convert::kernToDurationR(buffer);
         // if (duration >= 4) {
         //    // whole note or larger for note/chord, do not append a stem
         //    return;
         // } 
      }
      if (!((strchr(infile[i][j], '/') != NULL) || 
             (strchr(infile[i][j], '\\') != NULL))) {
         location = Convert::kernToDiatonicPitch(buffer) -
               baseline[row][col] - Middle;
         if (voice == 1) {
            addStem(buffer2, buffer, "/");
         } else if (voice == 2) {
            addStem(buffer2, buffer, "\\");
         } else {
            if (location > 0) {
               addStem(buffer2, buffer, "\\");
            } else {
               addStem(buffer2, buffer, "/");
            }
         }
         strcat(output, buffer2);
         if (k < tokencount-1) {
            strcat(output, " ");
         }
      } else {
         strcat(output, buffer);
         if (k < tokencount-1) {
            strcat(output, " ");
         }
      }
   }
   infile[i].setToken(j, output);
}



//////////////////////////////
//
// getVoice -- return 0 if the only spine in primary track, otherwise, return
// the nth column offset from 1 in the primary track.
//

int getVoice(HumdrumFile& infile, int row, int col) {
   int output = 0;
   int tcount = 0;
   int track = infile[row].getPrimaryTrack(col);
   int j;
   int testtrack;
   for (j=0; j<infile[row].getFieldCount(); j++) {
      testtrack = infile[row].getPrimaryTrack(j);
      if (testtrack == track) {
         tcount++;
      }
      if (col == j) {
         output = tcount;
      }
   }

   if (tcount == 1) {
      output = 0;
   }
   return output;
}



//////////////////////////////
//
// removeStem2 -- remove stem and any single x after the stem.
//

void removeStem2(HumdrumFile& infile, int row, int col) {
   Array<char> strin;
   int len = strlen(infile[row][col]);
   strin.setSize(len+1);
   strcpy(strin.getBase(), infile[row][col]);
   PerlRegularExpression pre;
   pre.sar(strin, "[\\\\/]x(?!x)", "", "g");
   pre.sar(strin, "[\\\\/](?!x)", "", "g");
   infile[row].setToken(col, strin.getBase());
}



//////////////////////////////
//
// addStem --
//

void addStem(char* output, const char* input, const char* piece) {
   PerlRegularExpression pre;

   if (pre.search(input, "(.*[ABCDEFG][n#-]*)(.*)$", "i")) {
      strcpy(output, pre.getSubmatch(1));
      strcat(output, piece);
      strcat(output, pre.getSubmatch(2));
   } else {
      strcpy(output, input);
      strcat(output, piece);
   }
}



///////////////////////////////
//
// getClefInfo -- Identify the clef of each note in the score.
//     Does not consider the case where a primary track contains 
//     more than one clef at a time.
//

void getClefInfo(Array >& baseline, HumdrumFile& infile) {
   Array<int> states;
   states.setSize(infile.getMaxTracks()+1); // +1 to allow for unused 0 index
   states.setAll(Convert::kernClefToBaseline("*clefG2"));

   int i, j;
   baseline.setSize(infile.getNumLines());
   for (i=0; i<baseline.getSize(); i++) {
      baseline[i].setSize(0);
   }

   int pt;
   for (i=0; i<infile.getNumLines(); i++) {
      if (infile[i].isInterpretation()) {
         for (j=0; j<infile[i].getFieldCount(); j++) {
            if (!infile[i].isExInterp(j, "**kern")) {
               continue;
            }
            if (strncmp(infile[i][j], "*clef", 5) == 0) {
               pt = infile[i].getPrimaryTrack(j);
               states[pt] = Convert::kernClefToBaseline(infile[i][j]);
            }
         }
      }
      if (!infile[i].isData()) {
         continue;
      }
      baseline[i].setSize(infile[i].getFieldCount());
      for (j=0; j<infile[i].getFieldCount(); j++) {
         if (!infile[i].isExInterp(j, "**kern")) {
            continue;
         }
         pt = infile[i].getPrimaryTrack(j);
         baseline[i][j] = states[pt];
      }
   }

}



//////////////////////////////
//
// checkOptions -- 
//

void checkOptions(Options& opts, int argc, char** argv) {
   opts.define("d|debug=b",    "Debugging information");
   opts.define("r|remove=b",   "Remove stems");
   opts.define("R|removeall=b","Remove all stems including explicit beams");
   opts.define("o|overwrite|replace=b","Overwrite non-explicit stems in input");
   opts.define("O|overwriteall|replaceall=b",  "Overwrite all stems in input");
   opts.define("u|up=b",       "Middle note on staff has stem up");
   opts.define("p|pos=b",     "Display only note vertical positions on staves");
   opts.define("v|voice=b",    "Display only voice/layer information");

   opts.define("author=b",     "Program author");
   opts.define("version=b",    "Program version");
   opts.define("example=b",    "Program examples");
   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, December 2010" << endl;
      exit(0);
   } else if (opts.getBoolean("version")) {
      cout << argv[0] << ", version: 26 December 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");
   removeQ       = opts.getBoolean("remove");
   removeallQ    = opts.getBoolean("removeall");
   noteposQ      = opts.getBoolean("pos");
   voiceQ        = opts.getBoolean("voice");
   overwriteQ    = opts.getBoolean("overwrite");
   overwriteallQ = opts.getBoolean("overwriteall");
   if (opts.getBoolean("up")) {
      Middle = 4;
   }
   removeallQ = opts.getBoolean("removeall");
   if (removeallQ) {
      removeQ = 1;
   }
   if (overwriteallQ) {
      overwriteQ = 1;
   }

}



//////////////////////////////
//
// example -- example function calls to the program.
//

void example(void) {


}



//////////////////////////////
//
// usage -- command-line usage description and brief summary
//

void usage(const char* command) {

}



//////////////////////////////
//
// getBeamState -- Analyze structure of beams and store note layout
//      directives at the same time.
//
// Type          Humdrum     MuseData
// start          L           [
// continue                   =
// end            J           ]
// forward hook   K           /
// backward hook  k           \  x
//

void getBeamState(Array > >& beams, HumdrumFile& infile) {
   int i, j, t;
   int ii;
   int len;
   int contin;
   int start;
   int stop;
   int flagr;
   int flagl;
   int track;
   RationalNumber rn;

   Array<Array<int> > beamstate;   // state of beams in tracks/layers
   Array<Array<int> > gracestate;  // independents state for grace notes

   Array<char> gbinfo;
   gbinfo.setSize(100);
   gbinfo.allowGrowth(0);

   beamstate.setSize(infile.getMaxTracks() + 1);
   gracestate.setSize(infile.getMaxTracks() + 1);
   beamstate.allowGrowth(0);
   gracestate.allowGrowth(0);
   for (i=0; i<beamstate.getSize(); i++) {
      beamstate[i].setSize(100);     // maximum of 100 layers in each track...
      gracestate[i].setSize(100);    // maximum of 100 layers in each track...
      beamstate[i].allowGrowth(0);
      gracestate[i].allowGrowth(0);
      beamstate[i].setAll(0);
      gracestate[i].setAll(0);
   }

   beams.setSize(infile.getNumLines());
   Array<int> curlayer;
   curlayer.setSize(infile.getMaxTracks() + 1);
   Array<int> laycounter;

   for (i=0; i<infile.getNumLines(); i++) {
      if (infile[i].isMeasure()) {
         // don't allow beams across barlines.  Mostly for 
         // preventing buggy beams from propagating...
         for (t=1; t<=infile.getMaxTracks(); t++) {
            beamstate[t].setAll(0);
            gracestate[t].setAll(0);
         }
      }

      if (!infile[i].isData() && !infile[i].isMeasure()) {
         continue;
      }

      if (!infile[i].isData()) {
         continue;
      }

      beams[i].setSize(infile[i].getFieldCount());
      for (j=0; j<beams[i].getSize(); j++) {
         beams[i][j].setSize(1);
         beams[i][j][0] = '\0';
      }

      curlayer.setAll(0);
      for (j=0; j<infile[i].getFieldCount(); j++) {
         track = infile[i].getPrimaryTrack(j);
         curlayer[track]++;
         if (strcmp(infile[i][j], ".") == 0) {
            // ignore null tokens
            continue;
         }         
         if (strchr(infile[i][j], 'r') != NULL) {
            // ignore rests.  Might be useful to not ignore
            // rests if beams extent over rests...
            continue;
         }         
         rn = Convert::kernToDurationR(infile[i][j]);
         if (rn >= 1) {
            beamstate[track][curlayer[track]] = 0;
            continue;
         }
         if (rn == 0) {

            // grace notes;
            countBeamStuff(infile[i][j], start, stop, flagr, flagl);
            if ((start != 0) && (stop != 0)) {
               cerr << "Funny error in grace note beam calculation" << endl;
               exit(1);
            }
            if (start > 7) {
               cerr << "Too many beam starts" << endl;
            }
            if (stop > 7) {
               cerr << "Too many beam ends" << endl;
            }
            if (flagr > 7) {
               cerr << "Too many beam flagright" << endl;
            }
            if (flagl > 7) {
               cerr << "Too many beam flagleft" << endl;
            }
            contin = gracestate[track][curlayer[track]];
            contin -= stop;
            gbinfo.setAll(0);
            for (ii=0; ii<contin; ii++) { 
               gbinfo[ii] = '=';
            }
            if (start > 0) {
               for (ii=0; ii<start; ii++) {
                  strcat(gbinfo.getBase(), "[");
               }
            } else if (stop > 0) {
               for (ii=0; ii<stop; ii++) {
                  strcat(gbinfo.getBase(), "]");
               }
            }
            for (ii=0; ii<flagr; ii++) {
               strcat(gbinfo.getBase(), "/");
            }
            for (ii=0; ii<flagl; ii++) {
               strcat(gbinfo.getBase(), "\\");
            }
            len = strlen(gbinfo.getBase());
            if (len > 6) {
               cerr << "Error too many grace note beams" << endl;
               exit(1);
            }
            beams[i][j].setSize(len+1);
            strcpy(beams[i][j].getBase(), gbinfo.getBase());
            gracestate[track][curlayer[track]] = contin;
            gracestate[track][curlayer[track]] += start;

         } else {
            // regular notes which are shorter than a quarter note
            // (including tuplet quarter notes which should be removed):

            countBeamStuff(infile[i][j], start, stop, flagr, flagl);
            if ((start != 0) && (stop != 0)) {
               cerr << "Funny error in note beam calculation" << endl;
               exit(1);
            }
            if (start > 7) {
               cerr << "Too many beam starts" << endl;
            }
            if (stop > 7) {
               cerr << "Too many beam ends" << endl;
            }
            if (flagr > 7) {
               cerr << "Too many beam flagright" << endl;
            }
            if (flagl > 7) {
               cerr << "Too many beam flagleft" << endl;
            }
            contin = beamstate[track][curlayer[track]];
            contin -= stop;
            gbinfo.setAll(0);
            for (ii=0; ii<contin; ii++) { 
               gbinfo[ii] = '=';
            }
            if (start > 0) {
               for (ii=0; ii<start; ii++) {
                  strcat(gbinfo.getBase(), "[");
               }
            } else if (stop > 0) {
               for (ii=0; ii<stop; ii++) {
                  strcat(gbinfo.getBase(), "]");
               }
            }
            for (ii=0; ii<flagr; ii++) {
               strcat(gbinfo.getBase(), "/");
            }
            for (ii=0; ii<flagl; ii++) {
               strcat(gbinfo.getBase(), "\\");
            }
            len = strlen(gbinfo.getBase());
            if (len > 6) {
               cerr << "Error too many grace note beams" << endl;
               exit(1);
            }
            beams[i][j].setSize(len+1);
            strcpy(beams[i][j].getBase(), gbinfo.getBase());
            beamstate[track][curlayer[track]] = contin;
            beamstate[track][curlayer[track]] += start;
         }
      }
   }
}



//////////////////////////////
//
// countBeamStuff --
//

void countBeamStuff(const char* token, int& start, int& stop, int& flagr, 
      int& flagl) {
   int i = 0;
   start = stop = flagr = flagl = 0;
   while (token[i] != '\0') {
      switch (token[i]) {
         case 'L': start++;  break;
         case 'J': stop++;   break;
         case 'K': flagr++;  break;
         case 'k': flagl++;  break;
      }
      i++;
   }
}



// md5sum: b9893a73d1185750447603c86fcc34d1 autostem.cpp [20110215]