//
// Programmer:    Craig Stuart Sapp <craig@ccrma.stanford.edu>
// Creation Date: Fri Jul  3 22:16:30 PDT 1998
// Last Modified: Sun May 26 19:59:21 PDT 2002 (use HumdrumFile::analyzeRhythm)
// Filename:      ...sig/examples/all/composite.cpp
// Web Address:   http://sig.sapp.org/examples/museinfo/humdrum/composite.cpp
// Syntax:        C++; museinfo
//
// Description:   Generates composite rhythm data spine from **kern input
//

#include "humdrum.h"

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

#ifndef OLDCPP
   #include <iostream>
   #include <sstream>
   #define SSTREAM stringstream
   #define CSTRING str().c_str()
   using namespace std;
#else
   #include <iostream.h>
   #ifdef VISUAL
      #include <strstrea.h>    /* for Windows 95 */
   #else
      #include <strstream.h>
   #endif
   #define SSTREAM strstream
   #define CSTRING str()
#endif


// function declarations
void         checkOptions(Options& opts, int argc, char* argv[]);
int          determineRest(HumdrumFile& infile, int line);
int          determineTie(HumdrumFile& infile, int line);
void         example(void);
void         generateMupOutput(HumdrumFile& aFile);
void         modifyRecordTie(HumdrumRecord& aRecord, int state);
void         numberToString(double number, char* string);
void         processRecords(HumdrumFile& infile, HumdrumFile& outfile);
void         usage(const char* command);


// global variables
Options      options;            // database for command-line arguments
double       meterposition = 1;  // position of current event in meter
double       scalevalue = 1;     // for changing duration size (not used yet)
int          durinit;            // for initializing duration function
int          restinit;           // for initializing rests function
int          tieinit;            // for initializing composite tie detector
int          lastTie = 0;        // for determining tie start/stop
char         title[1024] = {0};  // for MUP title


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


int main(int argc, char* argv[]) {
   HumdrumFile infile, outfile;

   // 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++) {
      durinit = restinit = tieinit = 1;
      lastTie = 0;
      infile.clear();
      outfile.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));
      }
      // analyze the input file according to command-line options
      processRecords(infile, outfile);

      // check to see if only the analysis spine is required
      if (!options.getBoolean("assemble")) {
         outfile.analyzeSpines();
         outfile = outfile.extract(-1);
      }

      if (!options.getBoolean("mup")) {
         outfile.write(cout);
      } else {
         generateMupOutput(outfile);
      }
   }

   return 0;
}


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


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

void checkOptions(Options& opts, int argc, char* argv[]) {
   opts.define("a|assemble=b");           // assemble analysis with input
   opts.define("r|rest|rests|no-rests=b");// don't indicate rests
   opts.define("m|mup=b");                // determine if to output mup
   opts.define("t|title=s:");             // sets the MUP title
   opts.define("debug=b");                // determine bad input line num
   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, July 1998" << endl;
      exit(0);
   } else if (opts.getBoolean("version")) {
      cout << argv[0] << ", version: 3 July 1998" << 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);
   }

}
  


//////////////////////////////
//
// determineRest -- extracts notes from humdrum data record
//      and determines if all notes are rests or not.  Notes from previous
//      records are remembered, and replace any null records in the
//      data.  Rests are represented by some negative value.
//

int determineRest(HumdrumFile& infile, int line) {
   int output = 0;
   const char* datum;
   int i;
   for (i=0; i<infile[line].getFieldCount(); i++) {
      if (strcmp(infile[line].getExInterp(i), "**kern") != 0) {
         continue;
      }
      datum = infile.getLastDatum(line, i);
      if (strchr(datum, 'r') == NULL) {
         output = 0;
         break;
      }
   }

/*   static Collection<int> lastnotes;  // for keeping track of null record notes
   int output = 1;
   int i;
   if (restinit) {
      restinit = 0;
      lastnotes.setSize(aRecord.getFieldCount());
      for (i=0; i<aRecord.getFieldCount(); i++) {
         lastnotes[i] = E_root_rest;
      }
      // remove non-kern spines from note list
      for (i=0; i<aRecord.getFieldCount(); i++) {
         if (aRecord.getExInterpNum(i) != E_KERN_EXINT) {
            lastnotes.setSize(lastnotes.getSize() - 1);
         }
      }
   }

   // determine chord notes and send to chord identifier
   Collection<int> currentNotes(lastnotes.getSize());
   int count = 0;
   for (i=0; i<aRecord.getFieldCount(); i++) {
      if (aRecord.getExInterpNum(i) == E_KERN_EXINT) {
         if (strcmp(aRecord[i], ".") == 0) {
            currentNotes[count] = lastnotes[count];
         } else {
            currentNotes[count] = Convert::kernToBase40(aRecord[i]);
            lastnotes[count] = currentNotes[count];
         }
         if (currentNotes[count] != E_root_rest) {
            output = 0;
         }
         count++;
      }
   }
*/

   return output;
}

