// // Programmer: Craig Stuart Sapp // Creation Date: Mon Apr 12 23:22:19 PDT 2004 // Last Modified: Mon Apr 12 23:22:22 PDT 2004 // Last Modified: Thu Feb 24 22:43:17 PST 2005 Added -k option // Last Modified: Wed Jun 24 15:39:58 PDT 2009 Updated for GCC 4.4 // Last Modified: Sun Sep 13 12:34:51 PDT 2009 Added -s option // Last Modified: Wed Nov 18 14:01:20 PST 2009 Added *Tr markers // Last Modified: Thu Nov 19 14:08:32 PST 2009 Added -q, -d and -c options // Last Modified: Thu Nov 19 15:12:01 PST 2009 Added -I options and *ITr marks // Last Modified: Thu Nov 19 19:28:26 PST 2009 Added -W and -C options // Last Modified: Sat Apr 28 08:49:41 PDT 2018 // Filename: ...sig/examples/all/transpose.cpp // Web Address: http://sig.sapp.org/examples/museinfo/humdrum/transpose.cpp // Syntax: C++; museinfo // // Description: Transpose **kern musical data. // #include "humdrum.h" #include "PerlRegularExpression.h" #include #include #include #include #include #define STYLE_CONCERT 0 #define STYLE_WRITTEN 1 // function declarations: void checkOptions (Options& opts, int argc, char** argv); void example (void); void usage (const char* command); void printFile (HumdrumFile& infile); void processFile (HumdrumFile& infile); void processFile (HumdrumFile& infile, Array& spines); void printNewKernString (const char* string, int transval); void printHumdrumKernToken (HumdrumRecord& record, int index, int transval); void printHumdrumDataRecord(HumdrumRecord& record, Array& spineprocess); int getBase40ValueFromInterval(const char* string); void printNewKeyInterpretation(HumdrumRecord& aRecord, int index, int transval); void printNewKeySignature (const char* keysig, int trans); void convertScore (HumdrumFile& infile, int style); void printTransposedToken (HumdrumFile& infile, int row, int col, int transval); void processInterpretationLine(HumdrumFile& infile, int line, Array& tvals, int style); int isKeyMarker (const char* string); int getTransposeInfo (HumdrumFile& infile, int row, int col); int hasTrMarkers (HumdrumFile& infile, int line); void convertToWrittenPitches(HumdrumFile& infile, int line, Array& tvals); void convertToConcertPitches(HumdrumFile& infile, int line, Array& tvals); // auto transpose functions: double pearsonCorrelation (int size, double* x, double* y); void doAutoTransposeAnalysis(HumdrumFile& infile); void addToHistogramDouble (Array >& histogram, int pc, double start, double dur, double tdur, int segments); double storeHistogramForTrack (Array >& histogram, HumdrumFile& infile, int track, int segments); void printHistograms (int segments, Array ktracks, Array > >& trackhist); void doAutoKeyAnalysis (Array > >& analysis, int level, int hop, int count, int segments, Array& ktracks, Array > >& trackhist); void doTrackKeyAnalysis (Array >& analysis, int level, int hop, int count, Array >& trackhist, Array& majorweights, Array& minorweights); void identifyKeyDouble (Array& correls, Array& histogram, Array& majorweights, Array& minorweights); void fillWeightsWithKostkaPayne(Array& maj, Array& min); void printRawTrackAnalysis (Array > >& analysis, Array& ktracks); void doSingleAnalysis (Array& analysis, int startindex, int length, Array >& trackhist, Array& majorweights, Array& minorweights); void identifyKey (Array& correls, Array& histogram, Array& majorweights, Array& minorweights); void doTranspositionAnalysis(Array > >& analysis); int calculateTranspositionFromKey(int targetkey, HumdrumFile& infile); // spine list parsing functions: void processFieldEntry (Array& field, const char* astring, HumdrumFile& infile); void fillFieldData (Array& field, const string& fieldstring, HumdrumFile& infile); void removeDollarsFromString(Array& buffer, int maxtrack); // User interface variables: Options options; int transval = 0; // used with -b option int ssetkeyQ = 0; // used with -k option int ssetkey = 0; // used with -k option int currentkey = 0; int autoQ = 0; // used with --auto option int debugQ = 0; // used with --debug option int spineQ = 0; // used with -s option string spinestring = ""; // used with -s option int octave = 0; // used with -o option int concertQ = 0; // used with -C option int writtenQ = 0; // used with -W option int quietQ = 0; // used with -q option int instrumentQ = 0; // used with -I option ////////////////////////////////////////////////////////////////////////// int main(int argc, char** argv) { checkOptions(options, argc, argv); HumdrumStream streamer(options); HumdrumFile infile; while (streamer.read(infile)) { processFile(infile); } return 0; } ////////////////////////////// // // processFile -- // void processFile(HumdrumFile& infile) { Array spineprocess(infile.getMaxTracks()); spineprocess.setGrowth(0); spineprocess.setAll(1); if (spineQ) { fillFieldData(spineprocess, spinestring, infile); if (debugQ) { int i; cout << "!! SPINE INFORMATION: "; for (i=0; i tvals; // transposition values for each spine tvals.setSize(infile.getMaxTracks() + 1); tvals.setAll(0); int ptrack; int i, j; for (i=0; i& tvals, int style) { PerlRegularExpression pre; int j; int ptrack; if (hasTrMarkers(infile, line)) { switch (style) { case STYLE_CONCERT: convertToConcertPitches(infile, line, tvals); break; case STYLE_WRITTEN: convertToWrittenPitches(infile, line, tvals); break; default: cout << infile[line]; } cout << "\n"; return; } for (j=0; j& tvals) { PerlRegularExpression pre; int j; int base; int ptrack; char buffer1[128] = {0}; char buffer2[128] = {0}; for (j=0; j& tvals) { PerlRegularExpression pre; int j; int base; int ptrack; char buffer1[128] = {0}; char buffer2[128] = {0}; for (j=0; j 40) { trans -= 40; } if (trans > 20) { trans = 40 - trans; trans = -trans; } if (trans < -40) { trans += 40; } if (trans < -20) { trans = -40 - trans; trans = -trans; } return trans; } ////////////////////////////// // // printTransposeInformation -- collect and print *Tr interpretations // at the start of the spine. Looks for *Tr markers at the start // of the file before any data. // void printTransposeInformation(HumdrumFile& infile, Array& spineprocess, int line, int transval) { int j; int ptrack; Array startvalues; startvalues.setSize(infile.getMaxTracks()+1); startvalues.setAll(0); Array finalvalues; finalvalues.setSize(infile.getMaxTracks()+1); finalvalues.setAll(0); for (j=0; j& spineprocess) { int i; int length; int diatonic; int j; PerlRegularExpression pre; const char* ptr; int interpstart = 0; for (i=0; i 4) || !spineprocess[infile[i].getPrimaryTrack(j)-1]) { cout << infile[i][j]; if (j= 0 && diatonic <= 6) { printNewKeyInterpretation(infile[i], j, transval); if (j& spineprocess) { int i; for (i=0; i ktracks; ktracks.setSize(infile.getMaxTracks()+1); ktracks.setGrowth(0); ktracks.setAll(0); int i; for (i=1; i<=infile.getMaxTracks(); i++) { if (infile.getTrackExInterp(i) == "**kern") { ktracks[i] = 1 + i; } } infile.analyzeRhythm("4"); int segments = int(infile.getTotalDuration()+0.5); if (segments < 1) { segments = 1; } Array > > trackhist; trackhist.setSize(ktracks.getSize()); trackhist.allowGrowth(0); trackhist[0].setSize(0); for (i=1; i > > analysis; doAutoKeyAnalysis(analysis, level, hop, count, segments, ktracks, trackhist); // print analyses raw results cout << "Raw key analysis by track:" << endl; printRawTrackAnalysis(analysis, ktracks); doTranspositionAnalysis(analysis); } ////////////////////////////// // // doTranspositionAnalysis -- // void doTranspositionAnalysis(Array > >& analysis) { int i, j, k; int value1; int value2; int value; for (i=0; i<1; i++) { for (j=2; j<3; j++) { for (k=0; k= 0 && analysis[j][k][24] >= 0) { value1 = (int)analysis[i][k][25]; if (value1 >= 12) { value1 = value1 - 12; } value2 = (int)analysis[j][k][25]; if (value2 >= 12) { value2 = value2 - 12; } value = value1 - value2; if (value < 0) { value = value + 12; } if (value > 6) { value = 12 - value; } cout << value << endl; } } } } } ////////////////////////////// // // printRawTrackAnalysis -- // void printRawTrackAnalysis(Array > >& analysis, Array& ktracks) { int i, j; int value; int value2; for (i=0; i= 12) { value = value - 12; } value2 = (int)analysis[j][i][25]; if (value2 >= 12) { value2 = value2 - 12; } cout << value; // if (value != value2) { // cout << "," << value2; // } } cout << "\n"; } } ////////////////////////////// // // doAutoKeyAnalysis -- // void doAutoKeyAnalysis(Array > >& analysis, int level, int hop, int count, int segments, Array& ktracks, Array > >& trackhist) { Array majorweights; Array minorweights; fillWeightsWithKostkaPayne(majorweights, minorweights); int size = 0; int i; for (i=1; i >& analysis, int level, int hop, int count, Array >& trackhist, Array& majorweights, Array& minorweights) { int i; for (i=0; i trackhist.getSize()) { break; } analysis.setSize(i+1); doSingleAnalysis(analysis[analysis.getSize()-1], i*hop+level, level, trackhist, majorweights, minorweights); } } ////////////////////////////// // // doSingleAnalysis -- // void doSingleAnalysis(Array& analysis, int startindex, int length, Array >& trackhist, Array& majorweights, Array& minorweights) { Array histsum(12); histsum.allowGrowth(0); histsum.setAll(0); int i, k; for (i=0; (i& maj, Array& min) { maj.setSize(12); maj.allowGrowth(0); min.setSize(12); min.allowGrowth(0); // found in David Temperley: Music and Probability 2006 maj[0] = 0.748; // C major weights maj[1] = 0.060; // C# maj[2] = 0.488; // D maj[3] = 0.082; // D# maj[4] = 0.670; // E maj[5] = 0.460; // F maj[6] = 0.096; // F# maj[7] = 0.715; // G maj[8] = 0.104; // G# maj[9] = 0.366; // A maj[10] = 0.057; // A# maj[11] = 0.400; // B min[0] = 0.712; // c minor weights min[1] = 0.084; // c# min[2] = 0.474; // d min[3] = 0.618; // d# min[4] = 0.049; // e min[5] = 0.460; // f min[6] = 0.105; // f# min[7] = 0.747; // g min[8] = 0.404; // g# min[9] = 0.067; // a min[10] = 0.133; // a# min[11] = 0.330; // b } //////////////////////////////////////// // // identifyKey -- correls contains the 12 major key correlation // values, then the 12 minor key correlation values, then two // more values: index=24 is the best key, and index=25 is the // second best key. If [24] or [25] is -1, then that means that // all entries in the original histogram were zero (all rests). // void identifyKey(Array& correls, Array& histogram, Array& majorweights, Array& minorweights) { correls.setSize(26); correls.allowGrowth(0); correls.setAll(0); 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]; } if (testsum == 0.0) { correls[24] = -1; correls[25] = -1; return; } for (i=0; i<12; i++) { correls[i] = pearsonCorrelation(12, majorweights.getBase(), h+i); correls[i+12] = pearsonCorrelation(12, minorweights.getBase(), h+i); } // find max value int besti = 0; for (i=1; i<24; i++) { if (correls[i] > correls[besti]) { besti = i; } } // find second best key int secondbesti = 0; if (besti == 0) { secondbesti = 1; } for (i=1; i<24; i++) { if (i == besti) { continue; } if (correls[i] > correls[secondbesti]) { secondbesti = i; } } correls[24] = besti; correls[25] = secondbesti; } ////////////////////////////// // // 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); } ////////////////////////////// // // printHistograms -- // void printHistograms(int segments, Array ktracks, Array > >& trackhist) { int i, j, k; int start; for (i=0; i >& histogram, HumdrumFile& infile, int track, int segments) { histogram.setSize(segments); histogram.allowGrowth(0); int i; int j; int k; for (i=0; i >& histogram, int pc, double start, double dur, double tdur, int segments) { pc = (pc + 12) % 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++; } } // // Automatic transposition functions // /////////////////////////////////////////////////////////////////////////// // // Spine field list extraction functions // ////////////////////////////// // // fillFieldData -- // void fillFieldData(Array& field, const string& fieldstring, HumdrumFile& infile) { int maxtrack = infile.getMaxTracks(); field.setSize(maxtrack+1); field.setGrowth(0); field.setAll(0); Array tempfield; tempfield.setSize(maxtrack); tempfield.setSize(0); PerlRegularExpression pre; Array buffer; buffer.setSize(fieldstring.size()+1); strcpy(buffer.getBase(), fieldstring.c_str()); pre.sar(buffer, "\\s", "", "gs"); int start = 0; int value = 0; value = pre.search(buffer.getBase(), "^([^,]+,?)"); while (value != 0) { start += value - 1; start += strlen(pre.getSubmatch(1)); processFieldEntry(tempfield, pre.getSubmatch(), infile); value = pre.search(buffer.getBase() + start, "^([^,]+,?)"); } int i; for (i=0; i& field, const char* astring, HumdrumFile& infile) { int maxtrack = infile.getMaxTracks(); PerlRegularExpression pre; Array buffer; buffer.setSize(strlen(astring)+1); strcpy(buffer.getBase(), astring); // remove any comma left at end of input string (or anywhere else) pre.sar(buffer, ",", "", "g"); // first remove $ symbols and replace with the correct values removeDollarsFromString(buffer, infile.getMaxTracks()); if (pre.search(buffer.getBase(), "^(\\d+)-(\\d+)$")) { int firstone = strtol(pre.getSubmatch(1), NULL, 10); int lastone = strtol(pre.getSubmatch(2), NULL, 10); if ((firstone < 1) && (firstone != 0)) { cerr << "Error: range token: \"" << astring << "\"" << " contains too small a number at start: " << firstone << endl; cerr << "Minimum number allowed is " << 1 << endl; exit(1); } if ((lastone < 1) && (lastone != 0)) { cerr << "Error: range token: \"" << astring << "\"" << " contains too small a number at end: " << lastone << endl; cerr << "Minimum number allowed is " << 1 << endl; exit(1); } if (firstone > maxtrack) { cerr << "Error: range token: \"" << astring << "\"" << " contains number too large at start: " << firstone << endl; cerr << "Maximum number allowed is " << maxtrack << endl; exit(1); } if (lastone > maxtrack) { cerr << "Error: range token: \"" << astring << "\"" << " contains number too large at end: " << lastone << endl; cerr << "Maximum number allowed is " << maxtrack << endl; exit(1); } int i; if (firstone > lastone) { for (i=firstone; i>=lastone; i--) { field.append(i); } } else { for (i=firstone; i<=lastone; i++) { field.append(i); } } } else if (pre.search(buffer.getBase(), "^(\\d+)")) { int value = strtol(pre.getSubmatch(1), NULL, 10); if ((value < 1) && (value != 0)) { cerr << "Error: range token: \"" << astring << "\"" << " contains too small a number at end: " << value << endl; cerr << "Minimum number allowed is " << 1 << endl; exit(1); } if (value > maxtrack) { cerr << "Error: range token: \"" << astring << "\"" << " contains number too large at start: " << value << endl; cerr << "Maximum number allowed is " << maxtrack << endl; exit(1); } field.append(value); } } ////////////////////////////// // // removeDollarsFromString -- substitute $ sign for maximum track count. // void removeDollarsFromString(Array& buffer, int maxtrack) { PerlRegularExpression pre; char buf2[128] = {0}; int value2; if (pre.search(buffer.getBase(), "\\$$")) { sprintf(buf2, "%d", maxtrack); pre.sar(buffer, "\\$$", buf2); } if (pre.search(buffer.getBase(), "\\$(?![\\d-])")) { // don't know how this case could happen, however... sprintf(buf2, "%d", maxtrack); pre.sar(buffer, "\\$(?![\\d-])", buf2, "g"); } if (pre.search(buffer.getBase(), "\\$0")) { // replace $0 with maxtrack (used for reverse orderings) sprintf(buf2, "%d", maxtrack); pre.sar(buffer, "\\$0", buf2, "g"); } while (pre.search(buffer.getBase(), "\\$(-?\\d+)")) { value2 = maxtrack - (int)fabs(strtol(pre.getSubmatch(1), NULL, 10)); sprintf(buf2, "%d", value2); pre.sar(buffer, "\\$-?\\d+", buf2); } } // // Spine field list extraction functions // /////////////////////////////////////////////////////////////////////////// /* BRIEF DOCUMENTATION transpose options: -t interval = transpose music by the specified interval, where interval is of the form: P1 = perfect unison (no transposition) m2 = up a minor second -m2 = down a minor second M3 = up a major third -A4 = down an augmented fourth d5 = up a diminished fifth -b interval = transpose by the base-40 equivalent to the -t option interval 0 = perfect unison (no transposition) 6 = up a minor second -6 = down a minor second 12 = up a major third -18 = down an augmented fourth 22 = up a diminished fifth -o octave = transpose (additionally by given octave) transpose -t m3 -o 1 = transpose up by an octave and a minor third. -s fieldstring = transpose only the given list of data spines. example: transpose -f1-2,4 -t P4 fourpart.krn transpose -f3 -t P4 fourpart.krn Note: does not work yet with the -k option ########################################################################## ## ## EXAMPLES ## input: file.krn -- an example with the key being a minor: **kern *a: 4A 4B 4c 4d 4e 4f 4g 4a *- ##################################################################### # # Transpose the file up a minor third (so that it is in C Minor): # tranpose -t m3 file.krn **kern *c: 4c 4d 4e- 4f 4g 4a- 4b- 4cc *- ##################################################################### # # Transpose the file down a minor third (so that it is in F# Minor): # tranpose -t -m3 file.krn **kern *f#: 4F# 4G# 4A 4B 4c# 4d 4e 4f# *- ##################################################################### # # Transpose the file up a perfect octave: # tranpose -t P8 file.krn **kern *a: 4A 4B 4cc 4dd 4ee 4ff 4gg 4aa *- ##################################################################### # # Force the file to a tonic on C rather than a: # transpose -k c file.krn **kern *c: 4c 4d 4e- 4f 4g 4a- 4b- 4cc *- # case -k option value is irrelevant: transpose -k C file.krn **kern *c: 4c 4d 4e- 4f 4g 4a- 4b- 4cc *- # Transpose from A Minor to G# Minor: transpose -k G# file.krn **kern *g#: 4G# 4A# 4B 4c# 4d# 4e 4f# 4g# *- # Changing input files to: **kern *C: 4c 4d 4e 4f *G: 4g 4a 4b 4cc *- # Using -k option will convert all keys to same in output: transpose -k e file.krn **kern *E: 4e 4f# 4g# 4a *E: 4e 4f# 4g# 4a *- ############################## ## ## octave transpositions ## # Back to original data file, transposing up a minor tenth: transpose -o 1 -t m3 file.krn **kern *E-: 8ee- 8ff 8gg 8aa- 8bb- 8ccc 8ddd 8eee- *- # transpose down two octaves: transpose -o -2 file.krn **kern *a: 4AAA 4BBB 4CC 4DD 4EE 4FF 4GG 4AA *- #################### ## ## base 40 -b option instead of -t option ## # Transpose down two octaves: transpose -b -80 file.krn **kern *a: 4AAA 4BBB 4CC 4DD 4EE 4FF 4GG 4AA *- */ // md5sum: 991c9a96decb3d2b47c044390f388112 transpose.cpp [20170605]