//
// Programmer:    Craig Stuart Sapp <craig@ccrma.stanford.edu>
// Creation Date: Thu May  6 17:15:41 PDT 2000
// Last Modified: Thu Jun 29 00:11:48 PDT 2000 updated to museinfo 1.1
// Last Modified: Thu Jun 29 00:11:48 PDT 2000 added shorten options
// Last Modified: Wed May 27 16:19:29 EDT 2009 added more keyboard commands
// Last Modified: Thu Jun  4 10:24:13 EDT 2009 added colorizing
// Last Modified: Fri Jun 12 18:25:00 PDT 2009 added muting/hiding/marking
// Last Modified: Thu Mar 24 04:22:03 PDT 2011 fixes for 64-bit compiling
// Filename:      ...sig/doc/examples/all/hplay/hplay.cpp
// Syntax:        C++ 
//
// Description:   Play **kern entries in a Humdrum file through MIDI.
//

#include "synthImprov.h"
#include "HumdrumFile.h"
#include "Convert.h"
#include <string.h>

// include string stream class which can have various names:
#ifndef OLDCPP
   #include <sstream>
   #define SSTREAM stringstream
   #define CSTRING str().c_str()
#else
   #ifdef VISUAL
      #include <strstrea.h>
   #else
      #include <strstream.h>
   #endif
   #define SSTREAM strstream
   #define CSTRING str()
#endif

/*----------------- beginning of improvization algorithms ---------------*/

// colorization messages:
#define COLOR_INVALID       -1
#define COLOR_RESET          0
#define COLOR_INIT           1
#define COLOR_BARLINE        2
#define COLOR_CLEAR_SCREEN   3
#define COLOR_MUTE           4
#define COLOR_OVERFILL       5
#define COLOR_INTERPRETATION 6
#define COLOR_TANDEM         7
#define COLOR_MARKS          8
#define COLOR_COMMENT        9

double tempo = 120.0;     // current metronome tempo
SigTimer timer;           // for playing the humdrum file
EventBuffer eventBuffer;  // for storing note offs when playing notes

HumdrumFile data;         // humdrum file to play
int linenum       = 0;    // for keeping track of current line in file
double tempoScale = 1.0;  // for global adjustment of tempo
int velocity      = 64;   // default velocity to play MIDI notes
int echoTextQ     = 1;    // boolean for displaying input file
int fileNumber    = 1;    // current file number being played
int pauseQ        = 0;    // pause or unpause the music.
int colorQ        = 0;    // colorize the display
int hideQ         = 0;    // hide/show non-kern spines
int noteonlyQ     = 0;    // hide/show stems and beaming in **kern spines
int colormode     = 'b';  // w = white background; b = black background
int tabsize       = 12;   // used with 't' and 'T' real-time commands
int transpose     = 0;    // used with 'l' and 'L' real-time commands
Array<int> trackmute;     // used to mute/unmute tracks
Array<int> markers;       // storage for markers created with space bar
int markerindex   =  0;   // place to store first marker

int mine          = 30;   // used with the -m option
int shortenQ      =  0;   // used with the -s option
int shortenamount = 30;   // used with the -s option

// non-synthImprov function declarations:

void     checkOptions(void);
void     inputNewFile(void);
void     playdata(HumdrumFile& data, int& linenum, 
                                  SigTimer& timer);
void     printInputLine(HumdrumFile& infile, int line);
void     processNotes(HumdrumRecord& record);
int      getMeasureLine(HumdrumFile& data, int number);
ostream& colormessage(ostream& out, int messagetype, int mode, 
                                  int status);
ostream& tabPrintLine(ostream& out, HumdrumRecord& record, 
                                  int tabsize, int sMessage = COLOR_INVALID);
int      getKernTrack(int number, HumdrumFile& infile);
int      goBackMeasures(HumdrumFile& data, int startline, int target);
int      goForwardMeasures(HumdrumFile& data, int startline, int target);
void     printExclusiveInterpLine(int linenum, HumdrumFile& infile);
int      getKernNoteOnlyLen(char* buffer, const char* ptr);
void     printMarkLocation(HumdrumFile& infile, int line, int mindex);
void     printMarkerInfo(ostream& out, int mindex, int line, 
                                  HumdrumFile& infile, int style);
void     printAllMarkers(ostream& out, Array<int>& markers, 
                                  HumdrumFile& infile);


