//
// Programmer: Craig Stuart Sapp <craig@ccrma.stanford.edu>
// Creation Date: Wed Oct 27 11:03:38 PDT 2004
// Last Modified: Wed Oct 27 12:33:26 PDT 2004
// Filename: ...sig/examples/all/modid.cpp
// Web Address: http://sig.sapp.org/examples/museinfo/humdrum/modid.cpp
// Syntax: C++; museinfo
//
// Description: Automatic modulation identification using the
// Krumhansl-Schmuckler key-finding algorithm.
//
// A modulation is defined with following algorithm:
// (1) defined a window size (e.g. 16 beats).
// (2) choose a test modulation position in the score.
// (3) if the window of music starting at that window generate the same
// key analysis as a 1/2 window at the same location, then there
// is a stable key after the test modulation position.
// (4) if the window of music before the starting position is stable
// according to the same method, then continue.
// (5) if a window centered at the modulation point gives a lower
// score for the two keys from the window before and after the
// test modulation point, then mark the position in the score
// as a modulation.
//
#include "humdrum.h"
// function declarations
void checkOptions(Options& opts, int argc, char* argv[]);
void example(void);
void usage(const char* command);
int getDurationLineStart(HumdrumFile& infile, double target);
int getDurationLineEnd(HumdrumFile& infile, double target);
void getModulation(int& mod, int& before, HumdrumFile& infile,
int line, double window);
void getModulationAnalysis(HumdrumFile& infile, Array<int>& modulations,
Array<int>& beforemod);
void printModulations(HumdrumFile& infile, Array<int>& modulations,
Array<int>& beforemod);
void printPitch(int value);
// user interface variables
Options options; // database for command-line arguments
int appendQ = 0; // used with -a option
int prependQ = 0; // used with -p option
double window = 16.0; // used with -w option
int debugQ = 0; // used with --debug option
///////////////////////////////////////////////////////////////////////////
int main(int argc, char* argv[]) {
// process the command-line options
checkOptions(options, argc, argv);
// if no command-line arguments read data file from standard input
HumdrumFile infile;
if (options.getArgCount() < 1) {
infile.read(cin);
} else {
infile.read(options.getArg(1));
}
infile.analyzeRhythm();
Array<int> modulations;
Array<int> beforemod;
getModulationAnalysis(infile, modulations, beforemod);
printModulations(infile, modulations, beforemod);
return 0;
}
///////////////////////////////////////////////////////////////////////////
//////////////////////////////
//
// printModulations --
//
void printModulations(HumdrumFile& infile, Array<int>& modulations,
Array<int>& beforemod) {
int i;
for (i=0; i<infile.getNumLines(); i++) {
if (appendQ) {
cout << infile[i];
}
if (appendQ) {
if (infile[i].getType() == E_humrec_data) {
if (modulations[i] == -1) {
cout << "\t.";
} else if (modulations[i] == -2) {
cout << "\t";
printPitch(beforemod[i]);
} else if (modulations[i] == -3) {
cout << "\t(";
printPitch(beforemod[i]);
cout << ")";
} else if (beforemod[i] == -1) {
cout << "\t.";
} else {
cout << "\t";
printPitch(beforemod[i]);
cout << ":";
printPitch(modulations[i]);
}
} else if (infile[i].getType() == E_humrec_data_measure) {
cout << "\t" << infile[i][0];
} else if (infile[i].getType() == E_humrec_interpretation) {
if (strncmp(infile[i][0], "**", 2) == 0) {
cout << "\t**mod";
} else if (strcmp(infile[i][0], "*-") == 0) {
cout << "\t*-";
} else {
cout << "\t*";
}
}
} else if (prependQ) {
if (infile[i].getType() == E_humrec_data) {
if (modulations[i] == -1) {
cout << ".\t";
} else if (modulations[i] == -2) {
printPitch(beforemod[i]);
cout << "\t";
} else if (modulations[i] == -3) {
cout << "(";
printPitch(beforemod[i]);
cout << ")";
cout << "\t";
} else if (beforemod[i] == -1) {
cout << ".\t";
} else {
printPitch(beforemod[i]);
cout << ":";
printPitch(modulations[i]);
cout << "\t";
}
} else if (infile[i].getType() == E_humrec_data_measure) {
cout << infile[i][0];
cout << "\t";
} else if (infile[i].getType() == E_humrec_interpretation) {
if (strncmp(infile[i][0], "**", 2) == 0) {
cout << "**mod\t";
} else if (strcmp(infile[i][0], "*-") == 0) {
cout << "*-\t";
} else {
cout << "*\t";
}
}
cout << infile[i];
} else {
if (infile[i].getType() == E_humrec_data) {
if (modulations[i] == -1) {
cout << ".";
} else if (modulations[i] == -2) {
printPitch(beforemod[i]);
} else if (modulations[i] == -3) {
cout << "(";
printPitch(beforemod[i]);
cout << ")";
} else if (beforemod[i] == -1) {
cout << ".";
} else {
cout << "";
printPitch(beforemod[i]);
cout << ":";
printPitch(modulations[i]);
}
} else if (infile[i].getType() == E_humrec_data_measure) {
cout << "" << infile[i][0];
} else if (infile[i].getType() == E_humrec_interpretation) {
if (strncmp(infile[i][0], "**", 2) == 0) {
cout << "**mod";
} else if (strcmp(infile[i][0], "*-") == 0) {
cout << "*-";
} else {
cout << "*";
}
} else {
cout << infile[i];
}
}
cout << "\n";
}
}
//////////////////////////////
//
// printPitch --
//
void printPitch(int value) {
int mode = 0;
if (value >= 12) {
value -= 12;
mode = 1; // minor key
}
int base40 = Convert::base12ToBase40(value);
base40 = (base40 + 40) % 40;
base40 = base40 + 3 * 40;
if (mode == 1) {
base40 += 40;
}
char buffer[64] = {0};
cout << Convert::base40ToKern(buffer, base40);
}
//////////////////////////////
//
// getModulationAnalysis --
//
void getModulationAnalysis(HumdrumFile& infile, Array<int>& modulations,
Array<int>& beforemod) {
modulations.setSize(infile.getNumLines());
modulations.setAll(-1);
beforemod.setSize(infile.getNumLines());
beforemod.setAll(-1);
int i;
int mod;
int before;
for (i=0; i<infile.getNumLines(); i++) {
if (infile[i].getType() != E_humrec_data) {
continue;
}
getModulation(mod, before, infile, i, window);
modulations[i] = mod;
beforemod[i] = before;
}
}
//////////////////////////////
//
// getModulation --
//
void getModulation(int& mod, int& before, HumdrumFile& infile, int line,
double window) {
if (infile[line].getAbsBeat() < window) {
// not enough music before the modulation point to analyze.
mod = -1;
before = -1;
return;
}
if (infile[infile.getNumLines()-1].getAbsBeat() -
infile[line].getAbsBeat() < window) {
mod = -1;
before = -1;
return;
}
Array<double> scores(24);
int wafter = -1; // ending window index after mod point
int w2after = -1; // ending half-window index after mod point
int wbefore = -1; // starting window index before mod point
int w2before = -1; // starting half-window index before mod point
int midval = -1;
double rafter = -1.0; // rvalue for window after mod point
double r2after = -1.0; // rvalue for half-window after mod point
double rbefore = -1.0; // rvalue for window before mod point
double r2before = -1.0; // rvalue for half-window before mod point
double rmidbefore = -1.0; // rvalue 1 for mid-window
double rmidafter = -1.0; // rvalue 1 for mid-window
int wafterstop = getDurationLineEnd(infile,
infile[line].getAbsBeat()+window);
int w2afterstop = getDurationLineEnd(infile,
infile[line].getAbsBeat()+window/2.0);
int wbeforestart = getDurationLineStart(infile,
infile[line].getAbsBeat()-window);
int w2beforestart = getDurationLineStart(infile,
infile[line].getAbsBeat()-window/2.0);
wafter = infile.analyzeKeyKS(scores, line, wafterstop);
rafter = scores[wafter];
w2after = infile.analyzeKeyKS(scores, line, w2afterstop);
r2after = scores[wafter];
wbefore = infile.analyzeKeyKS(scores, wbeforestart, line);
rbefore = scores[wafter];
w2before = infile.analyzeKeyKS(scores, w2beforestart, line);
r2before = scores[wafter];
midval = infile.analyzeKeyKS(scores, w2beforestart, w2afterstop);
rmidbefore = scores[wbefore];
rmidafter = scores[wafter];
if (debugQ) {
cout << "wa=" << wafter;
cout << "\tw2a=" << w2after;
cout << "\twb=" << wbefore;
cout << "\tw2b=" << w2before;
cout << endl;
}
mod = -1;
before = -1;
if ((midval == w2before) && (midval == w2after)) {
if ((w2before != wbefore) || (w2after != wafter)) {
before = midval;
mod = -3;
return;
}
}
if (wbefore != w2before) { return; }
if (wafter != w2after) { return; }
if (wbefore == wafter) {
if (midval == wbefore) { // indicate a stable local region
before = midval;
mod = -2;
}
return;
}
mod = wafter;
before = wbefore;
}
//////////////////////////////
//
// getDurationLineEnd -- return the index of the last line
// with the specified duration (or less).
//
int getDurationLineEnd(HumdrumFile& infile, double target) {
int i;
for (i=infile.getNumLines()-1; i>=0; i--) {
if (infile[i].getAbsBeat() <= target) {
return i;
}
}
return 0;
}
//////////////////////////////
//
// getDurationLineStart -- return the index of the first line
// with the specified duration (or greater).
//
int getDurationLineStart(HumdrumFile& infile, double target) {
int i;
for (i=0; i<infile.getNumLines(); i++) {
if (infile[i].getAbsBeat() >= target) {
return i;
}
}
return infile.getNumLines()-1;
}
//////////////////////////////
//
// checkOptions -- validate and process command-line options.
//
void checkOptions(Options& opts, int argc, char* argv[]) {
opts.define("a|append=b", "append analysis to original score");
opts.define("p|prepend=b", "prepend analysis to original score");
opts.define("w|window=d:16.0", "analysis window size");
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, Oct 2004" << endl;
exit(0);
} else if (opts.getBoolean("version")) {
cout << argv[0] << ", version: 27 Oct 2004" << 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);
}
appendQ = opts.getBoolean("append");
prependQ = opts.getBoolean("prepend");
window = opts.getDouble("window");
if (window <= 0) {
window = 16.0;
}
debugQ = opts.getBoolean("debug");
}
//////////////////////////////
//
// 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;
}
// md5sum: efada328c3889e333d3f590620aa5d30 modid.cpp [20050403]