//
// Programmer:    Craig Stuart Sapp <craig@ccrma.stanford.edu>
// Creation Date: Wed Apr 24 20:15:12 PDT 2002
// Last Modified: Wed Apr 24 20:15:15 PDT 2002
// Filename:      ...sig/examples/all/rism2kern.cpp
// Web Address:   http://sig.sapp.org/examples/museinfo/humdrum/rism2kern.cpp
// Syntax:        C++; museinfo
//
// Description:   Converts RISM II/A Plaine and Easie Code into **kern data.
//
// Todo:          expand beam groupings
//                identify fermatas
//                interpret trills
//                handle tuplets
//                measure repeat indicator
//                repeat barlines
//                !,F repetitions
//                natural sign indicator
//                clef changes (%F-4 )
//                full measure rests (single and multiple)
//                time signature
//                grace notes
//                acciaccatura notes (q and qq..r types)
//
// Done:          key signature
//                ties
//                beam groupings
//                accidentals (except for naturals, and tied accidentals)
//

#include "humdrum.h"

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

#ifndef OLDCPP
   #include <iostream>
   #include <fstream>
   using namespace std;
#else
   #include <iostream.h>
   #include <fstream.h>
#endif

typedef Array<char> ArrayChar;

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

class NoteObject {
   public:
             NoteObject(void) { clear(); };
      void   clear(void) {
                pitch = tie = beam = clef = clefnum = 0;
                duration = 0.0;
             };
      int    pitch;
      double duration;
      int    tie;
      int    beam;
      int    clef;
      int    clefnum;
};



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

// function declarations:
void      checkOptions(Options& opts, int argc, char** argv);
void      example(void);
void      usage(const char* command);
void      convertRism789ToKern(HumdrumFile& hfile, const char* line789);
void      getPieces(Array<ArrayChar>& pieces, const char* line);
void      getKeyInfo(Array<int>& key, Array<ArrayChar>& pieces);
void      printKeyInfo(ostream& out, Array<int>& key, Array<ArrayChar>& pieces);
void      generateMusicData(Array<NoteObject>& notes, 
                                 Array<ArrayChar>& pieces, Array<int>& key);
void      printPitches(ostream& out, Array<NoteObject>& notes);
int       getLineIndex(Array<ArrayChar>& pieces, const char* string);

// User interface variables:
Options   options;
int       debugQ = 0;          // used with the --debug option


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

int main(int argc, char** argv) {
   // process the command-line options
   checkOptions(options, argc, argv);

   HumdrumFile hfile;
   #ifndef OLDCPP
      ifstream infile(options.getArg(1));
   #else
      ifstream infile(options.getArg(1), ios::nocreate);
   #endif
   if (!infile.is_open()) {
      cout << "Error: cannot open file: " << options.getArg(1) << endl;
      exit(1);
   }
   char line[2048] = {0};
   while (!infile.eof()) {
      while (!infile.eof() && strncmp(line, "!!!marc789:", 11) != 0) {
         infile.getline(line, 1024, '\n');
      }
      if (strncmp(line, "!!!marc789:", 11) == 0) {
         convertRism789ToKern(hfile, line);
      }
      infile.getline(line, 1024, '\n');
   }

   return 0;
}

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


//////////////////////////////
//
// convertRism789ToKern --
//

void convertRism789ToKern(HumdrumFile& hfile, const char* line789) {

   // cout << "LINE: " << line789 << endl;
   Array<ArrayChar> pieces;
   getPieces(pieces, line789);

   // cout << "There are " << pieces.getSize() << " subfields" << endl;
   // for (int i=0; i<pieces.getSize(); i++) {
   //    cout << "\t" << pieces[i].getBase() << "\n";
   // }

   Array<NoteObject> notes;
   Array<int> key(7);
   key.setAll(0);
   getKeyInfo(key, pieces);
   generateMusicData(notes, pieces, key);

   cout << "**kern\n";
   printKeyInfo(cout, key, pieces);
   printPitches(cout, notes);
   cout << "*-\n\n";
}



