Goto: [ Program Documentation ]

//
// Programmer:    Craig Stuart Sapp <craig@ccrma.stanford.edu>
// Creation Date: Fri Apr 14 09:41:45 PDT 2000
// Last Modified: Sun Apr 16 14:27:17 PDT 2000
// Last Modified: Sun May 24 09:15:43 PDT 2009 (para octs removed from #6)
// Last Modified: Sun May 24 09:15:43 PDT 2009 (added -s, -d, and -d options)
// Last Modified: Sun May 24 19:23:l2 PDT 2009 (added -f option)
// Last Modified: Sun May 24 19:23:l2 PDT 2009 (exclude unison motion in #3)
// Last Modified: Fri Jun 12 22:58:34 PDT 2009 (renamed SigCollection class)
// Filename:      ...sig/examples/all/chorck.cpp
// Web Address:   http://sig.sapp.org/examples/museinfo/humdrum/chorck.cpp
// Syntax:        C++; museinfo
//
// Description:   Analyzes choral-style harmony exercises for possible
//		  errors such as parallel fifths and octaves.  The input
//		  Humdrum data must contain 4 spines of kern data which
//		  represents the four voices: bass, tenor, alto and soprano.
//		  If there are more than 4 **kern spines, then the first
//		  4 **kern spines are assumed to be the SATB voices.
//		  The order of the voice spines does not matter, the program
// 		  will automatically sort them before analysis starts.
//		  Errors are reported as global comments before the line
//		  on which offending error starts, and is of the form:
//		  !! Warning: <Error description and location>
//
// Error types detected by this program:
//
//	1. Parallel 5ths between two voices when moving to
//	   different pitch classes.
//	2. Parallel Octaves between two voices when moving to
//	   different pitch classes.
//	3. Contrary parallel 5ths -- when two voices move in
//	   parallel 5ths displaced by an octave.
//	4. Unequal 5ths -- when the bass part and another
//	   voice move from dim 5ths to perfect 5ths or vice versa.
//	5. Hidden 5ths -- when the soprano moves in similar
//	   motion with another voice and the soprano leaps
//	   to a perfect 5th with that voice.
//	6. Hidden 8va -- when the soprano moves in similar
//	   motion with another voice and the soprano leaps
//	   to a perfect octave with that voice.
//	7. Voice crossing -- when an inner voice goes above
//	   the soprano voice or below the bass voice.
//	8. Open spacing -- when the interval between successive
//	   voices other than the bass exceeds an octave.
//

#include "humdrum.h"

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

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

#define ERROR_PARA5     1
#define ERROR_PARA8     2
#define ERROR_CPARA5    3
#define ERROR_NEQ5      4
#define ERROR_HIDDEN5   5
#define ERROR_HIDDEN8   6
#define ERROR_VOICEX    7
#define ERROR_OPEN      8

class Error {
   public:
      int line;            // line number on which the error occurs
      char message[128];   // error message to display
      Error(void) { line = -1; message[0] = '\0';}
};


// function declarations
void   checkForErrors(void);
void   checkOptions(Options& opts, int argc, char* argv[]);
int    errorCompare(const void* a, const void* b);
void   errormessage(int errornumber, const char* voice1, 
                            const char* voice2, int linenumber);
void   example(void);
void   initialize(HumdrumFile& infile);
void   prepareVoices(void);
void   printRules(const char* ruleString);
void   printVoiceArray(void);
void   processRecords(HumdrumFile& infile);
void   usage(const char* command);
void   sortErrorMessages(Error* errors, int size);
void   sortVoices(void);
void   writeoutput(HumdrumFile& infile);
int    getVoiceCount(HumdrumRecord& record);

void   error1(void);
void   error2(void);
void   error3(void);
void   error4(void);
void   error5(void);
void   error6(void);
void   error7(void);
void   error8(void);

// global variables
Options      options;            // database for command-line arguments
int          chordinit;          // for initializing chord detection function
int          errorCheck[20] = {0}; // command line check for errors exclusion
const char*  header = "";        // error message start string
const char*  marker = "";        // Warning string start
int          voicemin = 0;       // used with -s, -d, -t option
int          fileQ    = 0;       // used with -f option
const char*  Filename = "";      // used with -f option

// Analysis variables
SigCollection<Error> errorList;  // a list of detected errors in chorale
Array<int>   linenum;            // line number in file of given pitch set
Array<int>   voices[4];          // pitches from SATB lines
int          voiceloc[4];        // SATB voice spine locations 

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


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

   // 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++) {
      initialize(infile);

      // if no command-line arguments read data file from standard input
      if (numinputs < 1) {
         infile.read(cin);
      } else {
         infile.read(options.getArg(i+1));
	 if (fileQ) {
            Filename = options.getArg(i+1);
         }
      }

      // build note array from the input file according to command-line options
      processRecords(infile);
      sortVoices();

      // make rests = 0, slured notes = -attack value
      // following line is no longer needed: done in processRecords()
      // prepareVoices();

      if (options.getBoolean("base40-array")) {
         printVoiceArray();
	 exit(0);
      }

      checkForErrors();
      writeoutput(infile);
   }

   return 0;
}


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


//////////////////////////////
//
// checkForErrors -- check the chorale notes for errors
//

