//
// Programmer: Craig Stuart Sapp <craig@ccrma.stanford.edu>
// Creation Date: Sun May 13 14:15:43 PDT 2001
// Last Modified: Sun Apr 24 12:48:19 PDT 2005
// Last Modified: Thu May 28 22:30:54 PDT 2009 Added continuous analysis
// Last Modified: Mon Mar 7 14:58:02 PST 2011 Added --name option
// Last Modified: Mon Sep 10 15:43:07 PDT 2012 Added enharmonic key labeling
// Last Modified: Thu Apr 18 13:40:06 PDT 2013 Enabled multiple segment input
// Last Modified: Sun Apr 21 21:52:30 PDT 2013 Added -e option
// Filename: ...sig/examples/all/keycordl.cpp
// Web Address: http://sig.sapp.org/examples/museinfo/humdrum/keycor.cpp
// Syntax: C++; museinfo
//
// Description: Key correlation measurements using the Krumhansl-Schmuckler
// key-finding algorithm. (and also the Gabura algorithm).
//
//
#include "humdrum.h"
#include <math.h>
#include <ctype.h>
// function declarations
void checkOptions(Options& opts, int argc, char* argv[]);
void example(void);
void printAnalysis(int bestkey, Array<double>& scores,
Array<double>& durhist, const char* filename,
Array<int>& b40hist, HumdrumFile& infile);
void usage(const char* command);
void readWeights(const char* filename);
int analyzeKeyRawCorrelation(double* scores, double* distribution,
int* pitch, double* durations, int size,
int rhythmQ, double* majorKey,
double* minorKey);
int analyzeKeyEuclidean(double* scores, double* distribution,
int* pitch, double* durations, int size,
int rhythmQ, double* majorKey,
double* minorKey);
void normalizeData(double* data, int asize);
void adjustData(double* data, int asize, double mean,
double sd);
double getStandardDeviation(double mean, double* data, int asize);
double getMean(double* data, int asize);
void equalizeData(double* data, int asize, double summation);
void analyzeContinuously(HumdrumFile& infile, int windowsize,
double stepsize, double* majorKey,
double* minorKey);
void loadHistograms(Array<Array<double> >& histograms,
HumdrumFile& infile, int segments);
void addToHistogramDouble(Array<Array<double> >& histogram, int pc,
double start, double dur, double tdur,
int segments);
void createHistogram(Array<double>& pitchhist, int start,
int count, Array<Array<double> >& segments);
void identifyKeyDouble(Array<double>& histogram,
Array<double>& correlations,
double* majorKey, double* minorKey);
void printBestKey(int keynumber);
double pearsonCorrelation(int size, double* x, double* y);
void printCorrelation(double value, int style);
void printHistogramTotals(Array<Array<double> >& segments);
double getConfidence(Array<double>& cors, int best);
void getLocations(Array<double>& measures, HumdrumFile& infile,
int segments);
void getBase40Histogram(Array<int>& base40, HumdrumFile& infile);
int identifyBranchCut(int base12, Array<int>& base40);
void printErrorMarker(HumdrumFile& infile, int best40,
const char* mode);
// user interface variables
Options options; // database for command-line arguments
int frequencyQ = 0; // used with -f option
int allQ = 0; // used with -a option
int rhythmQ = 1; // used with -q option
int binaryQ = 0; // used with -b option
int rawQ = 0; // used with --raw option
int normalizeQ = 0; // used with -n option
int euclideanQ = 0; // used with -e option
int mmaQ = 0; // used with -F option
double stepsize = 1.0; // used with --step option
int windowsize = 32; // used with --window option
int continuousQ = 0; // used with -c option
int roundQ = 1; // used with -R option
int debugQ = 0; // used with --debug option
int nameQ = 0; // used with --name option
int errorQ = 0; // used with -e option
double* majorKey;
double* minorKey;
// page 35 of Krumhansl: Cognitive foundations of musical pitch
double majorKeyKrumhansl[12] = {
6.35, // C
2.23, // C#
3.48, // D
2.33, // D#
4.38, // E
4.09, // F
2.52, // F#
5.19, // G
2.39, // G#
3.66, // A
2.29, // A#
2.88}; // B
double minorKeyKrumhansl[12] = {
6.33, // C
2.68, // C#
3.52, // D
5.38, // D#
2.60, // E
3.53, // F
2.54, // F#
4.75, // G
3.98, // G#
2.69, // A
3.34, // A#
3.17}; // B
// page 85 of Temperley: Music and Probability
double majorKeyKostkaPayne[12] = {
0.748, // C
0.060, // C#
0.488, // D
0.082, // D#
0.670, // E
0.460, // F
0.096, // F#
0.715, // G
0.104, // G#
0.366, // A
0.057, // A#
0.400}; // B
double minorKeyKostkaPayne[12] = {
0.712, // C
0.084, // C#
0.474, // D
0.618, // D#
0.049, // E
0.460, // F
0.105, // F#
0.747, // G
0.404, // G#
0.067, // A
0.133, // A#
0.330}; // B
// from Aarden's dissertation, also displayed graphically in
// Huron: Sweet Anticipation
double majorKeyAarden[12] = {
17.7661, // C
0.145624, // C#
14.9265, // D
0.160186, // D#
19.8049, // E
11.3587, // F
0.291248, // F#
22.062, // G
0.145624, // G#
8.15494, // A
0.232998, // A#
4.95122}; // B
double minorKeyAarden[12] = {
18.2648, // C
0.737619, // C#
14.0499, // D
16.8599, // D#
0.702494, // E
14.4362, // F
0.702494, // F#
18.6161, // G
4.56621, // G#
1.93186, // A
7.37619, // A#
1.75623 }; // B
// from Bellman's CMMR 2005 paper
double majorKeyBellman[12] = {
16.80, // C
0.86, // C#
12.95, // D
1.41, // D#
13.49, // E
11.93, // F
1.25, // F#
20.28, // G
1.80, // G#
8.04, // A
0.62, // A#
10.57 }; // B
double minorKeyBellman[12] = {
18.16, // C
0.69, // C#
12.99, // D
13.34, // D#
1.07, // E
11.15, // F
1.38, // F#
21.07, // G
7.49, // G#
1.53, // A
0.92, // A#
10.21 }; // B
// Made up by Craig Sapp (see ICMPC10 paper)
double majorKeySimple[12] = {
2.0, // C
0.0, // C#
1.0, // D
0.0, // D#
1.0, // E
1.0, // F
0.0, // F#
2.0, // G
0.0, // G#
1.0, // A
0.0, // A#
1.0}; // B
double minorKeySimple[12] = {
2.0, // C
0.0, // C#
1.0, // D
1.0, // D#
0.0, // E
1.0, // F
0.0, // F#
2.0, // G
1.0, // G#
0.0, // A
1.0, // A#
0.0}; // B
double majorKeyUser[12] = {0};
double minorKeyUser[12] = {0};
///////////////////////////////////////////////////////////////////////////
int main(int argc, char* argv[]) {
HumdrumFileSet infiles;
majorKey = majorKeyBellman;
minorKey = minorKeyBellman;
// process the command-line options
checkOptions(options, argc, argv);
// figure out the number of input files to process
int numinputs = options.getArgCount();
Array<double> absbeat;
Array<int> pitch;
Array<double> duration;
Array<double> level;
Array<double> distribution(12);
Array<double> scores(24);
const char* filename = "";
Array<int> b40hist;
int bestkey = 0;
int i, j;
if (numinputs < 1) {
infiles.read(cin);
} else {
for (i=0; i<numinputs; i++) {
infiles.readAppend(options.getArg(i+1));
}
}
for (i=0; i<infiles.getCount(); i++) {
filename = infiles[i].getFilename();
if (continuousQ) {
analyzeContinuously(infiles[i], windowsize, stepsize, majorKey,
minorKey);
continue;
}
infiles[i].getNoteArray(absbeat, pitch, duration, level);
for (j=0; j<pitch.getSize(); j++) {
pitch[j] = Convert::base40ToMidiNoteNumber(pitch[j]);
}
if (rawQ) {
if (normalizeQ) {
normalizeData(majorKey, 12);
normalizeData(minorKey, 12);
}
bestkey = analyzeKeyRawCorrelation(scores.getBase(),
distribution.getBase(), pitch.getBase(), duration.getBase(),
pitch.getSize(), rhythmQ, majorKey, minorKey);
} else if (euclideanQ) {
equalizeData(majorKey, 12, 1.0);
equalizeData(minorKey, 12, 1.0);
bestkey = analyzeKeyEuclidean(scores.getBase(),
distribution.getBase(), pitch.getBase(), duration.getBase(),
pitch.getSize(), rhythmQ, majorKey, minorKey);
} else {
bestkey = analyzeKeyKS2(scores.getBase(), distribution.getBase(),
pitch.getBase(), duration.getBase(), pitch.getSize(), rhythmQ,
majorKey, minorKey);
}
getBase40Histogram(b40hist, infiles[i]);
printAnalysis(bestkey, scores, distribution, filename, b40hist,
infiles[i]);
}
return 0;
}
///////////////////////////////////////////////////////////////////////////
//////////////////////////////
//
// analyzeContinuously --
//
void analyzeContinuously(HumdrumFile& infile, int windowsize,
double stepsize, double* majorKey, double* minorKey) {
Array<Array<double> > segments;
infile.analyzeRhythm("4");
int segmentCount = int(infile.getTotalDuration() / stepsize + 0.5);
if (segmentCount < windowsize) {
cout << "Not enough data for requested analysis" << endl;
return;
}
segments.setSize(segmentCount);
segments.allowGrowth(0);
int i;
for (i=0; i<segments.getSize(); i++) {
segments[i].setSize(12);
segments[i].allowGrowth(0);
segments[i].setAll(0);
}
loadHistograms(segments, infile, segmentCount);
if (debugQ) {
printHistogramTotals(segments);
}
Array<Array<double> > pitchhist;
Array<Array<double> > correlations;
pitchhist.setSize(segmentCount-windowsize);
correlations.setSize(segmentCount-windowsize);
pitchhist.allowGrowth(0);
correlations.allowGrowth(0);
for (i=0; i<segmentCount-windowsize; i++) {
pitchhist[i].setSize(13); //last spot for best key
pitchhist[i].allowGrowth(0);
correlations[i].setSize(24);
correlations[i].allowGrowth(0);
}
for (i=0; i<segmentCount - windowsize; i++) {
createHistogram(pitchhist[i], i, windowsize, segments);
identifyKeyDouble(pitchhist[i], correlations[i], majorKey, minorKey);
}
Array<double> measures;
getLocations(measures, infile, segmentCount);
cout << "**key\t**rval\t**conf\t**start\t**mid\t**end\n";
for (i=0; i<pitchhist.getSize(); i++) {
printBestKey(int(pitchhist[i][12] + 0.5));
cout << "\t";
printCorrelation(correlations[i][int(pitchhist[i][12]+ 0.1)], roundQ);
cout << "\t";
cout << getConfidence(correlations[i], int(pitchhist[i][12]+0.1));
cout << "\t";
cout << "=" << measures[i];
cout << "\t";
cout << "=" << int((measures[i] + measures[i+windowsize])/2+0.49);
cout << "\t";
cout << "=" << measures[i+windowsize];
cout << endl;
}
cout << "*-\t*-\t*-\t*-\t*-\t*-\n";
}
//////////////////////////////
//
// getLocations -- return the measure number in which a segment
// starts.
//
void getLocations(Array& measures, HumdrumFile& infile, int segments) {
infile.analyzeRhythm("4");
double totaldur = infile.getTotalDuration();
measures.setSize(segments);
measures.allowGrowth(0);
measures.setAll(-1);
int barnum = 0;
int index;
int i;
for (i=0; i<infile.getNumLines(); i++) {
if (!infile[i].isMeasure()) {
continue;
}
if (sscanf(infile[i][0], "=%d", &barnum) == 1) {
index = int(infile[i].getAbsBeat() / totaldur * segments);
measures[index] = barnum;
}
}
for (i=1; i<segments; i++) {
if (measures[i] < 0) {
measures[i] = measures[i-1];
}
}
index = -1;
for (i=0; i<segments; i++) {
if (measures[i] != -1) {
index = i - 1;
break;
}
}
if (index < 0) {
return;
}
if (index >= segments) {
return;
}
for (i=index; i>=0; i--) {
measures[i] = measures[index+1] - 1;
}
}
//////////////////////////////
//
// printHistogramTotals --
//
double getConfidence(Array& cors, int best) {
int i;
int start = 1;
int secondbest = 0;
if (best == 0) {
secondbest = 1;
start = 2;
}
for (i=start; i<24; i++) {
if (best == i) {
continue;
}
if (cors[i] > cors[secondbest]) {
secondbest = i;
}
}
double output = (cors[best] - cors[secondbest]) * 300;
output = int(output + 0.5);
if (output > 100.0) {
output = 100.0;
}
return output;
}
//////////////////////////////
//
// printHistogramTotals --
//
void printHistogramTotals(Array >& segments) {
Array<double> sums(12);
sums.allowGrowth(0);
sums.setAll(0);
int i, j;
for (i=0; i<segments.getSize(); i++) {
for (j=0; j<12; j++) {
sums[j] += segments[i][j];
}
}
cout << "!! C = " << sums[0] << endl;
cout << "!! C# = " << sums[1] << endl;
cout << "!! D = " << sums[2] << endl;
cout << "!! D# = " << sums[3] << endl;
cout << "!! E = " << sums[4] << endl;
cout << "!! F = " << sums[5] << endl;
cout << "!! F# = " << sums[6] << endl;
cout << "!! G = " << sums[7] << endl;
cout << "!! G# = " << sums[8] << endl;
cout << "!! A = " << sums[9] << endl;
cout << "!! A# = " << sums[10] << endl;
cout << "!! B = " << sums[11] << endl;
}
//////////////////////////////
//
// printCorrelation --
//
void printCorrelation(double value, int style) {
if (style == 0) {
cout << value;
return;
}
if (value == 1.0) {
cout << "1.000";
return;
} else if (value == 0.0) {
cout << "0.000";
return;
} else if (value == -1.0) {
cout << "-1.000";
return;
}
int newvalue = int(value * 1000.0 + 0.5);
int digit1 = newvalue % 10;
newvalue = newvalue / 10;
int digit2 = newvalue % 10;
newvalue = newvalue / 10;
int digit3 = newvalue % 10;
newvalue = newvalue / 10;
cout << "0." << digit3 << digit2 << digit1;
}
//////////////////////////////
//
// pearsonCorrelation --
//
double pearsonCorrelation(int size, double* x, double* y) {
double sumx = 0.0;
double sumy = 0.0;
double sumco = 0.0;
double meanx = x[0];
double meany = y[0];
double sweep;
double deltax;
double deltay;
int i;
for (i=2; i<=size; i++) {
sweep = (i-1.0) / i;
deltax = x[i-1] - meanx;
deltay = y[i-1] - meany;
sumx += deltax * deltax * sweep;
sumy += deltay * deltay * sweep;
sumco += deltax * deltay * sweep;
meanx += deltax / i;
meany += deltay / i;
}
double popsdx = sqrt(sumx / size);
double popsdy = sqrt(sumy / size);
double covxy = sumco / size;
return covxy / (popsdx * popsdy);
}
//////////////////////////////
//
// printBestKey --
//
void printBestKey(int keynumber) {
switch (keynumber) {
// major keys
case 0: cout << "C"; return;
case 1: cout << "D-"; return;
case 2: cout << "D"; return;
case 3: cout << "E-"; return;
case 4: cout << "E"; return;
case 5: cout << "F"; return;
case 6: cout << "F#"; return;
case 7: cout << "G"; return;
case 8: cout << "A-"; return;
case 9: cout << "A"; return;
case 10: cout << "B-"; return;
case 11: cout << "B"; return;
// minor keys
case 12: cout << "c"; return;
case 13: cout << "c#"; return;
case 14: cout << "d"; return;
case 15: cout << "e-"; return;
case 16: cout << "e"; return;
case 17: cout << "f"; return;
case 18: cout << "f#"; return;
case 19: cout << "g"; return;
case 20: cout << "g#"; return;
case 21: cout << "a"; return;
case 22: cout << "b-"; return;
case 23: cout << "b"; return;
}
}
//////////////////////////////
//
// createHistogram --
//
void createHistogram(Array<double>& pitchhist, int start, int count,
Array<Array<double> >& segments) {
pitchhist.setAll(0);
int i, j;
for (i=0; i<count; i++) {
for (j=0; j<12; j++) {
pitchhist[j] += segments[i+start][j];
}
}
}
////////////////////////////////////////
//
// identifyKeyDouble --
//
void identifyKeyDouble(Array<double>& histogram, Array<double>& correlations,
double* majorKey, double* minorKey) {
int i;
double h[24];
for (i=0; i<12; i++) {
h[i] = histogram[i];
h[i+12] = h[i];
}
double testsum = 0.0;
for (i=0; i<12; i++) {
testsum += h[i];
correlations[i] = pearsonCorrelation(12, majorKey, h+i);
correlations[i+12] = pearsonCorrelation(12, minorKey, h+i);
}
if (testsum == 0.0) {
histogram[12] = 24; // empty histogram, so going to display black
return;
}
// find max value
int besti = 0;
double bestsum = correlations[0];
for (i=1; i<24; i++) {
if (correlations[i] > bestsum) {
besti = i;
bestsum = correlations[i];
}
}
histogram[12] = besti;
}
//////////////////////////////
//
// loadHistograms --
//
void loadHistograms(Array<Array<double> >& histograms,
HumdrumFile& infile, int segments) {
infile.analyzeRhythm("4");
double totalduration = infile.getTotalDuration();
double duration;
int i;
int j;
int k;
char buffer[10000] = {0};
int pitch;
double start;
int tokencount;
for (i=0; i<infile.getNumLines(); i++) {
if (infile[i].getType() != E_humrec_data) {
continue;
}
start = infile[i].getAbsBeat();
for (j=0; j<infile[i].getFieldCount(); j++) {
if (strcmp(infile[i].getExInterp(j), "**kern") != 0) {
continue;
}
if (strcmp(infile[i][j], ".") == 0) {
continue; // ignore null tokens
}
tokencount = infile[i].getTokenCount(j);
for (k=0; k<tokencount; k++) {
infile[i].getToken(buffer, j, k);
if (strcmp(buffer, ".") == 0) {
continue; // ignore illegal inline null tokens
}
pitch = Convert::kernToMidiNoteNumber(buffer);
if (pitch < 0) {
continue; // ignore rests or strange objects
}
pitch = pitch % 12; // convert to chromatic pitch-class
duration = Convert::kernToDuration(buffer);
if (duration <= 0.0) {
continue; // ignore grace notes and strange objects
}
addToHistogramDouble(histograms, pitch,
start, duration, totalduration, segments);
}
}
}
}
//////////////////////////////
//
// addToHistogramDouble -- fill the histogram in the right spots.
//
void addToHistogramDouble(Array<Array<double> >& histogram, int pc,
double start, double dur, double tdur, int segments) {
pc = pc % 12;
double startseg = start / tdur * segments;
double startfrac = startseg - (int)startseg;
double segdur = dur / tdur * segments;
if (segdur <= 1.0 - startfrac) {
histogram[(int)startseg][pc] += segdur;
return;
} else if (1.0 - startfrac > 0.0) {
histogram[(int)startseg][pc] += (1.0 - startfrac);
segdur -= (1.0 - startfrac);
}
int i = (int)(startseg + 1);
while (segdur > 0.0 && i < histogram.getSize()) {
if (segdur < 1.0) {
histogram[i][pc] += segdur;
segdur = 0.0;
} else {
histogram[i][pc] += 1.0;
segdur -= 1.0;
}
i++;
}
}
//////////////////////////////
//
// printAnalysis --
//
void printAnalysis(int bestkey, Array<double>& scores, Array<double>& durhist,
const char* filename, Array<int>& b40hist, HumdrumFile& infile) {
char buffer[64] = {0};
if (mmaQ) {
cout << "{";
cout << durhist[8] << ", "; // G#
cout << durhist[3] << ", "; // D#
cout << durhist[10] << ", "; // A#
cout << durhist[5] << ", "; // F
cout << durhist[0] << ", "; // C
cout << durhist[7] << ", "; // G
cout << durhist[2] << ", "; // D
cout << durhist[9] << ", "; // A
cout << durhist[4] << ", "; // E
cout << durhist[11] << ", "; // B
cout << durhist[6] << ", "; // F#
cout << durhist[1] << ""; // C#
cout << "};" << endl;
return;
}
int best40;
if (bestkey < 12) {
if (nameQ) {
cout << filename << ":\t";
} else {
cout << "The best key is: ";
}
best40 = identifyBranchCut(bestkey, b40hist);
// cout << Convert::base12ToKern(buffer, bestkey+12*4)
cout << Convert::base40ToKern(buffer, best40+40*3)
<< " Major";
if (errorQ) {
printErrorMarker(infile, best40, "major");
}
cout << "\n";
} else {
if (nameQ) {
cout << filename << ":\t";
} else {
cout << "The best key is: ";
}
best40 = identifyBranchCut(bestkey, b40hist);
// cout << Convert::base12ToKern(buffer, bestkey+12*3)
cout << Convert::base40ToKern(buffer, best40+40*3)
<< " Minor";
if (errorQ) {
printErrorMarker(infile, best40, "minor");
}
cout << "\n";
}
int i;
if (allQ) {
for (i=0; i<12; i++) {
cout << "Major[" << i << "] = " << scores[i] << "\t\t\t"
<< "Minor[" << i << "] = " << scores[i+12] << "\n";
}
}
if (frequencyQ) {
for (i=0; i<12; i++) {
cout << "Pitch[" << i << "] = " << durhist[i] << "\n";
}
}
}
//////////////////////////////
//
// printErrorMarker -- Compare the computer's analysis against one
// found at the start of the file.
//
void printErrorMarker(HumdrumFile& infile, int best40, const char* mode) {
PerlRegularExpression pre;
int foundQ = 0;
int i, j;
for (i=0; i<infile.getNumLines(); i++) {
if (infile[i].isData()) {
break;
}
if (!infile[i].isInterpretation()) {
continue;
}
for (j=0; j<infile[i].getFieldCount(); j++) {
if (pre.search(infile[i][j], "^\\*([A-Ga-g#-]+):")) {
foundQ = 1;
break;
}
}
if (foundQ) {
break;
}
}
if (!foundQ) {
cout << "?";
return;
}
PerlRegularExpression pre2;
const char* testmode = "major";
if (islower(pre.getSubmatch(1)[0])) {
testmode = "minor";
}
int testbase40 = Convert::kernToBase40(pre.getSubmatch());
if ((testbase40 % 40) == (best40 % 40)) {
if (strcmp(mode, testmode) == 0) {
// the answer is correct
return;
}
}
// the answer was not correct, so print the correct answer
char buffer[1024] = {0};
Convert::base40ToKern(buffer, (testbase40 % 40) + 3 * 40);
cout << " X:" << buffer << " " << testmode;
}
//////////////////////////////
//
// checkOptions -- validate and process command-line options.
//
void checkOptions(Options& opts, int argc, char* argv[]) {
opts.define("a|all=b", "show all scores");
opts.define("Aarden|aarden|aa=b", "use Aarden profiles");
opts.define("Bellman|bellman|bb=b","use Bellman profiles");
opts.define("Krumhansl|k|kk=b", "use Krumhansl-Kessler profiles");
opts.define("Temperley|temperley|kp|=b","use Kostka-Payne profiles");
opts.define("G|gabura|raw=b", "use raw correlation");
opts.define("n|normalize=b", "normalize raw correlation input data");
opts.define("e|euclidean=b", "euclidean keyfinding method");
opts.define("s|simple=b", "do simple profile");
opts.define("D|no-duration=b", "ignore duration of notes in input");
opts.define("f|frequency|freq=b", "show pitch frequencies");
opts.define("F|Freq=b", "pitch frequencies MMA by fifths");
opts.define("w|weights=s:", "weighting factor file");
opts.define("step=d:1.0", "step size for continuous analysis");
opts.define("window=i:32", "window size for continuous analysis");
opts.define("c|continuous=b", "continuous analysis");
opts.define("R|no-round=b", "don't round correlation values");
opts.define("l|name=b", "print filenames");
opts.define("error|errors=b", "print * marker on analyses which are errors");
opts.define("debug=b", "trace input parsing");
opts.define("author=b", "author of the program");
opts.define("version=b", "compilation information");
opts.define("example=b", "example usage");
opts.define("h|help=b", "short description");
opts.process(argc, argv);
// handle basic options:
if (opts.getBoolean("author")) {
cout << "Written by Craig Stuart Sapp, "
<< "craig@ccrma.stanford.edu, May 2001" << endl;
exit(0);
} else if (opts.getBoolean("version")) {
cout << argv[0] << ", version: 26 May 2009" << 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);
}
allQ = opts.getBoolean("all");
rhythmQ = !opts.getBoolean("no-duration");
frequencyQ = opts.getBoolean("frequency");
if (opts.getBoolean("F")) {
frequencyQ = 1;
mmaQ = 1;
}
rawQ = opts.getBoolean("raw");
normalizeQ = opts.getBoolean("normalize");
euclideanQ = opts.getBoolean("euclidean");
continuousQ = opts.getBoolean("continuous");
windowsize = opts.getInteger("window");
stepsize = opts.getDouble("step");
roundQ = !opts.getBoolean("no-round");
debugQ = opts.getBoolean("debug");
nameQ = opts.getBoolean("name");
errorQ = opts.getBoolean("error");
if (opts.getBoolean("Krumhansl")) {
majorKey = majorKeyKrumhansl;
minorKey = minorKeyKrumhansl;
} else if (opts.getBoolean("Aarden")) {
majorKey = majorKeyAarden;
minorKey = minorKeyAarden;
} else if (opts.getBoolean("simple")) {
majorKey = majorKeySimple;
minorKey = minorKeySimple;
} else if (opts.getBoolean("Bellman")) {
majorKey = majorKeyBellman;
minorKey = minorKeyBellman;
} else if (opts.getBoolean("Temperley")) {
majorKey = majorKeyKostkaPayne;
minorKey = minorKeyKostkaPayne;
} else if (opts.getBoolean("weights")) {
readWeights(opts.getString("weights"));
majorKey = majorKeyUser;
minorKey = minorKeyUser;
} else {
// default weightings set at start of main().
}
}
//////////////////////////////
//
// readWeights --
//
void readWeights(const char* filename) {
int i;
int j;
int key;
double value;
HumdrumFile wfile;
wfile.read(filename);
for (i=0; i<wfile.getNumLines(); i++) {
if (wfile[i].getType() != E_humrec_data) {
continue;
}
key = -1;
value = -1000000.0;
for (j=0; j<wfile[i].getFieldCount(); j++) {
if (strcmp(wfile[i].getExInterp(j), "**kern") == 0) {
key = Convert::kernToMidiNoteNumber(wfile[i][j]) % 12;
if (islower(wfile[i][j][0])) {
key += 12;
}
} else if (strcmp(wfile[i].getExInterp(j), "**weight") == 0) {
sscanf(wfile[i][j], "%lf", &value);
}
}
if ((key >= 0) && (key < 24) && (value != -1000000.0)) {
if (key < 12) {
majorKeyUser[key] = value;
} else {
minorKeyUser[key-12] = value;
}
}
}
}
//////////////////////////////
//
// example -- example usage of the maxent program
//
void example(void) {
cout <<
" \n"
<< endl;
}
//////////////////////////////
//
// usage -- gives the usage statement for the quality program
//
void usage(const char* command) {
cout <<
" \n"
<< endl;
}
//////////////////////////////
//
// analyzeKeyEuclidean --
//
int analyzeKeyEuclidean(double* scores, double* distribution,
int* pitch, double* durations, int size, int rhythmQ, double* majorKey,
double* minorKey) {
int i, j;
int histogram[12] = {0};
for (i=0; i<24; i++) {
scores[i] = 0.0;
}
if (size == 0) {
return -1; // return -1 if no data to analyze
}
for (i=0; i<12; i++) {
distribution[i] = 0.0;
}
// generate a histogram of pitches
for (i=0; i<size; i++) {
histogram[pitch[i]%12]++;
if (rhythmQ) {
distribution[pitch[i]%12] += durations[i]/4.0; // dur in whole notes
} else {
distribution[pitch[i]%12] += 0.25;
}
}
equalizeData(distribution, 12, 1.0);
double maj_temp;
double min_temp;
int subscript;
double* r_major = scores;
double* r_minor = scores + 12;
double value_maj;
double value_min;
for (i=0; i<12; i++) {
maj_temp = min_temp = 0;
// Examine all pitches for each key,
for (j=0; j<12; j++) {
subscript = (i+j)%12;
value_maj = majorKey[j] - distribution[subscript];
value_min = minorKey[j] - distribution[subscript];
maj_temp += (value_maj * value_maj);
min_temp += (value_min * value_min);
}
if (maj_temp <= 0.0) {
r_major[i] = 0.0;
} else {
r_major[i] = sqrt(maj_temp);
}
if (min_temp <= 0.0) {
r_minor[i] = 0.0;
} else {
r_minor[i] = sqrt(min_temp);
}
}
// now figure out which key is the largest.
int bestkey = 0;
for (i=1; i<24; i++) {
if (scores[i] < scores[bestkey]) { // searching for minimum score
bestkey = i;
}
}
return bestkey;
}
//////////////////////////////
//
// analyzeKeyRawCorrelation -- apply the Krumhansl-Schmuckler
// key-finding algorithm. Return value is the base-12 most likely
// key. scores 0-11 are the major keys starting on C major. 12-23 are
// the minor keys starting on C minor. input array scores must have
// a size of 24 or greater. input array pitch must have a size of "size".
//
int analyzeKeyRawCorrelation(double* scores, double* distribution, int* pitch,
double* durations, int size, int rhythmQ, double* majorKey,
double* minorKey) {
int i, j;
int histogram[12] = {0};
for (i=0; i<24; i++) {
scores[i] = 0.0;
}
if (size == 0) {
return -1; // return -1 if no data to analyze
}
for (i=0; i<12; i++) {
distribution[i] = 0.0;
}
// generate a histogram of pitches
for (i=0; i<size; i++) {
histogram[pitch[i]%12]++;
if (rhythmQ) {
distribution[pitch[i]%12] += durations[i]/4.0; // dur in whole notes
} else {
distribution[pitch[i]%12] += 0.25;
}
}
if (normalizeQ) {
normalizeData(distribution, 12);
}
double maj_temp;
double min_temp;
int subscript;
double* r_major = scores;
double* r_minor = scores + 12;
for (i=0; i<12; i++) {
maj_temp = min_temp = 0;
// Examine all pitches for each key,
for (j=0; j<12; j++) {
subscript = (i+j)%12;
maj_temp += (majorKey[j] * distribution[subscript]);
min_temp += (minorKey[j] * distribution[subscript]);
}
if (maj_temp <= 0.0) {
r_major[i] = 0.0;
} else {
r_major[i] = maj_temp;
}
if (min_temp <= 0.0) {
r_minor[i] = 0.0;
} else {
r_minor[i] = min_temp;
}
}
// now figure out which key is the largest.
int bestkey = 0;
for (i=1; i<24; i++) {
if (scores[i] > scores[bestkey]) {
bestkey = i;
}
}
return bestkey;
}
//////////////////////////////
//
// normalizeData --
//
void normalizeData(double* data, int asize) {
double mean = getMean(data, asize);
double sd = getStandardDeviation(mean, data, asize);
adjustData(data, 12, mean, sd);
}
//////////////////////////////
//
// adjustData -- apply mean shift and standard deviation scaling.
//
void adjustData(double* data, int asize, double mean, double sd) {
int i;
for (i=0; i<asize; i++) {
data[i] = (data[i] - mean) / sd;
}
}
//////////////////////////////
//
// getMean --
//
double getMean(double* data, int asize) {
if (asize <= 0) {
return 0.0;
}
int i;
double sum = 0.0;
for (i=0; i<asize; i++) {
sum += data[i];
}
return sum / asize;
}
//////////////////////////////
//
// getStandardDeviation --
//
double getStandardDeviation(double mean, double* data, int asize) {
double sum = 0.0;
double value;
int i;
for (i=0; i<asize; i++) {
value = data[i] - mean;
sum += value * value;
}
return sqrt(sum / asize);
}
//////////////////////////////
//
// equalizeData -- make the max value equal to summation (assuming that
// all values are non-negative.
//
void equalizeData(double* data, int asize, double summation) {
double max = 0.0;
int i;
for (i=0; i<asize; i++) {
if (max < data[i]) {
max = data[i];
}
}
for (i=0; i<asize; i++) {
data[i] = data[i] / max * summation;
}
}
//////////////////////////////
//
// equalizeData2 -- make the sum of the sequence equal to summation
//
void equalizeData2(double* data, int asize, double summation) {
double sum = 0.0;
int i;
for (i=0; i<asize; i++) {
sum += fabs(data[i]);
}
for (i=0; i<asize; i++) {
data[i] = data[i] * summation / sum;
}
}
//////////////////////////////
//
// getBase40Histogram -- Get the enharmonic pitch-class histogram for the
// music. Counts note attacks only not weighted by durations, but
// difference between duration weighting and not duration weighting
// should be negligible.
//
void getBase40Histogram(Array& base40, HumdrumFile& infile) {
base40.setSize(40);
base40.allowGrowth(0);
base40.setAll(0);
int tcount;
int i, j, k;
int b40;
char buffer[1024] = {0};
for (i=0; i<infile.getNumLines(); i++) {
if (!infile[i].isData()) {
continue;
}
for (j=0; j<infile[i].getFieldCount(); j++) {
if (!infile[i].isExInterp(j, "**kern")) {
continue;
}
if (strcmp(infile[i][j], ".") == 0) {
// ignore null records.
continue;
}
if (strchr(infile[i][j], 'r') != NULL) {
// ignore rests
continue;
}
tcount = infile[i].getTokenCount(j);
for (k=0; k<tcount; k++) {
infile[i].getToken(buffer, j, k);
if (strchr(buffer, ']') != NULL) {
continue;
}
if (strchr(buffer, '_') != NULL) {
continue;
}
b40 = Convert::kernToBase40(buffer);
if (b40 <0) {
continue;
}
base40[b40%40]++;
}
}
}
}
//////////////////////////////
//
// identifyBranchCut -- distinguish between G# major and A-flat minor
// for example. Currently only checking for enharmonic differences
// between black-key notes (i.e., cannot distinguish between
// C major and B-sharp major, although the function could be
// generalized to do so). Returns a base-40 pitch class which
// can represent diatonic pitches with up to 2 chromatic step
// alterations.
//
int identifyBranchCut(int base12, Array& base40) {
int tval = Convert::base12ToBase40(base12+5*12);
int accidental = Convert::base40ToAccidental(tval);
int diatonic = Convert::base40ToDiatonic(tval) % 7;
if (accidental == 0) {
return tval % 40;
}
int sdia, fdia;
if (accidental == -1) {
sdia = (diatonic+6)%7;
fdia = diatonic;
} else if (accidental == +1) {
sdia = diatonic;
fdia = (diatonic+1)%7;
} else {
// something strange happened
return tval % 40;
}
int sb40, fb40;
sb40 = (Convert::base7ToBase40(sdia+4*7) + 1)%40;
fb40 = (Convert::base7ToBase40(fdia+4*7) - 1)%40;
int sscore, fscore;
sscore = base40[sb40] + base40[(sb40+23)%40];
fscore = base40[fb40] + base40[(fb40+23)%40];
if (sscore > fscore) {
return sb40;
} else if (fscore > sscore) {
return fb40;
}
// some strange condition where scores are equivalent.
return tval%40;
}
// md5sum: 20b211c8108cc922c81a7460ed2a366e keycor.cpp [20130504]