//
// Programmer:    Craig Stuart Sapp <craig@ccrma.stanford.edu>
// Creation Date: Wed Dec  3 11:13:50 PST 2008
// Last Modified: Wed Dec  3 11:13:55 PST 2008
// Filename:      ...museinfo/examples/all/hokey2humdrum.cpp
// Web Address:   http://sig.sapp.org/examples/museinfo/humdrum/hokey2humdrum.cpp
// Syntax:        C++
//
// Description:   Convert Hokey XML data files into Humdrum files.
//

#include "humdrum.h"

#define TIXML_USE_STL
#include "tinyxml.h"

#include <iostream>

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

typedef const char* charstar;


void   printToHumdrum(ostream& out, const char* filename);
void   convertToHumdrum(HumdrumFile& outfile, TiXmlNode* node);
void   printHeaderInformation(ostream& out, TiXmlNode* node, double& offset,
                                int& mtop, int& mbot, double& repeat, 
                                charstar& keyinfo);
void   printHumdrumPart(ostream& out, TiXmlNode* node, int stem);
void   printKernNote(ostream& out, TiXmlNode* node, int stem);
void   printKernRest(ostream& out, TiXmlNode* node, int stem);
int    getBase40FromHokeyPitch(const char* string);
double printRhythm(ostream& out, int dtop, int dbot);
void   printbarline(ostream& out, int barcounter, int voices, 
                                const char* barstyle);
void   printKeyInfo(ostream& out, const char* keyinfo, 
                                int voices);
void   printAllSpines(ostream& out, int voices, const char* string);
void   addTies(HumdrumFile& outfile);
void   fillNullToken(HumdrumFile& outfile, int line, int spine);
void   makeNewToken(char* newstring, const char* oldstring, 
                                const char* rhythm, int tiestate);
void   addBeaming(HumdrumFile& outfile, int spine);

#define STEMDEFAULT 0
#define STEMUP      1
#define STEMDOWN    2

#define TIENONE     0
#define TIESTART    1
#define TIEEND      2

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

int main(int argc, char* argv[]) {
   int i;
   for (i=1; i<argc; i++) {
      printToHumdrum(std::cout, argv[i]);
   }
   return 0;
}

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

///////////////////////////////
//
// printToHumdrum --
//

void printToHumdrum(ostream& out, const char* filename) {
   TiXmlDocument doc(filename);
   int status = doc.LoadFile();
   HumdrumFile outfile;
   if (status) {
      convertToHumdrum(outfile, &doc); 
   } else {
      cout << "Failed to load file " << filename << "\n";
      exit(1);
   }
   out << outfile;
}



//////////////////////////////
//
// printHeaderInformation --
//

void printHeaderInformation(ostream& out, TiXmlNode* node, double& offset,
      int& mtop, int& mbot, double& repeat, charstar& keyinfo) {
   node = node->FirstChild();
   offset = 0;
   mtop = mbot = 0;
   const char* index = "";
   const char* title = "";
   const char* offsetT = "";
   const char* repeatT = "";
   int count;
   TiXmlText* textnode;
   while (node  != NULL) {

      if (strcmp(node->Value(), "title") == 0) {
         textnode = node->FirstChild()->ToText();
         title = textnode->Value();
      } else if (strcmp(node->Value(), "index") == 0) {
         textnode = node->FirstChild()->ToText();
         index = textnode->Value();
      } else if (strcmp(node->Value(), "key") == 0) {
         textnode = node->FirstChild()->ToText();
         keyinfo = textnode->Value();
      } else if (strcmp(node->Value(), "start-time") == 0) {
         textnode = node->FirstChild()->ToText();
         offsetT = textnode->Value();
      } else if (strcmp(node->Value(), "repeat-time") == 0) {
         textnode = node->FirstChild()->ToText();
         repeatT = textnode->Value();
      } else if (strcmp(node->Value(), "meter") == 0) {
         textnode = node->FirstChild()->ToText();
         count = sscanf(textnode->Value(), "%d/%d/", &mtop, &mbot);
         if (count != 2) {
            cerr << "ERROR in time signature " << textnode->Value() << endl;
            exit(1);
         }
      }
      node = node->NextSibling();
   }
   
   if (title[0] != '\0') {
      out << "!!!COM:\t" << "Bach, Johann Sebastian" << "\n";
      out << "!!!CDT:\t" << "1685/02/21/-1750/07/28/" << "\n";
      out << "!!!OTL:\t" << title << "\n";
   }
   if (index[0] != '\0') {
      out << "!!!ONM:\t" << index << "\n";
      out << "!!!AGN:\t" << "chorale" << "\n";
   }

   if (offsetT[0] != '\0') {
      int top = 0;
      int bot = 0;
      sscanf(offsetT, "%d/%d", &top, &bot);
      offset = (double)top / (double)bot * 4.0;
   }

   if (repeatT[0] != '\0') {
      int rtop = 0;
      int rbot = 0;
      sscanf(repeatT, "%d/%d", &rtop, &rbot);
      repeat = (double)rtop / (double)rbot * 4.0;
   }


   if ((mtop == 0) || (mbot == 0)) {
      cerr << "ERROR unspecified time signature" << endl;
      exit(1);
   }

}