void checkForErrors(void) {
   if (errorCheck[0]) {
      error1();
   } 
   if (errorCheck[1]) {
      error2();
   } 
   if (errorCheck[2]) {
      error3();
   } 
   if (errorCheck[3]) {
      error4();
   } 
   if (errorCheck[4]) {
      error5();
   } 
   if (errorCheck[5]) {
      error6();
   } 
   if (errorCheck[6]) {
      error7();
   } 
   if (errorCheck[7]) {
      error8();
   } 
}



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

void checkOptions(Options& opts, int argc, char* argv[]) {
   opts.define("a|base40-array=b",  "show intermediate calculation array");
   opts.define("f|filename=b",      "display filename in warning");
   opts.define("l|line=b",          "display line nums with -a");
   opts.define("m|marker=s:Warning: ", "start of error message");
   opts.define("p|print-rules=s",   "print specified rule number");
   opts.define("r|rule=s:",         "include certain error checking rules");
   opts.define("s|no-single=b",     "exclude single note attacks");
   opts.define("d|no-double=b",     "exclude double note attacks and fewer");
   opts.define("t|no-triple=b",     "exclude triple note attacks and fewer");
   opts.define("w|warnings=b",      "show only warnings");
   opts.define("x|exclude=s:",      "exclude certain error checking rules");

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

   if (opts.getBoolean("no-single")) {
      voicemin = 2;
   }
   if (opts.getBoolean("no-double")) {
      voicemin = 3;
   }
   if (opts.getBoolean("no-triple")) {
      voicemin = 4;
   }

   int i;
   for (i=0; i<8; i++) {
      errorCheck[i] = 1;
   }
   if (opts.getBoolean("exclude")) {
      const char* pointer = opts.getString("exclude");
      int length = strlen(pointer);
      int i;
      for (i=0; i<length; i++) {
         if (isdigit(pointer[i]) && pointer[i] != '0') {
            errorCheck[pointer[i]  - '1'] = 0;
         }
      }
   }
   if (opts.getBoolean("rule")) {
      // turn off all rules
      for (i=0; i<8; i++) {
         errorCheck[i] = 0;
      }
      const char* pointer = opts.getString("rule");
      int length = strlen(pointer);
      int i;
      for (i=0; i<length; i++) {
         if (isdigit(pointer[i]) && pointer[i] != '0') {
            errorCheck[pointer[i]  - '1'] = 1;
         }
      }
   }

   if (opts.getBoolean("print-rules")) {
      printRules(opts.getString("print-rules"));
      exit(0);
   }

   marker = opts.getString("marker");
   fileQ  = opts.getBoolean("filename");
}



//////////////////////////////
//
// determineChordQuality -- extracts notes from humdrum data record
//	and sends them to a chord identifier.  Notes from previous
//	records are remembered, and replace any null records in the
//	data.  Rests are represented by some negative value 
//	and will be ignored by the chord identifier.
//

ChordQuality determineChordQuality(const HumdrumRecord& aRecord) {
   static SigCollection<int> lastnotes;  // keeping track of null record notes
   int i;
   if (chordinit) {
      chordinit = 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
   SigCollection<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];
         }
         count++;
      }
   }

   ChordQuality output;
   Convert::noteSetToChordQuality(output, currentNotes); 
   return output;
}



//////////////////////////////
//
// errorCompare -- insert an error message into the error buffer
//    for printing later
//

int errorCompare(const void* a, const void* b) {
   Error& A = *((Error*)a);
   Error& B = *((Error*)b);

   if (A.line < B.line) {
      return -1;
   } else if (A.line > B.line) {
      return 1;
   } else {
      int x, y;
      x = atoi(A.message);
      y = atoi(B.message);
      if (x < y) {
         return -1;
      } else if (x > y) {
         return 1;
      } else {
         return 0;
      }
   }
}



//////////////////////////////
//
// errormessage -- insert an error message into the error buffer
//    for printing later
//

void errormessage(int errornumber, const char* voice1, const char* voice2,
      int linenumber) {
   
   Error anError;
   anError.line = linenumber;
   switch (errornumber) {
      case 1:
         sprintf(anError.message, "1. Parallel 5th between %s and %s",
              voice1, voice2);
         break;
      case 2:
         sprintf(anError.message, "2. Parallel octave between %s and %s",
              voice1, voice2);
         break;
      case 3:
         sprintf(anError.message, "3. Contrary parallel 5th between %s and %s",
              voice1, voice2);
         break;
      case 4:
         sprintf(anError.message, "4. Unequal 5th between %s and %s",
              voice1, voice2);
         break;
      case 5:
         sprintf(anError.message, "5. Hidden 5th between %s and %s",
              voice1, voice2);
         break;
      case 6:
         sprintf(anError.message, "6. Hidden octave between %s and %s",
              voice1, voice2);
         break;
      case 7:
         sprintf(anError.message, "7. Voice crossing between %s and %s",
              voice1, voice2);
         break;
      case 8:
         sprintf(anError.message, "8. Open spacing between %s and %s",
              voice1, voice2);
         break;
      default:
         return;
   }

   errorList.append(anError);

}




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

void example(void) {
   cout <<
   "                                                                         \n"
   "# example usage of the quality program.                                  \n"
   "# analyze a Bach chorale for chord qualities:                            \n"
   "     quality chor217.krn                                                 \n"
   "                                                                         \n"
   "# display the chord analysis with original data:                         \n"
   "     quality -a chor217.krn                                              \n"
   "                                                                         \n"
   "# display only the roots of chords:                                      \n"
   "     quality -r chor217.krn                                              \n"
   "                                                                         \n"
   << endl;
}