//////////////////////////////
//
// determineTie -- determines if notes in record are all tied.
//

int determineTie(HumdrumFile& infile, int line) {
   int output = 0;

/*   static Collection<int> ties;         // for keeping track of notes states
   static Collection<int> sustains;     // for keeping track of note sustains
   static Collection<int> rests;        // for keeping track of notes states

   int i;
   if (tieinit) {
      tieinit = 0;
      ties.setSize(aRecord.getFieldCount());
      sustains.setSize(aRecord.getFieldCount());
      rests.setSize(aRecord.getFieldCount());
      for (i=0; i<aRecord.getFieldCount(); i++) {
         ties[i] = 0;
         sustains[i] = 0;
         rests[i] = 0;
      }
      // remove non-kern spines from note list
      for (i=0; i<aRecord.getFieldCount(); i++) {
         if (aRecord.getExInterpNum(i) != E_KERN_EXINT) {
            ties.setSize(ties.getSize() - 1);
            sustains.setSize(sustains.getSize() - 1);
            rests.setSize(rests.getSize() - 1);
         }
      }
   }

   // determine note states 
   Collection<int> currentTies(ties.getSize());
   Collection<int> currentSustains(sustains.getSize());
   Collection<int> currentRests(rests.getSize());
   int count = 0;
   for (i=0; i<aRecord.getFieldCount(); i++) {
      if (aRecord.getExInterpNum(i) == E_KERN_EXINT) {

         // sustain: check if a continuing note
         if (strcmp(aRecord[i], ".") == 0) {
            currentSustains[count] = 1;
         } else {
            currentSustains[count] = 0;
         }
    
         // tie: check for a tie marking
         if (strchr(aRecord[i], '[') != NULL) {
            currentTies[count] = 1;
         } else if (strchr(aRecord[i], ']') != NULL) {
            currentTies[count] = 0;
         } else {
            currentTies[count] = -1;
         }
         
         // rest: check for a rest
         if (strchr(aRecord[i], 'r') != NULL) {
            currentRests[count] = 1;
         } else {
            currentRests[count] = -1;
         }

         count++;
      }
   }

   // process the findings
   for (i=0; i<ties.getSize(); i++) {
      // resolve rests:
      if (currentRests[i] == -1 && currentSustains[i] == 1) {
         currentRests[i] = rests[i];
      } else if (currentRests[i] == -1) {
         currentRests[i] = 0;
      }

      // resolve ties:
      if (currentTies[i] == -1 && ties[i] == 1) {
         currentTies[i] = 1;
      } else if (currentTies[i] == -1) {
         currentTies[i] = 0;
      }
   }


   // determine output
   Collection<int> results(ties.getSize());
   int tiechange;
   for (i=0; i<ties.getSize(); i++) {
      if (ties[i] == 1 && currentTies[i] == 1) {
         tiechange = 1;
      } else if (ties[i] == 1 && currentTies[i] == 0) {
         tiechange = 1;
      } else {
         tiechange = 0;
      }
      if (tiechange || currentSustains[i] || currentRests[i]) {
         results[i] = 1;
      } else {
         results[i] = 0;
      }
   }
      
   // copy new values onto old values
   for (i=0; i<ties.getSize(); i++) {
      ties[i] = currentTies[i];
      rests[i] = currentRests[i];
      sustains[i] = currentSustains[i];
   }

   int output = 1;
   for (i=0; i<results.getSize(); i++) {
      if (results[i] == 0) {
         output = 0;
         break;
      }
   }

   int testrests = 1;
   for (i=0; i<results.getSize(); i++) {
      if (currentRests[i] == 0) {
         testrests = 0;
         break;
      }
   }

   if (testrests) {
      output = 0;
   }

*/
   return output;
}



//////////////////////////////
//
// numberToString --
//

void numberToString(double number, char* string) {
   SSTREAM temp;
   temp << number << ends;
   strncpy(string, temp.CSTRING, 64);
}



//////////////////////////////
//
// example -- example usage of the quality program
//

void example(void) {
   cout <<
   "                                                                         \n"
   "# example usage of the composite program.                                \n"
   "# analyze a Bach chorale for composite rhythm:                           \n"
   "     composite chor217.krn                                               \n"
   "                                                                         \n"
   "# display the composite spine with original data:                        \n"
   "     composite -a chor217.krn                                            \n"
   "                                                                         \n"
   << endl;
}