//////////////////////////////
//
// getHumdrumPart --
//

void printHumdrumPart(ostream& out, TiXmlNode* node, int stem) {
   node = node->FirstChild();
   out << "**kern\n"; 

   while (node != NULL) {
      if (strcmp(node->Value(), "note") == 0)  {
         printKernNote(out, node, stem);
         out << "\n";
      } else if (strcmp(node->Value(), "rest") == 0) {
         printKernRest(out, node, stem);
         out << "\n";
      } else {
         cerr << "Unknown ELEMENT <" << node->Value() << endl;
         exit(1);
      }
      node = node->NextSibling();
   }

   out << "*-\n"; 
}



///////////////////////////////
//
// getBase40FromHokeyPitch --
//

int getBase40FromHokeyPitch(const char* string) {
   int length = strlen(string);
   if (length == 0) {
      return 0;
   }

   int octave = 0;
   int count;
   int i;

   char string2[1024] = {0};
   strcpy(string2, string);
   for (i=0; i<length; i++) {
      if (isdigit(string2[i])) {
         count = sscanf(string2 + i, "%d", &octave);
         if (count != 1) {
            cerr << "ERROR in pitch octave" << endl;
            exit(1);
         }
         string2[i] = '\0';
      }
   }


   int diatonic;
   switch (string2[0]) {
      case 'A': case 'a': diatonic = 5; break;
      case 'B': case 'b': diatonic = 6; break;
      case 'C': case 'c': diatonic = 0; break;
      case 'D': case 'd': diatonic = 1; break;
      case 'E': case 'e': diatonic = 2; break;
      case 'F': case 'f': diatonic = 3; break;  // careful: f is also a flat
      case 'G': case 'g': diatonic = 4; break;
      default: 
         cerr << "ERROR IN DIATONIC PITCH NAME " << string2 << endl;
         exit(1);
   }

   int accidentals = 0;
   for (i=1; i<length; i++) {
      if (string[i] == 's') {
         accidentals++;
      } else if (string[i] == 'f') {
         accidentals--;
      }
   }
   
   int output = 0;

   switch (diatonic) {
      case 0:  output =  0; break;
      case 1:  output =  6; break;
      case 2:  output = 12; break;
      case 3:  output = 17; break;
      case 4:  output = 23; break;
      case 5:  output = 29; break;
      case 6:  output = 35; break;
   }

   output = output + 2 + accidentals;

   return output + 40 * octave;
}



//////////////////////////////
//
// printKernNote --
//