//////////////////////////////
//
// initialize -- setup for a new file
//

void initialize(HumdrumFile& infile) {
   infile.clear();
   for (int k=0; k<4; k++) {
      voices[k].setSize(100000);
      voices[k].setAllocSize(100000);
      voices[k].setSize(0);
      voices[k].allowGrowth();
   }
   linenum.setSize(100000);
   linenum.setAllocSize(100000);
   linenum.setSize(0);
   linenum.allowGrowth();

   errorList.setSize(100000);
   errorList.setAllocSize(100000);
   errorList.setSize(0);
   errorList.allowGrowth();

}



//////////////////////////////
//
// printRules -- print the requested rules
//

void printRules(const char* ruleString) {
   int length = strlen(ruleString);
   for (int i=0; i<length; i++) {
      if (isdigit(ruleString[i]) && ruleString[i] != '0') {
         switch (ruleString[i] - '0') {
            case 1:
               cout << "Rule 1: Parallel 5ths\n"
                    << "   Two voices moving to different pitch classes"
                    << " cannot form a perfect 5th\n"
                    << "   on two successive notes.\n\n";
               break;
            case 2:
               cout << "Rule 2: Parallel Octaves\n"
                    << "   Two voices moving to different pitch classes"
                    << " cannot form a perfect octave\n"
                    << "   on two successive notes.\n\n";
               break;
            case 3:
               cout << "Rule 3: Contrary parallel 5ths\n"
                    << "   Two voices moving to different pitch classes\n"
                    << "   in contrary motion cannot form a perfect 5ths\n"
                    << "   on two successive notes.\n\n";
               break;
            case 4:
               cout << "Rule 4: Unequal 5ths\n"
                    << "   The intervals between the bass and an upper\n"
                    << "   voice cannot form the successive pattern\n"
                    << "   perfect 5th/dim 5th or dim 5th/perfect 5th.\n\n";
               break;
            case 5:
               cout << "Rule 5: Hidden 5ths\n"
                    << "   When the soprano moves in similary motion\n"
                    << "   with another voice and the sporano leaps to \n"
                    << "   a perfect 5th with that voice.\n\n";
               break;
            case 6:
               cout << "Rule 6: Hidden Octave\n"
                    << "   When the soprano moves in similary motion\n"
                    << "   with another voice and the sporano leaps to \n"
                    << "   a perfect octave with that voice.\n\n";
               break;
            case 7:
               cout << "Rule 7: Voice Crossing\n"
                    << "   An inner voice (alto, tenor) may not go outside\n"
                    << "   the range of the encompassing "
                    << "voices (bass, soprano).\n\n";
               break;
            case 8:
               cout << "Rule 8: Open Spacing\n"
                    << "   The successive interval between upper voices\n"
                    << "   must not exceed an octave.\n\n";
               break;
         }
      }
   }

}



//////////////////////////////
//
// printVoiceArray -- print the intermediate voice array
//

void printVoiceArray(void) {
   int i;
   int lineQ = options.getBoolean("line");
   for (i=0; i<voices[0].getSize(); i++) {
      if (lineQ) {
         cout << linenum[i]+1 << '\t';
      }
      cout << voices[0][i] << '\t'
           << voices[1][i] << '\t'
           << voices[2][i] << '\t'
           << voices[3][i] << '\n';
   }
}



//////////////////////////////
//
// prepareVoices -- change rests to 0's and continued notes
//   to negative values for the attack note.
//

void prepareVoices(void) {
   int i, k;
   int current = 0;
   for (k=0; k<4; k++) {
      for (i=0; i<voices[k].getSize(); i++) {
         if (voices[k][i] < 0) {
            voices[k][i] = 0;
            current = 0;
         } else if (voices[k][i] == 0) {
            voices[k][i] = -current;
         } else {
            current = voices[k][i];
         }
      }
   }
}



//////////////////////////////
//
// processRecords -- extract pitches from choral in preparations for
//      error analysis.
//

void processRecords(HumdrumFile& infile) {
   int base40pitch;
   int vcount;
   for (int i=0; i<infile.getNumLines(); i++) {
      if (options.getBoolean("debug")) {
         cout << "processing line " << (i+1) << " of input ..." << endl;
      }
      switch (infile[i].getType()) {
         case E_humrec_none:
         case E_humrec_empty:
         case E_humrec_bibliography:
         case E_humrec_global_comment:
         case E_humrec_data_comment:
         case E_humrec_data_kern_measure:
            break;
         case E_humrec_interpretation:
         {
            if (strncmp(infile[i][0], "**", 2) != 0) {
               break;
            }
            int count = 0;
            for (int k=0; k<infile[i].getFieldCount(); k++) {
               if (infile[i].getExInterpNum(k) == E_KERN_EXINT) {
                  voiceloc[count] = k;
                  count++;
               }
               if (count == 4) {
                  break;
               }
            }
            if (count != 4) {
               cerr << "Error on line " << i+1 << " of input: "
                    << "not enought **kern spines for analysis." << endl;
               exit(1);
            }
         }
            break;
         case E_humrec_data:
         {
            const char* datap;
            int sign; // +1 = note attack, -1 = not
            vcount = getVoiceCount(infile[i]);
            if (vcount >= voicemin) {
               for (int k=0; k<4; k++) {
                  if (strcmp(infile[i][voiceloc[k]], ".") == 0) {
                     datap = infile.getDotValue(i, voiceloc[k]);
                     sign = -1;
                  } else {
                     datap = infile[i][voiceloc[k]];
                     sign = +1;
                     if (strchr(datap, ']') != NULL) {
                        sign = -1;
                     } else if (strchr(datap, '_') != NULL) {
                        sign = -1;
                     }
                  }
                  if (strchr(infile[i][voiceloc[k]], 'r') != NULL) {
                     base40pitch = 0;
                  } else {
                     base40pitch = sign * Convert::kernToBase40(datap);
                  }
                  voices[k].append(base40pitch);
               }
               linenum.append(i);
            }
         }
            break;
         default:
            cerr << "Error on line " << (i+1) << " of input" << endl;
            exit(1);
      }
   }
}