////////////////////
//
// generateMupOutput
//

void generateMupOutput(HumdrumFile& aFile) {
   int tietrace = 0;
   char aString[128] = {0};
   int starttest = -1;
   int measurestart = 1;

   cout << "// beat output\n\n";
   cout << "header\n   title (28) \" \"\n   title (14) \"\\f(TI) "
        << "composite rhythm\"\n";
   cout << "   title (18) \"\\f(TB)"; 
   if (strcmp(options.getString("title"), "") != 0) {
      cout << options.getString("title");
   } else if (strcmp(title, "") != 0) {
      cout << title;
   }
   cout << "\"\n\n";
   cout << "score\n   stafflines=1\n   scale=0.75\n   measnum=y\n\n";

   HumdrumFile analysis = aFile.extract(-1);
   HumdrumRecord currRecord;
   for (int i=0; i<analysis.getNumLines(); i++) {
      currRecord = analysis[i]; 
      switch (currRecord.getType()) {
         case E_humrec_none:
         case E_humrec_empty:
         case E_humrec_global_comment:
         case E_humrec_bibliography:
         case E_humrec_data_comment:
            break;
         case E_humrec_data_interpretation:
            if (currRecord[0][1] == 'M' && isdigit(currRecord[0][2])) {
               cout << "score\n   time=";
               strcpy(aString, &currRecord[0][2]);
               cout << aString << '\n';
               if (strcmp(aString, "1/4") == 0) {
                  cout << "   beamstyle=4\n";
               } else if (strcmp(aString, "2/4") == 0) {
                  cout << "   beamstyle=4,4\n";
               } else if (strcmp(aString, "3/4") == 0) {
                  cout << "   beamstyle=4,4,4\n";
               } else if (strcmp(aString, "4/4") == 0) {
                  cout << "   beamstyle=4,4,4,4\n";
               }
               cout << "\nmusic\n\n";
            }
            break;
         case E_humrec_data_measure:
            measurestart = 1;
            cout << '\n';
            if (starttest != -1) {
            if (currRecord[0][strlen(currRecord[0])-1] == '-') {
               cout << "\ninvisbar\n\n";
            } else if (isdigit(currRecord[0][1])) {
               cout << "\nbar\n\n";
            } else if (currRecord[0][1] == '=') {
               cout << "\nendbar\n\n";
            } else if (strcmp(currRecord[0], "=||") == 0) {
               cout << "\ndblbar\n\n";
            } else if (strcmp(currRecord[0], "=|!") == 0) {
               cout << "\nendbar\n\n";
            } else if (strcmp(currRecord[0], "=!|:") == 0) {
               cout << "\nrepeatstart\n\n";
            } else if (strcmp(currRecord[0], "=:|!") == 0) {
               cout << "\nrepeatend\n\n";
            } else if (strcmp(currRecord[0], "=:!!:") == 0) {
               cout << "\nrepeatboth\n\n";
            } else {
               cout << "\nbar\n\n";
            }
            cout << "music\n\n";
            break;
         case E_humrec_data:
            starttest = 1;
            // ignore null fields
            if (strcmp(currRecord[0], ".") == 0) {
               break;
            }

            if (measurestart) {
               measurestart = 0;
               cout << "\n1: ";
            }
 
            if (strchr(currRecord[0], '[') != NULL) {
               tietrace = 1;
            } else if (strchr(currRecord[0], ']') != NULL) {
               tietrace = 0;
            }

            if (strchr(currRecord[0], 'r') != NULL) {
               cout << currRecord[0];
            } else if (strchr(currRecord[0], '[') != NULL) {
               for (unsigned int j=0; j<strlen(currRecord[0]); j++) {
                  if (currRecord[0][j] != '[') {
                     cout << currRecord[0][j];
                  }
               }
               cout << 'e';
            } else if (strchr(currRecord[0], ']') != NULL) {
               for (unsigned int j=0; j<strlen(currRecord[0]); j++) {
                  if (currRecord[0][j] != ']') {
                     cout << currRecord[0][j];
                  }
               }
               cout << 'e';
            } else {
               cout << currRecord[0] << 'e';
            }
            
            if (tietrace) {
               cout << '~';
            }
          
            cout << "; ";

            break;
         default:
            cerr << "Error on line " << (i+1) << " of output" << endl;
            exit(1);
      }
   }
}
}



////////////////////
//
// modifyRecordTie -- adjust the tie for the last data in list
//	state = 1 --> turn on a tie
//	state = 0 --> turn off a tie
//

