//
// Programmer:    Craig Stuart Sapp <craig@ccrma.stanford.edu>
// Creation Date: Sun Oct 24 10:39:47 PDT 1999
// Last Modified: Sat Nov 25 20:17:58 PST 2000 added instrument selection
// Last Modified: Tue Feb 20 19:20:09 PST 2001 add articulation interpretation
// Last Modified: Sat Aug 23 08:57:54 PDT 2003 added *free/*strict control// Last Modified: Wed Mar 24 19:56:04 PST 2004 fixed initial *MM ignore
// Last Modified: Wed Apr  7 16:22:38 PDT 2004 grace-note noteoff hack
// Last Modified: Tue Sep  7 03:31:49 PDT 2004 added extra space at end
// Last Modified: Tue Sep  7 03:43:09 PDT 2004 more grace-note noteoff fixing
// Last Modified: Tue Sep  7 20:58:38 PDT 2004 initial dynamics processing
// Last Modified: Fri Sep 10 02:26:59 PDT 2004 added padding option
// Last Modified: Fri Sep 10 19:46:53 PDT 2004 added cresc. decresc. control
// Last Modified: Sun Sep 12 05:20:44 PDT 2004 added human and metric volume
// Last Modified: Wed Mar 23 00:35:18 PST 2005 added constant volume back
// Last Modified: Sat Dec 17 22:46:11 PST 2005 added **time processing
// Last Modified: Sat Jun  3 10:35:29 PST 2005 added **tempo processing
// Last Modified: Sun Jun  4 19:32:22 PDT 2006 added PerfViz match files
// Last Modified: Tue Sep 12 19:36:08 PDT 2006 added **idyn processing
// Last Modified: Sun Oct  1 21:04:35 PDT 2006 continued work in **idyn
// Last Modified: Thu May  3 22:55:15 PDT 2007 added *pan controls
// Last Modified: Thu Oct 30 12:42:35 PST 2008 added --no-rest option
// Last Modified: Thu Nov 20 08:19:40 PST 2008 added **Dcent interpretation
// Last Modified: Sun Nov 23 22:49:15 PST 2008 added --temperament option
// Last Modified: Tue May 12 12:14:09 PDT 2009 added rhythmic scaling factor
// Last Modified: Tue Feb 22 13:23:24 PST 2011 added --stdout
// Last Modified: Fri Feb 25 13:00:02 PST 2011 added --met
// Last Modified: Fri Feb 25 15:15:09 PST 2011 added --timbres and --autopan
// Last Modified: Wed Oct 12 16:23:02 PDT 2011 fixed --temperament pc 0 prob.
// Last Modified: Fri Aug  3 16:09:29 PDT 2012 added DEFAULT for --timbres
// Last Modified: Tue Oct 16 21:02:56 PDT 2012 added getTitle/song title
// Filename:      ...sig/examples/all/hum2mid.cpp
// Web Address:   http://sig.sapp.org/examples/museinfo/humdrum/hum2mid.cpp
// Syntax:        C++; museinfo
//
// Description:   Converts Humdrum **kern data into MIDI data in a
//                Standard MIDI File format.
//
// Todo:
// 	* Check to make sure input files are not the same as the -o filename
// 	* Allow multiple input / outputs
// 	* Allow multiple inputs one output (already done?)
//

#include "museinfo.h"
#include "PerlRegularExpression.h"
#include "SigString.h"

#include <string.h>
#include <stdio.h>
#include <time.h>
#include <math.h>

// stringstream, or strstream in older CPP is needed only for PerfViz files:
#ifndef OLDCPP
   #include <fstream>
   #include <sstream>
   #define SSTREAM stringstream
   #define CSTRING str().c_str()
   using namespace std;
#else
   #include <fstream.h>
   #ifdef VISUAL
      #include <strstrea.h>    /* for Windows 95 */
   #else
      #include <strstream.h>
   #endif
   #define SSTREAM strstream
   #define CSTRING str()
#endif

#define TICKSPERQUARTERNOTE 120

//  Dynamics to attack velocity conversions:

// Set based on conversions to MIDI files made in Finale:
// (Every 13 increments)
#define DYN_FFFF 127
#define DYN_FFF  114
#define DYN_FF   101
#define DYN_F    88
#define DYN_MF   75
#define DYN_MP   62
#define DYN_P    49
#define DYN_PP   36
#define DYN_PPP  23
#define DYN_PPPP 10


// PerfViz data structure

class PerfVizNote {
   public:   
      int pitch;           // pitch name of note (in base40 notation)
      int beat;            // beat number in measure of note
      int vel;             // MIDI attack velocity of note
      int ontick;          // tick time for note on
      int offtick;         // tick time for note off
      double scoredur;     // score duration of notes in beats
      double absbeat;      // absolute beat position of note in score

      double beatfrac;     // starting fraction of beat
      double beatdur;      // duration of note in beats
    
      static int sid;      // serial number id
      static int bar;      // current measure number
      static int key;      // used to print key signatures
      static int tstop;    // time signature top
      static int tsbottom; // time signature bottom
      static double approxtempo;  // the approximate tempo marking

};

int PerfVizNote::sid         =  0;
int PerfVizNote::bar         =  0;
int PerfVizNote::key         = -1;
int PerfVizNote::tstop       =  0;
int PerfVizNote::tsbottom    =  0;
double PerfVizNote::approxtempo =  0;


// global variables:
const char *outlocation = NULL;
int   trackcount  = 0;           // number of tracks in MIDI file
int   track       = 0;           // track number starting at 0
int   offset      = 0;           // start-time offset in ticks
int   tempo       = 60;          // default tempo

// user interface variables
Options options;
int     storeCommentQ    =   1;    // used with -C option
int     storeTextQ       =   1;    // used with -T option
int     plusQ            =   0;    // used with --plus option
int     defaultvolume    =  64;    // used with -v option
double  tscaling         =   1.0;  // used with -s option
int     multitimbreQ     =   1;    // used with -c option
int     instrumentQ      =   1;    // used with -I option
int     fixedChannel     =   0;    // used with -c option
int     instrumentnumber =  -1;    // used with -f option
int     forcedQ          =   0;    // used with -f option
int     mine             =   0;    // used with -m option
int     shortenQ         =   0;    // used with -s option
int     shortenamount    =   0;    // used with -s option
int     plainQ           =   0;    // used with -p option
int     debugQ           =   0;    // used with --debug option
int     dynamicsQ        =   1;    // used with -D option
int     padQ             =   1;    // used with -P option
int     humanvolumeQ     =   5;    // used with --hv option
int     metricvolumeQ    =   5;    // used with --mv option
int     sforzando        =  20;    // used with --sf option
int     fixedvolumeQ     =   0;    // used with -v option
int     timeQ            =   0;    // used with --time option
int     autopanQ         =   0;    // used with --autopan option
int     tempospineQ      =   0;    // used with --ts option
int     perfvizQ         =   0;    // used with --pvm option
int     idynQ            =   0;    // used with -d option
double  idynoffset       =   0.0;  // used with -d option
int     timeinsecQ       =   0;    // used with --time-in-seconds option
int     norestQ          =   0;    // used with --no-rest option
int     stdoutQ          =   0;    // used with --stdout option
int     starttick        =   0;    // used with --no-rest option
int     bendQ            =   0;    // used with --bend option
int     metQ             =   0;    // used with --met option
int     timbresQ         =   0;    // used with --timbres option
Array<SigString> TimbreName;       // used with --timbres option
Array<int> TimbreValue;            // used with --timbres option
Array<int> TimbreVolume;           // used with --timbres option
Array<int> VolumeMapping;          // used with --timbres option
double  bendamt          = 200;    // used with --bend option
int     bendpcQ          =   0;    // used with --temperament option
double  bendbypc[12]     = {0};    // used with --temperament and --monotune
int     monotuneQ        =   0;    // used with --monotune option
double  rhysc            = 1.0;    // used with -r option
    // for example, use -r 4.0 to make written sixteenth notes
    // appear as if they were quarter notes in the MIDI file.

SSTREAM *PVIZ = NULL;    // for storing individual notes in PerfViz data file.
double   tickfactor = 960.0 / 1000.0;

// function declarations:
void      assignTracks(HumdrumFile& infile, Array<int>& trackchannel);
double    checkForTempo(HumdrumRecord& record);
void      checkOptions(Options& opts, int argc, char** argv);
void      example(void);
int       makeVLV(uchar *buffer, int number);
void      reviseInstrumentMidiNumbers(const char* string);
int       setMidiPlusVolume(const char* kernnote);
void      storeMetaText(MidiFile& mfile, int track, const char* string, 
                               int tick, int metaType = 1);
void      storeMidiData(HumdrumFile& infile, MidiFile& outfile);
void      storeInstrument(int ontick, MidiFile& mfile, HumdrumFile& infile, 
                             int line, int row, int pcQ);
void      usage(const char* command);
void      storeFreeNote(Array<Array<int> >& array,int ptrack,int midinote);
void      getDynamics(HumdrumFile& infile, Array<Array<char> >& dynamics,
                             int defaultdynamic);
void      getDynamicAssignments(HumdrumFile& infile, Array<int>& assignments);
void      getStaffValues(HumdrumFile& infile, int staffline, 
                             Array<Array<int> >& staffvalues);
void      getNewDynamics(Array<int>& currentdynamic, 
                             Array<int>& assignments, 
                             HumdrumFile& infile, int line, 
                             Array<Array<char> >& crescendos,
                             Array<Array<char> >& accentuation);
void      processCrescDecresc(HumdrumFile& infile, 
                              Array<Array<char> >& dynamics, 
                              Array<Array<char> >& crescendos);
void      interpolateDynamics(HumdrumFile& infile, Array<char>& dyn, 
                             Array<char>& cresc);
void      generateInterpolation(HumdrumFile& infile, Array<char>& dyn, 
                             Array<char>& cresc, int startline, int stopline, 
                             int direction);
int       findtermination(Array<char>& dyn, Array<char>& cresc, int start);
char      adjustVolumeHuman(int startvol, int delta);
char      adjustVolumeMetric(int startvol, int delta, double metricpos);
char      applyAccentuation(int dynamic, int accent);
int       getMillisecondTime(HumdrumFile& infile, int line);
int       getFileDurationInMilliseconds(HumdrumFile& infile);
int       getMillisecondDuration(HumdrumFile& infile, int row, int col, 
                             int subcol);
void      addTempoTrack(HumdrumFile& infile, MidiFile& outfile);
void      getBendByPcData(double* bendbypc, const char* filename);
void      insertBendData(MidiFile& outfile, double* bendbypc);
void      getKernTracks(Array<int>& tracks, HumdrumFile& infile);
void      getTitle(Array<char>& title, HumdrumFile& infile);
void      addMonoTemperamentAdjustment(MidiFile& outfile, int track, 
                              int channel, int ticktime, int midinote, 
                              double* bendbypc);

// PerfViz related functions:
void      writePerfVizMatchFile(const char* filename, SSTREAM& contents);
ostream& operator<<            (ostream& out, PerfVizNote& note);
void     printPerfVizKey(int key);
void     printPerfVizTimeSig(int tstop, int tsbottom);
void     printPerfVizTempo(double approxtempo);
void     printRational(ostream& out, double value);
void     storePan(int ontime, MidiFile& outfile, 
                                HumdrumFile& infile, int row, int column);
void     adjustEventTimes(MidiFile& outfile, int starttick);
void     checkForBend(MidiFile& outfile, int notetick, int channel, 
                                HumdrumFile& infile, int row, int col, 
                                double scalefactor);
void     storeTimbres(Array<SigString>& name, Array<int>& value, 
                                Array<int>& volumes, const char* string);
void     autoPan(MidiFile& outfile, HumdrumFile& infile);

Array<int> tracknamed;      // for storing boolean if track is named
Array<int> trackchannel;    // channel of each track

#define TICKSPERQUARTERNOTE 120
int tpq = TICKSPERQUARTERNOTE;

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