//////////////////////////////
//
// getVoiceCount -- return the number of note attacks on the line.
//    Excludes tied notes, but includes rests (but not continuing
//    rests which are not necessary).
//

int getVoiceCount(HumdrumRecord& record) {
   int output = 0;
   int i;
   for (i=0; i<record.getFieldCount(); i++) {
      if (strcmp(record.getExInterp(i), "**kern") != 0) {
         continue; // ignore non-kern spines
      }
      if (strcmp(record[i], ".") == 0) {
         continue; // ignore null tokens (sounding notes or continuing rests)
      }
      if (strchr(record[i], ']') != NULL) {
         continue; // ignore tied notes (endings)
      }
      if (strchr(record[i], '_') != NULL) {
         continue; // ignore tied notes (continuations)
      }
      output++;

   }
   return output;
}



//////////////////////////////
//
// sortErrorMessages -- put the bass voice in the first array location,
//	then the tenor, alto, and soprano.
//

void sortErrorMessages(Error* errors, int size) {
  qsort(errors, size, sizeof(Error), errorCompare); 
}



//////////////////////////////
//
// sortVoices -- put the bass voice in the first array location,
//	then the tenor, alto, and soprano.
//

void sortVoices(void) {
   int length = voices[0].getSize();
   int count = 0;
   double voiceordering[4] = {0.0};
   int i;
   int k;
   int min, max;
   int temp;
   int minindex, maxindex;
   int tenorindex = 0;
   int altoindex = 0;
   for (i=0; i<length; i++) {
      if (voices[0][i] > 0 && voices[1][i] > 0 &&
            voices[2][i] > 0 && voices[3][i] > 0) {

         // find lowest and highest notes
         max = voices[0][i];
         min = voices[0][i];
         maxindex = minindex = 0;
         for (k=1; k<4; k++) {
            if (voices[k][i] > max) {
               max = voices[k][i];
               maxindex = k;
            }
            if (voices[k][i] < min) {
               min = voices[k][i];
               minindex = k;
            }
         }
         switch (maxindex) {
            case 0: switch (minindex) { 
                       case 0: tenorindex = -1; altoindex = -1; break;
                       case 1: tenorindex = 2; altoindex = 3; break;
                       case 2: tenorindex = 1; altoindex = 3; break;
                       case 3: tenorindex = 1; altoindex = 2; break;
                    }
                    break;
            case 1: switch (minindex) { 
                       case 0: tenorindex = 2; altoindex = 3; break;
                       case 1: tenorindex = -1; altoindex = -1; break;
                       case 2: tenorindex = 0; altoindex = 3; break;
                       case 3: tenorindex = 0; altoindex = 2; break;
                    }
                    break;

            case 2: switch (minindex) { 
                       case 0: tenorindex = 1; altoindex = 3; break;
                       case 1: tenorindex = 0; altoindex = 3; break;
                       case 2: tenorindex = -1; altoindex = -1; break;
                       case 3: tenorindex = 0; altoindex = 1; break;
                    }
                    break;

            case 3: switch (minindex) { 
                       case 0: tenorindex = 1; altoindex = 2; break;
                       case 1: tenorindex = 0; altoindex = 2; break;
                       case 2: tenorindex = 0; altoindex = 1; break;
                       case 3: tenorindex = -1; altoindex = -1; break;
                    }
                    break;
         }
         if (voices[tenorindex][i] > voices[altoindex][i]) {
             temp = tenorindex;
             tenorindex = altoindex; 
             altoindex = temp; 
         }

         voiceordering[minindex] += 0;
         voiceordering[tenorindex] += 1;
         voiceordering[altoindex] += 2;
         voiceordering[maxindex] += 3;
         count++;

      }
   }

   for (k=0; k<4; k++) {
      voiceordering[k] /= count;
      voiceordering[k] = (int)(voiceordering[k]+0.5);
   }

   int oldbass = 0;
   if (voiceordering[0] != 0) {
      if (voiceordering[1] == 0) {
         oldbass = 1;
      } else if (voiceordering[2] == 0) {
         oldbass = 2;
      } else {
         oldbass = 3;
      }

      for (i=0; i<voices[0].getSize(); i++) {
         temp = voices[0][i];
         voices[0][i] = voices[oldbass][i];
         voices[oldbass][i] = temp;
      }
   }


   int oldtenor = 1;
   if (voiceordering[1] != 1) {
      if (voiceordering[2] == 1) {
         oldtenor = 2;
      } else {
         oldtenor = 3;
      }

      for (i=0; i<voices[0].getSize(); i++) {
         temp = voices[1][i];
         voices[1][i] = voices[oldtenor][i];
         voices[oldtenor][i] = temp;
      }
   }

   if (voiceordering[2] > voiceordering[3]) {
      for (i=0; i<voices[0].getSize(); i++) {
         temp = voices[2][i];
         voices[2][i] = voices[3][i];
         voices[3][i] = temp;
      }
   }

}



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