//////////////////////////////
//
// generateMusicData --
//

void generateMusicData(Array<NoteObject>& notes, Array<ArrayChar>& pieces, 
      Array<int>& key) {

   notes.setSize(0);
   NoteObject tempnote;

   int index = getLineIndex(pieces, "$h");
   if (index == -1) {
      return;
   }

cout << "PAEDATA: " << pieces[index].getBase() << endl;

   int length = strlen(pieces[index].getBase());


   // measure octave at each position in the data string
   Array<int> octave;
   octave.setSize(length);
   octave.setAll(0);
   int i;
   for (i=1; i<length; i++) {
      switch (pieces[index][i]) {
         case '\'':
            if (pieces[index][i-1] == '\'') {
               octave[i] = octave[i-1] + 1;
            } else {
               octave[i] = 1;
            }
            break;
         case ',':
            if (pieces[index][i-1] == ',') {
               octave[i] = octave[i-1] - 1;
            } else {
               octave[i] = -1;
            }
            break;
         default:
            octave[i] = octave[i-1];
            break;
      }
   }
   for (i=0; i<length; i++) {
      switch (octave[i]) {
         case  0:  octave[i] = 4;  break;
         case  1:  octave[i] = 4;  break;
         case  2:  octave[i] = 5;  break;
         case  3:  octave[i] = 6;  break;
         case  4:  octave[i] = 7;  break;
         case  5:  octave[i] = 8;  break;
         case  6:  octave[i] = 9;  break;
         case -1:  octave[i] = 3;  break;
         case -2:  octave[i] = 2;  break;
         case -3:  octave[i] = 1;  break;
         case -4:  octave[i] = 0;  break;
         default:  octave[i] = 4;
      }
   }


   Array<int> acc;
   acc.setSize(length);
   acc.setAll(0);
   // measure accidentals at each position in the data string
   for (i=0; i<length; i++) {
      switch (pieces[index][i]) {
         case 'x': acc[i] = acc[i-1] + 1;  break;
         case 'b': acc[i] = acc[i-1] - 1;  break;
         case 'n': acc[i] = 100;           break;
      }
   }
   int currentacc = 0;
   for (i=1; i<length; i++) {
      if (pieces[index][i] == 'x' || pieces[index][i] == 'b' ||
          pieces[index][i] == 'n') {
         currentacc = acc[i];
      } else if ((pieces[index][i]-'A'>=0) && (pieces[index][i]-'A'<7)) {
         acc[i] = currentacc;
         currentacc = 0;
      }
   }

   // calculate slur information
   Array<int> tempslur;
   tempslur.setSize(length);
   tempslur.setAll(0);
   int lasti = 0;
   int ii;
   for (i=0; i<length; i++) {
      if ((pieces[index][i] - 'A' >= 0) && (pieces[index][i] - 'A' < 7)) {
         lasti = i;
      } else if (pieces[index][i] == '+') {
         if (tempslur[lasti]) {
            tempslur[lasti] = 3;
         } else {
            tempslur[lasti] = 1;
         }
         for (ii=i; ii<length; ii++) {
            if ((pieces[index][ii]-'A'>= 0) && (pieces[index][ii]-'A'<7)) {
               tempslur[ii] = 2;
               i = ii;
               break;
            }
         }
      }
   }

   // calculate beam grouping information
   Array<int> tempbeam;
   tempbeam.setSize(length);
   tempbeam.setAll(0);
   lasti = 0;
   for (i=0; i<length; i++) {
      if ((pieces[index][i] - 'A' >= 0) && (pieces[index][i] - 'A' < 7)) {
         lasti = i;
      } else if (pieces[index][i] == '}') {
         tempbeam[lasti] = 2;
      } else if (pieces[index][i] == '{') {
           for (ii=i; ii<length; ii++) {
              if ((pieces[index][ii]-'A'>= 0) && (pieces[index][ii]-'A'<7)) {
                 tempbeam[ii] = 1;
                 i = ii;
                 lasti = i;
                 break;
              }
           }
      }
   }


   // calculate durations for all positions in data string
   Array<double> duration;
   duration.setSize(length);
   duration.setAll(0.0);
   int dots = 0;
   double basedur = 0.0;
   for (i=1; i<length; i++) {
     switch (pieces[index][i]) {
         case '0': duration[i] = 16.0; basedur = duration[i]; dots = 0;
            break;
         case '1': duration[i] = 4.0; basedur = duration[i]; dots = 0;
            break;
         case '2': duration[i] = 2.0; basedur = duration[i]; dots = 0;
            break;
         case '3': duration[i] = 4.0/32.0; basedur = duration[i]; dots = 0;
            break;
         case '4': duration[i] = 1.0; basedur = duration[i]; dots = 0;
            break;
         case '5': duration[i] = 4.0/64.0; basedur = duration[i]; dots = 0;
            break;
         case '6': duration[i] = 0.25; basedur = duration[i]; dots = 0;
            break;
         case '7': duration[i] = 4.0/128.0; basedur = duration[i]; dots = 0;
            break;
         case '8': duration[i] = 0.5; basedur = duration[i]; dots = 0;
            break;
         case '9': duration[i] = 8.0; basedur = duration[i]; dots = 0;
            break;
         case '.':
            dots++;
            duration[i] = duration[i-1] + basedur * pow(2.0, -dots);
            break;
         default:
            dots = 0;
            duration[i] = duration[i-1];
            break;
      }
   }
   // adjust for tuplet durations here...


   Array<int> kkey;
   kkey.setSize(key.getSize());
   for (i=0; i<kkey.getSize(); i++) {
      if (key[i] < 0) {
         kkey[i] = -1;
      } else if (key[i] > 0) {
         kkey[i] = +1;
      } else {
         kkey[i] = 0;
      }
   }

   int accidental;
   Array<int> mkey(7);
   mkey = kkey;
   int natural = 0;
   int pitch;
   for (i=0; i<length; i++) {
      if (pieces[index][i] == '/') {
         mkey = kkey;
      }
      accidental = acc[i];
      if (accidental > 5) {
         // accidental = 0;
         natural = 1;
      }
      pitch = 1000;
      switch (pieces[index][i]) {
         case 'A': 
            if (accidental != 0) {
               if (natural) {
                   pitch = 31 + 40 * octave[i];
                   mkey[4] = 0;
                   natural = 0;
               } else {
                   pitch = 31 + 40 * octave[i] + accidental;
                   mkey[4] = accidental;
               }
            } else {
                pitch = 31 + 40 * octave[i] + mkey[4];
            }
            break;
         case 'B': 
            if (accidental != 0) {
               if (natural) {
                   pitch = 37 + 40 * octave[i];
                   mkey[6] = 0;
                   natural = 0;
               } else {
                   pitch = 37 + 40 * octave[i] + accidental;
                   mkey[6] = accidental;
               }
            } else {
                pitch = 37 + 40 * octave[i] + mkey[6];
            }
            break;
         case 'C': 
            if (accidental != 0) {
               if (natural) {
                   pitch = 2 + 40 * octave[i];
                   mkey[1] = 0;
                   natural = 0;
               } else {
                   pitch = 2 + 40 * octave[i] + accidental;
                   mkey[1] = accidental;
               }
            } else {
                pitch = 2 + 40 * octave[i] + mkey[1];
            }
            break;
         case 'D': 
            if (accidental != 0) {
               if (natural) {
                   pitch = 8 + 40 * octave[i];
                   mkey[3] = 0;
                   natural = 0;
               } else {
                   pitch = 8 + 40 * octave[i] + accidental;
                   mkey[3] = accidental;
               }
            } else {
                pitch = 8 + 40 * octave[i] + mkey[3];
            }
            break;
         case 'E': 
            if (accidental != 0) {
               if (natural) {
                   pitch = 14 + 40 * octave[i];
                   mkey[5] = 0;
                   natural = 0;
               } else {
                   pitch = 14 + 40 * octave[i] + accidental;
                   mkey[5] = accidental;
               }
            } else {
                pitch = 14 + 40 * octave[i] + mkey[5];
            }
            break;
         case 'F': 
            if (accidental != 0) {
               if (natural) {
                   pitch = 19 + 40 * octave[i];
                   mkey[0] = 0;
                   natural = 0;
               } else {
                   pitch = 19 + 40 * octave[i] + accidental;
                   mkey[0] = accidental;
               }
            } else {
                pitch = 19 + 40 * octave[i] + mkey[0];
            }
            break;
         case 'G': 
            if (accidental != 0) {
               if (natural) {
                   pitch = 25 + 40 * octave[i];
                   mkey[2] = 0;
                   natural = 0;
               } else {
                   pitch = 25 + 40 * octave[i] + accidental;
                   mkey[2] = accidental;
               }
            } else {
                pitch = 25 + 40 * octave[i] + mkey[2];
            }
            break;
         case '/': pitch = 0; break;
         case '-': pitch = -1000; break;
         case '=': pitch = -1000; break;
         default:
            break;
      }
      if (pitch < 1000) {
         tempnote.pitch    = pitch;
         tempnote.duration = duration[i];
         tempnote.tie      = tempslur[i];
         tempnote.beam     = tempbeam[i];
         notes.append(tempnote);
      }
   }

}