void printKernNote(ostream& out, TiXmlNode* node, int stem) {
   TiXmlText* textnode;
   node = node->FirstChild();
   int dtop = 0;
   int dbot = 0;
   int base40 = 0;
   int count;
   int fermata = 0;
   int tiestart = 0;
   int tiecontinue = 0;
   int tieend = 0;
   double duration;

   while (node != NULL) {
      // cout << "ELEMENT " << node->Value() << endl;
      if (strcmp(node->Value(), "fermata") == 0) {
         fermata = 1;
      } else if (strcmp(node->Value(), "tiestart") == 0) {
         tiestart = 1;
      } else if (strcmp(node->Value(), "tiecontinue") == 0) {
         tiecontinue = 1;
      } else if (strcmp(node->Value(), "tieend") == 0) {
         tieend = 1;
      } else if (strcmp(node->Value(), "duration") == 0) {
         textnode = node->FirstChild()->ToText();
	 count = sscanf(textnode->Value(), "%d/%d", &dtop, &dbot);
         if (count != 2) {
            cerr << "Error parsing rhythm: " << textnode->Value() << endl;
            exit(1);
         }
      } else if (strcmp(node->Value(), "pitch") == 0) {
         textnode = node->FirstChild()->ToText();
         base40 = getBase40FromHokeyPitch(textnode->Value());
      }
      node = node->NextSibling();
   }

   if (dtop == 0 || dbot == 0 || base40 == 0) {
      cerr << "SOME SORT OF ERROR" << endl;
      cerr << "dtop =\t" << dtop << endl;
      cerr << "dbot =\t" << dbot << endl;
      cerr << "base40 =\t" << base40 << endl;
      exit(1);
   }

   if (tiestart) {
      out << "[";
   }
   duration = printRhythm(out, dtop, dbot);
   char buffer[1024] = {0};
   out << Convert::base40ToKern(buffer, base40);
   if (fermata) {
      out << ";";
   }
   if (tiecontinue) {
      out << "_";
   } else if (tieend) {
      out << "]";
   }
   if (duration < 4.0) { // only print stem info if less than whole note
      if (stem == STEMUP) {
         out << "/";
      } else if (stem == STEMDOWN) {
         out << "\\";
      }
   }



}



//////////////////////////////
//
// printRhythm --
//

double printRhythm(ostream& out, int dtop, int dbot) {
   double duration = (double)dtop / (double) dbot;
   duration *= 4.0;
   char buffer[1024] = {0};
   out << Convert::durationToKernRhythm(buffer, duration);
   return duration;
}



//////////////////////////////
//
// printKernRest --
//

void printKernRest(ostream& out, TiXmlNode* node, int stem) {
   TiXmlText* textnode;
   node = node->FirstChild();
   int dtop = 0;
   int dbot = 0;
   int base40 = 0;
   int count;
   int fermata = 0;
   double duration;

   while (node != NULL) {
      // cout << "ELEMENT " << node->Value() << endl;
      if (strcmp(node->Value(), "fermata") == 0) {
         fermata = 1;
      } else if (strcmp(node->Value(), "duration") == 0) {
         textnode = node->FirstChild()->ToText();
	 count = sscanf(textnode->Value(), "%d/%d", &dtop, &dbot);
         if (count != 2) {
            cerr << "Error parsing rhythm: " << textnode->Value() << endl;
            exit(1);
         }
      } else if (strcmp(node->Value(), "pitch") == 0) {
         textnode = node->FirstChild()->ToText();
         base40 = getBase40FromHokeyPitch(textnode->Value());
      }
      node = node->NextSibling();
   }

   if (dtop == 0 || dbot == 0 || base40 != 0) {
      cerr << "SOME SORT OF ERROR 2" << endl;
      cerr << "dtop =\t" << dtop << endl;
      cerr << "dbot =\t" << dbot << endl;
      cerr << "base40 =\t" << base40 << endl;
      exit(1);
   }

   duration = printRhythm(out, dtop, dbot);
   out << "r";
   if (fermata) {
      out << ";";
   }
}



///////////////////////////////
//
// convertToHumdrum --
//

