//
// Programmer:    Craig Stuart Sapp <craig@ccrma.stanford.edu>
// Creation Date: Mon Apr 12 23:22:19 PDT 2004
// Last Modified: Mon Apr 12 23:22:22 PDT 2004
// Last Modified: Thu Feb 24 22:43:17 PST 2005 (added -k option)
// Last Modified: Wed Jun 24 15:39:58 PDT 2009 (updated for GCC 4.4)
// Filename:      ...sig/examples/all/transpose.cpp
// Web Address:   http://sig.sapp.org/examples/museinfo/humdrum/transpose.cpp
// Syntax:        C++; museinfo
//
// Description:   Transpose **kern musical data.
//

#include "humdrum.h"

#include <string.h>
#include <stdio.h>

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


// function declarations:
void      checkOptions(Options& opts, int argc, char** argv);
void      example(void);
void      usage(const char* command);
void      printFile(HumdrumFile& infile);
int       generateFieldList(Array<int>& fieldlist,const char* fieldstring);
void      processFile(HumdrumFile& infile);
void      printNewKernString(const char* string);
void      printHumdrumKernToken(HumdrumRecord& record, int index);
void      printHumdrumDataRecord(HumdrumRecord& record, 
                                 Array<int>& spineprocess);
int       getBase40ValueFromInterval(const char* string);
void      printNewKeyInterpretation(HumdrumRecord& aRecord, int index);

// User interface variables:
Options     options;
int         transval    = 0;     // used with -b option
const char* fieldstring = "";    // used with -f option
int         fieldQ      = 0;     // used with -f option
int         ssetkeyQ     = 0;    // used with -k option
int         ssetkey      = 0;    // used with -k option
int         currentkey   = 0;


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

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

   HumdrumFile infile;
   if (options.getArgCount() > 0) {
      infile.read(options.getArg(1));
   } else {
      infile.read(cin);
   }

   processFile(infile);

   return 0;
}

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


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

void processFile(HumdrumFile& infile) {
   int i;
   Array<int> fieldlist;
   Array<int> spineprocess(infile.getMaxTracks());
   if (fieldQ) {
      spineprocess.setAll(0);
      generateFieldList(fieldlist, fieldstring);
      for (i=0; i<fieldlist.getSize(); i++) {
         if (fieldlist[i] < 1) {
            continue;
         }
         if (fieldlist[i]-1 < spineprocess.getSize()) {
            spineprocess[fieldlist[i]-1] = 1;
         }
      }
   } else {
      spineprocess.setAll(1);
   }

   int length;
   int diatonic;
   int j;
   const char* ptr;
   for (i=0; i<infile.getNumLines(); i++) {
      switch (infile[i].getType()) {
         case E_humrec_data:
            printHumdrumDataRecord(infile[i], spineprocess);
            cout << "\n";
            break;
         case E_humrec_interpretation:
            // adjust *k[] values here.
            for (j=0; j<infile[i].getFieldCount(); j++) {
               length = strlen(infile[i][j]);
               ptr = strrchr(infile[i][j], ':');
               if ((length < 3) || (ptr == NULL) || (ptr - infile[i][j] > 4)) {
                  cout << infile[i][j];
                  if (j<infile[i].getFieldCount()-1) {
                     cout << "\t";
                  }
                  continue;
               }
               diatonic = tolower(infile[i][j][1]) - 'a';
               if (diatonic >= 0 && diatonic <= 6) {
                  printNewKeyInterpretation(infile[i], j);
                  if (j<infile[i].getFieldCount()-1) {
                     cout << "\t";
                  }
                  continue;
               }
               
               cout << infile[i][j];
               if (j<infile[i].getFieldCount()-1) {
                  cout << "\t";
               }
            }
            cout << "\n";
            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:
            cout << infile[i] << "\n";
      }
   }
}



//////////////////////////////
//
// printNewKeyInterpretation -- 
//