//////////////////////////////
//
// printPitches --
//

void printPitches(ostream& out, Array& notes) {
   char buffer[1024] = {0};
   int i;
   for (i=0; i<notes.getSize(); i++) {
      if (notes[i].pitch < 0) {
         if (notes[i].tie == 1) {
            out << "[";
         }
         out << Convert::durationToKernRhythm(buffer, notes[i].duration);
         out << "r";
         if (notes[i].tie == 2) {
            out << "]";
         } else if (notes[i].tie == 3) {
            out << "_";
         }
         if (notes[i].beam == 1) {
            out << "L";
         } else if (notes[i].beam == 2) {
            out << "J";
         }
      } else if (notes[i].pitch == 0) {
         out << "=";
      } else {
         if (notes[i].tie == 1) {
            out << "[";
         }
         out << Convert::durationToKernRhythm(buffer, notes[i].duration);
         out << Convert::base40ToKern(buffer, notes[i].pitch);
         if (notes[i].tie == 2) {
            out << "]";
         } else if (notes[i].tie == 3) {
            out << "_";
         }
         if (notes[i].beam == 1) {
            out << "L";
         } else if (notes[i].beam == 2) {
            out << "J";
         }
      }
      out << "\n";
   }


}



//////////////////////////////
//
// getKeyInfo -- read the key signature.
//

