//
// Programmer: Craig Stuart Sapp <craig@ccrma.stanford.edu>
// Creation Date: Tue Jul 7 22:29:32 PDT 1998
// Last Modified: Tue Jul 7 22:29:36 PDT 1998
// Filename: ...sig/examples/all/ana.cpp
// Web Address: http://sig.sapp.org/examples/museinfo/humdrum/ana.cpp
// Syntax: C++; museinfo
//
// Description: Generates and ESAC-like analysis summary of melodic lines.
//
#include "humdrum.h"
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
#include <math.h>
#ifndef OLDCPP
#include <iostream>
#include <sstream>
#define SSTREAM stringstream
#define CSTRING str().c_str()
using namespace std;
#else
#include <iostream.h>
#ifdef VISUAL
#include <strstrea.h> /* for Windows 95 */
#else
#include <strstream.h>
#endif
#define SSTREAM strstream
#define CSTRING str()
#endif
// function declarations
void checkOptions(Options& opts, int argc, char* argv[]);
void example(void);
void processRecords(HumdrumFile& infile);
void usage(const char* command);
// global variables
Options options; // database for command-line arguments
///////////////////////////////////////////////////////////////////////////
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++) {
infile.clear();
// if no command-line arguments read data file from standard input
if (numinputs < 1) {
infile.read(cin);
} else {
infile.read(options.getArg(i+1));
cout << "\n\nFilename: " << options.getArg(i+1) << endl;
}
// analyze the input file according to command-line options
processRecords(infile);
}
return 0;
}
///////////////////////////////////////////////////////////////////////////
//////////////////////////////
//
// checkOptions -- validate and process command-line options.
//
void checkOptions(Options& opts, int argc, char* argv[]) {
opts.define("s|spine|c|column=i:0"); // spine to analyze
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, July 1998" << endl;
exit(0);
} else if (opts.getBoolean("version")) {
cout << argv[0] << ", version: 7 July 1998" << 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);
}
}
//////////////////////////////
//
// 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"
" \n"
<< endl;
}
//////////////////////////////
//
// processRecords -- looks at humdrum records and determines chord
// quality
//
void processRecords(HumdrumFile& infile) {
char aString[128] = {0};
int lastBase40Pitch = 0x7fffffff;
int base40Pitch = 0x7fffffff;
int tempNote = 0x7fffffff;
int lastNote = 0x7fffffff;
int phraseCount = 0;
int phraseQ = 0;
int phraseBoundary = 1;
int barCount = -1;
int spine = options.getInteger("spine"); // the melodic spine to analyze
int key = 0x7fffffff;
int mode = 0x7fffffff;
HumdrumRecord currRecord;
Array<int> pitches(300);
pitches.allowGrowth(0);
pitches.zero();
Array<int> intervals(300);
Array<int> breakIntervals(300);
intervals.allowGrowth(0);
intervals.zero();
breakIntervals.allowGrowth(0);
breakIntervals.zero();
Array<int> phraseEndings;
phraseEndings.allowGrowth();
phraseEndings.setSize(0);
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_bibliography:
if (strncmp(currRecord[0], "!!!OTL:", 7) == 0) {
cout << "Title:" << &currRecord[0][7] << endl;
}
case E_humrec_global_comment:
break;
case E_humrec_data_comment:
break;
case E_humrec_interpretation:
if (strcmp(currRecord[spine], "**kern") != 0) {
cerr << "Error: input spine must be in **kern format. "
<< "Format is: " << currRecord[spine] << endl;
exit(1);
}
if (currRecord[spine][strlen(currRecord[spine])-1] == ':') {
if (strchr("abcdefg", tolower(currRecord[spine][1])) != NULL) {
key = Convert::kernToBase40Class(currRecord[spine]);
if (islower(currRecord[spine][1])) {
mode = 2;
} else if (isupper(currRecord[spine][1])) {
mode = 1;
} else {
mode = 0;
}
}
}
break;
case E_humrec_data_kern_measure:
barCount++;
break;
case E_humrec_data:
// ignore null records
if (strcmp(currRecord[spine], ".") == 0) {
break;
}
// extract the pitch
lastBase40Pitch = base40Pitch;
lastNote = tempNote;
tempNote = Convert::kernToBase40(currRecord[spine]);
if (tempNote != E_base40_rest) {
base40Pitch = tempNote;
}
if (tempNote == E_base40_rest && lastNote != E_base40_rest) {
pitches[0]++;
} else if (tempNote == base40Pitch) {
pitches[base40Pitch]++;
}
// check for phrase markings
if (strchr(currRecord[spine], '{') != NULL) {
if (phraseQ == 0) {
phraseQ = 1;
phraseBoundary = 1;
phraseCount++;
} else {
cerr << "Error: starting a phrase before ending a "
<< "phrase on line " << i << " of input" << endl;
exit(1);
}
} else if (strchr(currRecord[spine], '}') != NULL) {
phraseBoundary = 0;
if (phraseQ == 1) {
phraseQ = 0;
phraseEndings.append(base40Pitch);
} else {
cerr << "Error: ending a phrase before starting a "
<< "phrase on line " << i << " of input" << endl;
exit(1);
}
} else {
phraseBoundary = 0;
}
// check for intervals
if (lastNote != 0x7fffffff) {
if (tempNote != E_base40_rest && lastNote != E_base40_rest) {
if (phraseBoundary) {
breakIntervals[tempNote - lastNote + 80]++;
} else if (!phraseBoundary) {
intervals[tempNote - lastNote + 80]++;
}
} else if (tempNote == E_base40_rest) {
// do nothing
} else if (lastNote == E_base40_rest) {
if (phraseBoundary) {
breakIntervals[tempNote - lastNote + 80]++;
} else if (!phraseBoundary) {
intervals[tempNote - lastNote + 80]++;
}
} else if (lastNote == tempNote && lastNote == E_base40_rest) {
tempNote = base40Pitch;
}
}
break;
default:
cerr << "Error on line " << (i+1) << " of input" << endl;
exit(1);
}
}
///// analysis summary //////////////////////////////////////////////////
/// key information
cout << "key signature: ";
switch (mode) {
case 0:
cout << " unknown" << endl;
break;
case 1:
cout << Convert::kernPitchClass.getName(key) << " major"
<< endl;
break;
case 2:
cout << Convert::kernPitchClass.getName(key) << " minor"
<< endl;
break;
default:
cout << "Error (" << mode << ")" << endl;
}
/// pitch information
int lowindex = 1;
int highindex = 295;
while (pitches[lowindex] == 0) {
lowindex++;
}
while (pitches[highindex] == 0) {
highindex--;
}
cout << "Range: " << Convert::base40ToKern(aString, lowindex);
cout << " to " << Convert::base40ToKern(aString, highindex) << endl;
cout << "Pitch frequency: ";
for (int j=lowindex; j<=highindex; j++) {
if (pitches[j] != 0) {
cout << Convert::base40ToKern(aString, j) << ":"
<< pitches[j];
if (j != highindex) {
cout << ", ";
}
}
}
cout << endl;
cout << "Rest count: " << pitches[0] << endl;
/// interval information
cout << "phrase intervals: " << intervals.sum() << endl;
cout << " pi-down: " << intervals.sum(0, 79) << " ";
for (int j=79; j>=0; j--) {
if (intervals[j] != 0) {
cout << Convert::intervalNames.getName(-(j-80)) << ":"
<< intervals[j] << " ";
}
}
cout << "\n pi-unisons: " << intervals[80];
cout << "\n pi-up: " << intervals.sum(81, 200) << " ";
for (int j=81; j<80+80; j++) {
if (intervals[j] != 0) {
cout << Convert::intervalNames.getName(j-80) << ":"
<< intervals[j] << " ";
}
}
cout << endl;
cout << "phrase boundary intervals : " << breakIntervals.sum() << endl;
cout << " pbi-down: " << breakIntervals.sum(1, 79) << " ";
for (int j=79; j>=0; j--) {
if (breakIntervals[j] != 0) {
cout << Convert::intervalNames.getName(-(j-80)) << ":"
<< breakIntervals[j] << " ";
}
}
cout << "\n pbi-unisons: " << breakIntervals[80];
cout << "\n pbi-up: " << breakIntervals.sum(81, 200) << " ";
for (int j=81; j<80+80; j++) {
if (breakIntervals[j] != 0) {
cout << Convert::intervalNames.getName(j-80) << ":"
<< breakIntervals[j] << " ";
}
}
cout << endl;
/// phrase information
cout << "Bar count: " << barCount << endl;
cout << "Phrase count: " << phraseCount << endl;
cout << "Phrase endings: ";
for (int j=0; j<phraseEndings.getSize(); j++) {
cout << Convert::base40ToKern(aString, phraseEndings[j]);
if (j != phraseEndings.getSize()-1) {
cout << ", ";
}
}
cout << endl;
}
//////////////////////////////
//
// 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;
}
// md5sum: a6e3a2008ed3887355b2dc4fd7e4a2d7 ana.cpp [20080518]