//
// Programmer:    Craig Stuart Sapp <craig@ccrma.stanford.edu>
// Creation Date: Sat Sep 20 06:35:45 PDT 2008
// Last Modified: Sat Sep 20 06:36:19 PDT 2008
// Filename:      ...museinfo/examples/all/koto2xml.cpp
// Web Address:   http://sig.sapp.org/examples/museinfo/humdrum/koto2xml.cpp
// Syntax:        C++; museinfo
//
// Description:   Convert **koto data into MEI XML and MusicXML.
//

#include "humdrum.h"

#define MEIXML   1
#define MUSICXML 2


// function declarations
void  printTabs(ostream& outstream, int count);
void  example(void);
void  usage(const char* command);
void  checkOptions(Options& opts, int argc, char* argv[]);
void  processHumdrumFile(HumdrumFile& infile, int outputType);


// MEI XML functions:
void  printMeiXml(ostream& outstream, HumdrumFile& infile);
void  printMeiHeader(ostream& outstream, HumdrumFile& infile, int level);
void  printMeiWork(ostream& outstream, HumdrumFile& infile, int level);
void  printMeiKotoPart(ostream& outstream, HumdrumFile& infile, int spine, 
                          int level);
void  printMeiMeasure(ostream& outstream, HumdrumFile& infile, int spine, 
                          int start, int level);
void  printMeiNoteData(ostream& outstream, const char* string, int level);
void  printMeiChordData(ostream& outstream, HumdrumFile& infile, int spine, 
                          int line, int level);
void  printMeiPitch(ostream& outstream, int pitch);
void  printMeiRhythm(ostream& outstream, const char* data);

// MusicXML functions:
void  printMusicXml(ostream& outstream, HumdrumFile& infile);


// Shared functions:
char* getTitle(char* buffer, int buffsize, HumdrumFile& infile);
void  printEncodedString(ostream& outstream, char* buffer);
int   checkForTune(int* mapping, HumdrumRecord& arecord, int spine);
int   getKotoString(const char* data);
int   getRhythm(const char* data);
int   getDots(const char* data);


// User interface variables:
Options options;
int meiQ      = 1;   // default behavior
int musicxmlQ = 0;   // convert to MusicXML


int STRINGMAPPING[21] = {0};


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

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

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

   for (int i=0; i<numinputs || i==0; i++) {
      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));
      }

      processHumdrumFile(infile, MEIXML);
   }

   return 0;
}


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

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

void checkOptions(Options& opts, int argc, char* argv[]) {
   opts.define("debug=b",  "trace input parsing");
   opts.define("author=b",  "author of the program");
   opts.define("version=b", "compilation information");
   opts.define("example=b", "example usage");
   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, Sep 2008" << endl;
      exit(0);
   } else if (opts.getBoolean("version")) {
      cout << argv[0] << ", version: Sep 2008" << 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);
   }

}



//////////////////////////////
//
// processHumdrumFile -- 
//

void processHumdrumFile(HumdrumFile& infile, int outputType) {
   switch (outputType) {
      case MEIXML:     printMeiXml(std::cout, infile);     break;
      case MUSICXML:   printMusicXml(std::cout, infile);   break;
   }
}



//////////////////////////////
//
// example -- example usage of the scrub program
//

void example(void) {
   cout <<
   "                                                                        \n"
   << endl;
}



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

void usage(const char* command) {
   cout <<
   "                                                                        \n"
   << endl;
}



//////////////////////////////
//
// printTabs --
//

void printTabs(ostream& outstream, int count) {
   for (int i=0; i<count && i < 1000; i++) {
      // outstream << '\t';
      outstream << "  ";
   }
}


///////////////////////////////////////////////////////////////////////////
//
// MEI XML functions
//

//////////////////////////////
//
// printMeiXml --
//

