// // Programmer: Craig Stuart Sapp // Creation Date: Sun Jan 6 10:03:34 PST 2008 // Last Modified: Sun Jan 6 10:03:36 PST 2008 // Filename: ...sig/examples/all/keyboundary.cpp // Web Address: http://sig.sapp.org/examples/museinfo/humdrum/keyboundary.cpp // Syntax: C++; museinfo // // Description: Optimizing program for Krumhansl-Schmuckler key-finding // algorithm at modulation points. #include #include #include "humdrum.h" #ifndef OLDCPP #include #else #include #endif // function declarations: void checkOptions (Options& opts, int argc, char** argv); void example (void); void usage (const char* command); void fillBaseHistogram (Array >& beathist, HumdrumFile& infile); void printHistograms (Array > >& histograms); void printBaseHistograms (Array >& histograms); void addToHistogram (Array >& histogram, int pc, double start, double dur); void fillAllHistograms (Array > >& histograms); void fillCorrectData (Array >& correct, HumdrumFile& infile); double bareCorrelation (int size, double* x, double* y); double pearsonCorrelation (int size, double* x, double* y); void printWeights (Array weights); void runSearch (Array& weights, Array > > >& histograms, Array > >& correct, int trialCount, int stopLimit, double amplitude, double decay); double getScore (Array >& correct, Array >& answers); double getDistance (Array& a, Array& b); void makeAnswers (Array >& answers, Array > >& histograms, Array& weights); int findBestKey (Array& histogram, Array& weights); void makeRandomWeights (Array& weights, Array& initweights, double range); void normalizeWeights (Array& weights); void printAnswerHistogram (Array >& answers, Array > >& histograms, Array& weights); void printPPM (Array >& matrix); void generatePicture (Array >& answers); void printCorrect (Array >& correct); void getTwoKeys (int& lefthalf, int& righthalf, HumdrumFile& infile); double getStandardDeviation (double mean, Array& data); double getMean (int size, double* a); double getBoundaryLeft (int correct, Array& answers); double getBoundaryRight (int correct, Array& answers); // User interface variables: Options options; int beatHistogramQ = 0; // used with -b option int allHistogramsQ = 0; // used with -a option int trialCount = 100; // used with -c option double decay = 0.95; // used with -d option double amplitude = 1.0; // used with -s option int stopLimit = 10; // used with -l option int seed = 0; int simpleQ = 0; // used with --simple int aardenQ = 0; // used with --aarden int krumhanslQ = 0; // used with --krumhansl int temperleyQ = 0; // used with --temperley int hybridQ = 0; // used with --hybrid int pictureQ = 0; // used with --picture Array inputweights; // used with -w ////////////////////////////////////////////////////////////////////////// int main(int argc, char** argv) { inputweights.setSize(0); checkOptions(options, argc, argv); // process the command-line options int i, j; Array weights(24); weights.zero(); // set the starting position of the key-position weightings // if not using all zeros: if (inputweights.getSize() == 24) { weights = inputweights; } else if (simpleQ) { weights[0] = 2; // C (major) weights[1] = 0; // C# (major) weights[2] = 1; // D (major) weights[3] = 0; // E- (major) weights[4] = 1; // E (major) weights[5] = 1; // F (major) weights[6] = 0; // F# (major) weights[7] = 2; // G (major) weights[8] = 0; // A- (major) weights[9] = 1; // A (major) weights[10] = 0; // B- (major) weights[11] = 1; // B (major) weights[12] = 2; // c (minor) weights[13] = 0; // c# (minor) weights[14] = 1; // d (minor) weights[15] = 1; // e- (minor) weights[16] = 0; // e (minor) weights[17] = 1; // f (minor) weights[18] = 0; // f# (minor) weights[19] = 2; // g (minor) weights[20] = 1; // a- (minor) weights[21] = 0; // a (minor) weights[22] = 1; // b- (minor) weights[23] = 0; // b (minor) } else if (aardenQ) { weights[0] = 17.7661; // C (major) weights[1] = 0.145624; // C# (major) weights[2] = 14.9265; // D (major) weights[3] = 0.160186; // D# (major) weights[4] = 19.8049; // E (major) weights[5] = 11.3587; // F (major) weights[6] = 0.291248; // F# (major) weights[7] = 22.062; // G (major) weights[8] = 0.145624; // G# (major) weights[9] = 8.15494; // A (major) weights[10] = 0.232998; // A# (major) weights[11] = 4.95122; // B (major) weights[12] = 18.2648; // c (minor) weights[13] = 0.737619; // c# (minor) weights[14] = 14.0499; // d (minor) weights[15] = 16.8599; // e- (minor) weights[16] = 0.702494; // e (minor) weights[17] = 14.4362; // f (minor) weights[18] = 0.702494; // f# (minor) weights[19] = 18.6161; // g (minor) weights[20] = 4.56621; // a- (minor) weights[21] = 1.93186; // a (minor) weights[22] = 7.37619; // b- (minor) weights[23] = 1.75623; // b (minor) } else if (krumhanslQ) { weights[0] = 6.35; // C (major) weights[1] = 2.23; // C# (major) weights[2] = 3.48; // D (major) weights[3] = 2.33; // D# (major) weights[4] = 4.38; // E (major) weights[5] = 4.09; // F (major) weights[6] = 2.52; // F# (major) weights[7] = 5.19; // G (major) weights[8] = 2.39; // G# (major) weights[9] = 3.66; // A (major) weights[10] = 2.29; // A# (major) weights[11] = 2.88; // B (major) weights[12] = 6.33; // c (minor) weights[13] = 2.68; // c# (minor) weights[14] = 3.52; // d (minor) weights[15] = 5.38; // e- (minor) weights[16] = 2.60; // e (minor) weights[17] = 3.53; // f (minor) weights[18] = 2.54; // f# (minor) weights[19] = 4.75; // g (minor) weights[20] = 3.98; // a- (minor) weights[21] = 2.69; // a (minor) weights[22] = 3.34; // b- (minor) weights[23] = 3.17; // b (minor) } else if (temperleyQ) { weights[0] = 5.0; // C (major) weights[1] = 2.0; // C# (major) weights[2] = 3.5; // D (major) weights[3] = 2.0; // D# (major) weights[4] = 4.5; // E (major) weights[5] = 4.0; // F (major) weights[6] = 2.0; // F# (major) weights[7] = 4.5; // G (major) weights[8] = 2.0; // G# (major) weights[9] = 3.5; // A (major) weights[10] = 1.5; // A# (major) weights[11] = 4.0; // B (major) weights[12] = 5.0; // c (minor) weights[13] = 2.0; // c# (minor) weights[14] = 3.5; // d (minor) weights[15] = 4.5; // e- (minor) weights[16] = 2.0; // e (minor) weights[17] = 4.0; // f (minor) weights[18] = 2.0; // f# (minor) weights[19] = 4.5; // g (minor) weights[20] = 3.5; // a- (minor) weights[21] = 2.0; // a (minor) weights[22] = 1.5; // b- (minor) weights[23] = 4.0; // b (minor) } else if (hybridQ) { weights[0] = 11.38305; // C (major) weights[1] = 1.072812; // C# (major) weights[2] = 9.21325; // D (major) weights[3] = 1.080093; // E- (major) weights[4] = 12.15245; // E (major) weights[5] = 7.67935; // F (major) weights[6] = 1.145624; // F# (major) weights[7] = 13.281; // G (major) weights[8] = 1.072812; // A- (major) weights[9] = 5.82747; // A (major) weights[10] = 0.866499; // B- (major) weights[11] = 4.47561; // B (major) weights[12] = 11.6324; // c (minor) weights[13] = 1.3688095; // c# (minor) weights[14] = 8.77495; // d (minor) weights[15] = 10.67995; // e- (minor) weights[16] = 1.351247; // e (minor) weights[17] = 9.2181; // f (minor) weights[18] = 1.351247; // f# (minor) weights[19] = 11.55805; // g (minor) weights[20] = 4.033105; // a- (minor) weights[21] = 1.96593; // a (minor) weights[22] = 4.438095; // b- (minor) weights[23] = 2.878115; // b (minor) } // allow user starting weights here later Array infiles; if (options.getArgCount() == 0) { // if no command-line arguments read data file from standard input infiles.setSize(1); infiles.allowGrowth(0); infiles[0].clear(); infiles[0].read(cin); } else { infiles.setSize(options.getArgCount()); infiles.allowGrowth(0); for (i=0; i > > > histograms; Array > > correct; histograms.setSize(infiles.getSize()); correct.setSize(infiles.getSize()); histograms.allowGrowth(0); correct.allowGrowth(0); int totalbeats; int kk; for (kk=0; kk >& correct) { int i,j; for (i=0; i& initweights, Array > > >& histograms, Array > >& correct, int trialCount, int stopLimit, double initrange, double decay) { double range = initrange; Array bestweights = initweights; Array > > answers; double distance; int i, j, kk; Array bestscore; bestscore.setSize(histograms.getSize()); bestscore.allowGrowth(0); bestscore.setAll(-1000000); Array stepweights; Array nextbestweights; Array nextbestscore; nextbestscore.setSize(histograms.getSize()); nextbestscore.allowGrowth(0); nextbestscore = bestscore; Array stepscore; stepscore.setSize(histograms.getSize()); stepscore.allowGrowth(0); answers.setSize(histograms.getSize()); answers.allowGrowth(0); for (kk=0; kk 0.0) { currentLimit = 0; } else { currentLimit++; } /* if (nextbestscore <= bestscore) { currentLimit++; } else { currentLimit = 0; } */ betterQ = 1; for (kk=0; kk 1) { cout << "s"; } cout << ":\t"; for (kk=0; kk >& answers, Array > >& histograms, Array& weights) { Array anshist; anshist.setSize(25); anshist.allowGrowth(0); anshist.zero(); int i, j; int best; for (i=0; i >& answers, Array > >& histograms, Array& weights) { int i, j; for (i=0; i& histogram, Array& weights) { int i; double* maj = weights.getBase(); double* min = weights.getBase() + 12; double hist[24]; for (i=0; i<12; i++) { hist[i] = histogram[i]; hist[i+12] = histogram[i]; } int bestmaji = 0; double bestmajscore = COMPARATOR(12, maj, hist); double testmajscore; int bestmini = 0; double bestminscore = COMPARATOR(12, min, hist); double testminscore; for (i=1; i<12; i++) { testmajscore = COMPARATOR(12, maj, hist+i); if (testmajscore > bestmajscore) { bestmajscore = testmajscore; bestmaji = i; } testminscore = COMPARATOR(12, min, hist+i); if (testminscore > bestminscore) { bestminscore = testminscore; bestmini = i; } } double bestmajpearson = pearsonCorrelation(12, maj, hist+bestmaji); double bestminpearson = pearsonCorrelation(12, min, hist+bestmini); int result = -1; if (bestmajpearson > bestminpearson) { result = bestmaji; } else if (bestmajpearson < bestminpearson) { result = bestmini + 12; } else { result = 24; } //cout << "\n\n"; //cout << "RESULT = \t" << result << "\n"; //cout << "weight=\t"; //for (i=0; i& a, Array& b) { int i; double sum = 0.0; double value; for (i=0; i& weights, Array& initweights, double range) { int i; double delta = 0.0; for (i=0; i >& correct, Array >& answers) { int size = correct.getSize(); Array leftboundary; Array rightboundary; leftboundary.setSize(size); rightboundary.setSize(size); leftboundary.allowGrowth(0); rightboundary.allowGrowth(0); int leftside = correct[size-1][0]; int rightside = correct[size-1][correct[size-1].getSize()-1]; int i; for (i=0; i& answers) { int i; int size = answers.getSize(); int firsterror = -1; for (i=0; i& answers) { int i; int size = answers.getSize(); int firsterror = -1; for (i=size-1; i>=0; i--) { if (answers[i] != correct) { firsterror = i; break; } } if (firsterror < 0) { return 0.0 - size / 2.0; } return firsterror - size / 2.0; } ////////////////////////////// // // getScoreOld -- maximizes the area of the two correct answers // double getScoreOld(Array >& correct, Array >& answers) { int output = 0; int size = correct.getSize(); int leftside = correct[size-1][0]; int rightside = correct[size-1][correct[size-1].getSize()-1]; int i, j; int ans, cor; int leftcount = 0; int rightcount = 0; for (i=0; i 23 || cor < 0) { // ignore boundaries output += 0; } else { output += -2; } } } //int difference = abs(leftcount - rightcount); int difference = 0; return (double)(output - difference); } ////////////////////////////// // // fillCorrectData -- infile is currently not really used, add flexibility // later. // // statically set to f minor in the first half and A major in the second // half. // void fillCorrectData(Array >& correct, HumdrumFile& infile) { int totalbeats = (int)(infile.getTotalDuration()+0.9); int i, j; int lefthalf = -1; int righthalf = -1; getTwoKeys(lefthalf, righthalf, infile); double halfj; int rowsize; correct.setSize(totalbeats); for (i=0; i= 0) { correct[i][rowsize/2-1] = -1; } if (rowsize/2 + 1 < rowsize) { correct[i][rowsize/2+1] = -1; } */ } else { // blank out the region just off-center /* if (rowsize/2 - 1 >= 0) { correct[i][rowsize/2-1] = -1; } if (rowsize/2 < rowsize) { correct[i][rowsize/2] = -1; } */ } } } ////////////////////////////// // // getTwoKeys -- // void getTwoKeys(int& lefthalf, int& righthalf, HumdrumFile& infile) { int i; int counter = 0; int keyz[100] = {0}; keyz[0] = -1; keyz[1] = -1; int key; int length; for (i=0; i= 2) { break; } } lefthalf = keyz[0]; righthalf = keyz[1]; } ////////////////////////////// // // printWeights -- // void printWeights(Array weights) { char buffer[1024] = {0}; cout << "**kern\t**weight\n"; cout << "!!major weights (using C as the tonic)\n"; int i; for (i=0; i<12; i++) { cout << Convert::base12ToKern(buffer, i+48) << "\t" << fixed << weights[i] << "\n"; cout.unsetf(ios_base::floatfield); } cout << "!!minor weights (using c as the tonic)\n"; for (i=0; i<12; i++) { cout << Convert::base12ToKern(buffer, i+60) << "\t" << fixed << weights[i+12] << "\n"; cout.unsetf(ios_base::floatfield); } cout << "*-\t*-\n"; } ////////////////////////////// // // fillAllHistograms -- // void fillAllHistograms(Array > >& histograms) { int size = histograms.getSize(); int i, j, k; for (i=size-2; i>=0; i--) { for (j=0; j >& beathist, HumdrumFile& infile) { int i; int j; int k; int tokencount = 0; int pitch = 0; char buffer[1024] = {0}; double start = 0.0; double duration = 0.0; for (i=0; i >& histogram, int pc, double start, double dur) { int starti = (int)start; double startf = start - starti; if (dur <= 1.0 - startf) { histogram[starti][pc] += dur; return; } else if (1.0 - startf > 0.0) { histogram[starti][pc] += 1.0 - startf; dur -= (1.0 - startf); } int i = starti + 1; while(dur > 0.0) { if (dur < 1.0) { histogram[i][pc] += dur; dur = 0.0; } else { histogram[i][pc] += 1.0; dur -= 1.0; } i++; } } ////////////////////////////// // // printHistograms -- print the measured pitch histogram array // void printHistograms(Array > >& histograms) { cout << "range\tC\tC#\tD\tEb\tE\tF\tF#\tG\tAb\tA\tBb\tB\n"; int i, j, k; for (i=0; i >& histograms) { cout << "beat\tC\tC#\tD\tEb\tE\tF\tF#\tG\tAb\tA\tBb\tB\n"; int i, j; for (i=0; i& weights) { double minmaj = weights[0]; double minmin = weights[12]; double maxmaj = weights[0]; double maxmin = weights[12]; int i; for (i=1; i<12; i++) { if (weights[i] < minmaj) { minmaj = weights[i]; } if (weights[i+12] < minmin) { minmin = weights[i+12]; } if (weights[i] > maxmaj) { maxmaj = weights[i]; } if (weights[i+12] > maxmin) { maxmin = weights[i+12]; } } maxmaj += minmaj; maxmin += minmin; for (i=0; i<12; i++) { weights[i] = (weights[i] + minmaj) / maxmaj; weights[i+12] = (weights[i+12] + minmin) / maxmin; } } ////////////////////////////// // // checkOptions -- // void checkOptions(Options& opts, int argc, char* argv[]) { opts.define("a|all=b", "all segmentation histograms display"); opts.define("b|beat=b", "beat histogram display"); opts.define("c|count=i:100", "number of trials per step"); opts.define("d|decay=d:0.95", "number of trials per step"); opts.define("s|amplitude=d:1.0", "starting random amplitude"); opts.define("l|stoplimit=i:10", "number of trials with same error rate"); opts.define("seed=i:0", "random number generator seed"); opts.define("picture=b", "generate a picture of the current state"); opts.define("w|weights=s", "input starting weights"); opts.define("simple=b", "use simple weights"); opts.define("aarden=b", "use aarden weights"); opts.define("krumhansl=b", "use krumhansl weights"); opts.define("temperley=b", "use temperley weights"); opts.define("hybrid=b", "use hybrid (1/2 aarden, 1/2 temperley) weights"); opts.define("author=b", "author of program"); opts.define("version=b", "compilation info"); opts.define("example=b", "example usages"); opts.define("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, Jan 2008" << endl; exit(0); } else if (opts.getBoolean("version")) { cout << argv[0] << ", version: Jan 2008" << 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); } beatHistogramQ = opts.getBoolean("beat"); allHistogramsQ = opts.getBoolean("all"); trialCount = opts.getInteger("count"); stopLimit = opts.getInteger("stoplimit"); decay = opts.getDouble("decay"); amplitude = opts.getDouble("amplitude"); seed = opts.getInteger("seed"); simpleQ = opts.getBoolean("simple"); aardenQ = opts.getBoolean("aarden"); krumhanslQ = opts.getBoolean("krumhansl"); temperleyQ = opts.getBoolean("temperley"); hybridQ = opts.getBoolean("hybrid"); pictureQ = opts.getBoolean("picture"); if (seed == 0) { seed = time(NULL); } srand48(seed); int i; int j; int key; double value; int weightsQ = opts.getBoolean("weights"); if (weightsQ) { inputweights.setSize(24); inputweights.allowGrowth(0); inputweights.zero(); HumdrumFile wfile; wfile.read(opts.getString("weights")); for (i=0; i= 0) && (key < 24) && (value != -1000000.0)) { inputweights[key] = value; } } } } ////////////////////////////// // // example -- // void example(void) { } ////////////////////////////// // // usage -- // void usage(const char* command) { } ////////////////////////////// // // printPPM -- // void printPPM(Array >& matrix) { // Pitch to Color translations int red[40] = { 0, 9, 18, 0, 63, 63, 63, 73, 82, 0, 218, 237, 255, 255, 255, 255, 255, 255, 218, 182, 0, 45, 54, 63, 63, 63, 0, 109, 118, 127, 145, 164, 0, 255, 255, 255, 255, 255, 73, 36 }; int green[40] = { 255, 246, 237, 0, 123, 109, 95, 86, 77, 0, 9, 4, 0, 18, 36, 218, 237, 255, 255, 255, 0, 209, 200, 191, 177, 164, 0, 50, 41, 31, 27, 22, 0, 91, 109, 127, 145, 164, 255, 255 }; int blue[40] = { 0, 36, 73, 0, 255, 255, 255, 255, 255, 0, 73, 36, 0, 0, 0, 0, 0, 0, 0, 0, 0, 182, 218, 255, 255, 255, 0, 255, 255, 255, 219, 182, 0, 0, 0, 0, 0, 0, 0, 0 }; double minorscale = 0.75; // darkening of color for minor keys const char* background = "255 255 255"; int row, column; int maxrow = matrix.getSize(); int maxcolumn = matrix[0].getSize(); int i, j; int root; double modescale = 1.0; cout << "P3\n"; cout << maxcolumn << " " << maxrow << "\n"; cout << "255\n"; for (row=0; row=0; row--) { i = row; for (column=0; column= 40) { root -= 40; modescale = minorscale; } else { modescale = 1.0; } if (root >= 0) { cout << (int)(red[root] * modescale + 0.5) << " " << (int)(green[root] * modescale + 0.5) << " " << (int)(blue[root] * modescale + 0.5) << " "; } else { cout << " " << background << " "; } } cout << "\n"; } } ////////////////////////////// // // generatePicture -- // void generatePicture(Array >& answers) { int rows = answers.getSize() * 2; int columns = answers[answers.getSize()-1].getSize() * 2; Array > matrix; int i, j; matrix.setSize(rows); for (i=0; i= 24 || base12 < 0) { value = -1; } else if (base12 < 12) { // major key value = Convert::base12ToBase40(base12) % 40 - 2; } else { // minor key value = Convert::base12ToBase40(base12) % 40 + 40 - 2; } // cout << "BASE12 = " << base12 << "\tBASE40 = " << value << "\n"; matrix[ii][jj] = value; matrix[ii+1][jj] = value; matrix[ii][jj+1] = value; matrix[ii+1][jj+1] = value; } } printPPM(matrix); } ////////////////////////////// // // getMean -- // double getMean(int size, double* a) { if (size <= 0) { return 0.0; } int i; double sum = 0.0; for (i=0; i& data) { double sum = 0.0; double value; int i; for (i=0; i