void printNewKeyInterpretation(HumdrumRecord& aRecord, int index) {

   int mode = 0;
   if (islower(aRecord[index][1])) {
      mode = 1;
   }
   int base40 = Convert::kernToBase40(aRecord[index]);
   currentkey = base40;
   base40 = base40 + transval;
   base40 = base40 + 4000;
   base40 = base40 % 40;
   base40 = base40 + (3 + mode) * 40;
   char buffer[128] = {0};

   if (ssetkeyQ) {
      transval = (ssetkey%40 - currentkey%40);
      base40 = ssetkey + (3 + mode) * 40;
      if (transval > 40) {
         transval -= 40;
      } 
      if (transval > 20) {
         transval = 40 - transval;
         transval = -transval;
      }
      if (transval < -40) {
         transval += 40;
      }
      if (transval < -20) {
         transval = -40 - transval;
         transval = -transval;
      }
      // cout << "TRANSVAL = "<< transval << endl;
   }

   cout << "*" << Convert::base40ToKern(buffer, base40) << ":";
}



//////////////////////////////
//
// printHumdrumDataRecord --
//

void printHumdrumDataRecord(HumdrumRecord& record, Array& spineprocess) {
   int i;
   for (i=0; i<record.getFieldCount(); i++) {
      if (!spineprocess[record.getPrimaryTrack(i)-1]) {
         // don't try to transpose spines which were not indicated.
         cout << record[i];
         if (i<record.getFieldCount()-1) {
            cout << "\t";
         }
         continue;
      }
      if (record.getExInterpNum(i) != E_KERN_EXINT) {
         // don't try to transpose non-kern spines
         cout << record[i];
         if (i<record.getFieldCount()-1) {
            cout << "\t";
         }
         continue;
      }

      printHumdrumKernToken(record, i);
      if (i<record.getFieldCount()-1) {
         cout << "\t";
      }
      continue;
   }
   
}



//////////////////////////////
//
// printHumdrumKernToken --
//

void printHumdrumKernToken(HumdrumRecord& record, int index) {
   if (strcmp(record[index], ".") == 0) {
      // null record element (no pitch).
      cout << ".";
      return;
   }

   int k;
   static char buffer[1024] = {0};
   int tokencount = record.getTokenCount(index);

   for (k=0; k<tokencount; k++) {
      record.getToken(buffer, index, k);
      printNewKernString(buffer);
      if (k<tokencount-1) {
         cout << " ";
      }         
   }
}



//////////////////////////////
//
// printNewKernString --
//

void printNewKernString(const char* string) {
   if (strchr(string, 'r') != NULL) {
      cout << string;
      return;
   }

   char buffer[1024] = {0};
   char buffer2[1024] = {0};
   strcpy(buffer, string);
   int base40 = Convert::kernToBase40(string);
   char* ptr1 = strtok(buffer, "ABCDEFGabcdefg#-n");
   char* ptr2 = strtok(NULL, "ABCDEFGabcdefg#-n");
   char* ptr3 = strtok(NULL, "ABCDEFGabcdefg#-n");
   
   if (ptr3 != NULL) {
      cout << "Error in **kern pitch token: " << string << endl;
      exit(1);
   }

   if (ptr1 != NULL) {
      cout << ptr1;
   }
   cout << Convert::base40ToKern(buffer2, base40 + transval);
   if (ptr2 != NULL) {
      cout << ptr2;
   }
}



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

void checkOptions(Options& opts, int argc, char* argv[]) {
   opts.define("b|base40=i:0", "The base-40 transposition value");
   opts.define("o|octave=i:0", "The octave addition to tranpose value");
   opts.define("f|field=s", "The base-40 transposition value");
   opts.define("t|transpose=s", "musical interval transposition value");
   opts.define("k|setkey=s", "Transpose to the given key");

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

   transval    = opts.getInteger("base40");
   fieldstring = opts.getString("field");
   fieldQ      = opts.getBoolean("field");
   ssetkeyQ    = opts.getBoolean("setkey");
   ssetkey     = Convert::kernToBase40(opts.getString("setkey"));

   ssetkey = ssetkey % 40;


   if (opts.getBoolean("transpose")) {
      transval = getBase40ValueFromInterval(opts.getString("transpose"));
   }

   transval += 40 * opts.getInteger("octave");
}


//////////////////////////////
//
// getBase40ValueFromInterval -- note: only ninth interval range allowed
//

