//
// Programmer: Craig Stuart Sapp <craig@ccrma.stanford.edu>
// 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<NoteData>* preceding; // note preceding this one
Array<NoteData>* 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<NoteData>;
*preceding = *(aNote.preceding);
}
if (aNote.following != NULL) {
following = new Array<NoteData>;
*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<NoteData>;
following = new Array<NoteData>;
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<NoteData>;
*preceding = *(aNote.preceding);
}
if (aNote.following != NULL) {
following = new Array<NoteData>;
*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<int>& newroots,
Array<int>& chordstartlines,
Array<int>& realroots,
Array<Array<NoteData> >& chordnotes,
HumdrumFile& infile,
Array<RootSpectrum>& spectra);
void getChordStartLines(Array<int>& chordstartlines,
Array<int>& realroot, HumdrumFile& infile);
void printNoteInfo(NoteData& note);
void getNoteDatum(Array<NoteData>& chordnotes,
HumdrumFile& infile, int line, int spine,
int startline, int stopline, int datainit);
void extractNoteInformation(Array<NoteData>& chordnotes,
HumdrumFile& infile,
int startline, int stopline);
void checkForMelodicNotes(NoteData& note, HumdrumFile& infile, int line,
int spine, int startline, int stopline);
int calculateRoot(Array<NoteData>& 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<NoteData>& chordnotes);
void percent(int top, int bottom);
double checkMelodicContext(double value, int testroot, int notenum,
Array<NoteData>& 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
const char* 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<int> casecount;
Array<int> 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<int> chordstartlines;
Array<int> realroots;
// figure out the number of input files to process
int numinputs = options.getArgCount();
int j;
int chordsum = 0;
for (int i=0; i<numinputs || i==0; i++) {
hfile.clear();
// if no command-line arguments read data file from standard input
if (numinputs < 1) {
hfile.read(cin);
} else {
filename = options.getArg(i+1);
hfile.read(filename);
}
if (chordcountQ) {
getChordStartLines(chordstartlines, realroots, hfile);
for (j=0; j<realroots.getSize(); j++) {
// char buffer[1024];
if (realroots[j] >= 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<RCASE_SIZE; kk++) {
totalsum += casecount[kk];
errorsum += caseerror[kk];
}
cout << "!! \terrors/total\terror-rate(%)" << endl;
cout << "!! All cases:\t" << errorsum << "/" << totalsum;
if (totalsum < 1000) {
cout << "\t";
}
if (totalsum == 0) {
cout << "\t\t NA";
} else {
cout << "\t" ; percent(errorsum,totalsum);
}
cout << endl;
cout << "!! case135:\t" << caseerror[RCASE_135]
<< "/" << casecount[RCASE_135];
if (casecount[RCASE_135] == 0) {
cout << "\t\t NA";
} else {
cout << "\t\t" ; percent(caseerror[RCASE_135],casecount[RCASE_135]);
}
cout << endl;
cout << "!! case7:\t" << caseerror[RCASE_7]
<< "/" << casecount[RCASE_7];
if (casecount[RCASE_7] == 0) {
cout << "\t\t NA";
} else {
cout << "\t\t" ; percent(caseerror[RCASE_7],casecount[RCASE_7]);
}
cout << endl;
cout << "!! case2:\t" << caseerror[RCASE_2]
<< "/" << casecount[RCASE_2];
if (casecount[RCASE_2] == 0) {
cout << "\t\t NA";
} else {
cout << "\t\t" ; percent(caseerror[RCASE_2],casecount[RCASE_2]);
}
cout << endl;
cout << "!! case4:\t" << caseerror[RCASE_4]
<< "/" << casecount[RCASE_4];
if (casecount[RCASE_4] == 0) {
cout << "\t\t NA";
} else {
cout << "\t\t" ; percent(caseerror[RCASE_4],casecount[RCASE_4]);
}
cout << endl;
cout << "!! case6:\t" << caseerror[RCASE_6]
<< "/" << casecount[RCASE_6];
if (casecount[RCASE_6] == 0) {
cout << "\t\t NA";
} else {
cout << "\t\t" ; percent(caseerror[RCASE_6],casecount[RCASE_6]);
}
cout << endl;
cout << "!! case24:\t" << caseerror[RCASE_24]
<< "/" << casecount[RCASE_24];
if (casecount[RCASE_24] == 0) {
cout << "\t\t NA";
} else {
cout << "\t\t" ; percent(caseerror[RCASE_24],casecount[RCASE_24]);
}
cout << endl;
cout << "!! case26:\t" << caseerror[RCASE_26]
<< "/" << casecount[RCASE_26];
if (casecount[RCASE_26] == 0) {
cout << "\t\t NA";
} else {
cout << "\t\t" ; percent(caseerror[RCASE_26],casecount[RCASE_26]);
}
cout << endl;
cout << "!! case46:\t" << caseerror[RCASE_46]
<< "/" << casecount[RCASE_46];
if (casecount[RCASE_46] == 0) {
cout << "\t\t NA";
} else {
cout << "\t\t" ; percent(caseerror[RCASE_46],casecount[RCASE_46]);
}
cout << endl;
cout << "!! case246:\t" << caseerror[RCASE_246]
<< "/" << casecount[RCASE_246];
if (casecount[RCASE_246] == 0) {
cout << "\t\t NA";
} else {
cout << "\t\t" ; percent(caseerror[RCASE_246],casecount[RCASE_246]);
}
cout << endl;
}
return 0;
}
///////////////////////////////////////////////////////////////////////////
//////////////////////////////
//
// percent --
//
void percent(int top, int bottom) {
double value = 100.0 * top / bottom;
value = ((int)(value * 100))/100.0;
if (value < 100) {
cout << " ";
}
if (value < 10) {
cout << " ";
}
cout << value;
}
//////////////////////////////
//
// generateAnalysis --
//
void generateAnalysis(HumdrumFile& infile) {
infile.analyzeRhythm();
Array<int> chordstartlines;
Array<int> realroots;
getChordStartLines(chordstartlines, realroots, infile);
Array<Array<NoteData> > chordnotes;
chordnotes.setSize(chordstartlines.getSize());
int i;
for (i=0; i<chordstartlines.getSize(); i++) {
chordnotes[i].setSize(100);
chordnotes[i].setGrowth(1000);
chordnotes[i].setSize(0);
if (realroots[i] > 0) {
extractNoteInformation(chordnotes[i], infile, chordstartlines[i],
chordstartlines[i+1]);
}
}
Array<int> newroots;
newroots.setSize(realroots.getSize());
newroots.setAll(-1);
Array<RootSpectrum> spectra;
spectra.setSize(chordstartlines.getSize());
char buffer[128] = {0};
for (i=0; i<chordstartlines.getSize(); i++) {
if (realroots[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<NoteData>& 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; i<chordnotes[notenum].preceding->getSize(); 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; i<chordnotes[notenum].following->getSize(); 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<chordnotes.getSize(); notenum++) {
value = iweights[(chordnotes[notenum].pitch - (testroot) + 4000)%40];
if (melodyQ) {
value = checkMelodicContext(value, testroot, notenum, chordnotes);
}
// add rhythm weighting back at this point
sum += value;
}
if (minroot < 0) {
minroot = testroot;
minsum = sum;
} else if (sum < minsum) {
minroot = testroot;
minsum = sum;
}
spectrum[testroot] = sum;
}
if (minroot >= 0) {
minroot += 2;
if (minroot > 40) {
minroot -= 40;
}
}
if (debugQ) {
cout << "MEASURED ROOT: " << minroot << " SCORE = " << minsum << endl;
}
return minroot;
}
//////////////////////////////
//
// extractNoteInformation --
//
void extractNoteInformation(Array<NoteData>& chordnotes, HumdrumFile& infile,
int startline, int stopline) {
int i, j;
chordnotes.setSize(0);
int datainit = 0;
for (i=startline; i<stopline; i++) {
if (infile[i].getType() != E_humrec_data) {
continue;
}
for (j=0; j<infile[i].getFieldCount(); j++) {
if (strcmp(infile[i].getExInterp(j), "**kern") != 0) {
continue;
}
if (strcmp(infile[i][j], ".") == 0) {
// in the middle of a note being played.
if (datainit == 0) {
getNoteDatum(chordnotes, infile, i, j, startline, stopline,
datainit);
} else {
continue;
}
} else {
getNoteDatum(chordnotes, infile, i, j, startline, stopline,
datainit);
}
}
datainit = 1;
}
}
//////////////////////////////
//
// getNoteDatum -- get note information
//
void getNoteDatum(Array<NoteData>& 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<tokencount; i++) {
infile[lastline].getToken(buffer, lastspine, i);
if (buffer[0] == '\0') {
continue;
}
if (strchr(buffer, 'r') != NULL) {
continue;
}
note.pitch = Convert::kernToBase40(buffer);
if (strchr(buffer, '[') != NULL) {
note.dur = infile.getTiedDuration(line, spine, i);
} else if (strchr(buffer, '_') != NULL) {
note.dur = infile.getTiedDuration(line, spine, i);
} else if (strchr(buffer, ']') != NULL) {
note.dur = infile.getTiedDuration(line, spine, i);
} else {
note.dur = Convert::kernToDuration(buffer);
}
note.beat = infile.getBeat(lastline);
note.absbeat = infile.getAbsBeat(lastline);
if (note.absbeat + note.dur <= infile.getAbsBeat(stopline) + 0.001) {
note.outregion = 1;
} else {
note.outregion = 0;
}
checkForMelodicNotes(note, infile, line, spine, startline, stopline);
note.durin = getDurationInsideChordRegion(infile, line, spine,
startline, stopline, note.dur);
chordnotes.append(note);
}
} else {
note.beat = infile.getBeat(line);
note.absbeat = infile.getAbsBeat(line);
note.inregion = 1;
tokencount = infile[line].getTokenCount(spine);
for (i=0; i<tokencount; i++) {
infile[line].getToken(buffer, spine, i);
if (buffer[0] == '\0') {
continue;
}
if (strchr(buffer, 'r') != NULL) {
continue;
}
note.pitch = Convert::kernToBase40(buffer);
if (strchr(buffer, '[') != NULL) {
note.dur = infile.getTiedDuration(line, spine, i);
} else if (strchr(buffer, '_') != NULL) {
if (datainit == 0) {
note.dur = infile.getTiedDuration(line, spine, i);
} else {
// ignore tie continuation notes
continue;
}
} else if (strchr(buffer, ']') != NULL) {
if (datainit == 0) {
note.dur = infile.getTiedDuration(line, spine, i);
} else {
// ignore tie terminating notes
continue;
}
} else {
note.dur = Convert::kernToDuration(buffer);
}
if (note.absbeat + note.dur <= infile.getAbsBeat(stopline) + 0.001) {
note.outregion = 1;
} else {
note.outregion = 0;
}
// check for note coming before or after this one in the music.
checkForMelodicNotes(note, infile, line, spine, startline, stopline);
note.durin = getDurationInsideChordRegion(infile, line, spine,
startline, stopline, note.dur);
chordnotes.append(note);
}
}
}
//////////////////////////////
//
// getDurationInsideChordRegion -- does not handle tied chords yet.
//
double getDurationInsideChordRegion(HumdrumFile& infile, int line, int spine,
int startline, int stopline, double notedur) {
double startbeat = infile[startline].getAbsBeat();
double stopbeat = infile[stopline].getAbsBeat();
double notestart = 0.0;
double notestop = 0.0;
int lastline = -1;
int lastspine = -1;
if (strcmp(infile[line][spine], ".") == 0) {
// find the last note token that applies to this null token
lastline = infile.getLastDatumLine(lastspine, line, spine);
line = lastline;
spine = lastspine;
}
if ((strchr(infile[line][spine], '_') != NULL) ||
(strchr(infile[line][spine], ']') != NULL) ) {
// found the middle/end of a tie, search for the
// beginning point of the tie in the music.
notestart = infile.getTiedStartBeat(line, spine);
} else {
// either the start of a tied note or a regular note
notestart = infile[line].getAbsBeat();
}
notestop = notestart + notedur;
double inregion = notedur;
if (startbeat - notestart > 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; k<tokencount; k++) {
infile[preline].getToken(buffer, prespine, k);
if (strchr(buffer, 'r') != NULL) {
continue;
}
melnote.pitch = Convert::kernToBase40(buffer);
note.preceding->append(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; k<tokencount; k++) {
infile[postline].getToken(buffer, postspine, k);
if (strchr(buffer, 'r') != NULL) {
continue;
}
melnote.pitch = Convert::kernToBase40(buffer);
note.following->append(melnote);
}
}
}
//////////////////////////////
//
// getStartingMeasure --
//
int getStartingMeasure(HumdrumFile& infile) {
int output = 0;
int datastart = 0;
int i;
for (i=0; i<infile.getNumLines(); i++) {
if (infile[i].getType() == E_humrec_data) {
datastart = 1;
} else if (infile[i].getType() == E_humrec_data_measure) {
int count = sscanf(infile[i][0], "=%d", &output);
if (count != 1) {
output = 0;
} else {
output = output - 1;
}
break;
}
}
return output;
}
//////////////////////////////
//
// printRootIntervals(int root, Array<Array<NoteData> >& chordnotes) {
//
void printRootIntervals(int root, Array& chordnotes) {
cout << "{";
int i;
int value = 0;
char buffer[128] = {0};
for (i=0; i<chordnotes.getSize(); i++) {
value = (chordnotes[i].pitch + 400 - root) % 40;
cout << Convert::base40ToIntervalAbbr(buffer, value);
if (i<chordnotes.getSize()-1) {
cout << " ";
}
}
cout << "}";
}
//////////////////////////////
//
// printChordCase --
//
void printChordCase(int rootcase) {
switch (rootcase) {
case RCASE_135: cout << "case135"; break;
case RCASE_7: cout << "case7"; break;
case RCASE_2: cout << "case2"; break;
case RCASE_4: cout << "case4"; break;
case RCASE_6: cout << "case6"; break;
case RCASE_24: cout << "case24"; break;
case RCASE_26: cout << "case26"; break;
case RCASE_46: cout << "case46"; break;
case RCASE_246: cout << "case246"; break;
default: cout << "caseX"; break;
}
}
//////////////////////////////
//
// getChordCase --
//
int getChordCase(int root, Array& chordnotes) {
Array<int> tones(7);
tones.zero();
tones.allowGrowth(0);
int i;
int value = 0;
for (i=0; i<chordnotes.getSize(); i++) {
value = (chordnotes[i].pitch + 400 - root) % 40;
tones[Convert::base40ToDiatonic((value+2) + 120) % 7] += 1;
}
int rootcase = RCASE_UNKNOWN;
if (tones[1] && tones[3] && tones[5]) {
rootcase = RCASE_246;
} else if (!tones[1] && tones[3] && tones[5]) {
rootcase = RCASE_46;
} else if (tones[1] && !tones[3] && tones[5]) {
rootcase = RCASE_26;
} else if (tones[1] && tones[3] && !tones[5]) {
rootcase = RCASE_24;
} else if (!tones[1] && !tones[3] && tones[5]) {
rootcase = RCASE_6;
} else if (!tones[1] && tones[3] && !tones[5]) {
rootcase = RCASE_4;
} else if (tones[1] && !tones[3] && !tones[5]) {
rootcase = RCASE_2;
} else if (tones[6]) {
rootcase = RCASE_7;
} else if (tones[0] || tones[2] || tones[4]) {
rootcase = RCASE_135;
}
return rootcase;
}
//////////////////////////////
//
// printAnalysis --
//
void printAnalysis(Array<int>& newroots, Array<int>& chordstartlines,
Array<int>& realroots, Array<Array<NoteData> >& chordnotes,
HumdrumFile& infile, Array<RootSpectrum>& 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<infile.getNumLines(); i++) {
if (informationQ) {
if ((ii < chordstartlines.getSize()-1) && (i == chordstartlines[ii])) {
cout << "!!ROOT_SEGMENT: ";
if (realroots[ii] < 0) {
cout << "r";
} else {
cout << Convert::base40ToKern(buffer, (realroots[ii]%40) + 120);
}
cout << "\t=========================================";
cout << "\n";
for (j=0; j<chordnotes[ii].getSize(); j++) {
if (chordnotes[ii][j].pitch >= 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[0] != '\0') {
const char* ptr = strrchr(filename, '/');
if ((ptr != NULL) && shortQ) {
cout << " filename:" << ptr+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; i<note.preceding->getSize(); i++) {
cout << Convert::base40ToKern(buffer, (*(note.preceding))[i].pitch);
if (i < note.preceding->getSize() - 1) {
cout << " ";
}
}
}
if (note.hasFollowing()) {
cout << "\tpost:";
for (i=0; i<note.following->getSize(); i++) {
cout << Convert::base40ToKern(buffer, (*(note.following))[i].pitch);
if (i < note.following->getSize() - 1) {
cout << " ";
}
}
}
cout << "\n";
}
//////////////////////////////
//
// getChordStartLines --
//
void getChordStartLines(Array<int>& chordstartlines, Array<int>& 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<infile.getNumLines(); i++) {
if (infile[i].getType() != E_humrec_data) {
continue;
}
for (j=0; j<infile[i].getFieldCount(); j++) {
if (strcmp(infile[i].getExInterp(j), "**root") == 0) {
if (strcmp(infile[i][j], ".") == 0) {
break;
} else if (strchr(infile[i][j], 'r') != NULL) {
root = -1;
chordstartlines.append(i);
realroot.append(root);
break;
} else {
chordstartlines.append(i);
root = Convert::kernToBase40(infile[i][j]);
realroot.append(root);
break;
}
}
}
}
i = infile.getNumLines() - 1;
root = -1;
realroot.append(root);
chordstartlines.append(i);
chordstartlines.allowGrowth(0);
}
//////////////////////////////
//
// checkOptions -- validate and process command-line options.
//
void checkOptions(Options& opts, int argc, char* argv[]) {
opts.define("i|info|segments=b", "display note extraction information");
opts.define("pw|print-weights=b", "print interval weights then exit");
opts.define("a|append=b", "append analysis to input data");
opts.define("p|prepend=b", "prepend analysis to input data");
opts.define("e|error=b", "display error markers");
opts.define("c|count=b", "count chord regions then exit");
opts.define("s|summary=b", "summarize chord case accuracies");
opts.define("rs|root-spectrum=b", "print root spectrum for each segment");
opts.define("m|melodic=b", "add melodic context analysis");
opts.define("P|full-pathes=b", "print full pathname of files");
opts.define("t|t1|theta1=d:0.0", "chromatic angle (single angle)");
opts.define("t2|theta2=d:0.0", "chromatic angle (double angle)");
opts.define("mdebug|melodic-debug=b", "trace input parsing for melody");
opts.define("mf|melodic-factor=d:0.0", "melodic scaling factor");
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, Dec 2000" << endl;
exit(0);
} else if (opts.getBoolean("version")) {
cout << argv[0] << ", version: 2 Dec 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);
}
informationQ = opts.getBoolean("info");
printweightQ = opts.getBoolean("print-weights");
debugQ = opts.getBoolean("debug");
mdebugQ = opts.getBoolean("melodic-debug");
appendQ = opts.getBoolean("append");
prependQ = opts.getBoolean("prepend");
errorQ = opts.getBoolean("error");
chordcountQ = opts.getBoolean("count");
theta1 = opts.getDouble("theta1");
theta2 = opts.getDouble("theta2");
twothetaQ = opts.getBoolean("theta2");
shortQ =!opts.getBoolean("full-pathes");
summaryQ = opts.getBoolean("summary");
melodyQ = opts.getBoolean("melodic");
spectrumQ = opts.getBoolean("root-spectrum");
mfactor = opts.getDouble("melodic-factor");
if (appendQ) {
prependQ = 0;
}
soloQ = !(prependQ || appendQ);
}
//////////////////////////////
//
// 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: 58d8389193529505423e87ff84b675e9 roottest.cpp [20101226]