void printMeiXml(ostream& outstream, HumdrumFile& infile) {
   outstream << "<?xml version='1.0' encoding='UTF-8'?>" << "\n";
   outstream << "<!DOCTYPE mei SYSTEM 'mei18bFull.dtd'";
   outstream << '>' << '\n';


   outstream << "<mei version='1.8b'>\n";

   int level = 1;
   printMeiHeader(outstream, infile, level);
   printMeiWork(outstream, infile, level);
   
   outstream << "</mei>\n";
}


//////////////////////////////
//
// printMeiHeader --
//

void printMeiHeader(ostream& outstream, HumdrumFile& infile, int level) {
   #define BUFFSIZE 10000
   char buffer[BUFFSIZE];
   printTabs(outstream, level);   outstream << "<meihead>"     << "\n";
   printTabs(outstream, level+1); outstream << "<filedesc>"    << "\n";
   printTabs(outstream, level+2); outstream << "<titlestmt>"   << "\n";

   getTitle(buffer, BUFFSIZE, infile);
   if (strlen(buffer) == 0) {
      printTabs(outstream, level+3); outstream << "<title/>\n";
   } else {
      printTabs(outstream, level+3);
      outstream << "<title>";
      printEncodedString(outstream, buffer);
      outstream << "</title>\n";

   }

   printTabs(outstream, level+2);  outstream << "</titlestmt>" << "\n";
   printTabs(outstream, level+2);  outstream << "<pubstmt/>"   << "\n";
   printTabs(outstream, level+1);  outstream << "</filedesc>"  << "\n";
   printTabs(outstream, level);    outstream << "</meihead>"   << "\n";
}


//////////////////////////////
//
// printMeiWork --
//

void printMeiWork(ostream& outstream, HumdrumFile& infile, int level) {
   printTabs(outstream, level  ); outstream << "<music>"       << "\n";
   printTabs(outstream, level+1); outstream << "<body>"        << "\n";
   printTabs(outstream, level+2); outstream << "<mdiv>"        << "\n";
   printTabs(outstream, level+3); outstream << "<score>"       << "\n";

   printTabs(outstream, level+4); outstream << "<staffgrp>"    << "\n";
   printTabs(outstream, level+5); outstream << "<staffdef n='1'/>" << "\n";
   printTabs(outstream, level+4); outstream << "</staffgrp>"   << "\n";

   printMeiKotoPart(outstream, infile, 0, level+4);

   printTabs(outstream, level+3); outstream << "</score>"      << "\n";
   printTabs(outstream, level+2); outstream << "</mdiv>"       << "\n";
   printTabs(outstream, level+1); outstream << "</body>"       << "\n";
   printTabs(outstream, level  ); outstream << "</music>"      << "\n";
}



//////////////////////////////
//
// printMeiKotoPart --
//

void printMeiKotoPart(ostream& outstream, HumdrumFile& infile, int spine, 
      int level) {

   int i;
   for (i=0; i<infile.getNumLines(); i++) {
      switch (infile[i].getType()) {
         case E_humrec_interpretation:
            checkForTune(STRINGMAPPING, infile[i], spine);
            break;
	 case E_humrec_data_measure:
            if (strncmp(infile[i][spine], "==", 2) != 0) {
               // online print measure if note a double-barline
               // probably check later to see if there is an end of file
               printMeiMeasure(outstream, infile, spine, i, level);
            }
            break;
         //default:
             // do nothing
      }
   }
}


//////////////////////////////
//
// printMeiMeasure --
//

