//
// Programmer:    Craig Stuart Sapp <craig@ccrma.stanford.edu>
// Creation Date: Wed May  1 21:18:12 PDT 2002
// Last Modified: Wed May  1 21:18:16 PDT 2002
// Filename:      ...sig/examples/all/pitchmix.cpp
// Web Address:   http://sig.sapp.org/examples/museinfo/humdrum/pitchmix.cpp
// Syntax:        C++; museinfo
//
// Description:   Mix the ordering of pitches in a file, keeping the
//                durations in the same locations.
//

#include "humdrum.h"

#include <string.h>
#include <time.h>
#include <ctype.h>

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

class NoteUnit {
   public:
             NoteUnit(void) { clear(); };
      void   clear(void) {
                track = random = newpitch = pitch = line = spine = token = -1;
             };
      int    track;
      int    pitch;
      int    line;
      int    spine;
      int    token;
      int    random;
      int    newpitch;
};

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

// function declarations:
void      checkOptions(Options& opts, int argc, char** argv);
void      example(void);
void      usage(const char* command);
void      printNotes(Array<NoteUnit>& notes);
void      getNotes(Array<NoteUnit>& notes, HumdrumFile& infile);
int       compareNoteUnit(const void* A, const void* B);
int       compareNoteUnitTrack(const void* A, const void* B);
int       compareNoteSortTrack(const void* A, const void* B);
void      scrambleNotes(Array<NoteUnit>& notes);
void      replaceNotes(HumdrumFile& infile, Array<NoteUnit>& notes);
void      replaceNote(HumdrumFile& infile, int line, int spine, 
                                 int token, int newpitch, int oldpitch = -1);
void      updateTiedNotes(HumdrumFile& infile, int line, int spine, 
                                 int token, int newpitch, int oldpitch);
int       nearestNeighbor(int oldpitch, int newpitch);
int       throwDice(double piecefraction);


// User interface variables:
Options   options;
int       debugQ = 0;          // used with the --debug option
int       restQ = 0;           // mix rests as well with -r option
int       seed = 0;            // seed the random number generator
int       displaySeedQ = 0;    // used with the -S option
int       transQ = 0;          // display tranlation data only
int       trackQ = 0;          // randomize by track 
int       neighborQ = 0;       // used with -n option
int       lowlimit = 40;       // lowest note when tranposing
EnvelopeString distring;       // used with -d option
int       distQ = 0;           // used with -d option

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

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

   HumdrumFile infile;
   infile.read(options.getArg(1));
   if (distQ) {
      infile.analyzeRhythm();
   }
   Array<NoteUnit> notes;
   getNotes(notes, infile);
   scrambleNotes(notes);
   if (transQ) {
      printNotes(notes);
   } else {
      replaceNotes(infile, notes);
      cout << infile;
   }

   if (displaySeedQ) {
      cout << "!!!seed: " << seed << endl;
   }

   return 0;
}

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



//////////////////////////////
//
// replaceNotes --
//

void replaceNotes(HumdrumFile& infile, Array& notes) {
   int i;
   for (i=0; i<notes.getSize(); i++) {
      replaceNote(infile, notes[i].line, notes[i].spine,
         notes[i].token, notes[i].newpitch);
   }
}



//////////////////////////////
//
// replaceNote --
//