void convertToHumdrum(HumdrumFile& outfile, TiXmlNode* node) {
   SSTREAM headerstream;   // deal with printing it out later
   SSTREAM sopranostream;
   SSTREAM altostream;
   SSTREAM tenorstream;
   SSTREAM bassstream;

   if (node->Type() != TiXmlNode::DOCUMENT) {
      cout << "ERROR not at start of document" << endl;
      exit(1);
   }

   node = node->FirstChild();
   if ((strcmp(node->Value(), "chorale") != 0)) {
      cout << "ERROR did not find <chorale> marker" << endl;
      exit(1);
   }

   node = node->FirstChild();
   if ((strcmp(node->Value(), "info") != 0)) {
      cout << "ERROR did not find <info> marker" << endl;
      exit(1);
   }

   double offset = 0; // for keeping track of pickup-measures
   int mtop = 0;   // top    of timesignature;
   int mbot = 0;   // bottom of timesignature;
   const char* keyinfo = "";
   double repeat = 0;
   printHeaderInformation(headerstream, node, offset, mtop, mbot, repeat, 
         keyinfo);

   node = node->NextSibling();
   if ((strcmp(node->Value(), "satb") != 0)) {
      cout << "ERROR did not find <satb> marker" << endl;
      exit(1);
   }

   node = node->FirstChild();
   if ((strcmp(node->Value(), "soprano") != 0)) {
      cout << "ERROR did not find <soprano> marker" << endl;
      exit(1);
   }

   //printHumdrumPart(std::cout, node, STEMUP);
   printHumdrumPart(sopranostream, node, STEMUP);

   node = node->NextSibling();
   if ((strcmp(node->Value(), "alto") != 0)) {
      cout << "ERROR did not find <satb> marker" << endl;
      exit(1);
   }

   printHumdrumPart(altostream, node, STEMDOWN);

   node = node->NextSibling();
   if ((strcmp(node->Value(), "tenor") != 0)) {
      cout << "ERROR did not find <satb> marker" << endl;
      exit(1);
   }

   printHumdrumPart(tenorstream, node, STEMUP);

   node = node->NextSibling();
   if ((strcmp(node->Value(), "bass") != 0)) {
      cout << "ERROR did not find <satb> marker" << endl;
      exit(1);
   }

   printHumdrumPart(bassstream, node, STEMDOWN);

   Array<HumdrumFile> parts;
   parts.setSize(4);
   parts.allowGrowth(0);

   headerstream  << ends;
   sopranostream << ends;
   altostream    << ends;
   tenorstream   << ends;
   bassstream    << ends;

   // change order when making single part to a spine
   // look for "check the next line of spine order changes" below as well
   parts[3].read(altostream);
   parts[2].read(sopranostream);
   parts[1].read(bassstream);
   parts[0].read(tenorstream);


   outfile.clear();
   HumdrumFile::assemble(outfile, 4, parts.getBase());

   outfile.analyzeRhythm("4");

   SSTREAM newout;

   int repeatQ = 0;
   if (repeat > 0) {
      repeatQ = 1;
   }
   int barcounter = 1;
   double bardur = 4.0 * mtop / mbot;
   double nextbartime = bardur - offset;
   if (nextbartime >= bardur) {
      nextbartime = 0;	     
      barcounter = -1;
   }
   double absbeat;
   newout << headerstream.CSTRING;
   int i, j;
   int voices = outfile.getMaxTracks();
   for (i=0; i<outfile.getNumLines(); i++) {
      switch (outfile[i].getType()) {
         case E_humrec_data_comment:
            newout << outfile[i] << "\n";
            break;
         case E_humrec_data_kern_measure:
            newout << outfile[i] << "\n";
            break;
         case E_humrec_interpretation:
            if (strncmp(outfile[i][0], "**", 2) == 0) {
               // (temporarily place two voices on each staff)
               newout << "**kern\t**kern\n";
               newout << "*^\t*^\n";
	       newout << "*clefF4\t*clefF4\t*clefG2\t*clefG2\n";
               // check the next line of spine order changes
	       newout << "!tenor\t!bass\t!soprano\t!alto\n";
	       if (repeatQ) {
                  printAllSpines(newout, voices, "*>[A,A,B]");
                  printAllSpines(newout, voices, "*>norep[A,A,B]");
                  printAllSpines(newout, voices, "*>A");
               }
               printKeyInfo(newout, keyinfo, voices);
               // print meter:
               for (j=0; j<voices; j++) {
                  newout << "*M" << mtop << "/" << mbot;
                  if (j < voices - 1) {
                     newout << "\t";
                  }
               }
               newout << "\n";
               printAllSpines(newout, voices, "*MM100");
            } else if (strcmp(outfile[i][0], "*-") == 0) {
               // (temporarily place two voices on each staff)
               newout << "*v\t*v\t*\t*\n";
               newout << "*\t*v\t*v\n";
               newout << "*-\t*-\n";
            } else {
            newout << outfile[i] << "\n";
	    }
            break;
         case E_humrec_data:
	    absbeat = outfile[i].getAbsBeat();
            if (repeatQ && (repeat-offset <= absbeat)) {
               repeatQ = 0;
               if (nextbartime <= absbeat) {
                  printbarline(newout, barcounter, voices, ":|!");
                  printAllSpines(newout, voices, "*>B");
                  barcounter = abs(barcounter);
                  barcounter++;
                  nextbartime += bardur;
               } else {
                  printbarline(newout, -100, voices, ":|!");
                  printAllSpines(newout, voices, "*>B");
               }
            } else if (nextbartime <= absbeat) {
               printbarline(newout, barcounter, voices, "");
               barcounter = abs(barcounter);
               barcounter++;
               nextbartime += bardur;
            }
            newout << outfile[i] << "\n";
            if (strcmp(outfile[i+1][0], "*-") == 0) {
               printbarline(newout, -100, voices, "=");
	    }
            break;
         case E_humrec_none:
         case E_humrec_empty:
         case E_humrec_global_comment:
         case E_humrec_bibliography:
         default:
            newout << outfile[i] << "\n";
            break;
      }

   }

   newout << "!!!hum2abc: -Q ''\n";
   newout << "!!!title: @{ONM}. @{OTL}\n";

   newout << ends;
   outfile.clear();
   outfile.read(newout);

   outfile.analyzeRhythm("4");
   addTies(outfile);

   outfile.analyzeRhythm("4");
   addBeaming(outfile, 0);
   addBeaming(outfile, 1);
   addBeaming(outfile, 2);
   addBeaming(outfile, 3);

}