void getKeyInfo(Array& key, Array& pieces) {
   int i;
   for (i=0; i<pieces.getSize(); i++) {
      if (strncmp(pieces[i].getBase(), "$f", 2) == 0) {
         break;
      }
   }
   if (i >= pieces.getSize()) {
      return;
   }
   int line = i;
   int length = strlen(pieces[line].getBase());
   for (i=0; i<length; i++) {
      if (pieces[line][i] == ':') {
         break;
      }
   }
   if (i >= length) {
      return;
   }
   // cout << "Key: " << &pieces[line][i] << "\t=\t";

   // at the key information line, extract data
   int type = 1;   // 1 = sharps, 2 = flats
   int paren = 1;  // 1 = normal, 2 = inside of square brackets
   while (i < length) {
      switch (pieces[line][i]) {
         case 'b': type  = -1; break;
         case 'x': type  =  1; break;
         case '[': paren =  2; break;
         case ']': paren =  1; break;
         case 'F': key[0] = type * paren; break;
         case 'C': key[1] = type * paren; break;
         case 'G': key[2] = type * paren; break;
         case 'D': key[3] = type * paren; break;
         case 'A': key[4] = type * paren; break;
         case 'E': key[5] = type * paren; break;
         case 'B': key[6] = type * paren; break;
         case ' ': break;
         default:
            if (debugQ) {
               cout << "Error: unknown character: " << pieces[line][i]
                    << endl;
               exit(1);
            }
            break;
      }
      i++;
   }

}



