//
// Programmer: Craig Stuart Sapp <craig@ccrma.stanford.edu>
// Creation Date: Mon Nov 5 11:30:28 PST 2001
// Last Modified: Tue Nov 6 23:25:11 PST 2001
// Filename: ...sig/examples/all/melstep/melstep.cpp
// Web Address: http://sig.sapp.org/examples/museinfo/humdrum/melstep.cpp
// Syntax: C++; museinfo
//
// Description: Determines the melodic approach or departure of a note.
// 0000 = 0 = not approached or left by a 2nd in track
// 0001 = 1 = left by a 2nd in track
// 0010 = 2 = approached by a 2nd in track
// 0011 = 3 = approached and left by a 2nd in track
//
// Not yet implemented:
// 0000 = 0 = note approached or left by a 2nd in another track
// 0100 = 4 = note left by a 2nd in another track
// 1000 = 8 = note approached by a 2nd in another track
// 1100 = 12 = note approached and left by a 2nd in another track
//
#include "humdrum.h"
#include <string.h>
#include <stdio.h>
#include <math.h>
typedef Array<int> ArrayInt;
// function declarations:
void checkOptions(Options& opts, int argc, char* argv[]);
void getDataLines(HumdrumFile& infile, Array<int>& datalines);
void generateAnalysis(HumdrumFile& infile, Array<ArrayInt>& mstates,
Array<int>& datalines);
void printAnalysis(HumdrumFile& infile, Array<ArrayInt>& mstates,
Array<int>& datalines);
void printState(int state, int style);
void printSummary(HumdrumFile& infile, Array<ArrayInt>& mstates,
Array<int>& datalines);
void example(void);
void usage(const char* command);
template<class type> void switchvals(type& a, type& b);
// option variables:
Options options; // database for command-line arguments
int debugQ = 0; // for debugging
int appendQ = 0; // for appending analysis data to input data
int binaryQ = 0; // display analysis in binary format
int summaryQ = 0; // display transition statistics only
int minstep = 4; // minimum value for step (base40 interval)
int maxstep = 8; // maximum value for step (base40 interval)
int ridQ = 0; // rid plain **step data of non-data items
///////////////////////////////////////////////////////////////////////////
int main(int argc, char* argv[]) {
HumdrumFile infile;
checkOptions(options, argc, argv); // process the command-line options
// figure out the number of input files to process
int numinputs = options.getArgCount();
Array<ArrayInt> mstates;
Array<int> datalines;
for (int i=0; i<numinputs || i==0; i++) {
infile.clear();
// if no command-line arguments read data file from standard input
if (numinputs < 1) {
infile.read(cin);
} else {
infile.read(options.getArg(i+1));
}
infile.analyzeRhythm();
getDataLines(infile, datalines);
generateAnalysis(infile, mstates, datalines);
printAnalysis(infile, mstates, datalines);
}
return 0;
}
////////////////////////////////////////////////////////////////////////////
//////////////////////////////
//
// getDataLines -- returns an array of the lines number in the Humdrum
// File which contain data elements (notes and rests).
//
void getDataLines(HumdrumFile& infile, Array& datalines) {
datalines.setSize(infile.getNumLines());
datalines.setSize(0);
datalines.allowGrowth();
int i;
for (i=0; i<infile.getNumLines(); i++) {
if (infile[i].getType() == E_humrec_data) {
datalines.append(i);
}
}
datalines.allowGrowth(0);
}
//////////////////////////////
//
// generateAnalysis -- assign melodic state information for each note.
//
void generateAnalysis(HumdrumFile& infile, Array<ArrayInt>& mstates,
Array<int>& datalines) {
mstates.setSize(datalines.getSize());
static char buffer[1024] = {0};
static char bufferN[1024] = {0};
static char bufferL[1024] = {0};
Array<double> lastspines;
Array<double> nextspines;
int i, j;
int m;
int lindex, nindex;
int lspine, nspine;
int ltokens, ntokens;
int analysis = 0;
for (i=0; i<datalines.getSize(); i++) {
mstates[i].setSize(20);
mstates[i].setSize(0);
mstates[i].allowGrowth(0);
for (j=0; j<infile[datalines[i]].getFieldCount(); j++) {
if (infile[datalines[i]].getExInterpNum(j) != E_KERN_EXINT) {
continue;
}
infile[datalines[i]].getToken(buffer, j, 0);
if (strchr(buffer, ']') != NULL) { // ignore ending tied notes
continue;
}
if (strchr(buffer, '_') != NULL) { // ignore continuing tied notes
continue;
}
if (strchr(buffer, 'r') != NULL) { // ignore rests
continue;
}
if (strcmp(buffer, ".") == 0) { // ignore null tokens
continue;
}
if (debugQ) {
cout << "Line=" << datalines[i] << "\t"
<< "Track=" << infile[datalines[i]].getTrack(j) << "\t"
<< infile[datalines[i]][j] << "\t"
<< "Last=" << infile.getLastDatum(datalines[i], j) << "\t"
<< "Next=" << infile.getNextDatum(datalines[i], j) << "\t"
<< endl;
}
lindex = infile.getLastDatumLine(lspine, datalines[i], j);
nindex = infile.getNextDatumLine(nspine, datalines[i], j);
// check if there is a split or join between the last or next
// datafields.
// getLastSpines(lastspines, lspine, infile, lines, datalines[i], spine);
// getNextSpines(nextspines, nspine, infile, lines, datalines[i], spine);
int ndiff = 0;
int ldiff = 0;
int tokencount = infile[datalines[i]].getTokenCount(j);
for (m=0; m<tokencount; m++) {
infile[datalines[i]].getToken(buffer, j, m);
ltokens = -1;
if (lindex >= 0 && lspine >= 0) {
ltokens = infile[lindex].getTokenCount(lspine);
}
ntokens = -1;
if (nindex >= 0 && nspine >= 0) {
ntokens = infile[nindex].getTokenCount(nspine);
}
if (m < ltokens && lindex >= 0 && lspine >= 0) {
infile[lindex].getToken(bufferL, lspine, m);
} else {
bufferL[0] = '\0';
}
if (m < ntokens && nindex >= 0 && nspine >= 0) {
infile[nindex].getToken(bufferN, nspine, m);
} else {
bufferN[0] = '\0';
}
ndiff = 0;
if (bufferN[0] != '\0' && (strchr(bufferN, 'r') == NULL)) {
ndiff = abs(Convert::kernToBase40(bufferN) -
Convert::kernToBase40(buffer));
}
ldiff = 0;
if (bufferL[0] != '\0' && (strchr(bufferL, 'r') == NULL)) {
ldiff = abs(Convert::kernToBase40(bufferL) -
Convert::kernToBase40(buffer));
}
if (ndiff == 0 && ntokens != tokencount) {
if (ntokens - m - 1 >= 0 && nindex >= 0 && nspine >= 0) {
infile[nindex].getToken(bufferN, nspine, ntokens-m-1);
} else {
bufferN[0] = '\0';
}
if (bufferN[0] != '\0' && (strchr(bufferN, 'r') == NULL)) {
ndiff = abs(Convert::kernToBase40(bufferN) -
Convert::kernToBase40(buffer));
}
}
if (ldiff == 0 && ltokens != tokencount) {
if (ltokens - m - 1 >= 0 && lindex >= 0 && lspine >= 0) {
infile[lindex].getToken(bufferL, lspine, ltokens-m-1);
} else {
bufferL[0] = '\0';
}
if (bufferL[0] != '\0' && (strchr(bufferL, 'r') == NULL)) {
ldiff = abs(Convert::kernToBase40(bufferL) -
Convert::kernToBase40(buffer));
}
}
analysis = 0;
if (ldiff >= minstep && ldiff <= maxstep) {
analysis |= 0x02;
}
if (ndiff >= minstep && ndiff <= maxstep) {
analysis |= 0x01;
}
mstates[i].append(analysis);
} /* token loop */
} /* spine loop */
} /* line loop */
}
//////////////////////////////
//
// getLastSpines -- check for spine changes
//
/*void getLastSpines(Array<double>& lastspines, int lindex, int lspine,
HumdrumFile& infile, int curindex, int curspine) {
}
*/
//////////////////////////////
//
// getNextSpines -- check for spine changes
//
/*void getNextSpines(nextspines, nspine, infile, lines, datalines[i], spine) {
}
*/
//////////////////////////////
//
// printAnalysis -- print melodic state information for each note.
//
void printAnalysis(HumdrumFile& infile, Array<ArrayInt>& mstates,
Array<int>& datalines) {
int i, j, k;
if (summaryQ) {
printSummary(infile, mstates, datalines);
return;
}
if (!appendQ) {
k = 0;
for (i=0; i<infile.getNumLines(); i++) {
switch (infile[i].getType()) {
case E_humrec_global_comment:
case E_humrec_bibliography:
case E_humrec_none:
case E_humrec_empty:
if (!ridQ) {
cout << infile[i].getLine() << "\n";
}
break;
case E_humrec_data:
for (j=0; j<mstates[k].getSize(); j++) {
printState(mstates[k][j], binaryQ);
if (j<mstates[k].getSize()-1) {
cout << " ";
}
}
cout << "\n";
k++;
break;
case E_humrec_data_comment:
if (!ridQ) {
if (infile[i].equalFieldsQ("**kern")) {
cout << infile[i][0] << "\n";
} else {
cout << infile[i].getLine() << "\t!\n";
}
}
break;
case E_humrec_data_measure:
if (!ridQ) {
if (infile[i].equalFieldsQ("**kern")) {
cout << infile[i][0] << "\n";
} else {
cout << infile[i].getLine() << "\t=\n";
}
}
break;
case E_humrec_data_interpretation:
if (!ridQ) {
if (strncmp(infile[i][0], "**", 2) == 0) {
cout << "**step" << "\n";
} else if (infile[i].equalFieldsQ("**kern") &&
(strcmp(infile[i][0], "*+") != 0) &&
(strcmp(infile[i][0], "*^") != 0) &&
(strcmp(infile[i][0], "*v") != 0) &&
(strcmp(infile[i][0], "*x") != 0) ) {
cout << infile[i][0] << "\n";
} else {
cout << "*\n";
}
}
break;
}
}
return;
}
k = 0;
for (i=0; i<infile.getNumLines(); i++) {
switch (infile[i].getType()) {
case E_humrec_global_comment:
case E_humrec_bibliography:
case E_humrec_none:
case E_humrec_empty:
cout << infile[i].getLine() << "\n";
break;
case E_humrec_data:
cout << infile[i].getLine() << "\t";
for (j=0; j<mstates[k].getSize(); j++) {
printState(mstates[k][j], binaryQ);
if (j<mstates[k].getSize()-1) {
cout << " ";
}
}
cout << "\n";
k++;
break;
case E_humrec_data_comment:
if (infile[i].equalFieldsQ("**kern")) {
cout << infile[i].getLine() << "\t"
<< infile[i][0] << "\n";
} else {
cout << infile[i].getLine() << "\t!\n";
}
break;
case E_humrec_data_measure:
if (infile[i].equalFieldsQ("**kern")) {
cout << infile[i].getLine() << "\t"
<< infile[i][0] << "\n";
} else {
cout << infile[i].getLine() << "\t=\n";
}
break;
case E_humrec_data_interpretation:
if (strncmp(infile[i][0], "**", 2) == 0) {
cout << infile[i].getLine() << "\t";
cout << "**step" << "\n";
} else if (infile[i].equalFieldsQ("**kern") &&
(strcmp(infile[i][0], "*+") != 0) &&
(strcmp(infile[i][0], "*^") != 0) &&
(strcmp(infile[i][0], "*v") != 0) &&
(strcmp(infile[i][0], "*x") != 0) ) {
cout << infile[i].getLine() << "\t"
<< infile[i][0] << "\n";
} else {
cout << infile[i].getLine() << "\t*\n";
}
break;
}
}
}
//////////////////////////////
//
// switchvals --
//
template<class type>
void switchvals(type& a, type& b) {
type temp = a;
a = b;
b = temp;
}
/////////////////////////////
//
// printSummary -- print analysis summary.
//
void printSummary(HumdrumFile& infile, Array<ArrayInt>& mstates,
Array<int>& datalines) {
int c[4] = {0};
int i, j, m;
for (i=0; i<mstates.getSize(); i++) {
for (j=0; j<mstates[i].getSize(); j++) {
c[mstates[i][j] & 0x3]++;
}
}
double sum = c[0] + c[1] + c[2] + c[3];
cout << "Transition Summary:\n\n";
cout << "type\tcount\tfraction (total=" << sum << ")\n";
cout << "-------------------------------------\n";
for (m=0; m<4; m++) {
printState(m, binaryQ);
cout << "\t" << c[m] << "\t" << c[m] / sum << "\n";
}
Array<double> metpos;
metpos.setSize(500);
metpos.setSize(0);
metpos.allowGrowth();
double pos = 0;
int pindex = 0;
Array<double> metposb;
metposb.setSize(500);
metposb.setSize(0);
metposb.allowGrowth();
double posb = 0;
int pindexb = 0;
Array<ArrayInt> counts(4);
Array<ArrayInt> countsb(4);
counts.allowGrowth(0);
countsb.allowGrowth(0);
for (m=0; m<4; m++) {
counts[m].setSize(500);
counts[m].setSize(0);
counts[0].allowGrowth();
countsb[m].setSize(500);
countsb[m].setSize(0);
countsb[0].allowGrowth();
}
int zero = 0;
int pfound = 0;
int pfoundb = 0;
for (i=0; i<datalines.getSize(); i++) {
pos = infile[datalines[i]].getBeat();
pfound = 0;
for (m=0; m<metpos.getSize(); m++) {
if (fabs(metpos[m] - pos) < 0.001) {
pfound = 1;
pindex = m;
break;
}
}
if (!pfound) {
metpos.append(pos);
for (m=0; m<4; m++) {
counts[m].append(zero);
}
pindex = metpos.getSize() - 1;
}
posb = pos - (int)pos;
pfoundb = 0;
for (m=0; m<metposb.getSize(); m++) {
if (fabs(metposb[m] - posb) < 0.001) {
pfoundb = 1;
pindexb = m;
break;
}
}
if (!pfoundb) {
metposb.append(posb);
for (m=0; m<4; m++) {
countsb[m].append(zero);
}
pindexb = metposb.getSize() - 1;
}
for (j=0; j<mstates[i].getSize(); j++) {
counts[mstates[i][j] & 0x3][pindex]++;
countsb[mstates[i][j] & 0x3][pindexb]++;
}
}
// sort the summary by beat position
for (i=0; i<metpos.getSize(); i++) {
for (j=i+1; j<metpos.getSize(); j++) {
if (metpos[i] > metpos[j]) {
switchvals(metpos[i], metpos[j]);
for (m=0; m<4; m++) {
switchvals(counts[m][i], counts[m][j]);
}
}
}
}
for (i=0; i<metposb.getSize(); i++) {
for (j=i+1; j<metposb.getSize(); j++) {
if (metposb[i] > metposb[j]) {
switchvals(metposb[i], metposb[j]);
for (m=0; m<4; m++) {
switchvals(countsb[m][i], countsb[m][j]);
}
}
}
}
// print the metric data summary:
cout << "\nTransition counts for each metric position:\n\n";
cout << "\ttransitions:\n";
cout << "beat\t";
printState(0, binaryQ);
cout << "\t";
printState(1, binaryQ);
cout << "\t";
printState(2, binaryQ);
cout << "\t";
printState(3, binaryQ);
cout << "\tsum\tfraction\n";
cout << "-----------------------------------------------------------\n";
for (i=0; i<metpos.getSize(); i++) {
cout << metpos[i] << "\t"
<< counts[0][i] << "\t"
<< counts[1][i] << "\t"
<< counts[2][i] << "\t"
<< counts[3][i] << "\t"
<< counts[0][i] + counts[1][i] + counts[2][i] + counts[3][i] << "\t"
<< (counts[0][i] + counts[1][i] + counts[2][i] + counts[3][i])/sum
<< endl;
}
cout << "\nTransition counts for beat position:\n\n";
cout << "\ttransitions:\n";
cout << "subbeat\t";
printState(0, binaryQ);
cout << "\t";
printState(1, binaryQ);
cout << "\t";
printState(2, binaryQ);
cout << "\t";
printState(3, binaryQ);
cout << "\tsum\tfraction\n";
cout << "-----------------------------------------------------------\n";
for (i=0; i<metposb.getSize(); i++) {
cout << metposb[i] << "\t"
<< countsb[0][i] << "\t"
<< countsb[1][i] << "\t"
<< countsb[2][i] << "\t"
<< countsb[3][i] << "\t"
<< countsb[0][i] + countsb[1][i] + countsb[2][i] + countsb[3][i] << "\t"
<< (countsb[0][i] + countsb[1][i] + countsb[2][i] + countsb[3][i])/sum
<< endl;
}
}
//////////////////////////////
//
// printState -- print the analysis in decimal or binary form.
//
void printState(int state, int style) {
if (style) {
switch (state) {
case 0: cout << "00"; break;
case 1: cout << "01"; break;
case 2: cout << "10"; break;
case 3: cout << "11"; break;
default: cout << state; break;
}
} else {
cout << state;
}
}
//////////////////////////////
//
// checkOptions -- validate and process command-line options.
//
void checkOptions(Options& opts, int argc, char* argv[]) {
opts.define("a|append=b", "append analysis to data in output");
opts.define("s|summary=b", "summary of analysis only");
opts.define("b|binary=b", "analysis displayed in binary format");
opts.define("r|range=s:4-8", "base40 interval range for defining a step");
opts.define("n|min=i:4", "minimum base40 interval for defining a step");
opts.define("x|max=i:8", "maximum base40 interval for defining a step");
opts.define("i|interval=i:2", "interval defined as a step");
opts.define("R|rid=b", "rid data of non-data lines");
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, Nov 2001" << endl;
exit(0);
} else if (opts.getBoolean("version")) {
cout << argv[0] << ", version: 5 Nov 2001" << 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);
}
debugQ = opts.getBoolean("debug");
appendQ = opts.getBoolean("append");
summaryQ = opts.getBoolean("summary");
binaryQ = opts.getBoolean("binary");
ridQ = opts.getBoolean("rid");
sscanf(opts.getString("range"), "%d-%d", &minstep, &maxstep);
if (opts.getBoolean("min")) {
minstep = opts.getInteger("min");
}
if (opts.getBoolean("max")) {
maxstep = opts.getInteger("max");
}
if (opts.getBoolean("interval")) {
int interval = opts.getInteger("interval");
switch (interval) {
case 1: minstep = 0; maxstep = 2; break; // unisons
case 2: minstep = 4; maxstep = 8; break; // seconds
case 3: minstep = 10; maxstep = 14; break; // thirds
case 4: minstep = 15; maxstep = 19; break; // fourths
case 5: minstep = 21; maxstep = 25; break; // fifths
case 6: minstep = 27; maxstep = 31; break; // sixths
case 7: minstep = 33; maxstep = 37; break; // sevenths
default: cout << "Error: invalid interval: " << interval << endl;
exit(1);
}
}
if (minstep > maxstep) {
int temp = minstep;
minstep = maxstep;
maxstep = temp;
}
}
//////////////////////////////
//
// example -- example usage of the melstep program
//
void example(void) {
cout <<
" \n"
<< endl;
}
//////////////////////////////
//
// usage -- gives the usage statement for the melstep program
//
void usage(const char* command) {
cout <<
" \n"
<< endl;
}
// md5sum: dae85b0de4324139794b0d7376b9d1a2 melstep.cpp [20050403]