//////////////////////////////
//
// addBeaming --
//

void addBeaming(HumdrumFile& outfile, int spine) {
   Array<int> noterow;
   noterow.setSize(outfile.getNumLines());
   noterow.setSize(0);

   Array<double> durs;
   durs.setSize(outfile.getNumLines());
   durs.setSize(0);
   double duration;

   Array<int> beats;
   beats.setSize(outfile.getNumLines());
   beats.setSize(0);
   int beat;

   int i;
   for (i=0; i<outfile.getNumLines(); i++) {
      if (outfile[i].getType() != E_humrec_data) {
         continue;
      }
      if (strcmp(outfile[i][spine], ".") == 0) {
         continue;
      }
      if (strchr(outfile[i][spine], 'r') != NULL) {
         continue;
      }
      duration = Convert::kernToDuration(outfile[i][spine]);
      if (fabs(duration - 0.5) < 0.001) {
         duration = 0.5;
      }
      if (fabs(duration - 0.25) < 0.001) {
         duration = 0.25;
      }
      if (duration <= 0.0) {
         continue;
      }
      beat = (int)(outfile[i].getAbsBeat() + 0.0001);

      beats.append(beat);
      durs.append(duration);
      noterow.append(i);
   }

   char buffer1[1024] = {0};
   char buffer2[1024] = {0};
   char buffer3[1024] = {0};

   for (i=0; i<beats.getSize(); i++) {
      if (durs[i] >= 1.0) {
         continue;
      }
      if (i >= beats.getSize()-1) {
         continue;
      }
      if (beats[i] != beats[i+1]) {
         continue;
      }
      if (durs[i+1] >= 1.0) {
         continue;
      }
      if (strchr(outfile[noterow[i]][spine], 'L') != NULL) {
         continue;
      }
      if (strchr(outfile[noterow[i]][spine], 'J') != NULL) {
         continue;
      }


      if ((durs[i] == 0.5) && (durs[i+1] == 0.5)) {
         strcpy(buffer1, outfile[noterow[i]][spine]);
         strcpy(buffer2, outfile[noterow[i+1]][spine]);
         strcat(buffer1, "L");
         strcat(buffer2, "J");
         outfile[noterow[i]].changeField(spine, buffer1);
         outfile[noterow[i+1]].changeField(spine, buffer2);
         continue;
      }

      if ((durs[i] == 0.75) && (durs[i+1] == 0.25)) {
         strcpy(buffer1, outfile[noterow[i]][spine]);
         strcpy(buffer2, outfile[noterow[i+1]][spine]);
         strcat(buffer1, "L");
         strcat(buffer2, "Jk");
         outfile[noterow[i]].changeField(spine, buffer1);
         outfile[noterow[i+1]].changeField(spine, buffer2);
         continue;
      }

      if ((durs[i] == 0.25) && (durs[i+1] == 0.75)) {
         strcpy(buffer1, outfile[noterow[i]][spine]);
         strcpy(buffer2, outfile[noterow[i+1]][spine]);
         strcat(buffer1, "LK");
         strcat(buffer2, "J");
         outfile[noterow[i]].changeField(spine, buffer1);
         outfile[noterow[i+1]].changeField(spine, buffer2);
         continue;
      }

      if (i >= beats.getSize()-2) {
         continue;
      }
      if ((beats[i] != beats[i+2]) || durs[i+2] >= 1.0) {
         if ((durs[i] == 0.25) && (durs[i+1] == 0.25)) {
            strcpy(buffer1, outfile[noterow[i]][spine]);
            strcpy(buffer2, outfile[noterow[i+1]][spine]);
            strcat(buffer1, "LL");
            strcat(buffer2, "JJ");
            outfile[noterow[i]].changeField(spine, buffer1);
            outfile[noterow[i+1]].changeField(spine, buffer2);
            continue;
         }
         continue;
      }

      if ((durs[i] == 0.5) && (durs[i+1] == 0.25) && (durs[i+2] == 0.25)) {
         strcpy(buffer1, outfile[noterow[i]][spine]);
         strcpy(buffer2, outfile[noterow[i+1]][spine]);
         strcpy(buffer3, outfile[noterow[i+2]][spine]);
         strcat(buffer1, "L");
         strcat(buffer2, "L");
         strcat(buffer3, "JJ");
         outfile[noterow[i]].changeField(spine, buffer1);
         outfile[noterow[i+1]].changeField(spine, buffer2);
         outfile[noterow[i+2]].changeField(spine, buffer3);
         continue;
      }

      if ((durs[i] == 0.25) && (durs[i+1] == 0.25) && (durs[i+2] == 0.5)) {
         strcpy(buffer1, outfile[noterow[i]][spine]);
         strcpy(buffer2, outfile[noterow[i+1]][spine]);
         strcpy(buffer3, outfile[noterow[i+2]][spine]);
         strcat(buffer1, "LL");
         strcat(buffer2, "J");
         strcat(buffer3, "J");
         outfile[noterow[i]].changeField(spine, buffer1);
         outfile[noterow[i+1]].changeField(spine, buffer2);
         outfile[noterow[i+2]].changeField(spine, buffer3);
         continue;
      }

      if (i >= beats.getSize()-3) {
         continue;
      }

      if ((beats[i] != beats[i+3]) || durs[i+3] >= 1.0) {
         continue;
      }


      if ((durs[i] == 0.25) && (durs[i+1] == 0.25) && (durs[i+2] == 0.25)
            && (durs[i+3] == 0.25)) {
         strcpy(buffer1, outfile[noterow[i]][spine]);
         strcpy(buffer2, outfile[noterow[i+3]][spine]);
         strcat(buffer1, "LL");
         strcat(buffer2, "JJ");
         outfile[noterow[i]].changeField(spine, buffer1);
         outfile[noterow[i+3]].changeField(spine, buffer2);
         continue;
      }

      cout << "GOT HERE" << endl;

   }

}