void replaceNote(HumdrumFile& infile, int line, int spine, int token, 
      int newpitch, int oldpitch) {
   // just assume one token in spine for now
   HumdrumRecord& record = infile[line];
   char prebuffer[128] = {0};
   char postbuffer[128] = {0};
   char pbuffer[128] = {0};
   Convert::base40ToKern(pbuffer, newpitch);
   if (oldpitch == -1) {
      oldpitch = Convert::kernToBase40(infile[line][spine]);
   }
   char tokenbuffer[128] = {0};
   infile[line].getToken(tokenbuffer, spine, token);
   int state = 0;
   int length = strlen(tokenbuffer);
   int i;
   for (i=0; i<length; i++) {
      if (state == 0) {
         if ((tolower(tokenbuffer[i]) == 'a') ||
             (tolower(tokenbuffer[i]) == 'b') ||
             (tolower(tokenbuffer[i]) == 'c') ||
             (tolower(tokenbuffer[i]) == 'd') ||
             (tolower(tokenbuffer[i]) == 'e') ||
             (tolower(tokenbuffer[i]) == 'f') ||
             (tolower(tokenbuffer[i]) == 'g') ||
             (tolower(tokenbuffer[i]) == '-') ||
             (tolower(tokenbuffer[i]) == '#') ||
             (tolower(tokenbuffer[i]) == 'n')) {
            state = 1;
            prebuffer[i] = '\0';
         } else {
            prebuffer[i] = tokenbuffer[i];
         }
      } else if (state == 1) {
         if ((tolower(tokenbuffer[i]) == 'a') ||
            (tolower(tokenbuffer[i]) == 'b') ||
            (tolower(tokenbuffer[i]) == 'c') ||
            (tolower(tokenbuffer[i]) == 'd') ||
            (tolower(tokenbuffer[i]) == 'e') ||
            (tolower(tokenbuffer[i]) == 'f') ||
            (tolower(tokenbuffer[i]) == 'g') ||
            (tolower(tokenbuffer[i]) == '-') ||
            (tolower(tokenbuffer[i]) == '#') ||
            (tolower(tokenbuffer[i]) == 'n')) {
         } else {
            strcpy(postbuffer, &tokenbuffer[i]);
            state = 2;
            break;
         }
      } else {
         strcpy(postbuffer, &tokenbuffer[i]);
         break;
      }
   }
   
   // int newlen = strlen(prebuffer) + strlen(pbuffer) + strlen(postbuffer);
   
   // strcpy(newitem, prebuffer);
   strcat(prebuffer, pbuffer);
   strcat(prebuffer, postbuffer);
   record.changeToken(spine, token, prebuffer);
 
   if (strchr(prebuffer, '[') != NULL) {
      updateTiedNotes(infile, line, spine, token, newpitch, oldpitch);
   }
}



//////////////////////////////
//
// scrambleNotes --
//

void scrambleNotes(Array& notes) {
   Array<NoteUnit> notes2;
   notes2 = notes;
   int i;
   for (i=0; i<notes2.getSize(); i++) {
      notes2[i].random = rand();
   }

   if (trackQ) {
      qsort(notes.getBase(), notes.getSize(), sizeof(NoteUnit), 
         compareNoteSortTrack);
      qsort(notes2.getBase(), notes2.getSize(), sizeof(NoteUnit), 
         compareNoteUnitTrack);
   } else {
      qsort(notes2.getBase(), notes2.getSize(), sizeof(NoteUnit), 
         compareNoteUnit);
   }

   if (neighborQ) {
      for (i=0; i<notes.getSize(); i++) {
         notes[i].newpitch = nearestNeighbor(notes[i].pitch, notes2[i].pitch);
      }
   } else {
      for (i=0; i<notes.getSize(); i++) {
         notes[i].newpitch = notes2[i].pitch;
      }
   }
}



//////////////////////////////
//
// getNotes -- 
//

void getNotes(Array& notes, HumdrumFile& infile) {

   notes.setSize(100000);
   notes.setGrowth(100000);
   notes.setSize(0);
   NoteUnit tempnote;
   int tokencount;
   int track = 0;
   char buffer[1024] = {0};
   int i, j, k;
   int dice = 0;
   for (i=0; i<infile.getNumLines(); i++) {
      if (!infile[i].isData()) {
         continue;
      }
      for (j=0; j<infile[i].getFieldCount(); j++) {
         if (strcmp("**kern", infile[i].getExInterp(j)) != 0) {
            continue;
         }
         tokencount = infile[i].getTokenCount(j);
         track = infile[i].getPrimaryTrack(j);
         for (k=0; k<tokencount; k++) {
            infile[i].getToken(buffer, j, k);
            if (strcmp(buffer, ".") == 0) {
               continue;
            }
            if (strchr(buffer, '_') != NULL) {
               continue;
            }
            if (strchr(buffer, ']') != NULL) {
               continue;
            }
            tempnote.pitch = Convert::kernToBase40(buffer);
            if (restQ == 0 && tempnote.pitch < 0) {
               continue;
            }
            if (distQ) {
               dice = throwDice((double)infile[i].getAbsBeat()/
                     infile.getTotalDuration());
               if (!dice) {
                  continue;
               }
            }
            tempnote.line  = i;
            tempnote.spine = j;
            tempnote.token = k;
            tempnote.track = track;
            notes.append(tempnote);
         }
      }
   }
}