int main(int argc, char* argv[]) {
   VolumeMapping.setSize(0);

   // for humanizing processes 
   #ifndef VISUAL
      srand48(time(NULL));
   # else 
      srand(time(NULL));
   #endif

   SSTREAM *perfviz = NULL;

   HumdrumFile infile;
   MidiFile    outfile;

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

   if (perfvizQ) {
      tpq   = 480;
      tempo = 120;
   }
   outfile.setTicksPerQuarterNote(tpq);

   if (timeQ) {
      outfile.setMillisecondDelta();
   }

   // figure out the number of input files to process
   // only the first argument will be processed.  If there are
   // no arguments, then standard input will be used.
   int numinputs = options.getArgCount();

   for (int i=0; i<1 || i==0; i++) {
      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
      infile.analyzeRhythm("4", debugQ);

      if (perfvizQ) {
         perfviz = new SSTREAM[1];
	 const char *filename = NULL;
	 PVIZ = perfviz;
         perfviz[0] << "info(matchFileVersion,2.0).\n";
         if (numinputs < 1) {
            perfviz[0] << "info(scoreFileName,'STDIN').\n";
         } else {
            perfviz[0] << "info(scoreFileName,'";
	    filename = strrchr(options.getArg(i+1), '/');
            if (filename == NULL) {
               filename = options.getArg(i+1);
            } else {
               filename = filename + 1;
            }
            perfviz[0] << filename;
            perfviz[0] << "').\n";
         }
         if (options.getBoolean("output")) {
            perfviz[0] << "info(midiFileName,'";
	    filename = strrchr(options.getString("output"), '/');
            if (filename == NULL) {
               filename = options.getString("output");
            } else {
               filename = filename + 1;
            }
            perfviz[0] << filename;
            perfviz[0] << "').\n";
         } else {
            perfviz[0] << "info(midifileName,'STDOUT').\n";
         }
         perfviz[0] << "info(midiClockUnits,";
         perfviz[0] << tpq << ").\n";
         perfviz[0] << "info(midiClockRate,500000).\n";
      }

      trackcount = infile.getMaxTracks();
      tracknamed.setSize(trackcount + 1);
      trackchannel.setSize(trackcount + 1);
      for (int j=0; j<trackcount+1; j++) {
         tracknamed[j]   = 0;
         trackchannel[j] = fixedChannel;
      }
      if (multitimbreQ) {
         assignTracks(infile, trackchannel);
      }
      outfile.addTrack(trackcount);
      outfile.absoluteTime();

      /*      // removed this code because of all of the lousy free MIDI 
      // sequencing programs that choke on system exclusive messages.

      // store the "General MIDI activation" system exclusive:
      // don't bother if this is just a Type 0 MIDI file
      if (!options.getBoolean("type0")) {
         Array<uchar> gmsysex;
         gmsysex.setSize(6);
         gmsysex[0] = 0xf0;     // Start of SysEx
         gmsysex[1] = 0x7e;     // Universal (reserved) ID number 
         gmsysex[2] = 0x7f;     // Device ID (general transmission)
         gmsysex[3] = 0x09;     // Means 'This is a message about General MIDI' 
         gmsysex[4] = 0x01;     // Means 'Turn General MIDI On'. 02 means 'Off' 
         gmsysex[5] = 0xf7;     // End of SysEx
         outfile.addEvent(0, 0, gmsysex);
      }
      */ 

      if (bendpcQ) {
         insertBendData(outfile, bendbypc);
      } 
      storeMidiData(infile, outfile);

      if (tempospineQ) {
         addTempoTrack(infile, outfile);
      }

      outfile.sortTracks();
      if (norestQ) {
         adjustEventTimes(outfile, starttick);
      }
      if (stdoutQ) {
         outfile.write(cout);
      } else if (outlocation == NULL) {
         cout << outfile;
      } else {
         outfile.write(outlocation);
      }

      if (perfvizQ) {
         // currently you cannot create multiple PerfViz files from
         // multiple inputs.
         writePerfVizMatchFile(options.getString("perfviz"), perfviz[0]);
         delete [] perfviz;
      }
   }

   return 0;
}


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


//////////////////////////////
//
// insertBendData -- store pitch bend data, one channel per
//      pitch class.  Channel 10 is skipped because it is used
//      as a drum channel on General MIDI synthesizers.
//

void insertBendData(MidiFile& outfile, double* bendbypc) {
   int i, j;
   int channel;
   // int track = 0;
   double bendvalue;
   if (outfile.getNumTracks() > 1) {
      // don't store in first track if a multi-track file.
      // track = 1;
   }
   Array<uchar> mididata;
   mididata.setSize(2);
   if (instrumentnumber < 0) {
      mididata[1] = 0;
   } else {
      mididata[1] = instrumentnumber;
   }
   for (i=0; i<12; i++) {
      channel = i;
      if (channel >= 9) {
         channel++;
      }
      bendvalue = bendbypc[i];
      // outfile.addPitchBend(track, offset, channel, bendvalue);
      if (outfile.getNumTracks() == 0) {
         outfile.addPitchBend(0, offset, channel, bendvalue);
      } else {
         for (j=1; j<outfile.getNumTracks(); j++) {
            outfile.addPitchBend(j, offset, channel, bendvalue);
         }
      }
      if (forcedQ) {
         mididata[0] = 0xc0 | (0x0f & channel);
         // outfile.addEvent(track, offset, mididata);
         if (outfile.getNumTracks() == 0) {
            outfile.addEvent(0, offset, mididata);
         } else {
            for (j=1; j<outfile.getNumTracks(); j++) {
               outfile.addEvent(j, offset, mididata);
            }
         }
      }
   }
}



//////////////////////////////
//
// addMonoTemperamentAdjustment -- store a pitch bend message just before
//    an note is tured on in a
//

void addMonoTemperamentAdjustment(MidiFile& outfile, int track, int channel, 
   int ticktime, int midinote, double* bendbypc) {
   double bendvalue = bendbypc[midinote % 12];
   outfile.addPitchBend(track, ticktime, channel, bendvalue);
}



//////////////////////////////
//
// adjustEventTimes -- set the first event time to starttick and
//    also subtrace that value from all events in the MIDI file;
//

void adjustEventTimes(MidiFile& outfile, int starttick) {
   int i, j;
   MFEvent* eventptr;
   int atime;
   int minval = 1000000000;
   for (i=0; i<outfile.getTrackCount(); i++) {
      for (j=0; j<outfile.getNumEvents(i); j++) {
	 eventptr = &outfile.getEvent(i, j);
         if (eventptr->data.getSize() <= 0) {
            continue;
         }
         if ((eventptr->data[0] & 0xf0) == 0x90) {
            if (eventptr->time < minval) {
               minval = eventptr->time;
            }
            break;
         } 
      }
   }

   if (minval > 1000000) {
     // minimum time is ridiculously large, so don't do anything
     return;
   }

   for (i=0; i<outfile.getTrackCount(); i++) {
      for (j=0; j<outfile.getNumEvents(i); j++) {
         eventptr = &outfile.getEvent(i, j);
         atime = eventptr->time;
         atime = atime - minval;
         if (atime < 0) {
            atime = 0;
         }
         eventptr->time = atime;
      }
   }
}



//////////////////////////////
//
// addTempoTrack -- 
//

void addTempoTrack(HumdrumFile& infile, MidiFile& outfile) {
   int i, j;
   double tempo;
   double absbeat;
   int ontick;
   int ttempo;
   Array<uchar> mididata;

   // should erase previous contents of tempo track first...

   for (i=0; i<infile.getNumLines(); i++) {
      if (infile[i].getType() == E_humrec_data) {
         for (j=0; j<infile[i].getFieldCount(); j++) {
            if (strcmp(infile[i].getExInterp(j), "**tempo") == 0) {
               if (isdigit(infile[i][j][0])) {
                  tempo = strtod(infile[i][j], NULL);
                  //cout << "The tempo value read was " << tempo << endl;
                  tempo = tempo * tscaling;
                  mididata.setSize(6);
                  mididata[0] = 0xff;
                  mididata[1] = 0x51;
                  mididata[2] = 3;
                  ttempo = (int)(60000000.0 / tempo + 0.5);   
                  mididata[3] = (uchar)((ttempo >> 16) & 0xff);
                  mididata[4] = (uchar)((ttempo >> 8) & 0xff);
                  mididata[5] = (uchar)(ttempo & 0xff);
                  absbeat = infile.getAbsBeat(i);
                  ontick = int(absbeat * outfile.getTicksPerQuarterNote());
                  outfile.addEvent(0, ontick + offset, mididata);
               }
            }
         }
      }
   }

}



//////////////////////////////
//
// assignTracks -- give a track number for each **kern spine in the input file
//    trying to place the same instruments into the same channels if
//    there are not enough channels to go around.
//

void assignTracks(HumdrumFile& infile, Array& trackchannel) {
   int i, j;


   Array<int> instruments;        // MIDI instruments indicated in track
   trackchannel.setSize(infile.getMaxTracks() + 1);
   instruments.setSize(trackchannel.getSize());
   for (i=0; i<instruments.getSize(); i++) {
      if (forcedQ) {
         instruments[i] = instrumentnumber;
      } else {
         instruments[i] = -1;
      }
      trackchannel[i] = 0;
   }
   HumdrumInstrument x;
   int inst = -1;
   int ptrack = 0;
   int instcount = 0;
   PerlRegularExpression pre2;

   VolumeMapping.setSize(infile.getMaxTracks()+1);
   VolumeMapping.setAll(64);
   if (idynQ) {
      VolumeMapping.setSize(0);
   }
   for (i=0; i<infile.getNumLines(); i++) {
      if (debugQ) {
         cout << "line " << i+1 << ":\t" << infile[i] << endl;
      }

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

      for (j=0; j<infile[i].getFieldCount(); j++) {
         track = infile[i].getPrimaryTrack(j);
         if (strncmp(infile[i][j], "*I", 2) == 0) {
            if (timbresQ) {

               PerlRegularExpression pre;
               if (!pre.search(infile[i][j], "^\\*I\"\\s*(.*)\\s*", "")) {
                  inst = -1;
               } else {
                  SigString targetname;
                  targetname = pre.getSubmatch(1);
                  int i;
                  inst = -1;

                  for (i=0; i<TimbreName.getSize(); i++) {
                     if (pre2.search(targetname.getBase(), 
                           TimbreName[i].getBase(), "i")) {
                     // if (TimbreName[i] == targetname) {
                        inst = TimbreValue[i];
                        if (track < VolumeMapping.getSize()) {
                           VolumeMapping[track] = TimbreVolume[i];
                        }
                        break;
                     }
                  }

                  if (inst == -1) {
                     // search for default timbre setting if not otherwise
                     // found.
                     for (i=0; i<TimbreName.getSize(); i++) {
                        if (pre2.search("DEFAULT", TimbreName[i].getBase(), 
                              "i")) {
                        // if (TimbreName[i] == targetname) {
                           inst = TimbreValue[i];
                           if (track < VolumeMapping.getSize()) {
                              VolumeMapping[track] = TimbreVolume[i];
                           }
                           break;
                        }
                     }
                  }

               }

            } else if (!forcedQ) {
               inst = x.getGM(infile[i][j]);
            } else {
               inst = instrumentnumber;
            }
 
            if (inst != -1) {
               ptrack = infile[i].getPrimaryTrack(j);
               if (instruments[ptrack] == -1) {
                  if (!forcedQ) {
                     instruments[ptrack] = inst;
                  } else {
                     instruments[ptrack] = instrumentnumber;
                  }
                  instcount++;
               } 
            }
         }
      }
   }

   int nextChannel = 0;        // next midi channel available to assign

   if (instcount < 15) {
      // have enough space to store each instrument on a separate channel
      // regardless of instrumentation
      nextChannel = 1;        // channel 0 is for undefined instrument spines
      for (i=0; i<instruments.getSize(); i++) {
         if (instruments[i] == -1) {
            trackchannel[i] = 0;
         } else {
            trackchannel[i] = nextChannel++;
         }

         // avoid General MIDI percussion track.
         if (nextChannel == 9) {
            nextChannel++;
         }
      }
   } else {
      // need to conserve channels:  place duplicate instrument tracks on the 
      // same channel.
      nextChannel = 1;     // channel 0 is for undefined instrument spines
      int foundDup = -1;
      for (i=0; i<instruments.getSize(); i++) {
         foundDup = -1;
         for (j=0; j<i; j++) {
            if (instruments[j] == instruments[i]) {
               foundDup = j; 
            }
         }
         if (instruments[i] == -1) {
            trackchannel[i] = 0;
         } else if (foundDup != -1) {
            trackchannel[i] = trackchannel[foundDup];
         } else {
            trackchannel[i] = nextChannel++;
         }

         // avoid General MIDI percussion track.
         if (nextChannel == 9) {
            nextChannel++;
         }
      }

      if (nextChannel > 16) {
         // channel allocation is over quota: squash over-allocations:
         for (i=0; i<trackchannel.getSize(); i++) {
            if (trackchannel[i] > 15) {
               trackchannel[i] = 0;
            }
         }
      }

   }

   Array<int> kerntracks;
   getKernTracks(kerntracks, infile);

   // don't conserve tracks if there is enough to go around
   if (kerntracks.getSize() < 13) { 
      for (i=0; i<kerntracks.getSize(); i++) {
         trackchannel[kerntracks[i]] = i;
         if (i>9) {   // avoid general midi drum track channel
            trackchannel[kerntracks[i]] = i+1;
         }
      }
   }

}