//////////////////////////////
//
// printKeyInfo -- not worrying about mixed sharp/flat key sigs 
//   for now...
//

void printKeyInfo(ostream& out, Array& key, Array& pieces) {
   int i;
   for (i=0; i<pieces.getSize(); i++) {
      if (strncmp(pieces[i].getBase(), "$f", 2) == 0) {
         break;
      }
   }
   int pline = i;
   char* ptr;
   if (pline<pieces.getSize()) {
      for (i=0; i<7; i++) {
         if (abs(key[i]) > 1) {
            out << "!! Key signature";
            ptr = strchr(pieces[pline].getBase(), ':');
            if (ptr != NULL) {
               out << ptr;
            }
            out << "\n";
            break;
         }
      }
   }

   out << "*k[";
   if (key[0] > 0) {
      if (key[0] > 0) out << "f#"; else if (key[0] < 0) out << "f-";
      if (key[1] > 0) out << "c#"; else if (key[0] < 0) out << "c-";
      if (key[2] > 0) out << "g#"; else if (key[0] < 0) out << "g-";
      if (key[3] > 0) out << "d#"; else if (key[0] < 0) out << "d-";
      if (key[4] > 0) out << "a#"; else if (key[0] < 0) out << "a-";
      if (key[5] > 0) out << "e#"; else if (key[0] < 0) out << "e-";
      if (key[6] > 0) out << "b#"; else if (key[0] < 0) out << "b-";
   } else {
      if (key[6] < 0) out << "b-"; else if (key[0] > 0) out << "b#";
      if (key[5] < 0) out << "e-"; else if (key[0] > 0) out << "e#";
      if (key[4] < 0) out << "a-"; else if (key[0] > 0) out << "a#";
      if (key[3] < 0) out << "d-"; else if (key[0] > 0) out << "d#";
      if (key[2] < 0) out << "g-"; else if (key[0] > 0) out << "g#";
      if (key[1] < 0) out << "c-"; else if (key[0] > 0) out << "c#";
      if (key[0] < 0) out << "f-"; else if (key[0] > 0) out << "g#";
   }
   out << "]" << endl;
}




//////////////////////////////
//
// getPieces --
//

void getPieces(Array& pieces, const char* line) {
   // count the number of $ signs in line
   int count = 0;
   int i;
   int length = strlen(line);
   for (i=0; i<length; i++) {
      if (line[i] == '$') {
         count++;
      }
   }
   pieces.setSize(count);
   pieces.allowGrowth(0);
   for (i=0; i<pieces.getSize(); i++) {
      pieces[i].setSize(1024);
      pieces[i].setAll(0);
   }
   while (i<length && line[i] != '$') {
      i++;
   }
   if (i==length) {
      pieces.setSize(0);
      return;
   }

   int current = 0;
   char character[2] = {0};
   character[0] = line[i];
   strcat(pieces[0].getBase(), "$");
   i++;
   while (i<length) {
      if (line[i] == '$') {
         current++;
      } 
      character[0] = line[i];
      strcat(pieces[current].getBase(), character);
      i++;
   }
   current++;
   pieces.setSize(current);

}



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

void checkOptions(Options& opts, int argc, char* argv[]) {
   opts.define("debug=b",  "print debug information"); 

   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, April 2002" << endl;
      exit(0);
   } else if (opts.getBoolean("version")) {
      cout << argv[0] << ", version: 24 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);
   }
   
   debugQ      = opts.getBoolean("debug");
}



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

void example(void) {


}



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

void usage(const char* command) {

}




//////////////////////////////
//
// getLineIndex -- get the index location of the given string.
//

int getLineIndex(Array& pieces, const char* string) {
   int index = -1;
   int i;
   for (i=0; i<pieces.getSize(); i++) {
      if (strstr(pieces[i].getBase(), string) != NULL) {
         index = i;
         break;
      }
   }
   return index;
}



// md5sum: d1a6480ff58d1dbfc461df3ef8c82029 rism2kern.cpp [20050403]