//////////////////////////////
//
// printNotes -- 
//

void printNotes(Array& notes) {
   int i;
   for (i=0; i<notes.getSize(); i++) {
      cout << "P" << notes[i].pitch << "\tL" << notes[i].line << "\tS"
           << notes[i].spine << "\tT" << notes[i].token 
           << "\tR" << notes[i].track
           << "\tN" << notes[i].newpitch
           << "\n";
   }
}



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

void checkOptions(Options& opts, int argc, char* argv[]) {
   opts.define("debug=b",          "print debug information"); 
   opts.define("r|rests=b",        "mixup rest as well as pitches"); 
   opts.define("s|seed=b",         "seed the random number generator"); 
   opts.define("t|track=b",        "randomize by track"); 
   opts.define("m|mapping=b",      "display mapping of pitches"); 
   opts.define("d|distribution=s:0 1 1 1", "random mixing amount in file"); 
   opts.define("n|neighbor=b", "move random note octave to be near old note "); 
   opts.define("S|display-seed=b","print seed used in random number generator");
   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 2002" << endl;
      exit(0);
   } else if (opts.getBoolean("version")) {
      cout << argv[0] << ", version: 5 May 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("distribution")) {
      distring.setEnvelope(opts.getString("distribution"));
      distQ = 1;
   } else {
      distQ = 0;
//      distring.setEnvelope("0 1 1 0 2 1");
//      distQ = 1;
   }
   debugQ       = opts.getBoolean("debug");
   restQ        = opts.getBoolean("rests");
   seed         = opts.getInteger("seed");
   if (seed <= 0) {
      seed = time(NULL);
      srand(seed);
   } else {
      srand(seed);
   }
   trackQ       = opts.getBoolean("track");
   displaySeedQ = opts.getBoolean("S");
   transQ       = opts.getBoolean("mapping");
   neighborQ    = opts.getBoolean("neighbor");
}



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

void example(void) {


}



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

void usage(const char* command) {

}



//////////////////////////////
//
// compareNoteUnit -- sort by random number.
//

int compareNoteUnit(const void* A, const void* B) {
   NoteUnit& a = *((NoteUnit*)A);
   NoteUnit& b = *((NoteUnit*)B);

   if (a.random < b.random) {
      return -1;
   } else if (a.random > b.random) {
      return 1;
   } else {
      return 0;
   }
}




//////////////////////////////
//
// compareNoteUnitTrack -- sort by random number within each track
//

int compareNoteUnitTrack(const void* A, const void* B) {
   NoteUnit& a = *((NoteUnit*)A);
   NoteUnit& b = *((NoteUnit*)B);

   if (a.track < b.track) {
      return -1;
   } else if (a.track > b.track) {
      return 1;
   }

   if (a.random < b.random) {
      return -1;
   } else if (a.random > b.random) {
      return 1;
   } else {
      return 0;
   }
}



//////////////////////////////
//
// compareNoteUnitTrack -- sort by random number within each track
//

int compareNoteSortTrack(const void* A, const void* B) {
   NoteUnit& a = *((NoteUnit*)A);
   NoteUnit& b = *((NoteUnit*)B);

   if (a.track < b.track) {
      return -1;
   } else if (a.track > b.track) {
      return 1;
   } else {
      return 0;
   }
}



