//
// Programmer:    Craig Stuart Sapp <craig@ccrma.stanford.edu>
// Creation Date: Sat Nov 18 13:11:13 PST 2000
// Last Modified: Sat Nov 18 13:11:17 PST 2000
// Filename:      ...sig/examples/all/atttacks.cpp
// Web Address:   http://sig.sapp.org/examples/museinfo/humdrum/attacks.cpp
// Syntax:        C++; museinfo
//
// Description:   Counts attack notes for every position in the input score.
//                Examines **kern spines and ignores all other spines.
//

#include "humdrum.h"

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


// function declarations
void   checkOptions(Options& opts, int argc, char* argv[]);
int    countAttacks(HumdrumRecord& record);
int    doublecompare(const void* a, const void* b);
void   example(void);
void   generateAnalysis(HumdrumFile& infile, Array<int>& attacks);
void   generateSummary(HumdrumFile& infile, Array<int>& attacks);
void   generateSubSummary(HumdrumFile& infile, Array<int>& attacks, 
                               const char* metricmarker, int start, int stop);
void   printAnalysis(HumdrumFile& infile, Array<int>&metric);
void   usage(const char* command);


// global variables
Options      options;            // database for command-line arguments
int          debugQ     = 0;     // used with the --debug option
int          appendQ    = 0;     // used with the -a option
int          summaryQ   = 0;     // used with the -c option
int          densityQ   = 0;     // used with the -d option


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

int main(int argc, char* argv[]) {
   HumdrumFile infile;
   Array<int>  attacks;

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

      generateAnalysis(infile, attacks);
      if (summaryQ) {
         generateSummary(infile, attacks);
      } else {
         printAnalysis(infile, attacks);
      }
   }

   return 0;
}


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


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

void checkOptions(Options& opts, int argc, char* argv[]) {
   opts.define("a|append=b",        "append analysis to data in output");   
   opts.define("s|sum|summary=b",   "average value for each metrical position");
   opts.define("d|density|delta=b", "display the change in attack density");

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

   debugQ = opts.getBoolean("debug");
   appendQ = opts.getBoolean("append");

   if (opts.getBoolean("summary")) {
      summaryQ = 1;
      appendQ = 0;
   } else {
      summaryQ = 0;
   }

   if (opts.getBoolean("density")) {
      densityQ = 1;
   } else {
      densityQ = 0;
   }

}



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

void example(void) {
   cout <<
   "                                                                        \n"
   << endl;
}



//////////////////////////////
//
// generateAnalysis -- 
//

void generateAnalysis(HumdrumFile& infile, Array& attacks) {
   attacks.setSize(infile.getNumLines());

   for (int i=0; i<infile.getNumLines(); i++) {
      if (options.getBoolean("debug")) {
         cout << "processing line " << (i+1) << " of input ..." << endl;
      }
      if (infile[i].getType() != E_humrec_data) {
         attacks[i] = -1;
         continue;
      }

      attacks[i] = countAttacks(infile[i]);
   }
}



//////////////////////////////
//
// generateSummary -- generate the average measurement value for
//     each metrical position in the meter.
//

void generateSummary(HumdrumFile& infile, Array& attacks) {
   int start = 0;
   int stop = 0;
   int i=0;
   int m=0;
   int datatrigger = 0;
   int metertrigger = 0;
   int summarytrigger = 0;
   int length;
   const char* metricmarker = "";

   cout << "**takt\t**attack\t**avg\t**fract\n";

   while (i<infile.getNumLines()) {
      if (infile[i].getType() == E_humrec_data) {
         datatrigger = 1;
         if (start == 0) {
            start = i;
         }
      }

      if (infile[i].getType() == E_humrec_interpretation) {
         for (m=0; m<infile[i].getFieldCount(); m++) {
            if ((infile[i].getExInterpNum(m) == E_KERN_EXINT) && 
                  (strncmp(infile[i][m], "*M", 2) == 0)) {
               length = strlen(infile[i][m]);
               if ((length > 2) && isdigit(infile[i][m][2]) && 
                     (strchr(infile[i][m], '/') != NULL)) {
                  metertrigger = 1;
                  metricmarker = infile[i][m];
                  if (datatrigger) {
                     summarytrigger = 1;
                     stop = i-1;
                  } else {
                     start = i;
                  }
                  break;
               } else if (strncmp(infile[i][m], "*MM", 3) == 0) {
                  cout << infile[i][m] << "\t";
                  cout << infile[i][m] << "\t";
                  cout << infile[i][m] << "\n";
                  break;
               }
            } 
         }
      }
      if (strcmp(infile[i][0], "*-") == 0) {
         summarytrigger = 1;
         stop = i;
      }

      if (datatrigger == 1 && metertrigger == 0) {
         generateSubSummary(infile, attacks, "*MX", 0, infile.getNumLines());
         break;
      }

      if (summarytrigger) {
         generateSubSummary(infile, attacks, metricmarker, start, stop);
         summarytrigger = 0;
         datatrigger = 0;
         metricmarker = "";
         start = stop + 1;
      }
      i++;

   }
   cout << "*-\t*-\t*-\t*-\n";

}



