//
// Programmer: Craig Stuart Sapp <craig@ccrma.stanford.edu>
// Creation Date: Fri Jun 26 23:44:15 PDT 1998
// Last Modified: Fri Jun 26 23:44:19 PDT 1998
// Filename: ...sig/examples/all/harmanal.cpp
// Web Address: http://sig.sapp.org/examples/museinfo/humdrum/harmanal.cpp
// Syntax: C++; museinfo
//
// Description: Analyzes **kern data and generates a functional
// harmony analysis of timeslices (no melodic harmony).
//
#include "humdrum.h"
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
#ifndef OLDCPP
#include <iostream>
#else
#include <iostream.h>
#endif
// function declarations
void checkOptions(Options& opts, int argc, char* argv[]);
ChordQuality determineChordQuality(const HumdrumRecord& aRecord);
void example(void);
void processRecords(HumdrumFile& infile, HumdrumFile& outfile);
void usage(const char* command);
// global variables
Options options; // database for command-line arguments
char unknown[256] = {0}; // space for unknown chord simplification
int chordinit; // for initializing chord detection function
int key = E_root_C; // base pitch of key to analyze in
int mode = E_mode_major; // mode of the key
///////////////////////////////////////////////////////////////////////////
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++) {
chordinit = 1;
infile.clear();
outfile.clear();
// if no command-line arguments read data file from standard input
if (numinputs < 1) {
infile.read(cin);
} else {
infile.read(options.getArg(i+1));
}
// analyze the input file according to command-line options
processRecords(infile, outfile);
// check to see if only the analysis spine is required
if (options.getBoolean("extract")) {
outfile = outfile.extract(-1);
}
outfile.write(cout);
}
return 0;
}
///////////////////////////////////////////////////////////////////////////
//////////////////////////////
//
// checkOptions -- validate and process command-line options.
//
void checkOptions(Options& opts, int argc, char* argv[]) {
opts.define("r|riem|riemann=b"); // show **riem analysis
opts.define("x|e|extract=b"); // display analysis only
opts.define("f|format=s:t:i:r"); // control display style
opts.define("u|unknown=s:X"); // control display of unknowns
opts.define("d|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, June 1998" << endl;
exit(0);
} else if (opts.getBoolean("version")) {
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);
}
ChordQuality::setDisplay(opts.getString("format"));
Convert::chordType.setNullName(opts.getString("unknown"));
Convert::chordInversion.setNullName(opts.getString("unknown"));
Convert::chordRoot.setNullName(opts.getString("unknown"));
strncpy(unknown, opts.getString("unknown"), 64);
strcat(unknown, ":");
strncat(unknown, opts.getString("unknown"), 64);
strcat(unknown, ":");
strncat(unknown, opts.getString("unknown"), 64);
}
//////////////////////////////
//
// 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 Collection<int> lastnotes; // for 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.getExInt(i) != E_KERN_EXINT) {
lastnotes.setSize(lastnotes.getSize() - 1);
}
}
}
// determine chord notes and send to chord identifier
Collection<int> currentNotes(lastnotes.getSize());
int count = 0;
for (i=0; i<aRecord.getFieldCount(); i++) {
if (aRecord.getExInt(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++;
}
}
return Convert::noteSetToChordQuality(currentNotes);
}
//////////////////////////////
//
// determineFunctionalAnalysis -- mode 0 = major, mode 1 = minor
// default value: strlen = 32
//
void determineFunctionalAnalysis(char* output, const ChordQuality& aQuality,
int aKey, int aMode, int strlen = 32) {
if (aQuality.getType() == E_unknown) {
strncpy(output, "X", strlen);
return;
} else if (aQuality.getType() == E_chord_note) {
strncpy(output, "X", strlen);
return;
}
int rootInKeyQ = 0;
int rightKeyQualityQ = 1;
int degreeLocation = 0;
Collection<int> chordNotes;
Collection<int> scaleDegrees;
chordNotes = Convert::chordQualityToNoteSet(aQuality);
scaleDegrees = Convert::keyToScaleDegrees(aKey, aMode);
// question: is root in key?
int i;
for (i=0; i<scaleDegrees.getSize(); i++) {
if (chordNotes[0] == scaleDegrees[i]) {
rootInKeyQ = 1;
degreeLocation = i;
break;
}
}
// question: are chord notes in the key?
for (i=0; i<chordNotes.getSize(); i++) {
if (chordNotes[i] != scaleDegrees[(2*i+degreeLocation) %
scaleDegrees.getSize()] ) {
rightKeyQualityQ = 0;
break;
}
}
int chordThird = 0;
int chordFifth = 0;
if (rightKeyQualityQ) {
if (chordNotes.getSize() > 1) {
if (chordNotes[0] < chordNotes[1]) {
chordThird = chordNotes[1] - chordNotes[0];
} else {
chordThird = chordNotes[0] - chordNotes[1];
}
} else {
strncpy(output, "X", strlen);
return;
}
if (chordNotes.getSize() > 2) {
if (chordNotes[0] < chordNotes[2]) {
chordFifth = chordNotes[2] - chordNotes[0];
} else {
chordFifth = chordNotes[0] - chordNotes[2];
}
}
// can now build a chord in the key
switch (degreeLocation) {
case 0:
strcpy(output, "I");
break;
case 1:
strcpy(output, "II");
break;
case 2:
strcpy(output, "III");
break;
case 3:
strcpy(output, "IV");
break;
case 4:
strcpy(output, "V");
break;
case 5:
strcpy(output, "VI");
break;
case 6:
strcpy(output, "VII");
break;
default:
strncpy(output, "X", strlen);
return;
}
// adjust for major/minor third
i = 0;
while (output[i] != '\0') {
output[i] = tolower(output[i]);
}
if (chordFifth == E_base40_dim5 || (chordFifth == 0 &&
aQuality.getType() == E_chord_incmin)) {
strcat(output, "o");
} else if (chordFifth == E_base40_aug5) {
strcat(output, "+");
}
if (chordNotes.getSize() == 4) {
if (degreeLocation == 6) {
strcat(output, "0");
}
strcat(output, "7");
}
switch (aQuality.getInversion()) {
case 0:
break;
case 1:
strcat(output, "a");
break;
case 2:
strcat(output, "b");
break;
case 3:
strcat(output, "c");
break;
case 4:
strcat(output, "d");
break;
default:
strcat(output, "x");
}
return;
}
else {
strncpy(output, "X", strlen);
return;
}
}
//////////////////////////////
//
// 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 only the chord analysis: \n"
" quality -x chor217.krn \n"
" \n"
"# display only the roots of chords: \n"
" quality -r chor217.krn \n"
" \n"
<< endl;
}
//////////////////////////////
//
// processRecords -- looks at humdrum records and determines chord
// quality
//
void processRecords(HumdrumFile& infile, HumdrumFile& outfile) {
ChordQuality quality;
char aString[256] = {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:
if (currRecord.equalFieldsQ("**kern")) {
currRecord.appendField(currRecord[0]);
} else {
currRecord.appendField("*");
}
outfile.appendLine(currRecord);
break;
case E_humrec_data_kern_measure:
if (currRecord.equalFieldsQ("**kern")) {
currRecord.appendField(currRecord[0]);
} else {
currRecord.appendField("=");
}
outfile.appendLine(currRecord);
break;
case E_humrec_data_interpretation:
currRecord.appendField("**qual");
outfile.appendLine(currRecord);
break;
case E_humrec_data:
quality = determineChordQuality(infile[i]);
determineFunctionalAnalysis(aString, quality, key, mode);
currRecord.appendField(aString);
outfile.appendLine(currRecord);
break;
default:
cerr << "Error on line " << (i+1) << " of input" << endl;
exit(1);
}
}
}
//////////////////////////////
//
// usage -- gives the usage statement for the harmanal program
//
void usage(const char* command) {
cout <<
" \n"
"Places a **qual spine into humdrum data which analyzes the chord quality \n"
"of **kern spines. Currently, input spines cannot split or join. \n"
" \n"
" \n"
"Usage: " << command << "[-x][-t|-r|-i] [input1 [input2 ...]][output] \n"
" \n"
"Options: \n"
" -x = extract the **qual analysis spine to output only \n"
" --options = list of all options, aliases and default values \n"
" \n"
<< endl;
}
// md5sum: 759127396ae981f3d7fa1c0b51899cf8 harmanal.cpp [20050403]