void usage(const char* command) {
   cout <<
   "                                                                         \n"
   "Analyzes **kern data and generates a **qual spine which gives the chord  \n"
   "quality of the **kern spines.  Currently, input spines cannot split or   \n"
   "join.                                                                    \n"
   "                                                                         \n"
   "Usage: " << command << " [-a][-i|-r|-t] [input1 [input2 ...]]            \n"
   "                                                                         \n"
   "Options:                                                                 \n"
   "   --options = list of all options, aliases and default values           \n"
   "                                                                         \n"
   << endl;
}



////////////////////////////
//
// writeoutput -- write the input file plus the errors which
//    were detected.
//

void writeoutput(HumdrumFile& infile) {
   sortErrorMessages(errorList.getBase(), errorList.getSize());

   int errorIndex = 0;
   int i;

   if (options.getBoolean("warnings")) {
      for (i=0; i<errorList.getSize(); i++) {
         cout << header << errorList[i].message << " on line "
              << errorList[i].line + 1;
         if (fileQ && (strcmp(Filename, "") != 0)) {
            cout << " (" << Filename << ")";
         }
         cout << '\n';
      }
   } else {
      for (i=0; i<infile.getNumLines(); i++) {
         while (errorIndex < errorList.getSize() && 
               errorList[errorIndex].line == i) {
            cout << "!! ";
            cout << marker;
            cout << errorList[errorIndex].message;
            if (fileQ && (strcmp(Filename, "") != 0)) {
               cout << " (" << Filename << ")";
            }
            cout << '\n';
            errorIndex++;
         }
         cout << infile[i] << '\n';
      }
   }
}



///////////////////////////////////////////////////////////////////////////
//
// error-checking functions
//

////////////////////////////
//
//	1. Parallel 5ths between two voices when moving to
//	   different pitch classes.
//

void error1(void) { 
   int i;
   int bass, tenor, alto, soprano;
   int newbass, newtenor, newalto, newsoprano;
   int sdir, tdir, adir, bdir;   // melodic direction of the voices
   for (i=0; i<voices[0].getSize()-1; i++) {
      bass = abs(voices[0][i]);
      tenor = abs(voices[1][i]);
      alto = abs(voices[2][i]);
      soprano = abs(voices[3][i]);

      newsoprano = abs(voices[3][i+1]);
      newbass = abs(voices[0][i+1]);
      newtenor = abs(voices[1][i+1]);
      newalto = abs(voices[2][i+1]);
      
      sdir = newsoprano - soprano;
      adir = newalto - alto;
      tdir = newtenor - tenor;
      bdir = newbass - bass;

      if (sdir < 0) sdir = -1;
      if (adir < 0) adir = -1;
      if (tdir < 0) tdir = -1;
      if (bdir < 0) bdir = -1;

      if (sdir > 0) sdir = 1;
      if (adir > 0) adir = 1;
      if (tdir > 0) tdir = 1;
      if (bdir > 0) bdir = 1;

      // check against bass voice
      if (newbass == 0 || bass == 0 || bdir == 0) {
         goto tenorvoice1;
      }

      if (tenor != 0 && newtenor != 0 && bdir == tdir) {
         if ((tenor-bass+40)%40 == 23 && (newtenor-newbass+40)%40 == 23) {
            errormessage(1, "bass", "tenor", linenum[i]);
         }
      }

      if (newalto != 0 && alto != 0 && bdir == adir) {
         if ((alto-bass+40)%40 == 23 && (newalto-newbass+40)%40 == 23) {
            errormessage(1, "bass", "alto", linenum[i]);
         }
      }

      if (newsoprano != 0 && soprano != 0 && bdir == sdir) {
         if ((soprano-bass+40)%40 == 23 && (newsoprano-newbass+40)%40 == 23) {
            errormessage(1, "bass", "soprano", linenum[i]);
         }
      }

tenorvoice1:

      if (newtenor == 0 || tenor == 0 || tdir == 0) {
         goto altovoice1;
      }

      if (newalto != 0 && alto != 0 && adir == tdir) {
         if ((alto-tenor+40)%40 == 23 && (newalto-newtenor+40)%40 == 23) {
            errormessage(1, "tenor", "alto", linenum[i]);
         }
      }

      if (newsoprano != 0 && soprano != 0 && adir == sdir) {
         if ((soprano-tenor+40)%40 == 23 && (newsoprano-newtenor+40)%40 == 23) {
            errormessage(1, "tenor", "soprano", linenum[i]);
         }
      }


altovoice1:
      if (newalto == 0 || alto == 0 || adir == 0) {
         continue;
      }

      if (newsoprano != 0 && soprano != 0 && adir == sdir) {
         if ((soprano-alto+40)%40 == 23 && (newsoprano-newalto+40)%40 == 23) {
            errormessage(1, "alto", "soprano", linenum[i]);
         }
      }
   }
}