/*--------------------- maintenance algorithms --------------------------*/


//////////////////////////////
//
// description -- This function is called with the user presses 
//      the letter 'D' on the computer keyboard.
//

void description(void) { 
   printboxtop();      
   psl(" HUMPLAY -- by Craig Stuart Sapp, 4 May 2000");
   psl(" _ = lower volume   + = raise volume   s = silence current notes");
   psl(" , = slow down      . = speed up       p = toggle MIDI output");
   psl(" < = slow down more > = speed up more  space = toggle text output");
   printboxbottom();
}


//////////////////////////////
//
// initialization -- This function is called at the start of the program.
//

void initialization(void) { 
   checkOptions();
   timer.setPeriod(500); 
   timer.reset();
   eventIdler.setPeriod(0);
   eventBuffer.setPollPeriod(10);
   eventBuffer.setPort(synth.getOutputPort());
   if (colorQ) {
      colormessage(cout, COLOR_INIT, colormode, colorQ);
      colormessage(cout, COLOR_CLEAR_SCREEN, colormode, colorQ);
      print_commands();
      sleep(1);
   }
   trackmute.setSize(1000); // maximum track in humdrum file assumed to be 1000
                            // space 1000-10 is used for a special purpose.
   trackmute.allowGrowth(0);
   trackmute.setAll(0);

   markers.setSize(1001);
   markers.setAll(0);
   markers.allowGrowth(0);
   markerindex = 0;
}


//////////////////////////////
//
// finishup -- This function is called just before exiting the program.
//

void finishup(void) { 
   eventBuffer.off();
   colormessage(cout, COLOR_RESET, colormode, colorQ);
}

/*-------------------- main loop algorithms -----------------------------*/

//////////////////////////////
//
// mainloopalgorithms -- This function is called continuously while
//    the programming is running.  It checks to determine whether another
//    line of the Humdrum file is ready to be played.
//

void mainloopalgorithms(void) { 
   eventBuffer.checkPoll();
   if (pauseQ) {
      return;
   }
   if (timer.expired()) {
      playdata(data, linenum, timer);
      if (linenum >= data.getNumLines()) {
         inputNewFile();
      }
   }
}

/*-------------------- triggered algorithms -----------------------------*/

//////////////////////////////
//
// keyboardchar -- When the user presses a key on the computer keyboard,
//    it is sent to this program.  Certain capital letter keys are 
//    used by the interface (such as "?", "M", "D", "X", "Y", "G"), and 
//    will not be sent to this function.
//

