// // Programmer: Craig Stuart Sapp // Creation Date: Thu Mar 29 17:22:53 PST 2001 // Last Modified: Sun Feb 16 07:58:18 PST 2003 (convert to RootSpectrum class) // Last Modified: Sat Jan 29 13:50:23 PST 2005 // Filename: ...sig/examples/all/roottest.cpp // Web Address: http://sig.sapp.org/examples/museinfo/humdrum/roottest.cpp // Syntax: C++; museinfo // // Description: determine the root of chords in music according // to the timespan of a preexisting root analysis spine. // #include "humdrum.h" #include "stdio.h" class NoteData { public: int pitch; // pitch of the note double dur; // duration of the note double durin; // duration of the note inside the chord region double beat; // metric position in a meter double absbeat; // absolute beat position of note in piece int inregion; // 1 if attacked in region, 0 if attacked out of region int outregion; // 1 if terminated in region, 0 if outside of region Array* preceding; // note preceding this one Array* following; // note following this one ~NoteData() { if (preceding != NULL) delete preceding; if (following != NULL) delete following; } NoteData(void) { preceding = NULL; following = NULL; clear(); } NoteData(NoteData& aNote) { pitch = aNote.pitch; dur = aNote.dur; durin = aNote.durin; beat = aNote.beat; absbeat = aNote.absbeat; inregion = aNote.inregion; outregion = aNote.outregion; deallocate(); if (aNote.preceding != NULL) { preceding = new Array; *preceding = *(aNote.preceding); } if (aNote.following != NULL) { following = new Array; *following = *(aNote.following); } } void clear(void) { pitch = -1; dur = -1; durin = -1; beat = -1; absbeat = -1; inregion = -1; outregion = -1; deallocate(); } void allocate(void) { if (preceding != NULL) delete preceding; if (following != NULL) delete following; preceding = new Array; following = new Array; preceding->setSize(0); following->setSize(0); } void deallocate(void) { if (preceding != NULL) delete preceding; if (following != NULL) delete following; preceding = NULL; following = NULL; } int hasPreceding(void) { if (preceding == NULL) return 0; if (preceding->getSize() == 0) return 0; return 1; } int hasFollowing(void) { if (following == NULL) return 0; if (following->getSize() == 0) return 0; return 1; } NoteData& operator=(NoteData& aNote) { if (this == &aNote) { return aNote; } pitch = aNote.pitch; dur = aNote.dur; durin = aNote.durin; beat = aNote.beat; absbeat = aNote.absbeat; inregion = aNote.inregion; outregion = aNote.outregion; deallocate(); if (aNote.preceding != NULL) { preceding = new Array; *preceding = *(aNote.preceding); } if (aNote.following != NULL) { following = new Array; *following = *(aNote.following); } return *this; } }; #define RCASE_UNKNOWN 0 #define RCASE_135 1 #define RCASE_7 2 #define RCASE_2 3 #define RCASE_4 4 #define RCASE_6 5 #define RCASE_24 6 #define RCASE_26 7 #define RCASE_46 8 #define RCASE_246 9 #define RCASE_SIZE 10 // function declarations void checkOptions (Options& opts, int argc, char* argv[]); void example (void); void usage (const char* command); void generateAnalysis (HumdrumFile& hfile); void printAnalysis (Array& newroots, Array& chordstartlines, Array& realroots, Array >& chordnotes, HumdrumFile& infile, Array& spectra); void getChordStartLines (Array& chordstartlines, Array& realroot, HumdrumFile& infile); void printNoteInfo (NoteData& note); void getNoteDatum (Array& chordnotes, HumdrumFile& infile, int line, int spine, int startline, int stopline, int datainit); void extractNoteInformation (Array& chordnotes, HumdrumFile& infile, int startline, int stopline); void checkForMelodicNotes (NoteData& note, HumdrumFile& infile, int line, int spine, int startline, int stopline); int calculateRoot (Array& chordnotes, RootSpectrum& spectrum); int getStartingMeasure (HumdrumFile& infile); double getDurationInsideChordRegion(HumdrumFile& infile, int line, int spine, int startline, int stopline, double notedur); int getChordCase (int root, Array& chordnotes); void percent (int top, int bottom); double checkMelodicContext (double value, int testroot, int notenum, Array& chordnotes); // global variables Options options; // database for command-line arguments int debugQ = 0; // used with the --debug option int mdebugQ = 0; // used with the --melodic-debug option int informationQ = 0; // used with -i option int appendQ = 0; // used with -a option int prependQ = 0; // used with -p option int soloQ = 1; // used with -p and -a option int errorQ = 0; // used with -e option int printweightQ = 0; // used with --print-weights option double theta1 = 0.0; // used with -t option double theta2 = 0.0; // used with -t option int twothetaQ = 0; // used with the --t2 option string filename = ""; // file currently being processsed int chordcountQ = 0; // used with -c option int shortQ = 1; // used with -L option int summaryQ = 0; // used with -s option int melodyQ = 0; // used with -m option int spectrumQ = 0; // used with --rs option double speclevel = 2.0; // no input parameter yet. double mfactor = 0.0; // used with --mf option IntervalWeight iweights; Array casecount; Array caseerror; /////////////////////////////////////////////////////////////////////////// int main(int argc, char* argv[]) { casecount.setSize(RCASE_SIZE); caseerror.setSize(RCASE_SIZE); casecount.setAll(0); caseerror.setAll(0); casecount.allowGrowth(0); caseerror.allowGrowth(0); // process the command-line options checkOptions(options, argc, argv); HumdrumFile hfile; if (twothetaQ) { iweights.setChromatic(theta1, theta2); } else { iweights.setChromatic(theta1); } if (printweightQ) { cout << iweights; exit(0); } Array chordstartlines; Array realroots; // figure out the number of input files to process int numinputs = options.getArgCount(); int j; int chordsum = 0; for (int i=0; i= 0) { // cout << "XXX " << realroots[j] << "\t" // << Convert::base40ToKern(buffer, realroots[j]) // << endl; chordsum++; } } } else { generateAnalysis(hfile); } } if (chordcountQ) { cout << "!! Total Chord Segments: " << chordsum << endl; } else if (summaryQ) { cout << "!!" << endl; cout << "!! Chord Case Summary: " << endl; cout << "!!" << endl; int totalsum = 0; int errorsum = 0; for (int kk=1; kk chordstartlines; Array realroots; getChordStartLines(chordstartlines, realroots, infile); Array > chordnotes; chordnotes.setSize(chordstartlines.getSize()); int i; for (i=0; i 0) { extractNoteInformation(chordnotes[i], infile, chordstartlines[i], chordstartlines[i+1]); } } Array newroots; newroots.setSize(realroots.getSize()); newroots.setAll(-1); Array spectra; spectra.setSize(chordstartlines.getSize()); char buffer[128] = {0}; for (i=0; i= 0) { newroots[i] = calculateRoot(chordnotes[i], spectra[i]); } if (debugQ) { cout << "ROOT = "; if (realroots[i] < 0) { cout << "r"; } else { cout << Convert::base40ToKern(buffer, realroots[i]%40 + 120); } cout << " MEASURED: "; if (newroots[i] < 0) { cout << "r"; } else { cout << Convert::base40ToKern(buffer, newroots[i] + 120); } cout << endl; } } printAnalysis(newroots, chordstartlines, realroots, chordnotes, infile, spectra); } //////////////////////////////////////// // // checkMelodicContext -- check if the given notenum is linkable to // a chord tone of the given testroot according to the preceding // and following notes in the chordnotes entry for the notenum. // double checkMelodicContext(double value, int testroot, int notenum, Array& chordnotes) { int tinterval; int note1; // base-40 value of test root int note2; // base-40 value of test chord tone int dianote1; // diatonic value of input note int dianote2; // diatonic value of test chord tone int diaroot; // diatonic value of test root int rootdia1; // diatonic interval above root for input note int rootdia2; // diatonic interval above root for test chord tone diaroot = Convert::base40ToDiatonic(testroot) % 7; note1 = chordnotes[notenum].pitch; dianote1 = Convert::base40ToDiatonic(note1) % 7; rootdia1 = (dianote1 - diaroot + 700) % 7; char mbuffer[128] = {0}; if (mdebugQ) { cout << "Checking if note " << Convert::base40ToKern(mbuffer, note1) << " can be non-harm. for test-root "; cout << Convert::base40ToKern(mbuffer, testroot+120); cout << " (Diatonic int. is: " << rootdia1 << ")" << endl; } // if test note cannot be interpreted as the 2, 4, or 6 scale // degree of the chord, then skip. Note: this requirement should // be corrected so that enharmonic spelling is not important. if (!((rootdia1 == 1) || (rootdia1 == 3) || (rootdia1 == 5))) { return value; } if (mdebugQ) { cout << "YES IT IS POSSIBLE. Now search for linkable chord-note" << endl; } // the test note is a possible non-harmonic tone candidate. // so check the preceding and following notes of the test note // to see if they are chord-tones. int i; if (chordnotes[notenum].preceding != NULL) { for (i=0; igetSize(); i++) { note2 = (*chordnotes[notenum].preceding)[i].pitch; // if (debugQ) { // } if (note1 == note2) { // ignore repeated notes continue; } dianote2 = Convert::base40ToDiatonic(note2) % 7; rootdia2 = (dianote2 - diaroot + 700) % 7; tinterval = note2 - note1; if (mdebugQ) { cout << "Comparing test note: " << Convert::base40ToKern(mbuffer, note1); cout << "\tto test chord note: " << Convert::base40ToKern(mbuffer, note2); cout << "\twhen root is: " << Convert::base40ToKern(mbuffer, testroot + 120); cout << endl; cout << "Diatonic interval between notes: " << dianote1 - dianote2 << endl; cout << "Diatonic interval between test root " << "and test harmonic note: " << rootdia2 << endl; cout << "Diatonic interval between test root and " << "test non-harmonic note: " << rootdia1 << endl; } if (abs(tinterval) < 9) { // melodic note preeceding notenum // check to see notenum can be assigned a 2,4,6 position // in the chord (happens if note2 is in position 1,3,5) // but only above position or below position 5. // Note: should correct rule by ignoring enharmonic spellings. if ((rootdia2 == 0) && (rootdia1 == 1)) { // note can attach to root as a second // return 0.0; // return iweights[(note2 - testroot + 4000)%40]; return value * mfactor; // return value - mfactor; } else if ((rootdia2 == 2) && (rootdia1 == 1)) { // note can attach to third as a second // return 0.0; // return iweights[(note2 - testroot + 4000)%40]; return value * mfactor; // return value - mfactor; } else if ((rootdia2 == 2) && (rootdia1 == 3)) { // note can attach to third as a fourth // return 0.0; // return iweights[(note2 - testroot + 4000)%40]; return value * mfactor; // return value - mfactor; } else if ((rootdia2 == 4) && (rootdia1 == 3)) { // note can attach to fifth as a fourth // return 0.0; // return iweights[(note2 - testroot + 4000)%40]; return value * mfactor; // return value - mfactor; } else if ((rootdia2 == 4) && (rootdia1 == 5)) { // note can attach to fifth as a sixth // return 0.0; // return iweights[(note2 - testroot + 4000)%40]; return value * mfactor; // return value - mfactor; } else { // return value/2; } } } } else { if (mdebugQ) { cout << "No preceding notes in chord to link onto." << endl; } } // cout << "Test note has not forward linkage" << endl; // do similar thing for following notes. if (chordnotes[notenum].following != NULL) { for (i=0; igetSize(); i++) { note2 = (*chordnotes[notenum].following)[i].pitch; if (note1 == note2) { // ignore repeated notes continue; } dianote2 = Convert::base40ToDiatonic(note2) % 7; rootdia2 = (dianote2 - diaroot + 700) % 7; tinterval = note2 - note1; if (abs(tinterval) < 9) { // melodic note preeceding notenum // check to see notenum can be assigned a 2,4,6 position // in the chord (happens if note2 is in position 1,3,5) // but only above position or below position 5. // Note: should correct rule by ignoring enharmonic spellings. if ((rootdia2 == 0) && (rootdia1 == 1)) { // note can attach to root as a second // return 0.0; // return iweights[(note2 - testroot + 4000)%40]; return value * mfactor; // return value - mfactor; } else if ((rootdia2 == 2) && (rootdia1 == 1)) { // note can attach to third as a second // return 0.0; // return iweights[(note2 - testroot + 4000)%40]; return value * mfactor; // return value - mfactor; } else if ((rootdia2 == 2) && (rootdia1 == 3)) { // note can attach to third as a fourth // return 0.0; // return iweights[(note2 - testroot + 4000)%40]; return value * mfactor; // return value - mfactor; } else if ((rootdia2 == 4) && (rootdia1 == 3)) { // note can attach to fifth as a fourth // return 0.0; // return iweights[(note2 - testroot + 4000)%40]; return value * mfactor; // return value - mfactor; } else if ((rootdia2 == 4) && (rootdia1 == 5)) { // note can attach to fifth as a sixth // return 0.0; // return iweights[(note2 - testroot + 4000)%40]; return value * mfactor; // return value - mfactor; } else { // return value/2; } } } } else { if (mdebugQ) { cout << "No preceding notes in chord to link onto." << endl; } } // cout << "Test note has not backward linkage" << endl; // note could not be put into a non-harmonic melodic context, // so return the input value return value; } ////////////////////////////// // // calculateRoot -- Do all of the harmonic analysis work. // int calculateRoot(Array& chordnotes, RootSpectrum& spectrum) { int testroot; int notenum; double minsum = 0.0; int minroot = -1; double value; double sum = 0.0; for (testroot=0; testroot<40; testroot++) { sum = 0.0; for (notenum=0; notenum= 0) { minroot += 2; if (minroot > 40) { minroot -= 40; } } if (debugQ) { cout << "MEASURED ROOT: " << minroot << " SCORE = " << minsum << endl; } return minroot; } ////////////////////////////// // // extractNoteInformation -- // void extractNoteInformation(Array& chordnotes, HumdrumFile& infile, int startline, int stopline) { int i, j; chordnotes.setSize(0); int datainit = 0; for (i=startline; i& chordnotes, HumdrumFile& infile, int line, int spine, int startline, int stopline, int datainit) { NoteData note; if (strchr(infile[line][spine], 'r') != NULL) { return; } int i; int tokencount; char buffer[1024] = {0}; note.clear(); if (strcmp(infile[line][spine], ".") == 0) { int lastline = 0; int lastspine = 0; lastline = infile.getLastDatumLine(lastspine, line, spine); note.inregion = 0; tokencount = infile[line].getTokenCount(spine); for (i=0; i 0) { inregion = inregion - (startbeat - notestart); } if (notestop - stopbeat > 0) { inregion = inregion - (notestop - stopbeat); } return inregion; } /////////////////////////////// // // checkForMelodicNotes -- look for melodic notes attached to the // current note which is inside the chord region. // void checkForMelodicNotes(NoteData& note, HumdrumFile& infile, int line, int spine, int startline, int stopline) { int preline; int prespine; int postline; int postspine; if (startline == line) { preline = line; prespine = spine; } else { preline = infile.getLastDatumLine(prespine, line, spine); } if (stopline-1 == line) { postline = line; postspine = spine; } else { postline = infile.getNextDatumLine(postspine, line, spine); } int k; char buffer[128] = {0}; int tokencount; NoteData melnote; if ((preline >= startline) && (preline < line)) { // some previous notes to aquire; // cout << "Prenote data: " << infile[preline][prespine] << endl; note.allocate(); tokencount = infile[preline].getTokenCount(prespine); for (k=0; kappend(melnote); } } if ((postline > 0) && (postline < stopline) && (line != postline)) { // some following notes to aquire // cout << "Postnote data: " << infile[postline][postspine] << endl; if (note.following == NULL) { note.allocate(); } tokencount = infile[postline].getTokenCount(postspine); for (k=0; kappend(melnote); } } } ////////////////////////////// // // getStartingMeasure -- // int getStartingMeasure(HumdrumFile& infile) { int output = 0; // int datastart = 0; int i; for (i=0; i >& chordnotes) { // void printRootIntervals(int root, Array& chordnotes) { cout << "{"; int i; int value = 0; char buffer[128] = {0}; for (i=0; i& chordnotes) { Array tones(7); tones.zero(); tones.allowGrowth(0); int i; int value = 0; for (i=0; i& newroots, Array& chordstartlines, Array& realroots, Array >& chordnotes, HumdrumFile& infile, Array& spectra) { int i, j; int ii = 0; char buffer[128] = {0}; const char* pstring = "*"; int printroot = 0; int measure = getStartingMeasure(infile); int rootcase; for (i=0; i= 0) { printNoteInfo(chordnotes[ii][j]); } } } } printroot = 0; if ((ii < chordstartlines.getSize()-1) && (i == chordstartlines[ii])) { printroot = 1; } if (infile[i].getType() == E_humrec_data_measure) { int tempnum = 0; int tcount = sscanf(infile[i][0], "=%d", &tempnum); if (tcount == 1) { measure = tempnum; } } if ((ii < chordstartlines.getSize()-1) && (i == chordstartlines[ii])) { rootcase = getChordCase(realroots[ii]%40, chordnotes[ii]); casecount[rootcase]++; if (newroots[ii]%40 != realroots[ii]%40) { caseerror[rootcase]++; } if (errorQ) { if (newroots[ii]%40 != realroots[ii]%40) { cout << "!!ROOT_ERROR: "; if (newroots[ii] < 0) { cout << "r"; } else { cout << Convert::base40ToKern(buffer, newroots[ii] % 40 + 120); } cout << " instead of "; if (realroots[ii] < 0) { cout << "r"; } else { cout << Convert::base40ToKern(buffer, realroots[ii] % 40 + 120); } cout << " at absbeat:" << infile[i].getAbsBeat(); cout << " in measure:" << measure; cout << " beat:" << infile[i].getBeat(); if (filename != "") { size_t found = filename.find_last_of("/"); if ((found != string::npos) && shortQ) { cout << " filename:" << filename.substr(found+1); } else { cout << " filename:" << filename; } } cout << " "; printChordCase(rootcase); cout << ":"; printRootIntervals(realroots[ii]%40, chordnotes[ii]); cout << endl; } } if (spectrumQ) { char classbuffer[128] = {0}; int nminroot = spectra[ii].bestIndex(); cout << "!!ROOT_SPECTRUM:{"; for (int kk=0; kk<40; kk++) { if (spectra[ii][kk] > spectra[ii][nminroot]*speclevel) { continue; } cout << Convert::base40ToKern(classbuffer, kk + 120 + 2) << ":" << spectra[ii][kk]; cout << ", "; } cout << "}\n"; } } switch(infile[i].getType()) { case E_humrec_data_comment: if (prependQ) { cout << "!\t"; } else if (appendQ) { cout << infile[i] << "\t!\n"; } break; case E_humrec_data_kern_measure: if (prependQ) { cout << infile[i][0] << "\t" << infile[i] << "\n"; } else if (soloQ) { cout << infile[i][0] << "\n"; } else if (appendQ) { cout << infile[i] << "\t" << infile[i][0] << "\n"; } break; case E_humrec_interpretation: if (strncmp(infile[i][0], "**", 2) == 0) { pstring = "**root"; } else if (strcmp(infile[i][0], "*-") == 0) { pstring = "*-"; } else if (infile[i].equalFieldsQ()) { if (strcmp(infile[i][0], "*x") == 0) { pstring = "*"; } else if (strcmp(infile[i][0], "*v") == 0) { pstring = "*"; } else if (strcmp(infile[i][0], "*^") == 0) { pstring = "*"; } else if (strcmp(infile[i][0], "*+") == 0) { pstring = "*"; } else { pstring = infile[i][0]; } } else { pstring = "*"; } if (prependQ) { cout << pstring << "\t" << infile[i] << "\n"; } else if (soloQ) { cout << pstring << "\n"; } else if (appendQ) { cout << infile[i] << "\t" << pstring << "\n"; } break; case E_humrec_data: if (printroot) { Convert::base40ToKern(buffer, newroots[ii] + 120); } else { strcpy(buffer, "."); } if (prependQ) { cout << buffer << "\t" << infile[i] << "\n"; } else if (soloQ) { cout << buffer << "\n"; } else if (appendQ) { cout << infile[i] << "\t" << buffer << "\n"; } break; case E_humrec_none: case E_humrec_empty: case E_humrec_bibliography: case E_humrec_global_comment: default: cout << infile[i] << "\n"; break; } if (printroot) { ii++; printroot = 0; } } } /////////////////////////////// // // printNoteInfo -- // void printNoteInfo(NoteData& note) { char buffer[128] = {0}; cout << "!!\tNote: " << Convert::base40ToKern(buffer, note.pitch); cout << " \tdur:" << note.dur; cout << "\tbt:" << note.beat; cout << "\tatk:" << note.inregion << ":" << note.outregion; int i; if (note.hasPreceding()) { cout << "\tpre:"; for (i=0; igetSize(); i++) { cout << Convert::base40ToKern(buffer, (*(note.preceding))[i].pitch); if (i < note.preceding->getSize() - 1) { cout << " "; } } } if (note.hasFollowing()) { cout << "\tpost:"; for (i=0; igetSize(); i++) { cout << Convert::base40ToKern(buffer, (*(note.following))[i].pitch); if (i < note.following->getSize() - 1) { cout << " "; } } } cout << "\n"; } ////////////////////////////// // // getChordStartLines -- // void getChordStartLines(Array& chordstartlines, Array& realroot, HumdrumFile& infile) { chordstartlines.setSize(infile.getNumLines()); chordstartlines.setSize(0); realroot.setSize(infile.getNumLines()); realroot.setSize(0); int root; int i, j; for (i=0; i