int getBase40ValueFromInterval(const char* string) {
   int sign = 1;
   if (strchr(string, '-') != NULL) {
      sign = -1;
   }

   int length = strlen(string);
   char* buffer = new char[length+1];
   strcpy(buffer, string);
   int i;
   for (i=0; i<length; i++) {
      if (buffer[i] == 'p') { buffer[i] = 'P'; }
      if (buffer[i] == 'a') { buffer[i] = 'A'; }
      if (buffer[i] == 'D') { buffer[i] = 'd'; }
   }

   int output = 0;

   if (strstr(buffer, "dd1") != NULL)      { output = -2; }
   else if (strstr(buffer, "d1") != NULL)  { output = -1; }
   else if (strstr(buffer, "P1") != NULL)  { output =  0; }
   else if (strstr(buffer, "AA1") != NULL) { output =  2; }
   else if (strstr(buffer, "A1") != NULL)  { output =  1; }

   else if (strstr(buffer, "dd2") != NULL) { output =  3; }
   else if (strstr(buffer, "d2") != NULL)  { output =  4; }
   else if (strstr(buffer, "m2") != NULL)  { output =  5; }
   else if (strstr(buffer, "M2") != NULL)  { output =  6; }
   else if (strstr(buffer, "AA2") != NULL) { output =  8; }
   else if (strstr(buffer, "A2") != NULL)  { output =  7; }

   else if (strstr(buffer, "dd3") != NULL) { output =  9; }
   else if (strstr(buffer, "d3") != NULL)  { output = 10; }
   else if (strstr(buffer, "m3") != NULL)  { output = 11; }
   else if (strstr(buffer, "M3") != NULL)  { output = 12; }
   else if (strstr(buffer, "AA3") != NULL) { output = 14; }
   else if (strstr(buffer, "A3") != NULL)  { output = 13; }

   else if (strstr(buffer, "dd4") != NULL) { output = 15; }
   else if (strstr(buffer, "d4") != NULL)  { output = 16; }
   else if (strstr(buffer, "P4") != NULL)  { output = 17; }
   else if (strstr(buffer, "AA4") != NULL) { output = 19; }
   else if (strstr(buffer, "A4") != NULL)  { output = 18; }

   else if (strstr(buffer, "dd5") != NULL) { output = 21; }
   else if (strstr(buffer, "d5") != NULL)  { output = 22; }
   else if (strstr(buffer, "P5") != NULL)  { output = 23; }
   else if (strstr(buffer, "AA5") != NULL) { output = 25; }
   else if (strstr(buffer, "A5") != NULL)  { output = 24; }

   else if (strstr(buffer, "dd6") != NULL) { output = 26; }
   else if (strstr(buffer, "d6") != NULL)  { output = 27; }
   else if (strstr(buffer, "m6") != NULL)  { output = 28; }
   else if (strstr(buffer, "M6") != NULL)  { output = 29; }
   else if (strstr(buffer, "AA6") != NULL) { output = 31; }
   else if (strstr(buffer, "A6") != NULL)  { output = 30; }

   else if (strstr(buffer, "dd7") != NULL) { output = 32; }
   else if (strstr(buffer, "d7") != NULL)  { output = 33; }
   else if (strstr(buffer, "m7") != NULL)  { output = 34; }
   else if (strstr(buffer, "M7") != NULL)  { output = 35; }
   else if (strstr(buffer, "AA7") != NULL) { output = 37; }
   else if (strstr(buffer, "A7") != NULL)  { output = 36; }

   else if (strstr(buffer, "dd8") != NULL) { output = 38; }
   else if (strstr(buffer, "d8") != NULL)  { output = 39; }
   else if (strstr(buffer, "P8") != NULL)  { output = 40; }
   else if (strstr(buffer, "AA8") != NULL) { output = 42; }
   else if (strstr(buffer, "A8") != NULL)  { output = 41; }

   else if (strstr(buffer, "dd9") != NULL) { output = 43; }
   else if (strstr(buffer, "d9") != NULL)  { output = 44; }
   else if (strstr(buffer, "m9") != NULL)  { output = 45; }
   else if (strstr(buffer, "M9") != NULL)  { output = 46; }
   else if (strstr(buffer, "AA9") != NULL) { output = 48; }
   else if (strstr(buffer, "A9") != NULL)  { output = 47; }

   return output * sign;
}



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

void example(void) {


}



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

void usage(const char* command) {

}




//////////////////////////////
//
// generateFieldList --
//