//////////////////////////////
//
// checkForTempo --
//

double checkForTempo(HumdrumRecord& record) {
   if (timeQ) {
      // don't encode tempos if the --time option is set.
      return -1.0;
   }
   int i;
   float tempo = 60.0;
   PerlRegularExpression pre;

   // if (!metQ) {

      for (i=0; i<record.getFieldCount(); i++) {
         if (strncmp(record[i], "*MM", 3) == 0) {
            sscanf(&(record[i][3]), "%f", &tempo);
            // cout << "Found tempo marking: " << record[i] << endl;
            return (double)tempo * tscaling;
         }
      }

   // } else {
   if (metQ) {

      // mensural tempo scalings
      // O           = 58 bpm
      // O.          = 58 bpm
      // C.          = 58 bpm
      // C           = 48 bpm
      // C|          = 72 bpm
      // O2          = 75 bpm
      // C2          = 75 bpm
      // O|          = 76 bpm
      // C|3, 3, 3/2 = 110 bpm
      // C3          = 110 bpm
      // O/3         = 110 bpm
      // C|2, Cr     = 220 bpm
      char mensuration[1024] = {0};
      if (record.isGlobalComment() && pre.search(record[0], 
            "^\\!+primary-mensuration:.*met\\((.*?)\\)\\s*$")) {
         strcpy(mensuration, pre.getSubmatch(1));
      } else if (record.isInterpretation() && record.equalFieldsQ("**kern")) {
         for (i=0; i<record.getFieldCount(); i++) {
            if (record.isExInterp(i, "**kern")) {
               if (pre.search(record[i], "met\\((.*?)\\)")) {
                  strcpy(mensuration, pre.getSubmatch(1));
               }
               break;
            }
         }    
      }

      if (strcmp(mensuration, "O") == 0) {
         return (double)metQ * 1.0;
      } else if (strcmp(mensuration, "C|") == 0) {
         return (double)metQ * 1.241793;
      } else if (strcmp(mensuration, "C.") == 0) {
         return (double)metQ * 1.0;
      } else if (strcmp(mensuration, "O.") == 0) {
         return (double)metQ * 1.0;
      } else if (strcmp(mensuration, "C") == 0) {
         return (double)metQ * 0.8;
      } else if (strcmp(mensuration, "O|") == 0) {
         return (double)metQ * 1.310448;
      } else if (strcmp(mensuration, "C|3") == 0) {
         return (double)metQ * 1.8965517;
      } else if (strcmp(mensuration, "C3") == 0) {
         return (double)metQ * 1.8965517;
      } else if (strcmp(mensuration, "3") == 0) {
         return (double)metQ * 1.8965517;
      } else if (strcmp(mensuration, "3/2") == 0) {
         return (double)metQ * 1.8965517;
      } else if (strcmp(mensuration, "O/3") == 0) {
         return (double)metQ * 1.8965517;
      } else if (strcmp(mensuration, "O2") == 0) {
         return (double)metQ * 1.25;
      } else if (strcmp(mensuration, "C2") == 0) {
         return (double)metQ * 1.25;
      } else if (strcmp(mensuration, "C|2") == 0) {
         return (double)metQ * 3.791034;
      } else if (strcmp(mensuration, "Cr") == 0) {
         return (double)metQ * 3.791034;
      }

   }

   return -1.0;
}



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

void checkOptions(Options& opts, int argc, char* argv[]) {
   opts.define("C|nocomments=b",  "Do not store comments as meta text");
   opts.define("D|nodynamics=b",  "Do not encode dynamics found in file");
   opts.define("showdynamics=b",  "Show the calculated dynamics by input line");
   opts.define("n|note|comment=s","Store a comment line in the file");
   opts.define("T|notext=b",      "Do not non-musical data as meta text");
   opts.define("o|output=s:midi.mid", "Output midifile");
   opts.define("0|O|type0|zero=b","Generate a type 0 MIDI file");
   opts.define("plus=b",          "Create a MIDIPlus compliant MIDI file");
   opts.define("time=b",          "Use timing from a **time spine");
   opts.define("sec|time-in-seconds=b", "Use timing from a **time spine");
   opts.define("v|volume=i:64",   "Default attack velocity");
   opts.define("d|dyn|idyn=d:40.0","Extract attack velocities from **idyn");
   opts.define("t|tempo-scaling=d:1.0", "Tempo scaling");
   opts.define("ts|tempo|tempo-spine=b", "Use tempo markings from tempo spine");
   opts.define("I|noinstrument=b", "Do not store MIDI instrument programs");
   opts.define("i|instruments=s", "Specify MIDI conversions for instruments");
   opts.define("f|forceinstrument=i:0", "MIDI instrument for all tracks");
   opts.define("c|channel=i:-1",   "Channel to store for MIDI data");
   opts.define("m|min=i:0",        "Minimum tick duration of notes");
   opts.define("r|rhythmic-scaling=d:1.0", "Scale all score durations");
   opts.define("s|shorten=i:0",    "Shortening tick value for note durations");
   opts.define("p|plain=b",        "Play with articulation interpretation");
   opts.define("P|nopad=b",        "Do not pad ending with spacer note");
   opts.define("met=d:232",        "Tempo control from metrical symbols");
   opts.define("hv|humanvolume=i:5","Apply a random adjustment to volumes");
   opts.define("mv|metricvolume=i:3","Apply metric accentuation to volumes");
   opts.define("fs|sforzando=i:20","Increase sforzandos by specified amount");
   opts.define("no-rest=b",      "Do not put rests at start of midi");
   opts.define("pvm|perfviz=s:", "Create a PerfViz match file for MIDI output");
   opts.define("debug=b",        "Debugging turned on");
   opts.define("stdout=b",       "Print MIDI file to standard output");
   opts.define("mark=b",         "Handle marked notes somehow");
   opts.define("bend=d:200.0",   "Turn on pitch-bend with given half-depth");
   opts.define("temperament|tune=s:", "Turn on pitch-bend with given data file");
   opts.define("monotune=s:", "Turn on pitch-bend tuning for monophonic tracks");
   opts.define("timbres=s",      "Timbral assignments by instrument name");
   opts.define("autopan=b",      "Pan tracks from left to right");

   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: April 2002" << 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("instruments")) {
      reviseInstrumentMidiNumbers(opts.getString("instruments"));
   }

   if (opts.getBoolean("nocomments")) {
      storeCommentQ = 0;
   } else {
      storeCommentQ = 1;
   }

   if (opts.getBoolean("met")) {
      metQ = int(opts.getDouble("met")+0.5);
   } else {
      metQ = 0;
   }

   storeTextQ = opts.getBoolean("notext");
   plusQ      = opts.getBoolean("plus");
   plainQ     = opts.getBoolean("plain");

   if (opts.getBoolean("nodynamics")) {
      dynamicsQ = 0;
   } else {
      dynamicsQ = 1;
      if (opts.getBoolean("showdynamics")) {
         dynamicsQ += 1;
      }
   }

   defaultvolume = opts.getInt("volume");
   if (defaultvolume < 1) {
      defaultvolume = 1;
   } else if (defaultvolume > 127) {
      defaultvolume = 127;
   }
   
   tscaling = opts.getDouble("tempo-scaling");
   if (tscaling < 0.001) {
      tscaling = 1.0;
   } else if (tscaling > 1000.0) {
      tscaling = 1.0; 
   }
   // tscaling = 1.0 / tscaling;
   instrumentQ = !opts.getBoolean("noinstrument");

   if (opts.getBoolean("channel")) {
      multitimbreQ = 0;
      fixedChannel = opts.getInteger("channel") - 1;
      if (fixedChannel < 0) {
         fixedChannel = 0;
      } 
      if (fixedChannel > 15) {
         fixedChannel = 15;
      } 
      instrumentQ = 0;
   } else {
      multitimbreQ  = 1;
      fixedChannel = -1;
   }

   if (opts.getBoolean("output")) {
      outlocation = opts.getString("output");
   } else {
      outlocation = NULL;
   }

   forcedQ = opts.getBoolean("forceinstrument");
   if (forcedQ) {
      instrumentnumber = opts.getInteger("forceinstrument");
      if (instrumentnumber < 0 || instrumentnumber > 127) {
         instrumentnumber = 0;
      } 
   } else {
      instrumentnumber = -1;
   }

   mine = opts.getInteger("min");
   if (mine < 0) {
      mine = 0;
   }
   debugQ        =  opts.getBoolean("debug");
   stdoutQ       =  opts.getBoolean("stdout");
   shortenQ      =  opts.getBoolean("shorten");
   shortenamount =  opts.getInteger("shorten");
   padQ          = !opts.getBoolean("nopad");
   humanvolumeQ  =  opts.getInteger("humanvolume");
   fixedvolumeQ  =  opts.getBoolean("volume");
   timeQ         =  opts.getBoolean("time");
   timeinsecQ    =  opts.getBoolean("time-in-seconds");
   tempospineQ   =  opts.getBoolean("tempo-spine");
   perfvizQ      =  opts.getBoolean("perfviz");
   metricvolumeQ =  opts.getInteger("metricvolume");
   sforzando     =  opts.getInteger("sforzando");
   idynQ         =  opts.getBoolean("dyn");
   idynoffset    =  opts.getDouble("dyn");
   norestQ       =  opts.getBoolean("no-rest");
   autopanQ      =  opts.getBoolean("autopan");
   bendQ         =  opts.getBoolean("bend");
   rhysc         = opts.getDouble("rhythmic-scaling");
   if (bendQ) {
      bendamt    =  opts.getDouble("bend");
      if (bendamt <= 0.0) {
         bendamt = 200.0;
      }
   }
   bendpcQ       =  opts.getBoolean("temperament");
   monotuneQ     =  opts.getBoolean("monotune");
   if (bendpcQ) {
      bendQ = 0;   // disable other type of bending (but keep bendamt)
      forcedQ = 1; // force a timber setting for all channels (piano default)
      getBendByPcData(bendbypc, opts.getString("temperament"));
      // for different method, see: http://www.xs4all.nl/~huygensf/scala
   } else if (monotuneQ) {
      bendQ = 0;
      getBendByPcData(bendbypc, opts.getString("monotune"));
   }

   timbresQ = opts.getBoolean("timbres");
   if (timbresQ) {
      storeTimbres(TimbreName, TimbreValue, TimbreVolume, 
            opts.getString("timbres"));
   } else {
      TimbreName.setSize(0);
      TimbreValue.setSize(0);
   }
}



//////////////////////////////
//
// getBendByPcData --
//