//////////////////////////////
//
// generateSubSummary --
//

void generateSubSummary(HumdrumFile& infile, Array<int>& attacks, 
   const char* metricmarker, int start, int stop) {

   infile.analyzeRhythm();

   Array<double> metposition;
   metposition.setSize(0);
   metposition.allowGrowth();

   Array<int> count;
   count.setSize(0);
   count.allowGrowth();

   Array<int> data;
   data.setSize(0);
   data.allowGrowth();

   double current;
   int one = 1;

   const char* metermarker = "*";

   int sumcount = 0;
   int attackcount = 0;
   int i;
   int j;
   int k;
   int sindex;
   for (i=start; i<stop; i++) {
      if (infile[i].getType() == E_humrec_interpretation) {
         for (j=0; j<infile[i].getFieldCount(); j++) {
            if (strncmp(infile[i][j], "*M", 2) == 0) {
               if (isdigit(infile[i][j][2]) && 
                  (strchr(infile[i][j], '/') != NULL)) {
                  metermarker = infile[i][j];
               }
            }
         }
      }
      if (infile[i].getType() != E_humrec_data) {
         continue;
      }
      sindex = -1;
      if (metposition.getSize() == 0) {
         current = infile[i].getBeat();
         metposition.append(current);
         count.append(one);
         data.append(attacks[i]);
         sumcount++;
         attackcount +=attacks[i];
      } else {
         k = 0;
         while (k < metposition.getSize()) {
            if (fabs(metposition[k] - infile[i].getBeat()) < 0.001) {
               sindex = k;
               break;
            } 
            k++;
         }
         if (sindex >= 0) {
            count[sindex]++;
            data[sindex] += attacks[i];
            sumcount++;            
            attackcount += attacks[i];
         } else {
            current = infile[i].getBeat();
            metposition.append(current);
            count.append(one);
            data.append(attacks[i]);
            sumcount++;
            attackcount +=attacks[i];
         }
      }

   }

   Array<double> sortmet = metposition;

   qsort(sortmet.getBase(), metposition.getSize(), sizeof(double), 
      doublecompare);

   Array<double> average;
   Array<double> fraction;
   average.setSize(metposition.getSize());
   fraction.setSize(metposition.getSize());

   for (i=0; i<average.getSize(); i++) {
      average[i] = (double)data[i]/count[i];
      fraction[i] = (double)data[i]/attackcount;
   }

   cout << metermarker << "\t*lines=" << start+1 << ":" << stop+1 
        << "\t*cnt=" << attackcount << "\t*\n";
   for (i=0; i<metposition.getSize(); i++) {
      k = 0;
      while (k < metposition.getSize()) {
         if (metposition[k] == sortmet[i]) {
            break;
         }
         k++;
      }
      if (k > metposition.getSize()) {
         continue;
      }
      cout << sortmet[i]  << "\t"
           << data[k]     << "\t" 
           << average[k]  << "\t" 
           << fraction[k] << "\n";
   }

}



//////////////////////////////
//
// countAttacks -- count the number of notes that are struck
//    at each moment in the score.
//