//////////////////////////////
//
// updatedTiedNotes -- change ending tied notes to the new random pitch
//

void updateTiedNotes(HumdrumFile& infile, int line, int spine, int token,
   int newpitch, int oldpitch) {

// not quite perfect: if two primary tracks with common ties, will have prob:

   int m;
   int ptrack = infile[line].getPrimaryTrack(spine);
   int currentLine = line + 1;
   int length = infile.getNumLines();
   int done = 0;
   // int matchpitch;

   while (!done && currentLine < length) {
      if (!infile[currentLine].isData()) {
         currentLine++;
         continue;
      }

      for (m=0; m<infile[currentLine].getFieldCount(); m++) {
         if (ptrack != infile[currentLine].getPrimaryTrack(m)) {
            continue;
         }

         if (strchr(infile[currentLine][m], '_')) {
            // switch pitches (single tokens only for now)
            replaceNote(infile, currentLine, m, 0, newpitch, oldpitch);
            break;
         } else if (strchr(infile[currentLine][m], ']')) {
            replaceNote(infile, currentLine, m, 0, newpitch, oldpitch);
            done = 1;
            break;
         }
      }
      currentLine++;
   }

}



//////////////////////////////
//
// nearestNeighbor -- find the closest pitch class of the newpitch to
//    the oldpitches octave location
//

int nearestNeighbor(int oldpitch, int newpitch) {
   if (newpitch < 0 || oldpitch < 0) {
      return newpitch;
   }
   int octave = oldpitch / 40;
   int pc = newpitch % 40;
   int pitch0 = pc + octave * 40;
   int pitch1 = pc + (octave-1) * 40;
   int pitch2 = pc + (octave+1) * 40;
   int diff0 = abs(pitch0 - oldpitch);
   int diff1 = abs(pitch1 - oldpitch);
   int diff2 = abs(pitch2 - oldpitch);
 
   if ((diff0<diff1) && (diff0<diff2)) {
      if (pitch0 < lowlimit) {
         return pitch0 + 40;
      } else {
         return pitch0;
      }
   }
   if (diff1<diff2) {
      if (pitch1 < lowlimit) {
         return pitch1 + 40;
      } else {
         return pitch1;
      }
   }
   if (pitch2 < lowlimit) {
      return pitch2 + 40;
   } else {
      return pitch2;
   }
}



//////////////////////////////
//
// throwDice --
//

int throwDice(double piecefraction) {
   if (piecefraction < 0.0) {
      piecefraction = 0.0;
   } else if (piecefraction > 1.0) {
      piecefraction = 1.0;
   }

   // find the current point
   int i;
   int curr = distring.getNumPoints() - 1;
   for (i=0; i<distring.getNumPoints(); i++) {
      if (piecefraction <= distring.getValue(i, 0)) {
         curr = i;
         break;
      }
   }

   double fraction = 1.0;
   double slope = 0;
   double y1 = 0;
   double y2 = 0;
   double x1 = 0;
   double x2 = 0;
   if (curr > distring.getNumPoints() - 1) {
      fraction = distring.getValue(distring.getNumPoints()-1, 1);
   } else if (curr <= 0) {
      fraction = distring.getValue(0, 1);
   } else if (distring.getValue(curr, 0) == piecefraction) {
      fraction = distring.getValue(curr, 1);
   } else {
      x1 = distring.getValue(curr-1, 0);
      x2 = distring.getValue(curr,   0);

      y1 = distring.getValue(curr-1, 1);
      y2 = distring.getValue(curr,   1);

      slope = (y2-y1)/(x2-x1);
      fraction = slope * piecefraction + y2 - slope * x2;
   }

   #ifndef VISUAL
      if (drand48() > fraction) {
         return 0;
      } else { 
         return 1;
      }
   #else
      if ((1.0*rand()/INT_MAX) > fraction) {
         return 0;
      } else { 
         return 1;
      }
   #endif
}




// md5sum: 825362ac32bd38b19c76c8ef1d2bb416 pitchmix.cpp [20080518]