void getBendByPcData(double* bendbypc, const char* filename) {
   int i, j;
   for (i=0; i<12; i++) {
      bendbypc[i] = 0.0;
   }

   HumdrumFile temperamentdata;
   temperamentdata.read(filename);
   HumdrumFile& td = temperamentdata;

   int pc;
   double dcent;
   for (i=0; i<td.getNumLines(); i++) {
      if (td[i].getType() != E_humrec_data) {
         continue;
      }
      pc = -1;
      dcent = 0;
      for (j=0; j<td[i].getFieldCount(); j++) {
         if (strcmp(td[i].getExInterp(j), "**kern") == 0) {
            if (strcmp(td[i][j], ".") == 0) {
               // ignore null tokens
               continue;
            }
            if (strchr(td[i][j], 'r') != NULL) {
               // ignore rests
               continue;
            }
            pc = Convert::kernToMidiNoteNumber(td[i][j]) % 12;
         } else if (strcmp(td[i].getExInterp(j), "**Dcent") == 0) {
            if (strcmp(td[i][j], ".") == 0) {
               // determine value of null tokens
	       sscanf(td.getDotValue(i,j), "%lf", &dcent);
            } else {
	       sscanf(td[i][j], "%lf", &dcent);
            }
         }
      }
      if (pc >= 0 && pc < 12) {
         bendbypc[pc] = dcent / bendamt;
      }
   }
}



//////////////////////////////
//
// example --
//

void example(void) {

}



//////////////////////////////
//
// storeMetaText --
//

void storeMetaText(MidiFile& mfile, int track, const char* string, int tick,
      int metaType) {
   int i;
   int length = strlen(string);
   Array<uchar> metadata;
   uchar size[23] = {0};
   int lengthsize =  makeVLV(size, length);

   metadata.setSize(2+lengthsize+length);
   metadata[0] = 0xff;
   metadata[1] = metaType;
   for (i=0; i<lengthsize; i++) {
      metadata[2+i] = size[i];
   }
   for (i=0; i<length; i++) {
      metadata[2+lengthsize+i] = string[i];
   }

   mfile.addEvent(track, tick + offset, metadata);
}



//////////////////////////////
//
// makeVLV -- 
//

int makeVLV(uchar *buffer, int number) {

   unsigned long value = (unsigned long)number;

   if (value >= (1 << 28)) {
      cout << "Error: number too large to handle" << endl; 
      exit(1);
   }
  
   buffer[0] = (value >> 21) & 0x7f;
   buffer[1] = (value >> 14) & 0x7f;
   buffer[2] = (value >>  7) & 0x7f;
   buffer[3] = (value >>  0) & 0x7f;

   int i;
   int flag = 0;
   int length = -1;
   for (i=0; i<3; i++) {
      if (buffer[i] != 0) {
         flag = 1;
      }
      if (flag) {
         buffer[i] |= 0x80;
      }
      if (length == -1 && buffer[i] >= 0x80) {
         length = 4-i;
      }
   }

   if (length == -1) {
      length = 1;
   }

   if (length < 4) {
      for (i=0; i<length; i++) {
         buffer[i] = buffer[4-length+i];
      }
   }

   return length;
}



//////////////////////////////
//
// getIdynDynamics -- extracts **idyn amplitude values for notes.
//

void getIdynDynamics(HumdrumFile& infile, Array<Array<char> >& dynamics, 
      double idynoffset) {
   int i, j, k;
   int intdyn;
   char cdyn;
   double dyn;
   int notecount;
   char buffer[1024] = {0};
   dynamics.setSize(infile.getNumLines());
   for (i=0; i<dynamics.getSize(); i++) {
      dynamics[i].setSize(8);
      dynamics[i].setSize(0);
   }

   for (i=0; i<infile.getNumLines(); i++) {
      if (infile[i].getType() != E_humrec_data) {
         continue;
      }
      for (j=0; j<infile[i].getFieldCount(); j++) {
         if (strcmp(infile[i].getExInterp(j), "**idyn") != 0)  {
            continue;
         }
         if (strcmp(infile[i][j], ".") == 0) {
            continue;
         }

         notecount = infile[i].getTokenCount(j);
         for (k=0; k<notecount; k++) {
            dyn = 0.0;
            infile[i].getToken(buffer, j, k);
            if (sscanf(buffer, "%lf", &dyn)) {
               dyn += idynoffset;
               // dyn *= 2.0;
            }
            intdyn = int(dyn + 0.5);
            if (intdyn < 1) {
               intdyn = 1;
            }
            if (intdyn > 127) {
               intdyn = 127;
            }
	    cdyn = char(intdyn);
            dynamics[i].append(cdyn);
         }
      }
   }


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

}
            


//////////////////////////////
//
// storeMidiData --
//

