//
// Programmer:    Craig Stuart Sapp <craig@ccrma.stanford.edu>
// Creation Date: Wed May 16 14:14:47 PDT 2001
// Last Modified: Wed May 16 14:14:50 PDT 2001
// Filename:      ...sig/examples/all/scrub.cpp
// Web Address:   http://sig.sapp.org/examples/museinfo/humdrum/scrub.cpp
// Syntax:        C++; museinfo
//
// Description:   Remove chordal/non-chordal tones from input music.
// 
// Things to do:  Allow for pickup beats.
// 

#include "humdrum.h"

#include "ctype.h"
#include "string.h"


// function declarations
void   checkOptions(Options& opts, int argc, char* argv[]);
void   example(void);
void   usage(const char* command);
void   processFile(HumdrumFile& infile);
int    getDegree(int note, int root);
void   erasenote(HumdrumFile& infile, NoteList& note);

// command line options:
Options    options;        // database for command-line arguments
int        debugQ = 0;     // for debugging
Array<int> filter(10);     // notes to keep/remove.
double     timebase = 1.0; // duration of each chord


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

int main(int argc, char* argv[]) {
   HumdrumFile infile;           // input Humdrum Format file

   // 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));
      }
      infile.analyzeRhythm("4");   // force the beat to be quarter notes for now
      processFile(infile);
      cout << infile;
   }

   return 0;
}


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



//////////////////////////////
//
// processFile -- 
//

void processFile(HumdrumFile& infile) {
   double beatstart = 0.0;
   Array<double> scores(40);
   Array<int> roots(infile.getNumLines()*10);
   Array<double> starttimes(infile.getNumLines()*10);
   roots.setGrowth(infile.getNumLines()*10);
   starttimes.setGrowth(infile.getNumLines()*10);
   roots.setSize(0);
   starttimes.setSize(0);
   roots.allowGrowth(1);
   starttimes.allowGrowth(1);
   int root;
   Array<double> p(3);
   p[0] = 0.573;
   p[1] = -4.0;
   p[2] = -3.0;
   while (beatstart+timebase-0.01 < infile.getTotalDuration()) {
      root = infile.measureChordRoot(scores, p, beatstart, 
         beatstart+timebase-0.01, 0);
      starttimes.append(beatstart);
      roots.append(root);
      beatstart += timebase;
   }

   NoteListArray notelist;
   infile.generateNoteList(notelist, 0, infile.getNumLines() - 1);

   char buffer[128] = {0};
   int i;
   int degree;
   int k = 0;
   for (i=0; i<notelist.getSize(); i++) {
      while (k < starttimes.getSize() && 
            notelist[i].getAbsBeat() >= starttimes[k]) {
         if (debugQ) {
            cout << "beat " << starttimes[k] << "\troot = " 
                 << Convert::base40ToKern(buffer, 2+roots[k]+3*40) << endl;
         }
         k++;
      }
      if(debugQ) {
         cout << "\t" << Convert::base40ToKern(buffer, notelist[i].getPitch());
      }
      degree = getDegree(notelist[i].getPitch(), roots[k-1]+2);
      if (debugQ) {
         cout << "\t=\tdegree " << degree;
      }
      if (degree >= 0 && filter[degree] == 0) {
         if (debugQ) {
            cout << " (remove)";
         }
         notelist[i].setPitch(-1);
      }
      if (debugQ) {
         cout << endl;
      }
   }

   // list removed notes
   for (i=0; i<notelist.getSize(); i++) {
      if (notelist[i].getPitch() == -1) {
         if (debugQ) {
            cout << "Remove note at line=" 
                 << notelist[i].getLine()
                 << "\tspine=" << notelist[i].getSpine()
                 << "\ttoken=" << notelist[i].getToken()
                 << endl;
         }
         erasenote(infile, notelist[i]);
      }
   }
}



//////////////////////////////
//
// erasenote --
// 

void erasenote(HumdrumFile& infile, NoteList& note) {
   char buffer[64] = {0};
   Convert::durationToKernRhythm(buffer, note.getFirstDur());
   strcat(buffer, "r");
   infile[note.getLine()].changeToken(note.getSpine(), note.getToken(),
      buffer);
}