void printMeiMeasure(ostream& outstream, HumdrumFile& infile, int spine, 
      int start, int level) {
   int i;
   if (infile[start][spine][0] != '=') {
      std::cout << "ERROR: line does not have measure: " 
                << infile[start][spine] << endl;
      exit(1);
   }
   static int measure = -1;
   int flag = sscanf(infile[start][spine], "=%d", &measure);
   if (flag != 1) {
      measure++;
   }

   printTabs(outstream, level); 
   outstream << "<measure n='" << measure << "'>"  << "\n";

   printTabs(outstream, level+1); outstream << "<staff n='1'>" << "\n";

   start++;
   for (i=start; i<infile.getNumLines(); i++) {
      if (infile[i].getType() == E_humrec_interpretation) {
         checkForTune(STRINGMAPPING, infile[i], spine);
      } else if (infile[i].getType() == E_humrec_data_measure) {
         break;
      } else if (infile[i].getType() == E_humrec_data) {
         printMeiChordData(outstream, infile, spine, i, level+2);
      } else {
      }
   }

   printTabs(outstream, level+1); outstream << "</staff>"    << "\n";
   printTabs(outstream, level);   outstream << "</measure>"  << "\n";

   // perhaps return the new line here for processing next measure
}



//////////////////////////////
//
// printMeiChordData --
//

void printMeiChordData(ostream& outstream, HumdrumFile& infile, int spine, 
      int line, int level) {
   int tokencount = infile[line].getTokenCount(spine);


   if (tokencount < 1) {
      return;
   }
   if (tokencount == 1) {    // a single note
      printMeiNoteData(outstream, infile[line][spine], level); 
      return;
   }

   // print chord
   printTabs(outstream, level); outstream << "<chord>" << "\n";
   char buffer[1024];
   int i;
   for (i=0; i<tokencount; i++) {
      infile[line].getToken(buffer, spine, i, 1024);
      printMeiNoteData(outstream, buffer, level+1);
   }
   printTabs(outstream, level); outstream << "</chord>" << "\n";

}



//////////////////////////////
//
// printMeiNoteData --
//

void printMeiNoteData(ostream& outstream, const char* data, int level) {
   if (strcmp(data, ".") == 0) {
      // nothing to print
      return;
   }

   // WARNING: Have to deal with chords
   int string = getKotoString(data);
   

   printTabs(outstream, level);
   if (string == 0) {
      outstream << "<rest";
      printMeiRhythm(outstream, data);
      outstream << "/>\n";
   } else if (string == 14) {
      outstream << "<space";
      printMeiRhythm(outstream, data);
      outstream << "/>\n";   
   } else if (string > 0) {
      outstream << "<note tab.string='" << string << "'";
      printMeiPitch(outstream, STRINGMAPPING[string-1]);
      printMeiRhythm(outstream, data);
      outstream << "/>" << "\n";
   }

}



//////////////////////////////
//
// printMeiRhythm --
//

void printMeiRhythm(ostream& outstream, const char* data) {
   int rhythm = getRhythm(data);
   int dots   = getDots(data);

   outstream << " dur='" << rhythm << "'";
   if (dots > 0) {
      outstream << " dots='" << dots << "'";
   }
}



//////////////////////////////
//
// printMeiPitch --
//

void printMeiPitch(ostream& outstream, int pitch) {
  int octave      =  pitch / 40;
  int accidental  = Convert::base40ToAccidental(pitch);
  int diatonic    = Convert::base40ToDiatonic(pitch) % 7;
  
   if (diatonic < 0 || diatonic > 6) {
      std::cerr << "ERROR: invalid diatonic number: " 
                << diatonic << " (" << pitch << ")" << endl;
      exit(1);
   }

   char Diatonic[7] = {'c', 'd', 'e', 'f', 'g', 'a', 'b'};


   outstream << " pname='" << Diatonic[diatonic] << "'";
   outstream << " oct='" << octave << "'";
   if (accidental != 0) {
      outstream << " accid.ges='";
      switch (accidental) {
         case  2:  outstream << "ss"; break;
         case  1:  outstream << "s";  break;
         case -1:  outstream << "f";  break;
         case -2:  outstream << "ff"; break;
      }
      outstream << "'";
   }

}




///////////////////////////////////////////////////////////////////////////
//
// MusicXML functions
//


void printMusicXml(ostream& outstream, HumdrumFile& infile) {
  // do nothing for now 
}



///////////////////////////////////////////////////////////////////////////
//
// Shared functions
//