void storeMidiData(HumdrumFile& infile, MidiFile& outfile) {
   double duration = 0.0;
   int midinote = 0;
   int base40note = 0;
   double absbeat = 0.0;
   int ontick = 0;
   int idyncounter = 0;   // used for individual note volumes
   int offtick = 0;
   Array<uchar> mididata;
   int i, j, k;
   int ii;
   int tokencount = 0;
   char buffer1[1024] = {0};
   double pvscoredur = 0.0;
   int staccatoQ = 0;
   int accentQ = 0;
   int sforzandoQ = 0;
   int volume = defaultvolume;
   int ttempo;
   Array<int> freeQ;
   freeQ.setSize(infile.getMaxTracks());
   freeQ.setAll(0);
   Array<Array<int> > freenotestate;
   freenotestate.setSize(freeQ.getSize());
   for (i=0; i<freenotestate.getSize(); i++) {
      freenotestate[i].setSize(0);
      freenotestate[i].allowGrowth();
   }
   int ptrack = 0;

   Array<Array<char> > dynamics;
   if (idynQ) {
      getIdynDynamics(infile, dynamics, idynoffset);
   } else {
      getDynamics(infile, dynamics, defaultvolume);
   }

   // store a default tempo marking if the tempo scaling option 
   // was used.
   if (tscaling != 1.0) {
      ttempo = (int)(100 * tscaling);
      if (ttempo > 0) {
         mididata.setSize(6);
         mididata[0] = 0xff;
         mididata[1] = 0x51;
         mididata[2] = 3;
         ttempo = (int)(60000000.0 / ttempo + 0.5);   
         mididata[3] = (uchar)((ttempo >> 16) & 0xff);
         mididata[4] = (uchar)((ttempo >> 8) & 0xff);
         mididata[5] = (uchar)(ttempo & 0xff);
         outfile.addEvent(0, 0 + offset, mididata);
      }
   }

   if (options.getBoolean("comment")) {
      storeMetaText(outfile, 0, options.getString("comment"), 0);
   }

   if (perfvizQ) {
      // set the tempo to MM 120.0 at tick time 0.
      mididata.setSize(6);
      mididata[0] = 0xff;
      mididata[1] = 0x51;
      mididata[2] = 3;
      ttempo = (int)(60000000.0 / 120.0 + 0.5);   
      mididata[3] = (uchar)((ttempo >> 16) & 0xff);
      mididata[4] = (uchar)((ttempo >> 8) & 0xff);
      mididata[5] = (uchar)(ttempo & 0xff);
      outfile.addEvent(0, 0, mididata);
   }

   if (autopanQ) {
      autoPan(outfile, infile);
   }

   if (storeTextQ) {
      // store the title
      Array<char> title;
      title.setSize(0);
      getTitle(title, infile);
      if (title.getSize() > 0) {
         storeMetaText(outfile, 0, title.getBase(), 0, 3);
      }
   }

   for (i=0; i<infile.getNumLines(); i++) {
      if (debugQ) {
         cout << "Line " << i+1 << "::\t" << infile[i] << endl;
      }

      if (storeCommentQ && (infile[i].getType() == E_humrec_global_comment)) {
         if (timeQ || perfvizQ) {
            ontick = getMillisecondTime(infile, i);
            if (perfvizQ) {
               ontick = int(ontick * tickfactor + 0.5);
            }
         } else {
            absbeat = infile.getAbsBeat(i);
            ontick = int(absbeat * outfile.getTicksPerQuarterNote());
         }
         storeMetaText(outfile, 0, infile[i].getLine(), ontick + offset);
      } else if (storeTextQ && (infile[i].getType() == E_humrec_bibliography)) {
         if (timeQ || perfvizQ) {
            ontick = getMillisecondTime(infile, i);
            if (perfvizQ) {
               ontick = int(ontick * tickfactor + 0.5);
            }
         } else {
            absbeat = infile.getAbsBeat(i);
            ontick = int(absbeat * outfile.getTicksPerQuarterNote());
         }
         if (strncmp(&(infile[i].getLine()[3]), "YEC", 3) == 0) {
            storeMetaText(outfile, 0, infile[i].getLine(), ontick+offset, 2);
         // store OTL as regular text, creating sequence name separately
         //} else if (strncmp(&(infile[i].getLine()[3]), "OTL", 3) == 0) {
         //   storeMetaText(outfile, 0, infile[i].getLine(), ontick+offset, 3);
         } else if (storeCommentQ) {
            storeMetaText(outfile, 0, infile[i].getLine(), ontick + offset);
         }
      } 
      if (infile[i].isInterpretation() || infile[i].isGlobalComment()) {

         tempo = (int)checkForTempo(infile[i]);
         if (tempo > 0 && !tempospineQ && !perfvizQ) {
            // cout << "The tempo read was " <<  tempo << endl;
            ttempo = tempo;  // scaling already applied.
            mididata.setSize(6);
            mididata[0] = 0xff;
            mididata[1] = 0x51;
            mididata[2] = 3;
            ttempo = (int)(60000000.0 / ttempo + 0.5);   
            mididata[3] = (uchar)((ttempo >> 16) & 0xff);
            mididata[4] = (uchar)((ttempo >> 8) & 0xff);
            mididata[5] = (uchar)(ttempo & 0xff);
            if (timeQ || perfvizQ) {
               ontick = getMillisecondTime(infile, i);
               if (perfvizQ) {
                  ontick = int(ontick * tickfactor + 0.5);
               }
            } else {
               absbeat = infile.getAbsBeat(i);
               ontick = int(absbeat * outfile.getTicksPerQuarterNote());
            }
            outfile.addEvent(0, ontick + offset, mididata);
            // outfile.addEvent(0, 10 + offset, mididata);
            tempo = -1;
         }


         for (j=0; j<infile[i].getFieldCount(); j++) {
            if (timeQ || perfvizQ) {
               ontick = getMillisecondTime(infile, i);
               if (perfvizQ) {
                  ontick = int(ontick * tickfactor + 0.5);
               }
            } else {
               absbeat = infile.getAbsBeat(i);
               ontick = int(absbeat * outfile.getTicksPerQuarterNote());
            }
            int track = infile[i].getPrimaryTrack(j);

            if (strcmp(infile[i][j], "**kern") == 0 && forcedQ && !bendpcQ) {
               Array<uchar> mididata;
               mididata.setSize(2);
               mididata[0] = 0xc0 | (0x0f & trackchannel[track]);
               mididata[1] = instrumentnumber;
               outfile.addEvent(track, ontick + offset, mididata);
               continue;
            }

            if (strncmp(infile[i][j], "*I", 2) == 0) {
               storeInstrument(ontick + offset, outfile, infile, i, j, 
                     instrumentQ);
               continue;
            } 

            if ((!autopanQ) && (strncmp(infile[i][j], "*pan=", 5) == 0)) {
               storePan(ontick + offset, outfile, infile, i, j);
               continue;
            } 
	    
	    // capture info data for PerfViz:
            int length = strlen(infile[i][j]);
            int key;
            if (infile[i][j][length-1] == ':') {
               key = Convert::kernToBase40(infile[i][j]);
               if (key != PerfVizNote::key) {
                  printPerfVizKey(key);
                  PerfVizNote::key = key;
               }
            }
            int tstop;
            int tsbottom;
            if (sscanf(infile[i][j], "*M%d/%d", &tstop, &tsbottom) == 2) {
               if (tstop != PerfVizNote::tstop || 
                   tsbottom != PerfVizNote::tsbottom) {
                  printPerfVizTimeSig(tstop, tsbottom);
                  PerfVizNote::tstop = tstop;
                  PerfVizNote::tsbottom = tsbottom;
               }
            }
            double approxtempo;
            if (sscanf(infile[i][j], "*MM%lf", &approxtempo) == 1) {
               if (approxtempo != PerfVizNote::approxtempo) {
                  printPerfVizTempo(approxtempo);
                  PerfVizNote::approxtempo = approxtempo;
               }
            }
            // info fields to be addressed in the future:
            // info(beatSubdivisions,[3]).
            // info(tempoIndication,[andante]).
            // info(subtitle,[]).

            
            if (strcmp(infile[i][j], "*free") == 0) {
               freeQ[infile[i].getPrimaryTrack(j)-1] = 1;
            } else if (strcmp(infile[i][j], "*strict") == 0) {
               freeQ[infile[i].getPrimaryTrack(j)-1] = 0;

               // turn off any previous notes in the track
               // started during a free rhythm section
               ptrack = infile[i].getPrimaryTrack(j) - 1;
               for (ii=0; ii<freenotestate[ptrack].getSize(); ii++) {
                  // turn off the note if it is zero or above
                  if (freenotestate[ptrack][ii] >= 0) {
                     mididata.setSize(3);
                     if (bendpcQ) {
                        int pcchan = freenotestate[ptrack][ii] % 12;
                        if (pcchan >= 9) { 
                           pcchan++;
                        }
                        mididata[0] = 0x80 | pcchan;
                     } else {
                        mididata[0] = 0x80 | trackchannel[track];
                     }
                     mididata[1] = (uchar)freenotestate[ptrack][ii];
                     mididata[2] = 0;
                     outfile.addEvent(track, ontick + offset + 1, mididata);
                     // added 1 to the previous line for grace notes 7Apr2004
                     freenotestate[ptrack][ii] = -1;
                     if (ii == freenotestate[ptrack].getSize() - 1) {
                        // shrink-wrap the free note off array
                        freenotestate[ptrack].setSize(ii);
                        break;
                     }
                  }
               }
            }

         }
      } else if (infile[i].getType() == E_humrec_data_measure) {
         PerfVizNote::bar += 1;
      } else if (infile[i].getType() == E_humrec_data) {
         idyncounter = 0;
         for (j=0; j<infile[i].getFieldCount(); j++) {
            if (strcmp(infile[i][j], ".") == 0) {
               continue;
            }
            if (!infile[i].isExInterp(j, "**kern")) {
               continue;
            }

            if (idynQ) {
              if (dynamics[i].getSize() > idyncounter) {
                 volume = dynamics[i][idyncounter]; 
              } else {
                //  cout << "Warning: bad volume data on line " << i+1 << endl;
		//  cout << "Size of dynamics array is: " 
                //       << dynamics[i].getSize() << endl;
                //  cout << "Note counter on line: " << idyncounter << endl;
                //  volume = 64;
              }
            } else {
               volume = dynamics[infile[i].getPrimaryTrack(j)-1][i];
            }

            if (strcmp(infile[i][j], ".") == 0) {
               continue;
            }

            if (infile[i].getExInterpNum(j) != E_KERN_EXINT) {
               if (timeQ || perfvizQ) {
                  ontick = getMillisecondTime(infile, i);
                  if (perfvizQ) {
                     ontick = int(ontick * tickfactor + 0.5);
                  }
               } else {
                  absbeat = infile.getAbsBeat(i);
                  ontick = int(absbeat * outfile.getTicksPerQuarterNote());
               }
               track = infile[i].getPrimaryTrack(j);
               if (storeTextQ) {
                  storeMetaText(outfile, track, infile[i][j], ontick+offset);
               }
               continue;
            } else {
               // process **kern note events

               track      = infile[i].getPrimaryTrack(j);
               tokencount = infile[i].getTokenCount(j);
               ptrack     = infile[i].getPrimaryTrack(j) - 1;

               if (freeQ[ptrack] != 0) {
                  // turn off any previous notes in the track
                  // during a free rhythm section
                  for (ii=0; ii<freenotestate[ptrack].getSize(); ii++) {
                     // turn off the note if it is zero or above
                     if (freenotestate[ptrack][ii] >= 0) {
                        mididata.setSize(3);
                        if (bendpcQ) {
                           int pcchan = freenotestate[ptrack][ii] % 12;
                           if (pcchan >= 9) { 
                              pcchan++;
                           }
                           mididata[0] = 0x90 | pcchan;
                        } else {
                           mididata[0] = 0x80 | trackchannel[track];
                        }
                        mididata[1] = (uchar)freenotestate[ptrack][ii];
                        mididata[2] = 0;
                        outfile.addEvent(track, ontick + offset, mididata);
                        freenotestate[ptrack][ii] = -1;
                        if (ii == freenotestate[ptrack].getSize() - 1) {
                           // shrink-wrap the free note off array
                           freenotestate[ptrack].setSize(ii);
                           break;
                        }
                     }
                  }
               }
               // This code is disabling dynamics, should be fixed.
               // [20130424]
               // if (VolumeMapping.getSize() > 0) {
               //    volume = VolumeMapping[infile[i].getPrimaryTrack(j)];
               // }
               for (k=0; k<tokencount; k++) {
                  infile[i].getToken(buffer1, j, k); 

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

                  if (timeQ) { 
                     duration = getMillisecondDuration(infile, i, j, k);
                  } else if (perfvizQ) {
                     duration = getMillisecondDuration(infile, i, j, k);
                     // perfviz duration shortened later

                     // store the score duration of the note for perfviz
                     if (strchr(buffer1, '[')) {
                        // total tied note durations
                        pvscoredur = infile.getTiedDuration(i, j, k);
                     } else {
                        pvscoredur = rhysc * Convert::kernToDuration(buffer1);
                     }
                  } else {
                     if (strchr(buffer1, '[')) {
                        // total tied note durations
                        duration = infile.getTiedDuration(i, j, k);
                     } else {
                        duration = rhysc * Convert::kernToDuration(buffer1);
                     }
                  }
                  midinote = Convert::kernToMidiNoteNumber(buffer1); 
                  base40note = Convert::kernToBase40(buffer1);
                  // skip rests 
                  if (midinote < 0) {
                     continue;
                  }

                  if (!plainQ) {
                     accentQ    = strchr(buffer1, '^')  == NULL ? 0 : 1;
                     sforzandoQ = strchr(buffer1, 'z')  == NULL ? 0 : 1;
                     staccatoQ  = strchr(buffer1, '\'') == NULL ? 0 : 1;
                     // treat attacas/staccatissimos as staccatos
                     staccatoQ |= strchr(buffer1, '`') == NULL ? 0 : 1;
                     // treat pizzicatos as staccatos
                     staccatoQ |= strchr(buffer1, '"') == NULL ? 0 : 1;
                     // treat spiccatos as staccatos (maybe shorten later?)
                     staccatoQ |= strchr(buffer1, 's') == NULL ? 0 : 1;
                  }
   
                  if (shortenQ) {
                     duration -= shortenamount;
                     if (duration < mine) {
                        duration = mine;
                     }
                  }

                  if (staccatoQ) {
                     duration = duration * 0.5;
                  }
                  if (accentQ) {
                     volume = (int)(volume * 1.3 + 0.5);
                  }
                  if (sforzandoQ) {
                     volume = (int)(volume * 1.5 + 0.5);
                  }
                  if (volume > 127) {
                     volume = 127;
                  }
                  if (volume < 1) {
                     volume = 1;
                  }
   
                  if (plusQ) {
                     volume = setMidiPlusVolume(buffer1);
                  }
                  if (timeQ) {
                     ontick  = getMillisecondTime(infile, i);
                     offtick = (int)(ontick + duration + 0.5);
                  } else if (perfvizQ) {
                     ontick  = int(getMillisecondTime(infile,i)*tickfactor+0.5);
                     offtick = int(getMillisecondTime(infile,i)*tickfactor + 
                                duration*tickfactor + 0.5);
                  } else {
                     absbeat = infile.getAbsBeat(i);
                     ontick  = int(absbeat * outfile.getTicksPerQuarterNote());
                     offtick = int(duration * 
                           outfile.getTicksPerQuarterNote()) + ontick;
                  }
                  if (shortenQ) {
                     offtick -= shortenamount;
                     if (offtick - ontick < mine) {
                        offtick = ontick + mine;
                     }
                  }

                  // fix for grace note noteoffs (7 Sep 2004):
                  if (timeQ) {
                     if (offtick <= ontick) {
                        offtick = ontick + 100;
                     }
                  } else if (perfvizQ) {
                     if (offtick <= ontick) {
                        offtick = int(ontick + 100 * tickfactor + 0.5);
                     }
                  } else {
                     if (offtick <= ontick) {
                        offtick = ontick + (int)(infile[i].getDuration() 
                                   * outfile.getTicksPerQuarterNote()+0.5);
                     }
                     // in case the duration of the line is 0:
                     if (offtick <= ontick) {
                        offtick = ontick + 12; 
                     }
                  }

                  if (volume < 0) {
                     volume = 1;
                  }
                  if (volume > 127) {
                     volume = 127;
                  }
                  mididata.setSize(3);
                  if (bendpcQ) {
                     int pcchan = midinote % 12;
                     if (pcchan >= 9) { 
                        pcchan++;
                     }
                     mididata[0] = 0x90 | pcchan;
                  } else {
                     mididata[0] = 0x90 | trackchannel[track];
                  }
                  mididata[1] = midinote;
                  mididata[2] = volume;
                  if (fixedvolumeQ) {
                     mididata[2] = defaultvolume;
                  }
                  outfile.addEvent(track, ontick + offset, mididata);
                  if (bendQ) {
                     checkForBend(outfile, ontick + offset, trackchannel[track],
                           infile, i, j, bendamt);
                  }
                  idyncounter++;
                  if (perfvizQ && PVIZ != NULL) {
                     PerfVizNote pvnote;
                     pvnote.pitch	= base40note;
                     pvnote.scoredur    = pvscoredur;
                     pvnote.absbeat     = infile[i].getAbsBeat();
                     pvnote.beat	= int(infile[i].getBeat());
                     pvnote.beatfrac    = infile[i].getBeat() - pvnote.beat;
                     pvnote.beatdur     = pvscoredur;
                     pvnote.vel		= volume;
                     pvnote.ontick      = ontick;
                     pvnote.offtick     = offtick;
                     PVIZ[0] << pvnote;
                  }
                  if (freeQ[ptrack] == 0) {
                     // don't specify the note off duration when rhythm is free.
                     if (bendpcQ) {
                        int pcchan = midinote % 12;
                        if (pcchan >= 9) { 
                           pcchan++;
                        }
                        mididata[0] = 0x80 | pcchan;
                     } else {
                        mididata[0] = 0x80 | trackchannel[track];
                     }
                     if (monotuneQ) {
                        addMonoTemperamentAdjustment(outfile, track, 
                              trackchannel[track], offtick+offset, 
                              midinote, bendbypc);
                     }
                     outfile.addEvent(track, offtick + offset, mididata);
                  } else {
                     // store the notes to be turned off later
                     storeFreeNote(freenotestate, ptrack, midinote);
                  }
               }
            }
         }
      }
   }

   // now add the end-of-track message to all tracks so that they
   // end at the same time.

   mididata.setSize(3);
   
   if (timeQ) {
      ontick = getFileDurationInMilliseconds(infile);
      ontick += 1000;
   } else if (perfvizQ) {
      ontick = getFileDurationInMilliseconds(infile);
      ontick += 1000;
      ontick = int(ontick * tickfactor + 0.5);
   } else {
      absbeat = infile.getAbsBeat(infile.getNumLines()-1);
      ontick = int(absbeat * outfile.getTicksPerQuarterNote());
      // note that extra time is added so that the last note will not get 
      // cut off by the MIDI player.  
      ontick += 120;
   }

   // stupid Microsoft Media player (et al.) will still cut off early, 
   // so add a dummy note-off at end as well...

   if (options.getBoolean("type0")) {
      outfile.joinTracks();
      if (padQ) {
         mididata[0] = 0x90;
         mididata[1] = 0x00;
         mididata[2] = 0x00;
         outfile.addEvent(0, ontick-1+offset, mididata);
      }
      mididata[0] = 0xff;
      mididata[1] = 0x2f;
      mididata[2] = 0x00;
      outfile.addEvent(0, ontick+offset, mididata);
   } else {  // type 1 MIDI file
      int trackcount = outfile.getNumTracks();
      for (i=0; i<trackcount; i++) {
         // Skip padding the first track, because Windows Media Player 
	 // won't play the MIDI file otherwise.
         if (i > 0 && padQ) {
            mididata[0] = 0x90;
            mididata[1] = 0x00;
            mididata[2] = 0x00;
            outfile.addEvent(i, ontick-1+offset, mididata);
         }
         mididata[0] = 0xff;
         mididata[1] = 0x2f;
         mididata[2] = 0x00;
         outfile.addEvent(i, ontick+offset, mididata);
      }
   }
}