void modifyRecordTie(HumdrumRecord& aRecord, int state) {
   char temp[128];

   if (state) {                  // starting a tie
      strcpy(temp, "[");
      strcat(temp, aRecord[aRecord.getFieldCount() - 1]);
   } else {
      strcpy(temp, aRecord[aRecord.getFieldCount() - 1]);
      strcat(temp, "]");
   }

   aRecord.changeField(aRecord.getFieldCount()-1, temp);
}



//////////////////////////////
//
// processRecords -- looks at humdrum records and determines chord
//	quality
//

void processRecords(HumdrumFile& infile, HumdrumFile& outfile) {
   char   aString[256] = {0};
   double duration;
   int rest;
   int tie;
   int lastDataRecord = 0;
   infile.analyzeRhythm();

   HumdrumRecord currRecord;
   for (int i=0; i<infile.getNumLines(); i++) {
      if (options.getBoolean("debug")) {
         cout << "processing line " << (i+1) << " of input ..." << endl;
      }
      currRecord = infile[i]; 
      switch (currRecord.getType()) {
         case E_humrec_none:
         case E_humrec_empty:
            if (strncmp(currRecord[0], "!!!OTL: ", 8) == 0) {
               // title = new char[strlen(currRecord[0])];
               strcpy(title, &currRecord[0][8]);
            } else if (strncmp(currRecord[0], "!!!OTL:", 7) == 0) {
               // title = new char[strlen(currRecord[0])];
               strcpy(title, &currRecord[0][7]);
            }
         case E_humrec_global_comment:
         case E_humrec_bibliography:
            outfile.appendLine(currRecord);
            break;
         case E_humrec_data_comment:
            if (currRecord.equalFieldsQ("**kern")) {
               currRecord.appendField(currRecord[0]);
            } else {
               currRecord.appendField("!");
            }
            outfile.appendLine(currRecord);
            break;
         case E_humrec_data_interpretation:
            if (currRecord.hasExclusiveQ()) {
               currRecord.appendField("**composite");
               outfile.appendLine(currRecord);
            } else {
               if (currRecord.equalFieldsQ("**kern")) {
                  currRecord.appendField(currRecord[0]);
               } else {
                  currRecord.appendField("*");
               }
               outfile.appendLine(currRecord);
            }
            break;
         case E_humrec_data_kern_measure:
            if (currRecord.equalFieldsQ("**kern")) {
               currRecord.appendField(currRecord[0]);
            } else {
               currRecord.appendField("=");
            }
            outfile.appendLine(currRecord);
            meterposition = 1;
            break;
         case E_humrec_data:

            // handle null fields
            if (currRecord.equalFieldsQ("**kern", ".")) {
               currRecord.appendField(".");
               outfile.appendLine(currRecord);
               break;
            }

            duration = infile[i].getDuration();
            if (duration == 0.0) {
               strcpy(aString, "q");
            } else {
               Convert::durationToKernRhythm(aString, duration);
            }
            if (!options.getBoolean("no-rests")) {
               rest = determineRest(infile, i);
               if (rest) {
                  strcat(aString, "r");
               }
            }

            tie = determineTie(infile, i);
            if (tie == 1 && lastTie == 0) {
               modifyRecordTie(outfile[lastDataRecord], 1);
            } else if (tie == 0 && lastTie == 1) {
               modifyRecordTie(outfile[lastDataRecord], 0);
            }
            lastTie = tie;

            currRecord.appendField(aString);
            outfile.appendLine(currRecord);
          
            lastDataRecord = i;
            break;
         default:
            cerr << "Error on line " << (i+1) << " of input" << endl;
            exit(1);
      }
   }
}



//////////////////////////////
//
// usage -- gives the usage statement for the meter program
//

void usage(const char* command) {
   cout <<
   "                                                                         \n"
   "Analyzes **kern data and generates a composite rhythm spine from the     \n"
   "input **kern data records.  Currently, input spines cannot split or join.\n"
   "                                                                         \n"
   "Usage: " << command << " [-a][-c][input1 [input2 ...]]                   \n"
   "                                                                         \n"
   "Options:                                                                 \n"
   "   -a = assemble the composite analysis spine with the input data.       \n"
   "   -r = don't indicate rests in composite rhythm analysis.               \n"
   "   -m = generate mup output instead of humdrum output.                   \n"
   "   --options = list of all options, aliases and default values           \n"
   "                                                                         \n"
   << endl;
}



// md5sum: 50cc15ca60152d6816838c3d5161bbef composite.cpp [20110711]