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]