//////////////////////////////
//
// getTitle -- return the title of the work.  If OPR is present, then
//    include that first, then OTL
//

void getTitle(Array& title, HumdrumFile& infile) {
   title.setSize(1000);
   title.setSize(0);
   int opr = -1;
   int otl = -1;
   int i;
   for (i=0; i<infile.getNumLines(); i++) {
      if (!infile[i].isBibliographic()) {
         continue;
      }
      if ((opr < 0) && (strncmp(infile[i][0], "!!!OPR", 6) == 0)) {
         opr = i;
      }
      if ((otl < 0) && (strncmp(infile[i][0], "!!!OTL", 6) == 0)) {
         otl = i;
      }
   }

   char bufferopr[512] = {0};
   char bufferotl[512] = {0};
   if (opr >= 0) {
      infile[opr].getBibValue(bufferopr);
   }
   if (otl >= 0) {
      infile[otl].getBibValue(bufferotl);
   }
   char buffer[1024] = {0};
   strcat(buffer, bufferopr);
   if (otl >= 0) {
      if (opr >= 0) {
         strcat(buffer, "  ");
      }
   }
   strcat(buffer, bufferotl);
   int len = strlen(buffer);
   if (len == 0) {
      return;
   }
   title.setSize(len+1);
   strcpy(title.getBase(), buffer);
}



//////////////////////////////
//
// checkForBend -- 
//

void checkForBend(MidiFile& outfile, int notetick, int channel, 
      HumdrumFile& infile, int row, int col, double scalefactor) {
   double bendvalue = 0;

   int i;
   for (i=col+1; i<infile[row].getFieldCount(); i++) {
      if (strcmp(infile[row].getExInterp(i), "**Dcent") != 0) {
         continue;
      }
      if (strcmp(infile[row][i], ".") == 0) {
         // consider in the future to resolve "." to previous value
         // in case the **Dcent value is specified before a note attack
         break;
      }
      if (sscanf(infile[row][i], "%lf", &bendvalue)) {
         bendvalue = bendvalue / scalefactor;
         // store one tick before the expected note location
         // but this can cause an audible pitch aberation...
	 //notetick = notetick - 1;
	 //if (notetick < 0) {
         //   notetick = 0;
         // }
         outfile.addPitchBend(track, notetick, channel, bendvalue);
      }
      break;
   }
}



//////////////////////////////
//
// storeFreeNote -- store a midi note in the freenotestate array.
//

void storeFreeNote(Array >& array, int ptrack, int midinote) {
   int i;
   int loc = -1;
   for (i=0; i<array[ptrack].getSize(); i++) {
      if (array[ptrack][i] < 0) {
         loc = i;
         break;
      }
   }

   if (loc >= 0) {
      array[ptrack][loc] = midinote;
   } else {
      array[ptrack].append(midinote);
   }
}



//////////////////////////////
//
// reviseInstrumentMidiNumbers --
//

void reviseInstrumentMidiNumbers(const char* string) {
   const char* spaces = " \t\n,:;";
   char* buffer = new char[strlen(string) + 1];
   strcpy(buffer, string);
   HumdrumInstrument x;

   char* pointer = buffer;
   pointer = strtok(buffer, spaces);
   int instnum = 0;
   while (pointer != NULL) {
      char* humdrumname = pointer;
      pointer = strtok(NULL, spaces);
      if (pointer == NULL) {
         break;
      }
      instnum = 0;
      sscanf(pointer, "%d", &instnum);
      
      if (instnum < 0 || instnum > 127) {
         continue;
      }
      x.setGM(humdrumname, instnum);
      pointer = strtok(NULL, spaces);
   }
}



//////////////////////////////
//
// setMidiPlusVolume -- store slur and enharmonic spelling information.
//    based on definition in the book:
//       Beyond MIDI: The Handbook of Musical Codes. pp. 99-104.
//

int setMidiPlusVolume(const char* kernnote) {
   int output = 1 << 6;

   // check for slurs, but do not worry about separating multiple slurs
   if (strchr(kernnote, '(') != NULL) {
      // start of slur A
      output |= (1 << 2);
   } else if (strchr(kernnote, ')') != NULL) {
      // end of slur A
      output |= (1 << 4);
   }

   // set the accidental marking
   int base40class = Convert::kernToBase40(kernnote) % 40;
   int midinoteclass = Convert::kernToMidiNoteNumber(kernnote) % 12;

   int acheck = 0;
   switch (midinoteclass) {
      case 0:                           // C/Dbb/B#
         switch (base40class) {
            case  4: acheck = 1; break;  // Dbb
            case  0: acheck = 2; break;  // C
            case 36: acheck = 3; break;  // B#
            default: acheck = 0;
         }
         break;
      case 1:                           // C#/Db/B##
         switch (base40class) {
            case  5: acheck = 1; break;  // Db
            case  1: acheck = 2; break;  // C#
            case 37: acheck = 3; break;  // B##
            default: acheck = 0;
         }
         break;
      case 2:                           // D/C##/Ebb
         switch (base40class) {
            case 10: acheck = 1; break;  // Ebb
            case  6: acheck = 2; break;  // D
            case  2: acheck = 3; break;  // C##
            default: acheck = 0;
         }
         break;
      case 3:                           // D#/Eb/Fbb
         switch (base40class) {
            case 15: acheck = 1; break;  // Fbb
            case 11: acheck = 2; break;  // Eb
            case  7: acheck = 3; break;  // D#
            default: acheck = 0;
         }
         break;
      case 4:                           // E/Fb/D##
         switch (base40class) {
            case 16: acheck = 1; break;  // Fb
            case 12: acheck = 2; break;  // E
            case  8: acheck = 3; break;  // D##
            default: acheck = 0;
         }
         break;
      case 5:                           // F/E#/Gbb
         switch (base40class) {
            case 21: acheck = 1; break;  // Gbb
            case 17: acheck = 2; break;  // F
            case 13: acheck = 3; break;  // E#
            default: acheck = 0;
         }
         break;
      case 6:                           // F#/Gb/E##
         switch (base40class) {
            case 22: acheck = 1; break;  // Gb
            case 18: acheck = 2; break;  // F#
            case 14: acheck = 3; break;  // E##
            default: acheck = 0;
         }
         break;
      case 7:                           // G/Abb/F##
         switch (base40class) {
            case 27: acheck = 1; break;  // Abb
            case 23: acheck = 2; break;  // G
            case 19: acheck = 3; break;  // F##
            default: acheck = 0;
         }
         break;
      case 8:                           // G#/Ab/F###
         switch (base40class) {
            case 28: acheck = 1; break;  // Ab
            case 24: acheck = 2; break;  // G#
            default: acheck = 0;         // F###
         }
         break;
      case 9:                           // A/Bbb/G##
         switch (base40class) {
            case 33: acheck = 1; break;  // Bbb
            case 29: acheck = 2; break;  // A
            case 25: acheck = 3; break;  // G##
            default: acheck = 0;
         }
         break;
      case 10:                           // Bb/A#/Cbb
         switch (base40class) {
            case 38: acheck = 1; break;  // Cbb
            case 34: acheck = 2; break;  // Bb
            case 30: acheck = 3; break;  // A#
            default: acheck = 0;
         }
         break;
      case 11:                           // B/Cf/A##
         switch (base40class) {
            case 39: acheck = 1; break;  // Cb
            case 35: acheck = 2; break;  // B
            case 31: acheck = 3; break;  // A##
            default: acheck = 0;
         }
         break;
   }
     
   output |= acheck;
   return output; 
}



//////////////////////////////
//
// storePan --
//

void storePan(int ontime, MidiFile& outfile, HumdrumFile& infile, 
   int row, int column) {
   double value = 0.5;
   int mvalue = 64;
   sscanf(infile[row][column], "*pan=%lf", &value);
   if (value <= 1.0) {
      mvalue = int (value * 128.0);
   } else {
      mvalue = int(value + 0.5);
   }
   if (mvalue > 127) {
      mvalue = 127;
   } else if (mvalue < 0) {
      mvalue = 0;
   }
      
   int track = infile[row].getPrimaryTrack(column);
   int channel = 0x0f & trackchannel[track];   

   //ontime = ontime - 1;
   //if (ontime < 0) {
   //   ontime = 0;
   //}

   Array<uchar> mididata;
   mididata.setSize(3);
   mididata[0] = 0xb0 | channel;
   mididata[1] = 10;
   mididata[2] = mvalue;
   outfile.addEvent(track, ontime, mididata);
}


//////////////////////////////
//
// autoPan -- presuming multi-track MIDI file.
//

void autoPan(MidiFile& outfile, HumdrumFile& infile) {

   Array<int> kerntracks;
   getKernTracks(kerntracks, infile);

   double value = 0.0;
   int    mval  = 0;
   // Array<int> trackchannel;    // channel of each track
   
   Array<uchar> mididata;
   mididata.setSize(3);
   
   long ontime = 0;
   int i;
   int channel;
   int track;
   for (i=0; i<kerntracks.getSize(); i++) {
      track = kerntracks[i];
      channel = trackchannel[track];
      value = 127.0 * i/(kerntracks.getSize()-1);
      if (value < 0.0) { value = 0.0; }
      if (value > 127.0) { value = 127.0; }
      mval = (int)value;
      
      mididata[0] = 0xb0 | channel;
      mididata[1] = 10;
      mididata[2] = (char)mval;
      outfile.addEvent(track, ontime, mididata);
   }

}



//////////////////////////////
//
// storeInstrument --
//

void storeInstrument(int ontick, MidiFile& mfile, HumdrumFile& infile, 
      int line, int row, int pcQ) {

   PerlRegularExpression pre;
   PerlRegularExpression pre2;

   if (timbresQ && !forcedQ) {

      if (!pre.search(infile[line][row], "^\\*I\"\\s*(.*)\\s*", "")) {
         return;
      }
      SigString targetname;
      targetname = pre.getSubmatch(1);
      int i;
      int pc = -1;
      for (i=0; i<TimbreName.getSize(); i++) {
         if (pre2.search(targetname.getBase(), TimbreName[i].getBase(), "i")) {
            pc = TimbreValue[i];
            break;
         }
      }
      if (pc < 0) {
         for (i=0; i<TimbreName.getSize(); i++) {
            if (pre2.search("DEFAULT", TimbreName[i].getBase(), "i")) {
               pc = TimbreValue[i];
               break;
            }
         }
      }
      if (pc >= 0) {
         int track = -1;
         track = infile[line].getPrimaryTrack(row);
         int channel = 0x0f & trackchannel[track];   

         Array<uchar> mididata;
         mididata.setSize(2);
         mididata[0] = 0xc0 | channel;
         mididata[1] = (uchar)pc;
         mfile.addEvent(track, ontick + offset, mididata);
      }

   } else {
      static HumdrumInstrument huminst;
      int track = infile[line].getPrimaryTrack(row);
      const char* trackname = huminst.getName(infile[line][row]);
      int pc = huminst.getGM(infile[line][row]);
      if (forcedQ) {
         pc = instrumentnumber;
      }
      int channel = 0x0f & trackchannel[track];   
   
      // store the program change if requested:
      Array<uchar> mididata;
      mididata.setSize(2);
      mididata[0] = 0xc0 | channel;
      mididata[1] = (uchar)pc;
      if (pcQ && pc >= 0 && !forcedQ) {
         mfile.addEvent(track, ontick + offset, mididata);
      }

      if (!tracknamed[track]) {
         tracknamed[track] = 1;
         storeMetaText(mfile, track, trackname, 0, 3);    // Track Name
      }
      storeMetaText(mfile, track, trackname + offset, ontick, 4);  // Inst. Name
   }

}



//////////////////////////////
//
// usage --
//

void usage(const char* command) {

}


//////////////////////////////
//
// getDynamics --
//