//////////////////////////////
//
// getDegree -- convert a note to a chord degree
//   -1 = invalid
//    0 = root
//    1 = third
//    2 = fifth, etc.
//

int getDegree(int note, int root) {
   int interval = (note - root + 40) % 40;
   switch (interval) {
      case 0:   return 0;
      case 1:   return 7;
      case 2:   return 7;
      case 3:   return -1;
      case 4:   return 4;
      case 5:   return 4;
      case 6:   return 4;
      case 7:   return 4;
      case 8:   return 4;
      case 9:   return -1;
      case 10:  return 8;
      case 11:  return 8;
      case 12:  return 1;
      case 13:  return 1;
      case 14:  return 8;
      case 15:  return 5;
      case 16:  return 5;
      case 17:  return 5;
      case 18:  return 5;
      case 19:  return 5;
      case 20:  return -1;
      case 21:  return 9;
      case 22:  return 2;
      case 23:  return 2;
      case 24:  return 2;
      case 25:  return 9;
      case 26:  return -1;
      case 27:  return 6;
      case 28:  return 6;
      case 29:  return 6;
      case 30:  return 6;
      case 31:  return 6;
      case 32:  return -1;
      case 33:  return 10;
      case 34:  return 3;
      case 35:  return 3;
      case 36:  return 3;
      case 37:  return 3;
      case 38:  return 7;
      case 39:  return 7;
      default:  return -1;
   }

}



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

void checkOptions(Options& opts, int argc, char* argv[]) {
   opts.define("r|remove=s:", "scale degrees to remove");
   opts.define("k|keep=s:13579AB", "scale degrees to keep");
   opts.define("t|timebase=s:4", "chord analysis timebase");

   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, May 2001" << endl;
      exit(0);
   } else if (opts.getBoolean("version")) {
      cout << argv[0] << ", version: May 2001" << 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);
   }

   int i;
   const char* string;
   int length;
   for (i=0; i<filter.getSize(); i++) {
      filter[i] = 1;
   }
   if (opts.getBoolean("remove")) {
      for (i=0; i<filter.getSize(); i++) {
         filter[i] = 1;
      }
      string = opts.getString("remove");
      length = strlen(string);
      for (i=0; i<=length; i++) {
         switch (toupper(string[i])) {
            case '1':   filter[0] = 0; break;
            case '3':   filter[1] = 0; break;
            case '5':   filter[2] = 0; break;
            case '7':   filter[3] = 0; break;
            case '9':   filter[4] = 0; break;
            case 'A':   filter[5] = 0; break;
            case 'B':   filter[6] = 0; break;
            case 'C':   filter[7] = 0; break;
            case 'D':   filter[8] = 0; break;
            case 'E':   filter[9] = 0; break;
            case '2':   filter[4] = 0; break;
            case '4':   filter[5] = 0; break;
            case '6':   filter[6] = 0; break;
            case '8':   filter[0] = 0; break;
         }
      }

   } else if (opts.getBoolean("keep")) {
      for (i=0; i<filter.getSize(); i++) {
         filter[i] = 0;
      }
      string = opts.getString("keep");
      length = strlen(string);
      for (i=0; i<=length; i++) {
         switch (toupper(string[i])) {
            case '1':   filter[0] = 1; break;
            case '3':   filter[1] = 1; break;
            case '5':   filter[2] = 1; break;
            case '7':   filter[3] = 1; break;
            case '9':   filter[4] = 1; break;
            case 'A':   filter[5] = 1; break;
            case 'B':   filter[6] = 1; break;
            case 'C':   filter[7] = 1; break;
            case 'D':   filter[8] = 1; break;
            case 'E':   filter[9] = 1; break;
            case '2':   filter[4] = 1; break;
            case '4':   filter[5] = 1; break;
            case '6':   filter[6] = 1; break;
            case '8':   filter[0] = 1; break;
         }
      }

   }
 
   timebase = Convert::kernToDuration(opts.getString("timebase"));

   debugQ = opts.getBoolean("debug");
}



//////////////////////////////
//
// 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;
}



// md5sum: 6750cc5dc8228a3499322988390e0a8f scrub.cpp [20050403]