void keyboardchar(int key) { 
   static int lastkeytime = 0;
   static int number      = 0;

   if (t_time - lastkeytime > 5000) {
      // reset the number value if more than 5 seconds has elapsed
      // since the last key press.
      number = 0;
   }
   lastkeytime = t_time;

   if (isdigit(key)) {
      number = number * 10 + (key - '0');
      return;
   }
   switch (key) {
   // case 'a': break;
      case 'b':               // set color mode to black
         colorQ = 1;          // turn on colorization automatically
         colormode = 'b';
         colormessage(cout, COLOR_INIT, colormode, colorQ);
         cout << "!! CHANGING TO BLACK BACKGROUND" << endl;
         break;
      case 'c':               // toggle colorization
         colorQ = !colorQ;
         if (colorQ) {
            colormessage(cout, COLOR_INIT, colormode, colorQ);
            cout << "!! COLORIZATION TURNED ON" << endl;
         } else {
            colormessage(cout, COLOR_RESET, colormode, !colorQ);
            cout << "!! COLORIZATION TURNED OFF" << endl;
         }
         break;
   // case 'd': break;
      case 'e':               // print exclusive interpretation info for spines
         printExclusiveInterpLine(linenum, data);
         break;
   // case 'f': break;
   // case 'g': break;

      case 'h':               // hide/unhide non-kern spine
         hideQ = !hideQ;
         if (hideQ) {
            cout << "!! Hiding non-kern spines" << endl;
         } else {
            cout << "!! Showing all spines" << endl;
         }
         break;
   // case 'i': break;
   // case 'j': break;
   // case 'k': break;
      case 'l':               // transpose up specified number of semitones
         if (number < 100) {
            transpose = number;
            cout << "!! Transposing " << transpose << " steps up" << endl;
         }
         break;
      case 'L':               // transpose down specified number of semitones
         if (number < 100) {
            transpose = -number;
            cout << "!! Transposing " << -transpose << " steps down" << endl;
         }
         break;
      case 'm':               // mute or unmute all tracks
         if (number == 0) {
            trackmute.setAll(!trackmute[trackmute.getSize()-1]);
            if (trackmute[0]) {
               cout << "!! All spines are muted" << endl;
            } else {
               cout << "!! All spines are unmuted" << endl;
            }
         } else {
            int tracknum = getKernTrack(number, data); 
            trackmute[tracknum] = !trackmute[tracknum];
            if (trackmute[tracknum]) {
               cout << "!! **kern spine " << number << " is muted" << endl;
            } else {
               cout << "!! **kern spine " << number << " is unmuted" << endl;
            }
         }
         break;
         break;
      case 'n':               // toggle display of note only (supression
                              // of beam and stem display
         noteonlyQ = !noteonlyQ;
         if (noteonlyQ) {
            cout << "!! Notes only: supressing beams and stems in **kern data" 
                 << endl;
         } else {
            cout << "!! Displaying *kern data unmodified" << endl;
         }
         break;
      case 'o':               // set the tempo to a particular value
         if (number > 20 && number < 601) {
            cout << "!! TEMPO SET TO " << number << endl;
            tempo = number;
            tempoScale = 1.0;
         } else if (number == 0) {
            cout << "!! Current tempo: " << tempo * tempoScale << endl;
         }
         break;
      case 'p':               // toggle music pausing
         eventBuffer.off();
         timer.reset();
         pauseQ = !pauseQ;
         if (pauseQ) {
            cout << "!! Paused" << endl;
         }
         break;
      case 'q':               // toggle display of file while playing
         echoTextQ = !echoTextQ;
         if (echoTextQ) {
            cout << "!! FILE DISPLAY TURNED ON" << endl;
         } else {
            cout << "!! FILE DISPLAY TURNED OFF" << endl; 
         }
         break;
      case 'r':               // return to a marker
         if (number == 0) {
            linenum = markers[0];
            cout << "!! Going to line " << linenum << endl;
            eventBuffer.off();
            timer.reset();
         } else if (number < markers.getSize()) {
            linenum = markers[number];
            cout << "!! Going to line " << linenum << endl;
            eventBuffer.off();
            timer.reset();
         }
         break;
      case 'R':               // Print a list of all markers
         printAllMarkers(cout, markers, data);
         break;
      case 's':    // silence notes
         eventBuffer.off();
         break;
      case 't':    // increase tab size
         tabsize++;
         // cout << "!! tabsize = " << tabsize << endl;
         break;
      case 'T':    // decrease tab size
         tabsize--;
         if (tabsize < 3) {
            tabsize = 3;
         }
         // cout << "!! tabsize = " << tabsize << endl;
         break;
   // case 'u': break;
   // case 'v': break;
      case 'w':               // set color mode to white
         colorQ = 1;          // turn on colorization automatically
         colormode = 'w';
         colormessage(cout, COLOR_INIT, colormode, colorQ);
         cout << "!! CHANGING TO WHITE BACKGROUND" << endl;
         break;
      case 'x':               // clear the screen
         colormessage(cout, COLOR_CLEAR_SCREEN, colormode, 1);
         printInputLine(data, linenum-1);
         break;
   // case 'y': break;
   // case 'z': break;

      case ' ':               // mark the measure/beat/line of the music
         if ((number != 0) && (number < markers.getSize())) {
            markerindex = number;
         } else {
            markerindex++;
            if (markerindex > markers.getSize()-1) {
               markerindex = 1;
            }
         }
         printMarkLocation(data, linenum == 0 ? 0 : linenum-1, markerindex);
         break;
      case ',':    // slow down tempo 
         tempoScale *= 0.97;
         cout << "!! TEMPO SET TO " << (int)(tempo * tempoScale) << endl;
         break;
      case '<':
         tempoScale *= 0.93; 
         cout << "!! TEMPO SET TO " << (int)(tempo * tempoScale) << endl;
         break;
      case '.':    // speed up tempo 
         tempoScale *= 1.03;
         cout << "!! TEMPO SET TO " << (int)(tempo * tempoScale) << endl;
         break;
      case '>':
         tempoScale *= 1.07;
         cout << "!! TEMPO SET TO " << (int)(tempo * tempoScale) << endl;
         break;
      case '=': 
         {
            int newline = 0;
            if (number == 0) {
               newline = 0;
            } else {
               newline = getMeasureLine(data, number);
            }
            if (newline >= 0) {
               cout << "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" 
                    << " =" << number
                    << endl;
               linenum = newline;
               eventBuffer.off();
               timer.reset();
            }
         }
         break;
      case '(': 
         {
            int newline = goBackMeasures(data, linenum, number);
            cout << "!! back " << number << " measure" 
		 << (number==1? "":"s") << endl;
            linenum = newline;
            eventBuffer.off();
            timer.reset();
         }
         break;
      case ')': 
         {
            int newline = goForwardMeasures(data, linenum, number);
            cout << "!! forward " << number << " measure" 
                 << (number==1? "":"s") << endl;
            linenum = newline;
            eventBuffer.off();
            timer.reset();
         }
         break;
      case '+':    // louder
         velocity++;
         if (velocity > 127) {
            velocity = 127;
         }
         cout << "!! velocity = " << velocity << endl;
         break;

      case '_':    // sofer
         velocity--;
         if (velocity < 1) {
            velocity = 1;
         }
         cout << "!! velocity = " << velocity << endl;
         break;

      case '^':    // go to the start of the file
         linenum = 0;
         cout << "!! Going to start of file" << endl;
         break;
   }

   if (!isdigit(key)) {
      number = 0;
   }
}