void getDynamics(HumdrumFile& infile, Array<Array<char> >& dynamics, 
      int defaultdynamic) {
   int maxtracks = infile.getMaxTracks();
   Array<int> currentdynamic;
   currentdynamic.setSize(maxtracks);
   currentdynamic.setAll(defaultdynamic);

   Array<int> metlev;
   infile.analyzeMetricLevel(metlev);

   Array<Array<char> > crescendos;  // -1=decrescendo, +1=crescendo, 0=none
   crescendos.setSize(maxtracks);

   Array<Array<char> > accentuation;  // v = sf, sfz, fz, sffz
   accentuation.setSize(maxtracks);

   dynamics.setSize(maxtracks);

   int i; 
   int j;
   for (i=0; i<dynamics.getSize(); i++) {
      dynamics[i].setSize(infile.getNumLines());
      crescendos[i].setSize(infile.getNumLines());
      crescendos[i].setAll(0);
      accentuation[i].setSize(infile.getNumLines());
      accentuation[i].setAll(0);
   }

   Array<int> assignments;  // dynamic data which controls kern data
   getDynamicAssignments(infile, assignments);


   for (i=0; i<infile.getNumLines(); i++) {
      if (infile[i].getType() == E_humrec_data) {
         getNewDynamics(currentdynamic, assignments, infile, i, crescendos, 
              accentuation);
      }
      for (j=0; j<currentdynamic.getSize(); j++) {
         dynamics[j][i] = currentdynamic[j];
         // remove new dynamic marker:
         if (currentdynamic[j] < 0) {
            currentdynamic[j] = -currentdynamic[j];
         }
      }
   }

   processCrescDecresc(infile, dynamics, crescendos);

   // convert negative dynamics back into positive ones.
   if (dynamicsQ > 1) {
      cout << "----------------------------------------------" << endl;
      cout << "Dynamics profile of file:" << endl;
   }
   for (i=0; i<dynamics[0].getSize(); i++) {
      for (j=0; j<dynamics.getSize(); j++) {
         if (dynamics[j][i] < 0) {
            dynamics[j][i] = -dynamics[j][i];
         } 
         if (humanvolumeQ) {
            dynamics[j][i] = adjustVolumeHuman(dynamics[j][i], humanvolumeQ);
         }
         if (metricvolumeQ) {
            dynamics[j][i] = adjustVolumeMetric(dynamics[j][i], 
                              metricvolumeQ, -metlev[i]);
         }
         if (dynamicsQ) {
            // probably should change accentuation of this type
            // into a continuous controller....
            dynamics[j][i] = applyAccentuation(dynamics[j][i], 
                                                     accentuation[j][i]);
         }
         if (dynamicsQ > 1) {
            cout << (int)dynamics[j][i] << "\t";
         }
      }
      if (dynamicsQ > 1) {
         cout << endl;
      }
   }
   if (dynamicsQ > 1) {
      cout << "----------------------------------------------" << endl;
   }

}



//////////////////////////////
//
// applyAccentuation -- 
//

char applyAccentuation(int dynamic, int accent) {
   switch (accent) {
      case 'v':
         dynamic += sforzando;
         break;
   }

   if (dynamic > 127) {
      dynamic = 127;
   } else if (dynamic <= 0) {
      dynamic = 1; 
   }

   return (char)dynamic;
}



///////////////////////////////
//
// adjustVolumeMetric -- adjust the attack volume based on the
//  metric position of the note (on the beat, off the beat, on 
//  a metrically strong beat).
//

char adjustVolumeMetric(int startvol, int delta, double metricpos) {
   if (metricpos > 0.0) {
      startvol += delta;
   } else if (metricpos < 0) {
      startvol -= delta;
   }
 
   if (startvol <= 0) {
      startvol = 1;
   } else if (startvol > 127) {
      startvol = 127;
   }
   
   return (char)startvol;
}



////////////////////////////////
//
// adjustVolumeHuman -- add a randomness to the volume
//

char adjustVolumeHuman(int startvol, int delta) {
   int randval;
   #ifndef VISUAL
      randval = lrand48() % (delta * 2 + 1) - delta;
   #else
      randval = rand() % (delta * 2 + 1) - delta;
   #endif

   startvol += randval;
   if (startvol <= 0) {
      startvol = 1;
   }
   if (startvol > 127) {
      startvol = 127;
   }

   return (char)startvol;
}



//////////////////////////////
//
// processCrescDecresc -- adjust attack velocities based on the
//    crescendos and decrescendos found in the file.
//

void processCrescDecresc(HumdrumFile& infile, Array<Array<char> >& dynamics, 
   Array<Array<char> >& crescendos) {

   int i;
   for (i=0; i<dynamics.getSize(); i++) {
      interpolateDynamics(infile, dynamics[i], crescendos[i]);
   }
}



//////////////////////////////
//
// interpolateDynamics --
//

void interpolateDynamics(HumdrumFile& infile, Array<char>& dyn, 
      Array<char>& cresc) {
   int direction = 0;
   int i;
   int ii;
  
   for (i=0; i<dyn.getSize(); i++) {
      if (cresc[i] != 0) {
         if (cresc[i] > 0) {
            direction = +1;
         } else if (cresc[i] < 0) {
            direction = -1;
         } else {
            direction = 0;
         }
         ii = findtermination(dyn, cresc, i);
         generateInterpolation(infile, dyn, cresc, i, ii, direction);

         // skip over calm water:
         i = ii - 1;
      }

   }
}



//////////////////////////////
//
// generateInterpolation -- do the actual interpolation work.
//

void generateInterpolation(HumdrumFile& infile, Array<char>& dyn, 
      Array<char>& cresc, int startline, int stopline, int direction) {

   double startbeat = infile[startline].getAbsBeat();  
   double stopbeat  = infile[stopline].getAbsBeat();

   if (startbeat == stopbeat) {
      // nothing to do
      return;
   }

   int startvel = dyn[startline];
   int stopvel  = dyn[stopline];
   if (startvel < 0)   startvel = -startvel;
   if (stopvel  < 0)   stopvel  = -stopvel;

   if (stopvel == startvel) {
      if (direction > 0) {
         stopvel += 10;
      } else {
         stopvel -= 10;
      }
   } else if (stopvel>startvel && direction<0) {
      stopvel = startvel - 10;
   } else if (stopvel<startvel && direction>0) {
      stopvel = startvel + 10;
   }

   int i;
   double slope = (double)(stopvel-startvel)/(double)(stopbeat-startbeat);
   double currvel = 0.0;
   double currbeat = 0.0;
   for (i=startline+1; i<stopline && i<dyn.getSize(); i++) { 
      currbeat = infile[i].getAbsBeat();
      currvel  = slope * (currbeat - startbeat) + startvel;
      if (currvel > 127.0) {
         currvel = 127.0;
      }
      if (currvel < 0.0) {
         currvel = 0.0;
      }
      dyn[i] = (char)(currvel+0.5);
   }

}



//////////////////////////////
//
// findtermination --
//

int findtermination(Array& dyn, Array& cresc, int start) {
   int i;
   for (i=start+1; i<dyn.getSize(); i++) {
      if (cresc[i] != 0) {
         break;
      } else if (dyn[i] < 0) {
         break;
      }
   }
   
   if (i>=dyn.getSize()) {
      i = dyn.getSize()-1;
   }
   return i;
}



//////////////////////////////
//
// getNewDynamics --
//

void getNewDynamics(Array<int>& currentdynamic, Array<int>& assignments, 
      HumdrumFile& infile, int line, Array<Array<char> >& crescendos,
      Array<Array<char> >& accentuation) {

   if (infile[line].getType() != E_humrec_data) {
      return;
   }

   int i;
   int j;
   int k;
   int length;
   int track = -1;
   int dval = -1;
   int accent = 0;
   int cresval = 0;
   char buffer[2048] = {0};
   for (i=0; i<infile[line].getFieldCount(); i++) {
      dval = -1;
      cresval = 0;
      accent = 0;
      if (strcmp(infile[line][i], ".") == 0) {
         continue;
      }
      if (strcmp(infile[line].getExInterp(i), "**dynam") != 0) {
         continue;
      }

      // copy the **dynam value, removing any X information:
      length = strlen(infile[line][i]);
      k = 0;
      for (j=0; j<length && j<1024; j++) {
         if (infile[line][i][j] == 'm' ||
             infile[line][i][j] == 'p' ||
             infile[line][i][j] == 'f'
             ) {
            buffer[k++] = infile[line][i][j];
         } else {
            // do not store character in dynamic string
         }
      }
      buffer[k] = '\0';

      track = infile[line].getPrimaryTrack(i) - 1;

      if (strstr(buffer, "ffff") != NULL) {
         dval = DYN_FFFF;
      } else if (strcmp(buffer, "fff") == 0) {
         dval = DYN_FFF;
      } else if (strcmp(buffer, "ff") == 0) {
         dval = DYN_FF;
      } else if (strcmp(buffer, "f") == 0) {
         dval = DYN_F;
      } else if (strcmp(buffer, "mf") == 0) {
         dval = DYN_MF;
      } else if (strcmp(buffer, "mp") == 0) {
         dval = DYN_MP;
      } else if (strcmp(buffer, "p") == 0) {
         dval = DYN_P;
      } else if (strcmp(buffer, "pp") == 0) {
         dval = DYN_PP;
      } else if (strcmp(buffer, "ppp") == 0) {
         dval = DYN_PPP;
      } else if (strstr(buffer, "pppp") != NULL) {
         dval = DYN_PPPP;
      }

      // cannot have both > and < on the same line.
      if (strchr(infile[line][i], '<') != NULL) {
         cresval = +1;
      } else if (strchr(infile[line][i], '>') != NULL) {
         cresval = -1;
      }

      // look for accentuatnon
      if (strchr(infile[line][i], 'v') != NULL) {
         accent = 'v';
      }
      
      if (dval > 0 || cresval !=0) {
         for (j=0; j<assignments.getSize(); j++) {
            if (assignments[j] == track) {
               // mark new dynamics with a minus sign 
               // which will be removed after cresc/decresc processing
               if (dval > 0) {
                  currentdynamic[j] = -dval;
               }
               if (cresval != 0) {
                  crescendos[j][line] = cresval;
               }
               if (accent != 0) {
                  accentuation[j][line] = accent;
               }
            }
         }
      }
   }

}



//////////////////////////////
//
// getDynamicAssignments --
//

void getDynamicAssignments(HumdrumFile& infile, Array& assignments) {
   assignments.setSize(infile.getMaxTracks());
   assignments.setAll(-1);

   // *staff assignment assumed to be all on one line, and before
   // any note data.

   int staffline = -1;
   int exinterp = -1;
   int i;
   int j;
   for (i=0; i<infile.getNumLines(); i++) {
      if (infile[i].getType() == E_humrec_data) {
         break;
      }
      if (infile[i].getType() == E_humrec_interpretation) {
         if (strncmp(infile[i][0], "**", 2) == 0) {
            exinterp = i;
            continue;
         }
         if (strstr(infile[i][0], "staff") != NULL) {
            staffline = i;
            break;
         }
      }
   }

   // first assume that there are no *staff assignments and setup
   // the default values.

   int currdyn = -1;
   for (i=infile[exinterp].getFieldCount()-1; i>=0; i--) {
      if (strcmp(infile[exinterp][i], "**dynam") == 0) {
         currdyn = i;
      }
      if (currdyn >= 0) {
         assignments[i] = infile[exinterp].getPrimaryTrack(currdyn)-1;
      } else {
         assignments[i] = -1;
      }
   }

   if (staffline == -1) {
      // there is no staff assignments in the file, so attach any
      // dynamics to the first **kern spine on the left.

      return;
   }

   // there is a *staff assignment line, so follow the directions found there

   Array<Array<int> > staffvalues;
   getStaffValues(infile, staffline, staffvalues);

   int k;
   // assignments.setSize(infile.getMaxTracks());
   for (i=0; i<infile[exinterp].getFieldCount(); i++) {
      if (strcmp(infile[exinterp][i], "**kern") != 0) {
         continue;
      }
      for (j=0; j<infile[exinterp].getFieldCount(); j++) {
         if (strcmp(infile[exinterp][j], "**dynam") != 0) {
            continue;
         }
         for (k=0; k<staffvalues[j].getSize(); k++) {
            if (staffvalues[i][0] == staffvalues[j][k]) {
               assignments[infile[exinterp].getPrimaryTrack(i)-1] = 
                  infile[exinterp].getPrimaryTrack(j)-1;
            }
         }
      }
   }

   // for (i=0;i<assignments.getSize();i++) {
   //    cout << "ASSIGNMENT " << i << " = " << assignments[i] << endl;
   // }

}