//////////////////////////////
//
// addTies --
//

void addTies(HumdrumFile& outfile) {
   int i, j;
   int measureQ = 0;
   for (i=0; i<outfile.getNumLines(); i++) {
      if (outfile[i].getType() == E_humrec_data_measure) {
         measureQ = 1;
      } else if (measureQ && (outfile[i].getType() == E_humrec_data)) {
         for (j=0; j<outfile[i].getFieldCount(); j++) {
            if (strcmp(outfile[i][j], ".") == 0) {
               fillNullToken(outfile, i, j);
            }
         }
         measureQ = 0;
      }
   }
}



//////////////////////////////
//
// fillNullToken --
//

void fillNullToken(HumdrumFile& outfile, int line, int spine) {
   int row = outfile[line].getDotLine(spine);
   int col = outfile[line].getDotSpine(spine);

   double dur = Convert::kernToDuration(outfile[row][col]);
   double firstdur = outfile[line].getAbsBeat() - outfile[row].getAbsBeat();
   double seconddur = dur - firstdur;
   
   char rhythm1[1024] = {0};
   Convert::durationToKernRhythm(rhythm1, firstdur);

   char rhythm2[1024] = {0};
   Convert::durationToKernRhythm(rhythm2, seconddur);


   char oldstring[1024] = {0};
   strcpy(oldstring, outfile[row][col]);
   char newstring[1024] = {0};
   makeNewToken(newstring, oldstring, rhythm1, TIESTART);
   outfile[row].changeField(col, newstring);

   makeNewToken(newstring, oldstring, rhythm2, TIEEND);
   outfile[line].changeField(spine, newstring);
}