////////////////////////////
//
//	2. Parallel Octaves between two voices when moving to
//	   different pitch classes.
//

void error2(void) { 
   int i;
   int bass, tenor, alto, soprano;
   int newbass, newtenor, newalto, newsoprano;
   int sdir, tdir, adir, bdir;   // melodic direction of the voices
   for (i=0; i<voices[0].getSize()-1; i++) {
      bass    = abs(voices[0][i]);
      tenor   = abs(voices[1][i]);
      alto    = abs(voices[2][i]);
      soprano = abs(voices[3][i]);

      newsoprano = abs(voices[3][i+1]);
      newbass    = abs(voices[0][i+1]);
      newtenor   = abs(voices[1][i+1]);
      newalto    = abs(voices[2][i+1]);
      
      sdir = newsoprano - soprano;
      adir = newalto - alto;
      tdir = newtenor - tenor;
      bdir = newbass - bass;

      if (sdir < 0) sdir = -1;
      if (adir < 0) adir = -1;
      if (tdir < 0) tdir = -1;
      if (bdir < 0) bdir = -1;

      if (sdir > 0) sdir = 1;
      if (adir > 0) adir = 1;
      if (tdir > 0) tdir = 1;
      if (bdir > 0) bdir = 1;

      // check against bass voice
      if (newbass == 0 || bass == 0 || bdir == 0) {
         goto tenorvoice2;
      }

      if (tenor != 0 && newtenor != 0 && bdir == tdir && tenor != bass) {
         if ((tenor-bass+400)%40 == 0 && (newtenor-newbass+400)%40 == 0) {
            errormessage(2, "bass", "tenor", linenum[i]);
         }
      }

      if (newalto != 0 && alto != 0 && bdir == adir && alto != bass) {
         if ((alto-bass+400)%40 == 0 && (newalto-newbass+400)%40 == 0) {
            errormessage(2, "bass", "alto", linenum[i]);
         }
      }

      if (newsoprano != 0 && soprano != 0 && bdir == sdir && soprano != bass) {
         if ((soprano-bass+400)%40 == 0 && (newsoprano-newbass+400)%40 == 0) {
char buffer[1024];
cout << "bass = " << bass << Convert::base40ToKern(buffer, bass);
cout << "\tnewbass = " << newbass << Convert::base40ToKern(buffer, newbass) << endl;
cout << "sopr = " << soprano << Convert::base40ToKern(buffer, soprano);
cout << "\tnewsopr = " << newsoprano << Convert::base40ToKern(buffer, newsoprano) << endl;
cout << "diff = " << soprano - bass << "\tdiff2 = " << newsoprano - newbass << endl;
            errormessage(2, "bass", "soprano", linenum[i]);
         }
      }

tenorvoice2:

      if (newtenor == 0 || tenor == 0 || tdir == 0) {
         goto altovoice2;
      }

      if (newalto != 0 && alto != 0 && adir == tdir && tenor != alto) {
         if ((alto-tenor+400)%40 == 0 && (newalto-newtenor+400)%40 == 0) {
            errormessage(2, "tenor", "alto", linenum[i]);
         }
      }

      if (newsoprano != 0 && soprano != 0 && adir == sdir && tenor != soprano) {
         if ((soprano-tenor+400)%40 == 0 && (newsoprano-newtenor+400)%40 == 0) {
            errormessage(2, "tenor", "soprano", linenum[i]);
         }
      }


altovoice2:

      if (newalto == 0 || alto == 0 || adir == 0) {
         continue;
      }

      if (newsoprano != 0 && soprano != 0 && adir == sdir && alto != soprano) {
         if ((soprano-alto+400)%40 == 0 && (newsoprano-newalto+400)%40 == 0) {
            errormessage(2, "alto", "soprano", linenum[i]);
         }
      }
   }
}



////////////////////////////
//
//	3. Contrary parallel 5ths -- when two voices move in
//	   parallel 5ths displaced by an octave.
//