/*------------------ end improvization algorithms -----------------------*/


//////////////////////////////
//
// printAllMarkers --
//

void printAllMarkers(ostream& out, Array& markers, HumdrumFile& infile) {
   int i;
   colormessage(cout, COLOR_MARKS, colormode, colorQ);
   out << "**mark\t**line\t**abeat\t**bar\t**beat\n";
   for (i=1; i<markers.getSize(); i++) {
      if (markers[i] > 0) {
         printMarkerInfo(out, i, markers[i], infile, 0);
         out << "\n";
      }
   }
   out << "*-\t*-\t*-\t*-\t*-" << endl;
   colormessage(cout, COLOR_INIT, colormode, colorQ);
}



//////////////////////////////
//
// printMarkerInfo --
//  style = 0 : print without text labels.
//  style = 1 : print with text labels.
//

void printMarkerInfo(ostream& out, int mindex, int line, HumdrumFile& infile, 
      int style) {
   double absbeat = infile.getAbsBeat(line);
   double measure = -10;
   double beat = infile[line].getBeat();
 
   int i;
   int counter = 0;
   for (i=line; i>=0; i--) {
      if (data[i].isMeasure()) {
         counter++;
         if (sscanf(data[i][0], "=%lf", &measure)) {
            break;
         }
         if (counter >= 2) {
            // don't search more than two measures backwards
            break;
         }
      }
   }

   if (style == 0) {
      out << mindex << "\t" << line+1 << "\t" << absbeat;
      out << "\t" << measure << "\t" << beat;
   } else { 
      out << "Mark:"         << mindex 
           << "\tLine:"       << line+1
           << "\tAbsbeat:"    << absbeat;
      if (measure > -10) {
         out << "\tMeasure:" << measure;
      }
      if (beat >= 0) {
         out << "\tBeat:"    << beat;
      }
   }

}



//////////////////////////////
//
// printMarkLocation -- print where you are in the file.
//

void printMarkLocation(HumdrumFile& infile, int line, int mindex) {
   if (line < 0 || line > infile.getNumLines()-1) {
      return;
   }
   colormessage(cout, COLOR_COMMENT, colormode, colorQ);
   cout << "!! ";
   colormessage(cout, COLOR_MARKS, colormode, colorQ);
   printMarkerInfo(cout, mindex, line, infile, 1);
   colormessage(cout, COLOR_INIT, colormode, colorQ);
   cout << endl;

   markers[mindex] = line;
   markers[0]      = line;
}



//////////////////////////////
//
// printExclusiveInterpLine --
//

