//
// Programmer:    Craig Stuart Sapp <craig@ccrma.stanford.edu>
// Creation Date: Sun Feb  3 16:03:55 PST 2008
// Last Modified: Sun Feb  3 16:03:58 PST 2008
// Filename:      ...sig/examples/all/nscale.cpp
// Web Address:   http://sig.sapp.org/examples/museinfo/humdrum/nscale.cpp
// Syntax:        C++; museinfo
//
// Description:   Create Fast Keyscapes using MIDI file or Humdrum file.
//

#include "humdrum.h"
#include "MidiFile.h"

#include <math.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>             /* for qsort and bsearch functions */

#ifndef OLDCPP
   #include <iostream>
   #include <fstream>
#else
   #include <iostream.h>
   #include <fstream.h>
#endif

#define PHRASEBOUNDARY 0x8080
#define REST 0x8081

// function declarations:
void     checkOptions(Options& opts, int argc, char** argv);
void     example(void);
void     usage(const char* command);
void     printAnalysis(Array<double>& sequence);
void     getSequence(Array<double>& sequence, HumdrumFile& infile,
                                 int pspine = 1);
int      getPspineIndex(HumdrumFile& infile, int line, int pspine);
void     fillScaleArray(Array<double>& scaledegrees, int keychroma, 
		                 int keymode);
int      getSubsequence(Array<double>& subsequence, 
                                 Array<double>& sequence, int start);
double   getDegree(double value);

// User interface variables:
Options   options;
int       ccount = 5;            // for -n option
int       displaycountQ = 0;     // for -c option
int       repeatQ = 1;           // for -R option
int       keepphraseQ = 0;       // for -P option


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

int main(int argc, char** argv) {
   Array<double> sequence;
   sequence.setSize(0);
   HumdrumFile infile;
   checkOptions(options, argc, argv);

   int numinputs = options.getArgCount();
   int i;

   for (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));
      }

      getSequence(sequence, infile);
      printAnalysis(sequence);

   }

   return 0;
}

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


//////////////////////////////
//
// getSequence --
//

void getSequence(Array& sequence, HumdrumFile& infile, int pspine) {
   int keychroma = 0;
   int keymode = 0; // 0 = major; 1 = minor

   sequence.setSize(infile.getNumLines());
   sequence.setSize(0);

   Array<double> base40toScaleDegree(40);
   base40toScaleDegree.zero();
   fillScaleArray(base40toScaleDegree, 2, 0); // C major
   
   int i;
   int ii;
   int octave;
   double value;
   int base40;
   int length;
   for (i=0; i<infile.getNumLines(); i++) {
      switch (infile[i].getType()) {

         case E_humrec_interpretation:
            ii = getPspineIndex(infile, i, pspine);
            if (strncmp("**kern", infile[i][ii], 2) == 0) {
               if (strcmp("**kern", infile[i][ii]) != 0) {
                  cerr << "Error: input spine is not **kern: " 
                       << infile[i][ii] << endl;
                  exit(1);
               }
            }
            // check for key signature marker
            length = strlen(infile[i][ii]);
            if (length >= 3 && length <= 4) {
               if (infile[i][ii][length-1] == ':') {
                  keychroma = Convert::kernToBase40(infile[i][ii]);
                  octave = keychroma % 40;
                  if (octave >= 4) {
                     keymode = 0; // major
                  } else {
                     keymode = 1; // minor
                  }
                  keychroma = keychroma % 40;
               }
//             cout << "KEY = " << infile[i][ii] << endl;
               fillScaleArray(base40toScaleDegree, keychroma, keymode);
            }

            break;
         case E_humrec_data:
            ii = getPspineIndex(infile, i, pspine);
            if (strcmp(infile[i][ii], ".") == 0) {
               break;
            } else if (strchr(infile[i][ii], '_') != NULL) {
               // ignore tied note mid notes
            } else if (strchr(infile[i][ii], ']') != NULL) {
               // ignore tied note end notes
            } else if (strchr(infile[i][ii], 'r') != NULL) {
               value = REST;
               // sequence.append(value); don't store for now
            } else {
	       if (keepphraseQ) {
                  if (strchr(infile[i][ii], '{') != NULL) {
                     value = PHRASEBOUNDARY;
                     sequence.append(value);
                  }
               }
               base40 = Convert::kernToBase40(infile[i][ii]);
               if (base40 >= 0) {
                  octave = (base40 - keychroma + 40) / 40;
                  value = base40toScaleDegree[base40 % 40];
                  value = value + octave * 7;
		  if (repeatQ == 0) {
                     if (sequence[sequence.getSize()-1] != value) {
                        sequence.append(value);
                     }
                  } else {
                     sequence.append(value);
                  }
               }
            }
            break;

         case E_humrec_none:
         case E_humrec_empty:
         case E_humrec_global_comment:
         case E_humrec_bibliography:
         case E_humrec_data_comment:
         case E_humrec_data_kern_measure:
         default:
            // don't do anything
            break;
      }

   }

}