//////////////////////////////
//
// makeNewToken -- replace the old duration with a new one, and add
//    tie start/end marks as needed.  Assumes that the token is not
//    a chord.
//


void makeNewToken(char* newstring, const char* oldstring, const char* rhythm,
      int tiestate) {

   char single[2] = {0};
   single[1] = '\0';
   newstring[0] = '\0';

   int restQ = 0;
   if (strchr(oldstring, 'r') != NULL) {
      restQ = 1;
   }

   if (tiestate == TIESTART) {
      if (!restQ) {
         strcat(newstring, "[");
      }
   }

   int rhythmfound = 0;
   int i;
   int tieprinted = 0;
   int len = strlen(oldstring);

   for (i=0; i<len; i++) {
      if (isdigit(oldstring[i]) || (oldstring[i] == '.')) {
         if (rhythmfound == 0) {
            strcat(newstring, rhythm);
            rhythmfound = 1;
         }
      } else if ((tiestate == TIEEND) && ((oldstring[i] == '/') || 
            (oldstring[i] == '\\'))) {
         if (!restQ) {
            strcat(newstring, "]");
         }
         tieprinted = 1;
      } else if ((tiestate == TIESTART) && (oldstring[i] == ';')) {
         // don't do anything: suppress fermata on first of tied notes
      } else {
         single[0] = oldstring[i];
         strcat(newstring, single);
      }
   }

   if ((tieprinted == 0) && (tiestate == TIEEND)) {
      if (!restQ) {
         strcat(newstring, "]");
      }
   }
}





//////////////////////////////
//
// printKeyInfo --
//