void printExclusiveInterpLine(int linenum, HumdrumFile& infile) {
   int dataline = -1;
   int i;
   for (i=0; i<data.getNumLines(); i++) { 
      if (infile[i].hasSpines()) {
         dataline = i;
         break;
      }
   }
   if (dataline < 0) {
      return;
   }

   SSTREAM tempstream;
   for (i=0; i<infile[dataline].getFieldCount(); i++) {
      tempstream << infile[dataline].getExInterp(i);
      if (i < infile[dataline].getFieldCount()-1) {
         tempstream << "\t";
      }
   }
   tempstream << "\n";
   for (i=0; i<infile[dataline].getFieldCount(); i++) {
      tempstream << "*-";
      if (i < infile[dataline].getFieldCount()-1) {
         tempstream << "\t";
      }
   }
   tempstream << ends;

   HumdrumFile dummyfile;
   dummyfile.read(tempstream);
   printInputLine(dummyfile, 0);
}



//////////////////////////////
//
// goBackMeasures --
//

int goBackMeasures(HumdrumFile& data, int startline, int target) {
   if (target < 0) {
      target = 1; 
   }
   target++; // need to go back past the current measure barline as well.
   int i;
   int counter = 0;
   for (i=startline; i>=0; i--) {
      if (data[i].isMeasure()) {
         counter++;
         if (counter > target) {
            break;
         }
      }
   }
   
   return i;
}



//////////////////////////////
//
// goForwardMeasures --
//

int goForwardMeasures(HumdrumFile& data, int startline, int target) {
   if (target < 0) {
      target = 1; 
   }
   if (startline > data.getNumLines()-1) {
      return data.getNumLines()-1;
   }
   int i;
   int counter = 0;
   for (i=startline; i<data.getNumLines(); i++) {
      if (data[i].isMeasure()) {
         counter++;
         if (counter > target) {
            break;
         }
      }
   }
   
   return i;
}



//////////////////////////////
//
// getMeasureLine -- return the line number on which the measure
//     number is found.  Return -1 if measure is not found.
//

int getMeasureLine(HumdrumFile& data, int number) {
   int i;
   int testnum = -1;
   for (i=0; i<data.getNumLines(); i++) {
      if (data[i].isMeasure()) {
         if (sscanf(data[i][0], "=%d", &testnum) == 1) {
            if (number == testnum) {
               return i;
            }
         }
      }
   }

   return -1;
}



//////////////////////////////
//
// getKernTrack -- Returns the track number of the nth **kern spine.
//

int getKernTrack(int number, HumdrumFile& infile) {
   int counter = 0;
   int i;

   for (i=1; i<=infile.getMaxTracks(); i++) {
      if (strcmp("**kern", infile.getTrackExInterp(i)) == 0) {
         counter++;
      }
      if (counter == number) {
         return i;
      }
   }

   // Give some high-numbered spine which is not likely
   // to be used (but is valid based on the size of trackmute).
   return trackmute.getSize()-10;  
}



//////////////////////////////
//
// checkOptions --  process command-line options and setup the
//   humdrum data to play.
//

void checkOptions(void) {
   options.define("t|tempo=d:120.0", "Base tempo");
   options.define("e|tempo-scale=d:1.0", "Tempo scaling factor");
   options.define("c|color|colour=s:b", "colorize the display");
   options.define("p|pause=d:1.0", "pause time in seconds between files");
   options.define("q|quiet=b", "Turn off data echoing while playing");
   options.define("v|velocity=i:64", "Default MIDI key velocity");
   options.define("m|min=i:30",      "minimum millisecond duration of notes");
   options.define("s|shorten=i:30",  "shortening millisecond value for note durations");
   options.process();

   velocity = options.getInteger("velocity");
   tempoScale = options.getDouble("tempo-scale");
   tempo = options.getDouble("tempo");
   if (options.getBoolean("quiet")) {
      echoTextQ = 0;
   }

   if (options.getArgCount() < 1) {
      data.read(cin);
   } else {
      inputNewFile();
   }

   mine = options.getInteger("min");
   if (mine < 0) {
      mine = 0;
   }
   shortenQ = !options.getBoolean("shorten");
   shortenamount = options.getInteger("shorten");

   colorQ = !options.getBoolean("color");
   colormode = options.getString("color")[0];
   if ((colormode != 'b') && (colormode != 'w')) {
      colormode = 'w';
   }
}



//////////////////////////////
//
// inputNewFile -- load in a new Humdrum file.
//

void inputNewFile(void) {
   data.clear();
   linenum = 0;    

   int count = options.getArgCount();
   if (fileNumber > count) {
      finishup();
      exit(0);
   }

   data.read(options.getArg(fileNumber));
   data.analyzeRhythm("4");

   if (fileNumber > 1) {
      millisleep((float)(1000 * options.getDouble("pause")));
   }
   fileNumber++;
}