//////////////////////////////
//
// fillScaleArray --
//

void fillScaleArray(Array<double>& scaledegrees, int keychroma, int keymode) {
   
   if (keymode == 1) {
      scaledegrees[(38 + keychroma) % 40] = 7.0 - 1;  // C--
      scaledegrees[(39 + keychroma) % 40] = 7.5 - 1;  // C-
      scaledegrees[(0  + keychroma) % 40] = 1.0 - 1;  // C
      scaledegrees[(1  + keychroma) % 40] = 1.5 - 1;  // C#
      scaledegrees[(2  + keychroma) % 40] = 2.0 - 1;  // C##
      scaledegrees[(4  + keychroma) % 40] = 1.0 - 1;  // D--
      scaledegrees[(5  + keychroma) % 40] = 1.5 - 1;  // D-
      scaledegrees[(6  + keychroma) % 40] = 2.0 - 1;  // D
      scaledegrees[(7  + keychroma) % 40] = 3.0 - 1;  // D#
      scaledegrees[(8  + keychroma) % 40] = 3.5 - 1;  // D##
      scaledegrees[(10 + keychroma) % 40] = 2.0 - 1;  // E--
      scaledegrees[(11 + keychroma) % 40] = 3.0 - 1;  // E-
      scaledegrees[(12 + keychroma) % 40] = 3.5 - 1;  // E
      scaledegrees[(13 + keychroma) % 40] = 4.0 - 1;  // E#
      scaledegrees[(14 + keychroma) % 40] = 4.5 - 1;  // E##
      scaledegrees[(15 + keychroma) % 40] = 3.0 - 1;  // F--
      scaledegrees[(16 + keychroma) % 40] = 3.5 - 1;  // F-
      scaledegrees[(17 + keychroma) % 40] = 4.0 - 1;  // F
      scaledegrees[(18 + keychroma) % 40] = 4.5 - 1;  // F#
      scaledegrees[(19 + keychroma) % 40] = 5.0 - 1;  // F##
      scaledegrees[(21 + keychroma) % 40] = 4.0 - 1;  // G--
      scaledegrees[(22 + keychroma) % 40] = 4.5 - 1;  // G-
      scaledegrees[(23 + keychroma) % 40] = 5.0 - 1;  // G
      scaledegrees[(24 + keychroma) % 40] = 6.0 - 1;  // G#
      scaledegrees[(25 + keychroma) % 40] = 6.5 - 1;  // G##
      scaledegrees[(27 + keychroma) % 40] = 5.0 - 1;  // A--
      scaledegrees[(28 + keychroma) % 40] = 6.0 - 1;  // A-
      scaledegrees[(29 + keychroma) % 40] = 6.5 - 1;  // A
      scaledegrees[(30 + keychroma) % 40] = 7.0 - 1;  // A#
      scaledegrees[(31 + keychroma) % 40] = 7.5 - 1;  // A##
      scaledegrees[(33 + keychroma) % 40] = 6.5 - 1;  // B--
      scaledegrees[(34 + keychroma) % 40] = 7.0 - 1;  // B-
      scaledegrees[(35 + keychroma) % 40] = 7.5 - 1;  // B
      scaledegrees[(36 + keychroma) % 40] = 1.0 - 1;  // B#
      scaledegrees[(37 + keychroma) % 40] = 1.5 - 1;  // B##
   } else {
      scaledegrees[(38 + keychroma) % 40] = 6.5 - 1;  // C--
      scaledegrees[(39 + keychroma) % 40] = 7.0 - 1;  // C-
      scaledegrees[(0  + keychroma) % 40] = 1.0 - 1;  // C
      scaledegrees[(1  + keychroma) % 40] = 1.5 - 1;  // C#
      scaledegrees[(2  + keychroma) % 40] = 2.0 - 1;  // C##
      scaledegrees[(4  + keychroma) % 40] = 1.0 - 1;  // D--
      scaledegrees[(5  + keychroma) % 40] = 1.5 - 1;  // D-
      scaledegrees[(6  + keychroma) % 40] = 2.0 - 1;  // D
      scaledegrees[(7  + keychroma) % 40] = 2.5 - 1;  // D#
      scaledegrees[(8  + keychroma) % 40] = 3.0 - 1;  // D##
      scaledegrees[(10 + keychroma) % 40] = 2.0 - 1;  // E--
      scaledegrees[(11 + keychroma) % 40] = 2.5 - 1;  // E-
      scaledegrees[(12 + keychroma) % 40] = 3.0 - 1;  // E
      scaledegrees[(13 + keychroma) % 40] = 4.0 - 1;  // E#
      scaledegrees[(14 + keychroma) % 40] = 4.5 - 1;  // E##
      scaledegrees[(15 + keychroma) % 40] = 2.5 - 1;  // F--
      scaledegrees[(16 + keychroma) % 40] = 3.0 - 1;  // F-
      scaledegrees[(17 + keychroma) % 40] = 4.0 - 1;  // F
      scaledegrees[(18 + keychroma) % 40] = 4.5 - 1;  // F#
      scaledegrees[(19 + keychroma) % 40] = 5.0 - 1;  // F##
      scaledegrees[(21 + keychroma) % 40] = 4.0 - 1;  // G--
      scaledegrees[(22 + keychroma) % 40] = 4.5 - 1;  // G-
      scaledegrees[(23 + keychroma) % 40] = 5.0 - 1;  // G
      scaledegrees[(24 + keychroma) % 40] = 5.5 - 1;  // G#
      scaledegrees[(25 + keychroma) % 40] = 6.0 - 1;  // G##
      scaledegrees[(27 + keychroma) % 40] = 5.0 - 1;  // A--
      scaledegrees[(28 + keychroma) % 40] = 5.5 - 1;  // A-
      scaledegrees[(29 + keychroma) % 40] = 6.0 - 1;  // A
      scaledegrees[(30 + keychroma) % 40] = 6.5 - 1;  // A#
      scaledegrees[(31 + keychroma) % 40] = 7.0 - 1;  // A##
      scaledegrees[(33 + keychroma) % 40] = 6.0 - 1;  // B--
      scaledegrees[(34 + keychroma) % 40] = 6.5 - 1;  // B-
      scaledegrees[(35 + keychroma) % 40] = 7.0 - 1;  // B
      scaledegrees[(36 + keychroma) % 40] = 1.0 - 1;  // B#
      scaledegrees[(37 + keychroma) % 40] = 1.5 - 1;  // B##
   }
}