void printKeyInfo(ostream& out, const char* keyinfo, int voices) {
   char buffer[1024] = {0};
   strcpy(buffer, keyinfo);
   char* ptr1;
   char* ptr2;
   ptr1 = strtok(buffer, "-");
   ptr2 = strtok(NULL, "-");
   if ((ptr1 == NULL) || (ptr2 == NULL)) {
      cerr << "ERROR in key " << keyinfo << endl;
      exit(1);
   }

   int base40 = getBase40FromHokeyPitch(ptr1) % 40;
   int octave = 3; // major by default
   if (strcmp(ptr2, "Minor") == 0) {
      octave = 4;
   } else if (strcmp(ptr2, "Dorian") == 0) {
      octave = 4;
   } else if (strcmp(ptr2, "Phrygian") == 0) {
      octave = 4;
   } else if (strcmp(ptr2, "Locrian") == 0) {
      octave = 4;
   }
   
   base40 = base40 + octave * 40;

   char tempstring[1024] = {0};
   char keyname[1024] = {0};
   Convert::base40ToKern(tempstring, base40);
   strcpy(keyname, "*");
   strcat(keyname, tempstring);
   strcat(keyname, ":");
   
   int keynumber = 0;

   if (strcmp(keyname, "*C-:") == 0)      { keynumber = -7; }
   else if (strcmp(keyname, "*G-:") == 0) { keynumber = -6; }
   else if (strcmp(keyname, "*D-:") == 0) { keynumber = -5; }
   else if (strcmp(keyname, "*A-:") == 0) { keynumber = -4; }
   else if (strcmp(keyname, "*E-:") == 0) { keynumber = -3; }
   else if (strcmp(keyname, "*B-:") == 0) { keynumber = -2; }
   else if (strcmp(keyname, "*F:") == 0) { keynumber = -1; }
   else if (strcmp(keyname, "*C:") == 0) { keynumber =  0; }
   else if (strcmp(keyname, "*G:") == 0) { keynumber = +1; }
   else if (strcmp(keyname, "*D:") == 0) { keynumber = +2; }
   else if (strcmp(keyname, "*A:") == 0) { keynumber = +3; }
   else if (strcmp(keyname, "*E:") == 0) { keynumber = +4; }
   else if (strcmp(keyname, "*B:") == 0) { keynumber = +5; }
   else if (strcmp(keyname, "*F#:") == 0) { keynumber = +6; }
   else if (strcmp(keyname, "*C#:") == 0) { keynumber = +7; }
	     
   // minor keys
   else if (strcmp(keyname, "*a-:") == 0) { keynumber = -7; }
   else if (strcmp(keyname, "*e-:") == 0) { keynumber = -6; }
   else if (strcmp(keyname, "*b-:") == 0) { keynumber = -5; }
   else if (strcmp(keyname, "*f:") == 0) { keynumber = -4; }
   else if (strcmp(keyname, "*c:") == 0) { keynumber = -3; }
   else if (strcmp(keyname, "*g:") == 0) { keynumber = -2; }
   else if (strcmp(keyname, "*d:") == 0) { keynumber = -1; }
   else if (strcmp(keyname, "*a:") == 0) { keynumber =  0; }
   else if (strcmp(keyname, "*e:") == 0) { keynumber = +1; }
   else if (strcmp(keyname, "*b:") == 0) { keynumber = +2; }
   else if (strcmp(keyname, "*f#:") == 0) { keynumber = +3; }
   else if (strcmp(keyname, "*c#:") == 0) { keynumber = +4; }
   else if (strcmp(keyname, "*g#:") == 0) { keynumber = +5; }
   else if (strcmp(keyname, "*d#:") == 0) { keynumber = +6; }
   else if (strcmp(keyname, "*a#:") == 0) { keynumber = +7; }

   // adjust based on the mode

   if (strcmp(ptr2, "Dorian") == 0) {
      // add one sharp to the minor mode (e.g. one flat to natural for D Dorian)
      keynumber++;
   } else if (strcmp(ptr2, "Phrygian") == 0) {
      // subtract one (E minor has one sharp, E phrygian has no sharps)
      keynumber--;
   } else if (strcmp(ptr2, "Lydian") == 0) {
      // add a sharp
      keynumber++;
   } else if (strcmp(ptr2, "Mixolydian") == 0) {
      // subtract a sharp
      keynumber--;
   } else if (strcmp(ptr2, "Locrian") == 0) {
      // subtract 5 sharps
      keynumber-=5;
   }
	     

   // cerr << "BASE-40 " << base40 << endl;
   // cerr << "KEY-NAME " << keyname << endl;
   // cerr << "KEY-NUMBER " << keynumber << endl;
   
   const char* keystring = Convert::keyNumberToKern(keynumber);
   
   int i;

   // first print the key signature
   for (i=0; i<voices; i++) {
      out << keystring;
      if (i < voices - 1) {   
         out << "\t";
      }
   }
   out << "\n";
   
   // next print the key (major or minor part)
   for (i=0; i<voices; i++) {
      out << keyname;
      if (i < voices - 1) {   
         out << "\t";
      }
   }
   out << "\n";

   if (strcmp(ptr2, "Dorian") == 0) {
      printAllSpines(out, voices, "*KDor");
   } else if (strcmp(ptr2, "Phrygian") == 0) {
      printAllSpines(out, voices, "*KPhr");
   } else if (strcmp(ptr2, "Lydian") == 0) {
      printAllSpines(out, voices, "*KLyd");
   } else if (strcmp(ptr2, "Mixolydian") == 0) {
      printAllSpines(out, voices, "*KMix");
   } else if (strcmp(ptr2, "Locrian") == 0) {
      printAllSpines(out, voices, "*KLoc");
   }

 
   // finally print the mode if not major or minor

}



//////////////////////////////
//
// printAllSpines --
//

void printAllSpines(ostream& out, int voices, const char* string) {
   int i;
   for (i=0; i<voices; i++) {
      out << string;
      if (i < voices - 1) {
         out << "\t";
      }
   }
   out << "\n";
}



//////////////////////////////
//
// printbarline --
//

void printbarline(ostream& out, int barcounter, int voices, 
      const char* barstyle) {

   char number[1024] = {0};
   if (barcounter == -1) {
      strcat(number, "1-");
   } else if (barcounter > 0) {
      sprintf(number, "%d", barcounter);
   }
   
   int i;
   for (i=0; i<voices; i++) {
      out << "=" << number << barstyle;
      if (i < voices - 1) {
         out << "\t";
      }
   }
   out << "\n";
}



// md5sum: de629c4e12fae751a17b0b52551a4a3e hokey2humdrum.cpp [20081221]