//////////////////////////////
//
// playdata -- play the next line of the humdrum file, update the 
//     line number and the time for the next events to be read 
//     from the file.
//

void playdata(HumdrumFile& data, int& linenum, SigTimer& timer) {
   double duration = 0;     // duration of the current line;
   int type = data[linenum].getType();

   while (linenum < data.getNumLines() && duration == 0.0) {
      duration = data[linenum].getDuration();   

      if (type == E_humrec_data) {
         processNotes(data[linenum]);
      } else if (type == E_humrec_interpretation) {
         if (strncmp(data[linenum][0], "*MM", 3) == 0) {
            tempo = atoi(&data[linenum][0][3]);
         }
      }
      if (echoTextQ) {
         printInputLine(data, linenum);
      }
      if (duration > 0.0) {
         timer.setPeriod(60000 / tempo / tempoScale * duration);
         timer.reset();
      }
      linenum++;
      if (linenum < data.getNumLines()) {
         type = data[linenum].getType();
      }
   }
}



//////////////////////////////
//
// printInputLine -- print the current line of the file,
//  omitting the duration field at the end of the line
//

void printInputLine(HumdrumFile& infile, int line) {
   if ((line < 0) || (line >= infile.getNumLines())) {
      return;
   }
   HumdrumRecord& record = infile[line];
   if (record.isMeasure()) {
      colormessage(cout, COLOR_BARLINE, colormode, colorQ);
      tabPrintLine(cout, record, tabsize, COLOR_BARLINE);
      colormessage(cout, COLOR_INIT, colormode, colorQ);
      cout << endl;
   } else if (record.isTandem()) {
      colormessage(cout, COLOR_TANDEM, colormode, colorQ);
      tabPrintLine(cout, record, tabsize, COLOR_TANDEM);
      colormessage(cout, COLOR_INIT, colormode, colorQ);
      cout << endl;
   } else if (record.isInterpretation()) {
      colormessage(cout, COLOR_INTERPRETATION, colormode, colorQ);
      tabPrintLine(cout, record, tabsize, COLOR_INTERPRETATION);
      colormessage(cout, COLOR_INIT, colormode, colorQ);
      cout << endl;
   } else {
      tabPrintLine(cout, record, tabsize);
      cout << endl;
   }
}



//////////////////////////////
//
// tabPrintLine -- print a line with tabs changed to spaces
// default value sMessage = COLOR_INVALID
//

ostream& tabPrintLine(ostream& out, HumdrumRecord& record, int tabsize, int sMessage) {
   int i, j;
   int len;
   int track;
   int suppressColor = record.isMeasure() || record.isInterpretation();

   // count the **kern spines (used with hideQ varaible):
   int kerncount = 0;
   int lastkern = -1;
   for (i=0; i<record.getFieldCount(); i++) {
      if (strcmp(record.getExInterp(i), "**kern") == 0) {
         kerncount++;
         lastkern = i;
      }
   }

   const char *ptr = NULL;
   char buffer[1024] = {0};
   for (i=0; i<record.getFieldCount()-1; i++) {
      if (hideQ && (strcmp(record.getExInterp(i), "**kern") != 0)) {
         continue;
      }
      if (hideQ && (lastkern == i)) {
         // print the last kern spine outside of the loop below
         break;
      }
      track = record.getPrimaryTrack(i);
      ptr = record[i];
      if (noteonlyQ && strcmp(record.getExInterp(i), "**kern") == 0) {
         len = getKernNoteOnlyLen(buffer, ptr);
         ptr = buffer;
      } else {
         len = strlen(ptr);
      }

      if (len < tabsize) {
         if (!suppressColor && trackmute[track]) {
            colormessage(cout, COLOR_MUTE, colormode, colorQ);
            out << ptr;
            colormessage(cout, COLOR_INIT, colormode, colorQ);
         } else {
            out << ptr;
         }
         for (j=len; j<tabsize; j++) {
            out << ' ';
         }
      } else {
         if (!suppressColor && trackmute[track]) {
            colormessage(cout, COLOR_MUTE, colormode, colorQ);
         }
         for (j=0; j<tabsize - 1; j++) {
            out << ptr[j];
         }
         if (!trackmute[track]) {
            colormessage(cout, COLOR_OVERFILL, colormode, colorQ);
         }
         out << '|';
         if (suppressColor) {
               colormessage(cout, sMessage, colormode, colorQ);
         } else {
            if (!trackmute[track]) {
               colormessage(cout, COLOR_INIT, colormode, colorQ);
            }
   
            if (!suppressColor && trackmute[track]) {
               colormessage(cout, COLOR_INIT, colormode, colorQ);
            }
         }
      }
   }

   int lastindex = 0;
   if (hideQ && (lastkern >= 0)) {
      track = record.getPrimaryTrack(lastkern);
      lastindex = lastkern; 
   } else {
      lastindex = record.getFieldCount()-1;
      track = record.getPrimaryTrack(lastindex);
   }

   ptr = record[lastindex];
   if (noteonlyQ && strcmp(record.getExInterp(lastindex), "**kern") == 0) {
      len = getKernNoteOnlyLen(buffer, ptr);
      ptr = buffer;
   } else {
      len = strlen(ptr);
   }
   
   if ((track > 0) && !suppressColor && trackmute[track]) {
      colormessage(cout, COLOR_MUTE, colormode, colorQ);
   }
   out << ptr;
   if ((track > 0) && !suppressColor && trackmute[track]) {
      colormessage(cout, COLOR_INIT, colormode, colorQ);
   }
   out << flush;

   return out;
}