//////////////////////////////
//
// getPspineIndex --
//

int getPspineIndex(HumdrumFile& infile, int line, int pspine) {
   int i;
   int output = 0;
   for (i=0; i<infile[i].getFieldCount(); i++) {
      if (infile[line].getPrimaryTrack(i) == pspine) {
         output = i;
         break;
      }
   }

   return output;
}



//////////////////////////////
//
// printAnalysis --
//

void printAnalysis(Array<double>& sequence) {
   int i,j;
   Array<double> subsequence;
   subsequence.setSize(ccount);
   subsequence.allowGrowth(0);
   subsequence.zero();
   for (i=0; i<sequence.getSize(); i++) {
      if (getSubsequence(subsequence, sequence, i)) {
         if (displaycountQ) {
            cout << subsequence.getSize() << "\t";
         }
	 cout << getDegree(subsequence[0]);
         for (j=1; j<subsequence.getSize(); j++) {
            //cout << "\t:" << subsequence[j] << ":";
            cout << "\t" << (subsequence[j] - subsequence[j-1]);
            //cout << "\t" << getDegree(subsequence[j]);
         }
         cout << "\n";
      }
   }
}



//////////////////////////////
//
// getDegree --
//

double getDegree (double value) {
   int octave = int(value / 7);
   return (value - octave * 7) + 1;
}



//////////////////////////////
//
// getSubsequence --
//

int getSubsequence(Array<double>& subsequence, Array<double>& sequence, 
      int start) {

   int status = 0;
   int i;
   int subi;
   
   if (start + subsequence.getSize() >= sequence.getSize()) {
      return status;
   }

   i = start;
   subi = 0;
   while (i < sequence.getSize() && subi < subsequence.getSize()) {
      if (sequence[i] == PHRASEBOUNDARY) {
         if (keepphraseQ) {
            i++;
            continue;
         } else {
            return status;
         }
      } else {
         subsequence[subi++] = sequence[i++];
      }
   }

   if (subi == subsequence.getSize()) {
      status = 1;
   }
   return status; 
}



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

void checkOptions(Options& opts, int argc, char* argv[]) {
   opts.define("n|length=i:5",  "length of sequences");
   opts.define("c|count=b",     "display length of sequences");
   opts.define("P|phrase=b",    "ignore phrase information");
   opts.define("R|repeat=b",    "ignore repeated notes");

   opts.define("author=b",  "author of program");
   opts.define("version=b", "compilation info");
   opts.define("example=b", "example usages");
   opts.define("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 2008" << endl;
      exit(0);
   } else if (opts.getBoolean("version")) {
      cout << argv[0] << ", version: 18 May 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);
   }

   ccount        =  opts.getInteger("length");
   displaycountQ =  opts.getBoolean("count");
   repeatQ       = !opts.getBoolean("repeat");
   keepphraseQ   =  opts.getBoolean("phrase");
}



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

void example(void) {


}



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

void usage(const char* command) {

}



// md5sum: e212ecd364af401d89c93082f81af4f4 nscale.cpp [20080920]