//////////////////////////////
//
// getStaffValues --
//

void getStaffValues(HumdrumFile& infile, int staffline, 
      Array<Array<int> >& staffvalues) {

   staffvalues.setSize(infile[staffline].getFieldCount());

   int i;
   for (i=0; i<staffvalues.getSize(); i++) {
      staffvalues[i].setSize(0);
   }

   int value;
   const char* cptr;
   char* newcptr;
   for (i=0; i<infile[staffline].getFieldCount(); i++) {
      if (strncmp(infile[staffline][i], "*staff", 6) == 0) {
         if (strchr(infile[staffline][i], '/') == NULL) {
            if (sscanf(infile[staffline][i], "*staff%d", &value) == 1) {
               staffvalues[i].append(value);
            }
         } else {
            // more than one number in the staff assignment
            cptr = &(infile[staffline][i][6]);
            while (strlen(cptr) > 0) {
               if (!isdigit(cptr[0])) {
                  break;
               }
               value = strtol(cptr, &newcptr, 10);
               staffvalues[i].append(value);
               cptr = newcptr;
               if (cptr[0] != '/') {
                  break;
               } else {
                  cptr = cptr + 1;
               }
            }
         }
      }
   }
}



//////////////////////////////
//
// getMillisecondTime -- return the time in milliseconds found
//    on the current line in the file.
//

int getMillisecondTime(HumdrumFile& infile, int line) {
   double output = -100;
   int flag;
   int i;

   while ((line < infile.getNumLines()) && 
          (infile[line].getType() != E_humrec_data)) {
      line++;
   }
   if (line >= infile.getNumLines() - 1) {
      return getFileDurationInMilliseconds(infile);
   }

   for (i=0; i<infile[line].getFieldCount(); i++) {
      if (strcmp(infile[line].getExInterp(i), "**time") == 0) {
         if (strcmp(infile[line][i], ".") == 0) {
            cout << "Error on line " << line + 1 << ": no time value" << endl;
            exit(1);
         }         
         //if (strcmp(infile[line][i], ".") == 0) {
         //   output = -1.0;
         //} else {
         //   flag = sscanf(infile[line][i], "%lf", &output);
         //}
         flag = sscanf(infile[line][i], "%lf", &output);
         break;
      }
   }

   if (timeinsecQ) {
      output *= 1000.0;
   }

   if (output < 0.0) {
      return -1;
   } else {
      return (int)(output + 0.5);
   }
}



//////////////////////////////
//
// getMillisecondDuration --
//

int getMillisecondDuration(HumdrumFile& infile, int row, int col, int subcol) {
   char buffer1[1024] = {0};

   int startime = getMillisecondTime(infile, row);

   double output = -100.0;

   double duration = 0.0;

   if (strchr(buffer1, '[')) {
      // total tied note durations
      duration = infile.getTiedDuration(row, col, subcol);
   } else {
      infile[row].getToken(buffer1, col, subcol); 
      duration = rhysc * Convert::kernToDuration(buffer1);
   }

   double stopbeat = duration + infile[row].getAbsBeat();

   int i, j;
   for (i=row+1; i<infile.getNumLines(); i++) {
      if (infile[i].getType() != E_humrec_data) {
         continue;
      }
      if (infile[i].getAbsBeat() >= (stopbeat-0.0002)) {
         for (j=0; j<infile[i].getFieldCount(); j++) {
            if (strcmp(infile[i].getExInterp(j), "**time") == 0) {
               sscanf(infile[i][j], "%lf", &output);
               break;
            }
         }
         break;
      }
   }

   if (timeinsecQ) {
      output *= 1000.0;
   }


   if (output - startime < 0.0) {
      return 0;
   } else {
      return (int)(output - startime + 0.5);
   }
}



//////////////////////////////
//
// getFileDurationInMilliseconds --
//

int getFileDurationInMilliseconds(HumdrumFile& infile) {

   int lastdataline = infile.getNumLines() - 1;
   while ((lastdataline > 0) && 
          (infile[lastdataline].getType() != E_humrec_data)) {
      lastdataline--;
   } 

   double ctime = getMillisecondTime(infile, lastdataline);
   double cbeat = infile[lastdataline].getAbsBeat();
   double nbeat = infile[infile.getNumLines()-1].getAbsBeat();

   int preindex = -1;

   int i;
   for (i=lastdataline-1; i>=0; i--) {
      if (infile[i].getType() != E_humrec_data) {
         continue;
      }

      preindex = i;
      break;
   }

   if (preindex < 0) {
       return -1;
   }

   double pbeat = infile[preindex].getAbsBeat();
   double ptime = -1.0;

   for (i=0; i<infile[preindex].getFieldCount(); i++) {
      if (strcmp("**time", infile[preindex].getExInterp(i)) != 0) {
         continue;
      }

      sscanf(infile[preindex][i], "%lf", &ptime);
      break;
   }

   if (ptime < 0.0) {
      return -1;
   }

   if (timeinsecQ) {
      ptime *= 1000.0;
   }

   double db2 = nbeat - cbeat;
   double db1 = cbeat - pbeat;
   double dt1 = ctime - ptime;

   // cout << "<< DB1 = " << db1 << " >> ";
   // cout << "<< DB2 = " << db2 << " >> ";
   // cout << "<< DT1 = " << dt1 << " >> ";

   double result = ctime + db2 * dt1 / db1;

   return (int)(result + 0.5);
}


///////////////////////////////////////////////////////////////////////////
//
// PerfViz related functions
//

ostream& operator<<(ostream& out, PerfVizNote& note) {

   int anchor		= ++note.sid;
   int measure 		= note.bar;
   int beat   		= note.beat;
   int velocity		= note.vel;
   int onset		= note.ontick;   // tick on time for note
   int offset		= note.offtick;  // tick off time for note
   int adjoffset	= offset;        // not taking pedalling into account
   double absbeaton     = note.absbeat;
   double absbeatoff	= note.absbeat + note.scoredur;
   char pitchname[1024] = {0};
   Convert::base40ToPerfViz(pitchname, note.pitch);
   double soffset	= note.beatfrac;
   double dur		= note.beatdur;
   
   out << "snote(n"
       << anchor	<< ","
       << pitchname	<< ","
       << measure	<< ":"
       << beat		<< ",";

   printRational(out, soffset);
   out << ",";
   printRational(out, dur);
   out << ",";

   out << absbeaton	<< ","
       << absbeatoff	<< ","
       << "[])-";
   out << "note("
       << anchor	<< ","
       << pitchname	<< ","
       << onset		<< ","
       << offset	<< ","
       << adjoffset	<< ","
       << velocity	
       << ").\n";

   return out;
}



//////////////////////////////
//
// printRational --
//

void printRational(ostream& out, double value) {
   if (value <= 0.0002) {
      out << "0";
      return;
   }


   if (fabs(value-0.5) < 0.0002) {
      out << "1/2";
   } else if (fabs(value - 0.25) < 0.0002) {
      out << "1/16";
   } else if (fabs(value - 1.5) < 0.0002) {
      out << "3/2";
   } else if (fabs(value - 2.5) < 0.0002) {
      out << "5/2";
   } else if (fabs(value - 0.75) < 0.0002) {
      out << "3/16";
   } else if (fabs(value - 0.16667) < 0.0002) {
      out << "1/8/3";
   } else if (fabs(value - int(value)) <= 0.0002) {
      out << int(value);
   } else if (fabs(value - int(value)) >= 0.9998) {
      out << int(value)+1;
   } else if (value == 1.0) {
      out << "1";
   } else if (fabs(value - 0.3333) < 0.0002) {
      out << "1/4/3";
   } else if (fabs(value - 0.6667) < 0.0002) {
      out << "2/4/3";
   } else {
      out << "{{" << value << "}}";
   }

}



//////////////////////////////
//
// writePerfVizMatchFile --
//

void writePerfVizMatchFile(const char* filename, SSTREAM& contents) {
   ofstream outputfile(filename);
   contents   << ends;
   outputfile << contents.CSTRING;
   outputfile.close();
}


//////////////////////////////
//
// printPerfVizKey --
//

void printPerfVizKey(int key) {
   int octave = key / 40;
   int diatonic = Convert::base40ToDiatonic(key) % 7;
   int accidental = Convert::base40ToAccidental(key);

   if (PVIZ == NULL) {
      return;
   }

   PVIZ[0] << "info(keySignature,[";
   switch (diatonic) {
      case 0:  PVIZ[0] << "c"; break;
      case 1:  PVIZ[0] << "d"; break;
      case 2:  PVIZ[0] << "e"; break;
      case 3:  PVIZ[0] << "f"; break;
      case 4:  PVIZ[0] << "g"; break;
      case 5:  PVIZ[0] << "a"; break;
      case 6:  PVIZ[0] << "b"; break;
      default: PVIZ[0] << "X";
   }
   switch (accidental) {
      case -2: PVIZ[0] << "bb"; break;
      case -1: PVIZ[0] << "b"; break;
      case  0: PVIZ[0] << "n"; break;
      case  1: PVIZ[0] << "#"; break;
      case  2: PVIZ[0] << "##"; break;
   }
   PVIZ[0] << ",";
   if (octave == 3) {
      PVIZ[0] << "major";
   } else if (octave == 4) {
      PVIZ[0] << "minor";
   } else {
      PVIZ[0] << "locrian";
   }
   PVIZ[0] << "]).\n";
}



//////////////////////////////
//
// printPerfVizTimeSig --
//

void printPerfVizTimeSig(int tstop, int tsbottom) {
   if (PVIZ == NULL) {
      return;
   }

   PVIZ[0] << "info(timeSignature," << tstop << "/" << tsbottom << ").\n";

}



//////////////////////////////
//
// printPerfVizTempo --
//

void printPerfVizTempo(double approxtempo) {
   if (PVIZ == NULL) {
      return;
   }
   
   PVIZ[0] << "info(approximateTempo," << approxtempo << ").\n";

}



//////////////////////////////
//
// storeTimbres --
//

void storeTimbres(Array<SigString>& name, Array<int>& value, 
      Array<int>& volumes, const char* string) {
   PerlRegularExpression pre;
   Array<Array<char> > tokens;
   pre.getTokens(tokens, "\\s*;\\s*", string);
   name.setSize(tokens.getSize());
   value.setSize(tokens.getSize());
   volumes.setSize(tokens.getSize());
   name.setSize(0);
   value.setSize(0);
   volumes.setSize(0);

   SigString tempstr;
   int temp;
   int i;
   for (i=0; i<tokens.getSize(); i++) {

      if (pre.search(tokens[i], "(.*)\\s*:\\s*i?(\\d+),v(\\d+)", "")) {
         temp = atoi(pre.getSubmatch(2));
         value.append(temp);
         temp = atoi(pre.getSubmatch(3));
         volumes.append(temp);
         tempstr = pre.getSubmatch(1);
         name.append(tempstr);

      } else if (pre.search(tokens[i], "(.*)\\s*:\\s*(\\d+)", "")) {
         temp = atoi(pre.getSubmatch(2));
         value.append(temp);
         tempstr = pre.getSubmatch(1);
         name.append(tempstr);
      }

   }
}



//////////////////////////////
//
// getKernTracks --  Return a list of the **kern primary tracks found
//     in the Humdrum data.  Currently all tracks are independent parts.
//     No grand staff parts are considered if the staves are separated 
//     into two separate spines.
//
//

void getKernTracks(Array& tracks, HumdrumFile& infile) {
   tracks.setSize(infile.getMaxTracks());
   tracks.setSize(0);
   int i;
   for (i=1; i<=infile.getMaxTracks(); i++) {
      if (strcmp(infile.getTrackExInterp(i), "**kern") == 0) {
         tracks.append(i);
      }
   }
}



// md5sum: 550cc9663fd7fac80e14a7b9024429ce hum2mid.cpp [20130504]