Goto: [ Program Documentation ]

//
// Programmer:    Craig Stuart Sapp <craig@ccrma.stanford.edu>
// Creation Date: Fri Jun 26 13:02:06 PDT 1998
// Last Modified: Fri Jul  3 14:17:58 PDT 1998
// Last Modified: Mon Apr 17 14:46:51 PDT 2000 (process incomplete beats)
// Filename:      ...sig/doc/examples/museinfo/humdrum/beat/beat.cpp
// Syntax:        C++; humdrum
//
// Description:   Generates metrical location data for **kern entries
//
// There are two cases when an incomplete measure needs to
//    be counted backwards.  These cases will be handled by
//    the beat program:
//    (1) an initial pickup beat
//    (2) a repeat sign breaks a true measure
//

#include "humdrum.h"
#include <stdlib.h>
#include <iostream.h>
#include <stdio.h>
#include <ctype.h>
#include <string.h>

#ifdef VISUAL
   #include <strstrea.h>    /* for windows 95 */
#else
   #include <strstream.h>
#endif


// function declarations
void     checkOptions(Options& opts, int argc, char* argv[]);
double   determineDuration(const HumdrumRecord& aRecord);
void     example(void);
void     fixIncompleteBars(HumdrumFile& outfile);
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          init;               // for initializing duration function
int          zero = 0;           // zero offset instead of 1 for first beat


// incomplete measure checking variables
Array<int>     lineC;            // file line number of the current item
Array<double>  sumC;             // current summation of the beat
Array<double>  durationC;        // duration of the current item
Array<double>  beatlocationC;    // beat position of the current item
Array<double>  meterbeatsC;      // number of beats in current meter
Array<double>  timebaseC;        // duration of a beat
Array<double>  errorsC;          // errors in the measure length
				 // note: errorsC is not used yet
int lineOffset = 0;

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


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++) {
      init = 1;
      infile.clear();
      outfile.clear();

      // incomplete measure checking variables

      lineC.setSize(10000);
      sumC.setSize(10000);
      durationC.setSize(10000);
      beatlocationC.setSize(10000);
      meterbeatsC.setSize(10000);
      timebaseC.setSize(10000);
      errorsC.setSize(10000);

      lineC.setAllocSize(10000);
      sumC.setAllocSize(10000);
      durationC.setAllocSize(10000);
      beatlocationC.setAllocSize(10000);
      meterbeatsC.setAllocSize(10000);
      timebaseC.setAllocSize(10000);
      errorsC.setAllocSize(10000);

      lineC.setSize(0);
      sumC.setSize(0);
      durationC.setSize(0);
      beatlocationC.setSize(0);
      meterbeatsC.setSize(0);
      timebaseC.setSize(0);
      errorsC.setSize(0);

      lineC.allowGrowth();
      sumC.allowGrowth();
      durationC.allowGrowth();
      beatlocationC.allowGrowth();
      meterbeatsC.allowGrowth();
      timebaseC.allowGrowth();
      errorsC.allowGrowth();


      // 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 = outfile.extract(-1);
         if (!options.getBoolean("keep-nulls")) {
            outfile = outfile.removeNullRecords();
         }
      }

      outfile.write(cout);
   }

   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("b|beat|base|t|timebase=s:"); // rhythmic unit of one beat
   opts.define("d|dur|duration=b");       // display rhymic duration of records
   opts.define("s|sum=b");                // sum the duration of each measure
   opts.define("z|zero|zero-offset=b");   // first beat is represented by a 0
   opts.define("n|nulls|keep-nulls=b");   // doesn't remove nulls with -s option
   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, May 1998" << endl;
      exit(0);
   } else if (opts.getBoolean("version")) {
      cout << argv[0] << ", version: 17 April 2000" << endl;
      cout << "compiled: " << __DATE__ << endl;
      cout << HUMDRUM_VERSION << endl;
      exit(0);
   } else if (opts.getBoolean("help")) {
      usage(opts.getCommand());
      exit(0);
   } else if (opts.getBoolean("example")) {
      example();
      exit(0);
   }

   if (options.getBoolean("zero")) {
      zero = 1;
   }
}
  


//////////////////////////////
//
// determineDuration -- determines the duration of the **kern
//	entries before a new **kern entry.
//