void error3(void) { 
   int i;
   int bass, tenor, alto, soprano;
   int newbass, newtenor, newalto, newsoprano;
   int sdir, tdir, adir, bdir;   // melodic direction of the voices
   for (i=0; i<voices[0].getSize()-1; i++) {
      bass    = abs(voices[0][i]);
      tenor   = abs(voices[1][i]);
      alto    = abs(voices[2][i]);
      soprano = abs(voices[3][i]);

      newsoprano = abs(voices[3][i+1]);
      newbass    = abs(voices[0][i+1]);
      newtenor   = abs(voices[1][i+1]);
      newalto    = abs(voices[2][i+1]);
      
      sdir = newsoprano - soprano;
      adir = newalto - alto;
      tdir = newtenor - tenor;
      bdir = newbass - bass;

      if (sdir < 0) sdir = -1;
      if (adir < 0) adir = -1;
      if (tdir < 0) tdir = -1;
      if (bdir < 0) bdir = -1;

      if (sdir > 0) sdir = 1;
      if (adir > 0) adir = 1;
      if (tdir > 0) tdir = 1;
      if (bdir > 0) bdir = 1;

      // check against bass voice
      if (newbass == 0 || bass == 0 || bdir == 0) {
         goto tenorvoice3;
      }
      if ((bass-newbass+400)%40 == 0) {
         goto tenorvoice3;
      }
	        

      if (tenor != 0 && newtenor != 0 && bdir != tdir) {
         if ((tenor-bass+400)%40 == 23 && (newtenor-newbass+400)%40 == 23) {
            errormessage(3, "bass", "tenor", linenum[i]);
         }
      }

      if (newalto != 0 && alto != 0 && bdir != adir) {
         if ((alto-bass+400)%40 == 23 && (newalto-newbass+400)%40 == 23) {
            errormessage(3, "bass", "alto", linenum[i]);
         }
      }

      if (newsoprano != 0 && soprano != 0 && bdir != sdir) {
         if ((soprano-bass+400)%40 == 23 && (newsoprano-newbass+400)%40 == 23) {
            errormessage(3, "bass", "soprano", linenum[i]);
         }
      }

tenorvoice3:

      if (newtenor == 0 || tenor == 0 || tdir == 0) {
         goto altovoice3;
      }
      if ((tenor-newtenor+400)%40 == 0) {
         goto altovoice3;
      }

      if (newalto != 0 && alto != 0 && adir != tdir) {
         if ((alto-tenor+40)%40 == 23 && (newalto-newtenor+40)%40 == 23) {
            errormessage(3, "tenor", "alto", linenum[i]);
         }
      }

      if (newsoprano != 0 && soprano != 0 && adir != sdir) {
         if ((soprano-tenor+40)%40 == 23 && (newsoprano-newtenor+40)%40 == 23) {
            errormessage(3, "tenor", "soprano", linenum[i]);
         }
      }


altovoice3:

      if (newalto == 0 || alto == 0 || adir == 0) {
         continue;
      }
      if ((alto-newalto+400)%40 == 0) {
         return;
      }

      if (newsoprano != 0 && soprano != 0 && adir != sdir) {
         if ((soprano-alto+40)%40 == 23 && (newsoprano-newalto+40)%40 == 23) {
            errormessage(3, "alto", "soprano", linenum[i]);
         }
      }
   }
}



////////////////////////////
//
//	4. Unequal 5ths -- when the bass part and another
//	   voice move from dim 5ths to perfect 5ths or vice versa.
//

void error4(void) { 
   int i;
   int bass, tenor, alto, soprano;
   int newbass, newtenor, newalto, newsoprano;
   for (i=0; i<voices[0].getSize()-1; i++) {
      bass = abs(voices[0][i]);
      tenor = abs(voices[1][i]);
      alto = abs(voices[2][i]);
      soprano = abs(voices[3][i]);

      newsoprano = abs(voices[3][i+1]);
      newbass = abs(voices[0][i+1]);
      newtenor = abs(voices[1][i+1]);
      newalto = abs(voices[2][i+1]);

      if (bass == 0 || newbass == 0) {
         continue;
      }

      int interval1, interval2;

      if (newtenor != 0 && tenor != 0) {
         interval1 = (tenor-bass+40)%40;
         interval2 = (newtenor-newbass+40)%40;
         if (interval1 == 23 && interval2 == 22) {
            errormessage(4, "bass", "tenor", linenum[i]);
         }
      }

      if (newalto != 0 && alto != 0) {
         interval1 = (alto-bass+40)%40;
         interval2 = (newalto-newbass+40)%40;
         if (interval1 == 23 && interval2 == 22) {
            errormessage(4, "bass", "alto", linenum[i]);
         }
      }

      if (newsoprano != 0 && soprano != 0) {
         interval1 = (soprano-bass+40)%40;
         interval2 = (newsoprano-newbass+40)%40;
         if (interval1 == 23 && interval2 == 22) {
            errormessage(4, "bass", "soprano", linenum[i]);
         }
      }
      
   }
}



////////////////////////////
//
//	5. Hidden 5ths -- when the soprano moves in similar
//	   motion with another voice and the soprano leaps
//	   to a perfect 5th with that voice.
//

void error5(void) {
   int i;
   int bass, tenor, alto, soprano;
   int newbass, newtenor, newalto, newsoprano;
   int sdir, tdir, adir, bdir;   // melodic direction of the voices
   for (i=0; i<voices[0].getSize()-1; i++) {
      bass = abs(voices[0][i]);
      tenor = abs(voices[1][i]);
      alto = abs(voices[2][i]);
      soprano = abs(voices[3][i]);

      newsoprano = abs(voices[3][i+1]);
      newbass = abs(voices[0][i+1]);
      newtenor = abs(voices[1][i+1]);
      newalto = abs(voices[2][i+1]);

      if (soprano == 0 || newsoprano == 0) {
         continue;
      }
      
      sdir = newsoprano - soprano;
      adir = newalto - alto;
      tdir = newtenor - tenor;
      bdir = newbass - bass;

      if (sdir < 0) sdir = -1;
      if (adir < 0) adir = -1;
      if (tdir < 0) tdir = -1;
      if (bdir < 0) bdir = -1;

      if (sdir > 0) sdir = 1;
      if (adir > 0) adir = 1;
      if (tdir > 0) tdir = 1;
      if (bdir > 0) bdir = 1;

      if (abs(newsoprano - soprano) > 7) {
         if (sdir == adir && newalto != 0 && alto != 0) {
            if ((newsoprano-newalto+40)%40 == 23) {
               errormessage(5, "soprano", "alto", linenum[i]);
            }
         }

         if (sdir == tdir && newtenor != 0 && tenor != 0) {
            if ((newsoprano-newtenor+40)%40 == 23) {
               errormessage(5, "soprano", "tenor", linenum[i]);
            }
         }

         if (sdir == bdir && newbass != 0 && bass != 0) {
            if ((newsoprano-newbass+40)%40 == 23) {
               errormessage(5, "soprano", "bass", linenum[i]);
            }
         }

      }
   }
}