//////////////////////////////
//
// getKernNoteOnlyLen -- return the length of the **kern data excluding beaming
//   and steming information.
//

int getKernNoteOnlyLen(char* buffer, const char* ptr) {
   buffer[0] = '\0';
   int counter = 0;
   int len = strlen(ptr);
   int i;
   for (i=0; i<len; i++) {
      switch (ptr[i]) {
         case 'k': case 'K': case 'L': case 'J':   // beaming characters
         case '/': case '\\':                      // stem direction characters
            break;
         default:
            buffer[counter++] = ptr[i];
            buffer[counter] = '\0';
      }
   }

   buffer[counter] = '\0';
   return counter;
}



//////////////////////////////
//
// processNotes -- play all kern notes in the current record and
//    return the shortest note duration.  Don't play notes
//    if they are in a track which is muted.
//

void processNotes(HumdrumRecord& record) {
   NoteEvent note;
   int pitch = 0;
   double duration = 0.0;
   int staccatoQ = 0;
   int accentQ = 0;
   int sforzandoQ = 0;
   int i, j;
   int notecount = 0;
   char buffer[128] = {0};
   for (i=0; i<record.getFieldCount(); i++) {
      if ((record.getPrimaryTrack(i) < trackmute.getSize()) 
            && trackmute[record.getPrimaryTrack(i)]) {
         continue;
      }
      if (record.getExInterpNum(i) == E_KERN_EXINT) {
         notecount = record.getTokenCount(i);
         if (strcmp(record[i], ".") == 0) {
            continue;
         }
         for (j=0; j<notecount; j++) {
            record.getToken(buffer, i, j);
            if (strchr(buffer, '[')) {
               // total tied note durations
               duration = data.getTiedDuration(linenum, i, j);
            } else {
               duration = Convert::kernToDuration(buffer);
            }
            pitch = Convert::kernToMidiNoteNumber(buffer); 
            // skip rests
            if (pitch < 0) {
               continue;
            }
            pitch += transpose;
            // don't play note which is transposed too extremely
            if (pitch < 0)   { continue; }
            if (pitch > 127) { continue; }

            // skip tied notes
            if (strchr(buffer, '_') || strchr(buffer, ']')) {
               continue;
            }

            accentQ    = strchr(buffer, '^')  == NULL? 0 : 1;
            sforzandoQ = strchr(buffer, 'z')  == NULL? 0 : 1;
            staccatoQ  = strchr(buffer, '\'') == NULL? 0 : 1;
            note.setChannel(0);
            note.setKey(pitch);
            note.setOnTime(t_time);
            duration = duration * 60000 / tempo / tempoScale;
            if (shortenQ) {
               duration -= shortenamount;
               if (duration < mine) {
                  duration = mine;
               }
            }
            note.setDur((int)duration);
            if (staccatoQ) {
               note.setDur((int)(0.5 * note.getDur()));
            }
            note.setKey(pitch);
            note.setVelocity(velocity);
            if (accentQ) {
               note.setVelocity((int)(note.getVelocity() * 1.3));
            }
            if (sforzandoQ) {
               note.setVelocity((int)(note.getVelocity() * 1.5));
            }
 
            note.activate();
            note.action(eventBuffer);
            eventBuffer.insert(note);
         }
      }
   }
}