int generateFieldList(Array& fieldlist, const char* fieldstring) {
cout << "ENTERING GENERATEFIELD LIST" << endl;
   int maxfield = 0;
   int length = strlen(fieldstring);
   char* buffer = new char[length+1];
   strcpy(buffer, fieldstring);
   int starti, stopi;
   int temp;
   int num;
   char* ptr = strtok(buffer, " ,;:");
   while (ptr != NULL) {
      if (strchr(ptr, '-') != NULL) {
         sscanf(ptr, "%d-%d", &starti, &stopi);
         if (starti > stopi) {
            temp = starti;
            starti=stopi;
            stopi = temp;
         } 
         for (num=starti; num<=stopi; num++) {
            fieldlist.append(num);
         }
         if (num > maxfield) {
            maxfield = num;
         }
      } else {
         sscanf(ptr, "%d", &num);
         fieldlist.append(num);
         if (num > maxfield) {
            maxfield = num;
         }
      }
      ptr = strtok(NULL, " ,;:");
   }
cout << "LEAVING GENERATEFIELD LIST" << endl;
   return maxfield;
}



/* BRIEF DOCUMENTATION

transpose options:

   -t interval = transpose music by the specified interval, where 
                 interval is of the form:
                    P1 = perfect unison(no transposition)
                    m2 = up a minor second
                   -m2 = down a minor second
                    M3 = up a major third
                   -A4 = down an augmented fourth
                    d5 = up a diminished fifth

   -b interval = transpose by the base-40 equivalent to the -t option interval
                     0 = perfect unison(no transposition)
                     6 = up a minor second
                    -6 = down a minor second
                    12 = up a major third
                   -18 = down an augmented fourth
                    22 = up a diminished fifth


   -o octave  = transpose(additionally by given octave)
		transpose -t m3 -o 1 = transpose up by an octave and a minor
			               third.


   -f fieldstring = transpose only the given list of data spines.
		    example:
 			transpose -f1-2,4 -t P4 fourpart.krn
 			transpose -f3     -t P4 fourpart.krn
		   Note: does not work yet with the -k option

                    

##########################################################################
##
## EXAMPLES
##



input: file.krn  -- an example with the key being a minor:

**kern
*a:
4A
4B
4c
4d
4e
4f
4g
4a
*-


#####################################################################
#
# Transpose the file up a minor third(so that it is in C Minor):
#

tranpose -t m3 file.krn

**kern
*c:
4c
4d
4e-
4f
4g
4a-
4b-
4cc
*-

#####################################################################
#
# Transpose the file down a minor third(so that it is in F# Minor):
#

tranpose -t -m3 file.krn

**kern
*f#:
4F#
4G#
4A
4B
4c#
4d
4e
4f#
*-


#####################################################################
#
# Transpose the file up a perfect octave:
#

tranpose -t P8 file.krn

**kern
*a:
4A
4B
4cc
4dd
4ee
4ff
4gg
4aa
*-




#####################################################################
#
# Force the file to a tonic on C rather than a:
#

transpose -k c file.krn

**kern
*c:
4c
4d
4e-
4f
4g
4a-
4b-
4cc
*-


# case -k option value is irrelevant:

transpose -k C file.krn

**kern
*c:
4c
4d
4e-
4f
4g
4a-
4b-
4cc
*-


# Transpose from A Minor to G# Minor:

transpose -k G# file.krn

**kern
*g#:
4G#
4A#
4B
4c#
4d#
4e
4f#
4g#
*-


# Changing input files to:

**kern
*C:
4c
4d
4e
4f
*G:
4g
4a
4b
4cc
*-


# Using -k option will convert all keys to same in output:

transpose -k e file.krn

**kern
*E:
4e
4f#
4g#
4a
*E:
4e
4f#
4g#
4a
*-



##############################
##
## octave transpositions
##


# Back to original data file, transposing up a minor tenth:

transpose -o 1 -t m3 file.krn

**kern
*E-:
8ee-
8ff
8gg
8aa-
8bb-
8ccc
8ddd
8eee-
*-

# transpose down two octaves:

transpose -o -2 file.krn

**kern
*a:
4AAA
4BBB
4CC
4DD
4EE
4FF
4GG
4AA
*-


####################
##
## base 40 -b option instead of -t option
##


# Transpose down two octaves:
transpose -b -80 file.krn

**kern
*a:
4AAA
4BBB
4CC
4DD
4EE
4FF
4GG
4AA
*-

*/


// md5sum: 28a5b69adb369d26bcba3835299da2a8 transpose.cpp [20090626]