////////////////////////////
//
//	6. Hidden 8va -- when the soprano moves in similar
//	   motion with another voice and the soprano leaps
//	   to a perfect octave with that voice.
//         Excluding parallel octaves which are covered
//         by rule 2.
//

void error6(void) { 
   int i;
   int bass, tenor, alto, soprano;
   int newbass, newtenor, newalto, newsoprano;
   int sdir, tdir, adir, bdir;   // melodic direction of the voices
   for (i=0; i<voices[0].getSize()-1; i++) {
      bass    = abs(voices[0][i]);
      tenor   = abs(voices[1][i]);
      alto    = abs(voices[2][i]);
      soprano = abs(voices[3][i]);

      newbass    = abs(voices[0][i+1]);
      newtenor   = abs(voices[1][i+1]);
      newalto    = abs(voices[2][i+1]);
      newsoprano = abs(voices[3][i+1]);

      if (soprano == 0 || newsoprano == 0) {
         continue;
      }
      
      sdir = newsoprano - soprano;
      adir = newalto    - alto;
      tdir = newtenor   - tenor;
      bdir = newbass    - bass;

      if (sdir < 0) sdir = -1;
      if (adir < 0) adir = -1;
      if (tdir < 0) tdir = -1;
      if (bdir < 0) bdir = -1;

      if (sdir > 0) sdir = 1;
      if (adir > 0) adir = 1;
      if (tdir > 0) tdir = 1;
      if (bdir > 0) bdir = 1;

      if (abs(newsoprano - soprano) > 7) {
         if ((sdir == adir) && (newalto != 0) && (alto != 0)) {
            if (((newsoprano-newalto+400)%40 == 0) && (newsoprano != newalto)) {
               if ((soprano - alto + 400)%40 != 0) { // exclude para octaves
                  errormessage(6, "soprano", "alto", linenum[i]);
               }
            }
         }

         if ((sdir == tdir) && (newtenor != 0) && (tenor != 0)) {
            if (((newsoprano-newtenor+400)%40 == 0) && 
                  (newsoprano != newtenor)) {
               if ((soprano - tenor + 400)%40 != 0) { // exclude para octaves
                  errormessage(6, "soprano", "tenor", linenum[i]);
               }
            }
         }

         if ((sdir == bdir) && (newbass != 0) && (bass != 0)) {
            if (((newsoprano-newbass+400)%40 == 0) && (newsoprano != newbass)) {
               if ((soprano - bass + 400)%40 != 0) { // exclude para octaves
                  errormessage(6, "soprano", "bass", linenum[i]);
               }
            }
         }

      }
   }
}



////////////////////////////
//
//	7. Voice crossing -- when an inner voice goes above
//	   the soprano voice or below the bass voice.
//

void error7(void) { 
   int i;
   int bass, tenor, alto, soprano;
   for (i=0; i<voices[0].getSize(); i++) {
      bass = abs(voices[0][i]);
      tenor = abs(voices[1][i]);
      alto = abs(voices[2][i]);
      soprano = abs(voices[3][i]);

      if (tenor > 0 && bass > 0) {
         if (tenor - bass < 0) {
            errormessage(7, "tenor", "bass", linenum[i]);
         }
      } 
      if (alto > 0 && bass > 0) {
         if (alto - bass < 0) {
            errormessage(7, "alto", "bass", linenum[i]);
         }
      }
      
      if (soprano > 0 && alto > 0) {
         if (soprano - alto < 0) {
            errormessage(7, "soprano", "alto", linenum[i]);
         }
      } 
      if (soprano > 0 && tenor > 0) {
         if (soprano - tenor < 0) {
            errormessage(7, "soprano", "tenor", linenum[i]);
         }
      }
   }
}



////////////////////////////
//
//	8. Open spacing -- when the interval between successive
//	   voices other than the bass exceeds an octave.
//

void error8(void) { 
   int i;
   int bass, tenor, alto, soprano, temp;
   for (i=0; i<voices[0].getSize(); i++) {
      bass = abs(voices[0][i]);
      tenor = abs(voices[1][i]);
      alto = abs(voices[2][i]);
      soprano = abs(voices[3][i]);

      if (tenor > 0 && alto > 0) {
         if (alto - tenor > 40) {
            errormessage(8, "alto", "tenor", linenum[i]);
         } else if (alto - tenor < 0) {
            temp = alto;
	    alto = tenor;
            tenor = alto;
         }
      }

      if (alto > 0 && soprano > 0) {
         if (soprano - alto > 40) {
            errormessage(8, "soprano", "alto", linenum[i]);
         } 
      }

   }

}


// md5sum: 0ff7f42a51c5592f2188fa0099730025 chorck.cpp [20090626]