//////////////////////////////
//
// colormessage --
//

ostream& colormessage(ostream& out, int messagetype, int mode, int status) {
   if (messagetype == COLOR_INVALID) {
      return out;
   }

   #define ANSI_RESET          "\033[0m"   /* go back to the original colors */
   #define ANSI_UNDERLINE_ON   "\033[4m"
   #define ANSI_UNDERLINE_OFF  "\033[24m"
   #define ANSI_CLEAR_SCREEN   "\033[2J\033[H" /* and move to the top */

   #define ANSI_BLACK          "\033[37;40m"  /* black background; white text */
   #define ANSI_WHITE          "\033[30;107m" /* white background; black text */

   #define ANSI_BLACK_TX       "\033[30m"
   #define ANSI_RED_TX         "\033[31m"
   #define ANSI_GREEN_TX       "\033[32m"
   #define ANSI_YELLOW_TX      "\033[33m"
   #define ANSI_BLUE_TX        "\033[34m"
   #define ANSI_MAGENTA_TX     "\033[35m"
   #define ANSI_CYAN_TX        "\033[36m"
   #define ANSI_WHITE_TX       "\033[37m"

   #define ANSI_HI_BLACK_TX    "\033[90m"
   #define ANSI_HI_RED_TX      "\033[91m"
   #define ANSI_HI_GREEN_TX    "\033[92m"
   #define ANSI_HI_YELLOW_TX   "\033[93m"
   #define ANSI_HI_BLUE_TX     "\033[94m"
   #define ANSI_HI_MAGENTA_TX  "\033[95m"
   #define ANSI_HI_CYAN_TX     "\033[96m"
   #define ANSI_HI_WHITE_TX    "\033[97m"

   #define ANSI_GRAY_BG        "\033[47m"

   if (status == 0) {
      return out;   // don't do any coloring
   }

   if (mode == 'b') {

      switch (messagetype) {
         case COLOR_RESET:         out << ANSI_RESET;         break;
         case COLOR_INIT:          out << ANSI_BLACK;         break;
         case COLOR_BARLINE:       out << ANSI_YELLOW_TX;     break;
         case COLOR_CLEAR_SCREEN:  out << ANSI_CLEAR_SCREEN;  break;
         case COLOR_MUTE:          out << ANSI_BLUE_TX;       break;
         case COLOR_OVERFILL:      out << ANSI_HI_BLUE_TX;    break; 
         case COLOR_INTERPRETATION:out << ANSI_HI_RED_TX;     break; 
         case COLOR_TANDEM:        out << ANSI_MAGENTA_TX;    break; 
         case COLOR_MARKS:         out << ANSI_CYAN_TX;       break; 
         case COLOR_COMMENT:       out << ANSI_GREEN_TX;      break; 
         default: return colormessage(out, COLOR_INIT, mode, status);
      }

   } else if (mode == 'w') {

      switch (messagetype) {
         case COLOR_RESET:         out << ANSI_RESET;         break;
         case COLOR_INIT:          out << ANSI_WHITE;         break;
         case COLOR_BARLINE:       out << ANSI_GRAY_BG;       break;
         case COLOR_CLEAR_SCREEN:  out << ANSI_CLEAR_SCREEN;  break;
         case COLOR_MUTE:          out << ANSI_YELLOW_TX;     break;
         case COLOR_OVERFILL:      out << ANSI_HI_CYAN_TX;    break; 
         case COLOR_INTERPRETATION:out << ANSI_HI_RED_TX;     break; 
         case COLOR_TANDEM:        out << ANSI_MAGENTA_TX;    break; 
         case COLOR_MARKS:         out << ANSI_CYAN_TX;       break; 
         case COLOR_COMMENT:       out << ANSI_GREEN_TX;      break; 
         default: return colormessage(out, COLOR_INIT, mode, status);
      }

   }

   out << flush;
   return out;
}


// md5sum: 63ac4308c53de9619f8ed9884f89ed33 humplay.cpp [20130319]