//////////////////////////////
//
// printEncodedString -- escape quote output string
//   < = <
//   > = >
//   & = &
//   ' = '
//   " = "
//

void printEncodedString(ostream& outstream, char* buffer) {
   int i;
   int length = strlen(buffer);
   for (i=0; i<length; i++) {
      switch (buffer[i]) {
         case '<':  outstream << "<";  break;
         case '>':  outstream << ">";  break;
         case '&':  outstream << "&"; break;
         case '\'': outstream << "&"; break;
         case '\"': outstream << "&"; break;
         default:   outstream << buffer[i];
      }
   }
}



//////////////////////////////
//
// getTitle -- Return the first !!!OTL record in the file.
//

char* getTitle(char* buffer, int buffsize, HumdrumFile& infile) {
   int i, j;
   int length;
   int colon = -1;
   buffer[0] = 0;
   for (i=0; i<infile.getNumLines(); i++) {
      if (infile[i].getType() == E_humrec_bibliography) {
         if (strncmp(infile[i][0], "!!!OTL", 6) == 0) {
            length = strlen(infile[i][0]);
            for (j=6; j<length; j++) {
               if (infile[i][0][j] == ':') {
                  colon = j+1;
                  break;
               }
            }
            if (colon < 0) {
               return buffer;
            }
            while (j < length && (infile[i][0][colon] == ' ' ||
                                  infile[i][0][colon] == '\n')) {
               colon++;
            }
            strncpy(buffer, &(infile[i][0][colon]), buffsize-1);
         }
      }
   }
   
   return buffer;
}



//////////////////////////////
//
// checkForTune -- Look for tuning directive in data
//

int checkForTune(int* mapping, HumdrumRecord& arecord, int spine) {
   if (strncmp(arecord[spine], "*tune[", 6) != 0) {
      return 0;
   }

   char buffer[1024];
   strncpy(buffer, &(arecord[spine][6]), 1023);
   char* ptr = strtok(buffer, ": \n\t]");
   int counter = 0;
   while (ptr != NULL && counter < 21) {
      mapping[counter] = Convert::kernToBase40(ptr);
      // std::cout << "FOUND " << counter << " to be " << ptr << endl;
      counter++;
      ptr = strtok(NULL, ": \n\t]");
   }
   // std::cout << "COUNTER = " << counter << endl;

   return counter;
}



//////////////////////////////
//
// getKotoString --
//

int getKotoString(const char* data) {
   int i;
   int length = strlen(data);
   for (i=0; i<length; i++) {
      switch (data[i]) {
         case '1': return 1;
         case '2': return 2;
         case '3': return 3;
         case '4': return 4;
         case '5': return 5;
         case '6': return 6;
         case '7': return 7;
         case '8': return 8;
         case '9': return 9;
         case 'A': return 10;
         case 'B': return 11;
         case 'C': return 12;
         case 'D': return 13;
         case '0': return 0;
         case '-': return 14;
      }
   }

   return -1;
}



//////////////////////////////
//
// getRhythm -- return the numeric part of the rhythm (excluding 
//      augmentation dots)
//

int getRhythm(const char* data) {
   double duration = Convert::kotoToDuration(data);
   char buffer[1024];
   Convert::durationToKernRhythm(buffer, duration);
   int i;
   int length = strlen(buffer);
   int output = 0;
   for (i=0; i<length; i++) {
      if (isdigit(buffer[i])) {
         sscanf(&(buffer[i]), "%d", &output);
         break;
      }
   }

   return output;
}



//////////////////////////////
//
// getDots -- return the number of augmentation dots in the rhythm.
//

int getDots(const char* data) {
   int i;
   int length = strlen(data);
   int output = 0;
   for (i=0; i<length; i++) {
      if (data[i] == '.') {
         output++;
      }
   }
   return output;
}




// md5sum: 7ec5706a9a636a3405f6aa95317dce7d koto2xml.cpp [20101226]