int countAttacks(HumdrumRecord& record) {
   int i, j;
   int count = 0;
   int tcount;
   char buffer[128] = {0};
   for (i=0; i<record.getFieldCount(); i++) {
      if (record.getExInterpNum(i) != E_KERN_EXINT) {
         continue;
      }
      if (strcmp(record[i], ".") == 0) {
         continue;
      }
      tcount = record.getTokenCount(i);
      for (j=0; j<tcount; j++) {
         record.getToken(buffer, i, j);         
         if (strcmp(buffer, ".") == 0) {
            continue;
         }

         if (strchr(buffer, '_') != NULL) {
            continue;
         }
         if (strchr(buffer, ']') != NULL) {
            continue;
         }

         count++;
      }
   }

   return count;
}



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

void printAnalysis(HumdrumFile& infile, Array& attacks) {
   int lastAttack = -1;
   int i;
   if (appendQ) {
      for (i=0; i<infile.getNumLines(); i++) {
         switch (infile[i].getType()) {
         case E_humrec_global_comment:
         case E_humrec_bibliography:
         case E_humrec_none:
         case E_humrec_empty:
            cout << infile[i].getLine() << "\n";
            break;
         case E_humrec_data:
            cout << infile[i].getLine() << "\t";
            if (densityQ) {
               if (lastAttack == -1) {
                  cout << "[" << attacks[i] << "]" << "\n";
               } else {
                  cout << attacks[i] - lastAttack << "\n";
               }
               lastAttack = attacks[i];
            } else {
               cout << attacks[i] << "\n";
            }
            break;
         case E_humrec_data_comment:
            if (infile[i].equalFieldsQ("**kern")) {
               cout << infile[i].getLine() << "\t"
                    << infile[i][0] << "\n";
            } else {
               cout << infile[i].getLine() << "\t!\n";
            }
            break;
         case E_humrec_data_measure:
            if (infile[i].equalFieldsQ("**kern")) {
               cout << infile[i].getLine() << "\t"
                    << infile[i][0] << "\n";
            } else {
               cout << infile[i].getLine() << "\t=\n";
            }
            break;
         case E_humrec_data_interpretation:
            if (strncmp(infile[i][0], "**", 2) == 0) {
               cout << infile[i].getLine() << "\t";
               if (densityQ) {
                  cout << "**ddelta" << "\n";
               } else {
                  cout << "**attack" << "\n";
               }
            } else if (infile[i].equalFieldsQ("**kern")) {
               cout << infile[i].getLine() << "\t"
                    << infile[i][0] << "\n";
            } else {
               cout << infile[i].getLine() << "\t*\n";
            }
            break;
         }
      }

   } else {

      for (i=0; i<infile.getNumLines(); i++) {
         switch (infile[i].getType()) {
         case E_humrec_global_comment:
         case E_humrec_bibliography:
         case E_humrec_none:
         case E_humrec_empty:
            cout << infile[i].getLine() << "\n";
            break;
         case E_humrec_data:
            if (densityQ) {
               if (lastAttack == -1) {
                  cout << "[" << attacks[i] << "]" << "\n";
               } else {
                  cout << attacks[i] - lastAttack << "\n";
               }
               lastAttack = attacks[i];
            } else {
               cout << attacks[i] << "\n";
            }
            break;
         case E_humrec_data_comment:
            if (infile[i].equalFieldsQ("**kern")) {
               cout << infile[i][0] << "\n";
            } else {
               // do nothing
            }
            break;
         case E_humrec_data_measure:
            if (infile[i].equalFieldsQ("**kern")) {
               cout << infile[i][0] << "\n";
            } else {
               cout << "\t=\n";
            }
            break;
         case E_humrec_data_interpretation:
            if (strncmp(infile[i][0], "**", 2) == 0) {
               if (densityQ) {
                  cout << "**ddelta" << "\n";
               } else {
                  cout << "**attack" << "\n";
               }
            } else if (infile[i].equalFieldsQ("**kern")) {
               cout << infile[i][0] << "\n";
            } else {
               // do nothing
            }
            break;
         }
      }
   }
}



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

void usage(const char* command) {
   cout <<
   "                                                                        \n"
   << endl;
}



//////////////////////////////
//
// doublecompare -- compare two doubles for ordering
//

int doublecompare(const void* a, const void* b) {
   if (*((double*)a) < *((double*)b)) {
      return -1;
   } else if (*((double*)a) > *((double*)b)) {
      return 1;
   } else {
      return 0;
   }
}
              

// md5sum: 692c1c4950ec2ab6deb5a27223909892 attacks.cpp [20050403]