double determineDuration(const HumdrumRecord& aRecord) {
   static Collection<double> lastdurations;  // for keeping track of durations
   static Collection<double> runningstatus;  // for keeping track current dur

   int i;
   // initialization:
   if (init) {
      init = 0;
      lastdurations.setSize(aRecord.getFieldCount());
      runningstatus.setSize(aRecord.getFieldCount());
      for (i=0; i<aRecord.getFieldCount(); i++) {
         lastdurations[i] = 0;
         runningstatus[i] = 0;
      }
      // remove non-kern spines from note list
      for (i=0; i<aRecord.getFieldCount(); i++) {
         if (aRecord.getExInt(i) != E_KERN_EXINT) {
            lastdurations.setSize(lastdurations.getSize() - 1);
            runningstatus.setSize(runningstatus.getSize() - 1);
         }
      }
   }


   // Step (1): if lastdurations == runningstatus, then zero running
   // status.
   for (i=0; i<runningstatus.getSize(); i++) {
      if (runningstatus[i] == lastdurations[i]) {
         runningstatus[i] = 0;
      }
   }

 
   // Step (2): input new durations into the lastdurations array
   int count = 0;
   for (i=0; i<aRecord.getFieldCount(); i++) {
      if (aRecord.getExInt(i) == E_KERN_EXINT) {
         if (strcmp(aRecord[i], ".") == 0) {
            ; // don't do anything
         } else {
            lastdurations[i] = Convert::kernToDuration(aRecord[i]);
         }
         count++;
      }
   }
   if (count != runningstatus.getSize()) {
      cerr << "Error: spine count has changed" << endl;
   }


   // Step (3): find minimum duration by subtracting last from running
   double min = 99999999;
   double test;
   for (i=0; i<lastdurations.getSize(); i++) {
      test = lastdurations[i] - runningstatus[i];
      if (test < min) {
         min = test;
      }
   }
   

   // Step (4): add the duration to the running values and to meter position
   for (i=0; i<runningstatus.getSize(); i++) {
      runningstatus[i] += min;
   }
   meterposition += min;

   return min;
}


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

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



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

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



//////////////////////////////
//
// processRecords -- looks at humdrum records and determines the 
//      beat location.
//

void processRecords(HumdrumFile& infile, HumdrumFile& outfile) {
   char   aString[256] = {0};      // for creating strings
   char   aString2[256] = {0};     // for creating strings
   double summation = 0;           // for summing measure duration
   int summationLine = -1;         // for locating summation line in output
   double duration;
   HumdrumRecord tempRecord;       // for *beat: interpretation
   char* slash;                    // for metronome marking
   double measureBeats = 0.0;
   double mmarker = -1.0;
   double temp = 0.0;

   double timebase = 4.0;
   if (strcmp(options.getString("base"), "") != 0) {
      timebase = options.getInteger("base");
 
      // check for prolongation dot.  Ignore any double dots
      if (strchr(options.getString("base"), '.') != NULL) {
         timebase = timebase * 2.0/3.0;
      }
   }

   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:
         case E_humrec_comment:
         case E_humrec_global_comment:
            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_tandem:
            // check for time signature
            if (currRecord[0][1] == 'M' && isdigit(currRecord[0][2])) {
               measureBeats = atof(&currRecord[0][2]);
               slash = strchr(currRecord[0], '/');
               if (slash != NULL && 
                     strcmp(options.getString("base"), "") == 0) {
                  timebase = atof(&slash[1]);
               }
               
               // create a beat interpretation if manually specified
               if (strcmp(options.getString("base"), "") != 0 &&
                     tempRecord.getFieldCount() == 0) {
                     tempRecord = currRecord;
                  for (int j=0; j<currRecord.getFieldCount(); j++) {
                     tempRecord.changeField(j, "*");
                  }
                  strcpy(aString, "*base:");
                  Convert::durationToKernRhythm(aString2, 4.0/timebase);
                  strcat(aString, aString2);
                  tempRecord.appendField(aString);
                  outfile.appendLine(tempRecord);
                  lineOffset++;
               }
            }

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

            lineC.append(i+lineOffset);
            sumC.append(mmarker);
            durationC.append(mmarker);
            beatlocationC.append(mmarker);
            meterbeatsC.append(mmarker);
            timebaseC.append(mmarker);

            outfile.appendLine(currRecord);
            meterposition = 1;
            if (summationLine != -1 && options.getBoolean("sum")) {
               numberToString(summation, aString);               
               outfile[summationLine].changeField(
                  outfile[summationLine].getFieldCount()-1,
                  aString);
               summationLine = outfile.getNumLines();
            }
            summation = 0;
            break;
         case E_humrec_data_interpretation:
            if (options.getBoolean("duration")) {
               currRecord.appendField("**duration");
            } else if (options.getBoolean("sum")) {
               currRecord.appendField("**beatsum");
            } else {
               currRecord.appendField("**beat");
            }
            outfile.appendLine(currRecord);
            break;
         case E_humrec_data:
            if (summationLine == -1) {
                summationLine = outfile.getNumLines();
            }
            // handle null fields
            if (currRecord.equalFieldsQ("**kern", ".")) {
               currRecord.appendField(".");
               outfile.appendLine(currRecord);
               break;
            }
            if (options.getBoolean("duration")) {
               duration = determineDuration(infile[i]) * timebase / 4.0;
               numberToString(duration, aString);
               currRecord.appendField(aString);
               outfile.appendLine(currRecord);
            } else if (options.getBoolean("sum")) {
               currRecord.appendField(".");
               outfile.appendLine(currRecord);
               duration = determineDuration(infile[i]);
               summation += duration * timebase/4.0;
            } else {
               numberToString(meterposition*timebase/4.0 - 
                  timebase/4.0 + 1 - zero, aString);
               currRecord.appendField(aString);
               outfile.appendLine(currRecord);
               duration = determineDuration(infile[i]);
               summation += duration * timebase/4.0;

               lineC.append(i+lineOffset);
               sumC.append(summation);
               durationC.append(duration);
               temp = meterposition*timebase/4.0 - timebase/4.0;
               beatlocationC.append(temp);
               temp = measureBeats * 4.0 / timebase;
               meterbeatsC.append(measureBeats);
               timebaseC.append(timebase);

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

   if (!options.getBoolean("sum") && !options.getBoolean("duration")) {
      fixIncompleteBars(outfile);
   }
}



//////////////////////////////
//
// fixIncompleteBars -- resolve when incomplete bars are
//    supposed to be the ends of measures rather than the
//    beginnings of measures.
//

void fixIncompleteBars(HumdrumFile& outfile) {
   int i, k;
   int lastk = 0;
   double lastsum = 0;
   double newsum = 0;
   char newentry[1024] = {0};

   for (i=0; i<lineC.getSize(); i++) { 
      // at each measure line determine one of three
      // cases: (1) all ok (2) partial measure (3) incorrect measure
      if (durationC[i] == -1) {       
        
         // case 1: measure sum is complete
         if (i==0) {
            continue;
         }
         if (sumC[i-1] == meterbeatsC[i-1]) {
            lastsum = meterbeatsC[i-1];
            lastk = i-1;
            continue;
         }

         // incomplete measure encountered determine if it
         // is an error or not.

         // case 2a: start of piece
         if (lastsum == 0) {
            k = i-1;
            newsum = 0;
            while (k >= 0 && sumC[k] != -1) {
               newsum += durationC[k];
               numberToString((meterbeatsC[k] - newsum) 
                   * timebaseC[k]/4.0 + 1 - zero, newentry);
               outfile[lineC[k]].changeField(outfile[lineC[k]].getFieldCount()
                  -1, newentry);
               k--;
            }
            lastk = i-1;
            lastsum = sumC[i-1];
            continue;
         }
         // case 2b: repeat bar or something splitting up
         // a regular bar
         if ((lastsum + sumC[i-1]) == timebaseC[i-1]/4.0 * meterbeatsC[i-1]) {
            k = i-1;
            newsum = 0;
            while (k >= 0 && sumC[k] != -1) {
               newsum += durationC[k];
               numberToString((meterbeatsC[k] - newsum) 
                   * timebaseC[k]/4.0 + 1 - zero, newentry);
               outfile[lineC[k]].changeField(outfile[lineC[k]].getFieldCount()
                  -1, newentry);
               k--;
            }
            lastk = i-1;
            lastsum = sumC[i-1];
         } else {
            lastk = i-1;
            lastsum = sumC[i-1];
         }

          // don't worry about incorrect bars for now
      }
   }
}



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

void usage(const char* command) {
   cout <<
   "                                                                         \n"
   "Analyzes **kern data and generates a rhythmic analysis which gives       \n"
   "the beat location of **kern data records in the measure.  Currently,     \n"
   "input spines cannot split or join.                                       \n"
   "                                                                         \n"
   "Usage: " << command << " [-a][-b base-rhythm][-s|-d][input1 [input2 ...]]\n"
   "                                                                         \n"
   "Options:                                                                 \n"
   "   -a = assemble the analysis spine with the input data.                 \n"
   "   -b = set the base rhythm for analysis to specified kern rhythm value. \n"
   "   -d = gives the duration of each kern record in beat measurements.     \n"
   "   -s = sum the beat count in each measure.                              \n"
   "   --options = list of all options, aliases and default values           \n"
   "                                                                         \n"
   << endl;
}