//
// Programmer: Craig Stuart Sapp <craig@ccrma.stanford.edu>
// Creation Date: Thu Dec 6 23:09:26 PST 2001
// Last Modified: Sun Dec 9 17:32:12 PST 2001
// Filename: ...sig/examples/all/attacksum.cpp
// Web Address: http://sig.sapp.org/examples/museinfo/humdrum/attacksum.cpp
// Syntax: C++; museinfo
//
// Description: Generate a rhythmic summary of each measure by
// generating a binary number based on note attacks.
//
// This program could also be modified to handle doubles
// for the summation, then non integer attack locations
// could be linearly distributed between positions.
//
// Options: -f field numbers separated by dash for range and/or comma
// Gives the fields to use for analysis.
// -d Divisions per quarter note duration to provied analysis.
// -b binary form only -- do not sum number of attacks.
// -s sum all metric positions.
// -r raw summation data only (rid -GLId applied).
// -a append summation data to input data.
// -c code table version
// -C display code table
//
// Example analysis: 400321530043204
//
#include "humdrum.h"
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
typedef Array<int> ArrayInt;
typedef Array<char> ArrayChar;
// function declarations
void checkOptions(Options& opts, int argc, char* argv[]);
void example(void);
void usage(const char* command);
void ZeroArray(Array<int>& array, int value = 0);
void printAnalysis(HumdrumFile& infile, Array<ArrayInt>& analysis,
Array<int>& line, Array<double>& beats,
double divisions);
void generateAnalysis(HumdrumFile& infile, Array<ArrayInt>& analysis,
Array<int>& line, Array<double>& beats,
double divisions);
void addLineToSum(HumdrumFile& infile, int line,
Array<int>& sumarray, Array<int>& fields,
double divisions);
void generateFieldList(Array<int>& fieldList,const char* fieldstring);
char printDigit(int number);
void printSum(Array<int>& analysis);
void clearAnalysis(Array<ArrayInt>& array);
int stringcompare(const void* a, const void* b);
int findInList(Array<ArrayChar>& codeTable,
Array<char> searchstring);
void switchArrays(Array<ArrayChar>& codeTable, int currentpos,
int founditem);
void replaceWithCode(Array<char>& tableitem, int founditem);
void generateCodeTable(Array<ArrayChar>& codeTable,
Array<ArrayInt>& analysis);
void printBase26(int i);
// global variables
Options options; // database for command-line arguments
int debugQ = 0; // used with the --debug option int
int appendQ = 0; // used with the -a option
int rawQ = 0; // used with the -r option
int sumQ = 0; // used with the -s option
int binaryQ = 0; // display only binary attacks
double qdiv = 4.0; // number of divisions per quarter note
int maxfield = 0; // for -f option
int fieldsQ = 0; // for -f option
Array<int> fields; // **kern fields to decide attacks
Array<int> totalsum; // for -s option
const char* CurrentFile = ""; // for -s option
int codeQ = 0; // for -c option
int codeTableQ = 0; // for -C option
///////////////////////////////////////////////////////////////////////////
int main(int argc, char* argv[]) {
HumdrumFile infile;
// process the command-line options
checkOptions(options, argc, argv);
// figure out the number of input files to process
int numinputs = options.getArgCount();
Array<ArrayInt> analysis;
Array<double> beats;
Array<int> line;
analysis.setGrowth(500);
analysis.setSize(0);
beats.setSize(500);
beats.setGrowth(500);
beats.setSize(0);
line.setSize(500);
line.setGrowth(500);
line.setSize(0);
int i;
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 {
CurrentFile = options.getArg(i+1);
infile.read(options.getArg(i+1));
}
clearAnalysis(analysis);
generateAnalysis(infile, analysis, line, beats, qdiv);
printAnalysis(infile, analysis, line, beats, qdiv);
}
if (sumQ && numinputs > 1) {
cout << "TOTAL:\t";
for (i=0; i<totalsum.getSize(); i++) {
cout << totalsum[i];
if (i < totalsum.getSize() - 1) {
cout << " ";
}
}
cout << endl;
}
return 0;
}
///////////////////////////////////////////////////////////////////////////
//////////////////////////////
//
// clearAnalysis --
//
void clearAnalysis(Array& array) {
int i;
for (i=0; i<array.getSize(); i++) {
array[i].setSize(0);
}
array.setSize(0);
}
//////////////////////////////
//
// generateAnalysis --
//
void generateAnalysis(HumdrumFile& infile, Array<ArrayInt>& analysis,
Array<int>& line, Array<double>& beats, double divisions) {
line.setSize(infile.getNumLines());
line.setSize(0);
line.allowGrowth(1);
infile.analyzeRhythm("4");
Array<int> measuresum;
measuresum.setSize(5);
ZeroArray(measuresum);
int currentLine = -1;
double meterbeats = 4.0;
int top = 4;
int bottom = 4;
int newsize;
int i;
for (i=0; i<infile.getNumLines(); i++) {
if (debugQ) {
cout << "Line " << i + 1 << ": " << infile[i] << endl;
}
switch (infile[i].getType()) {
case E_humrec_none:
case E_humrec_empty:
case E_humrec_global_comment:
case E_humrec_bibliography:
case E_humrec_data_comment:
break;
case E_humrec_data_kern_measure: // process current sum
// store old array
if (currentLine < 0) {
currentLine = i;
}
line.append(currentLine);
analysis.append(measuresum);
beats.append(meterbeats);
currentLine = -1;
newsize = (int)(divisions * meterbeats);
if (newsize == 0) {
newsize = 1;
}
measuresum.setSize(newsize);
ZeroArray(measuresum);
break;
case E_humrec_interpretation:
if (strncmp(infile[i][0], "*M", 2) == 0) {
int count = sscanf(infile[i][0], "*M%d/%d", &top, &bottom);
if (count == 2) {
meterbeats = 4.0 * top / bottom;
if (currentLine == -1) {
newsize = (int)(divisions * meterbeats);
if (newsize == 0) {
newsize = 1;
}
measuresum.setSize(newsize);
ZeroArray(measuresum);
}
}
}
break;
case E_humrec_data: // store attacks
if (currentLine == -1) {
currentLine = i;
}
addLineToSum(infile, i, measuresum, fields, divisions);
break;
default:
break;
}
}
line.allowGrowth(0);
}
//////////////////////////////
//
// addLineToSum --
//
void addLineToSum(HumdrumFile& infile, int line, Array<int>& sumarray,
Array<int>& fields, double divisions) {
int i, j, k;
int tokencount;
int pitch = 0;
int position;
char buffer[1024] = {0};
if (fieldsQ) {
for (i=0; i<fields.getSize(); i++) {
for (j=0; j<infile[line].getFieldCount(); j++) {
if (infile[line].getPrimaryTrack(j) == fields[i]) {
tokencount = infile[line].getTokenCount(j);
if (strcmp(infile[line].getExInterp(j), "**kern") != 0) {
// ignore non-kern spines
continue;
}
for (k=0; k<infile[line].getTokenCount(j); k++) {
infile[line].getToken(buffer, j, k);
if (strcmp(buffer, ".") == 0) {
continue; // ignore null tokens
}
if (strchr(buffer, '_') != NULL) {
continue; // ignore continued tie notes
}
if (strchr(buffer, ']') != NULL) {
continue; // ignore ending tie notes
}
position = (int)((infile[line].getBeat()-1)*divisions+0.001);
pitch = Convert::kernToBase40(buffer);
if (pitch > 0) {
sumarray[position]++;
} // otherwise a rest
}
}
}
}
} else {
for (j=0; j<infile[line].getFieldCount(); j++) {
tokencount = infile[line].getTokenCount(j);
if (strcmp(infile[line].getExInterp(j), "**kern") != 0) {
// ignore non-kern spines
continue;
}
for (k=0; k<infile[line].getTokenCount(j); k++) {
infile[line].getToken(buffer, j, k);
if (strcmp(buffer, ".") == 0) {
continue; // ignore null tokens
}
if (strchr(buffer, '_') != NULL) {
continue; // ignore continued tie notes
}
if (strchr(buffer, ']') != NULL) {
continue; // ignore ending tie notes
}
position = (int)((infile[line].getBeat()-1)*divisions+0.001);
pitch = Convert::kernToBase40(buffer);
if (pitch > 0) {
sumarray[position]++;
} // otherwise a rest
}
}
}
}
//////////////////////////////
//
// ZeroArray -- set all values in array to zero.
//
void ZeroArray(Array& array, int value) {
for (int i=0; i<array.getSize(); i++) {
array[i] = value;
}
}
//////////////////////////////
//
// 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("b|binary=b", "count presence/absence of beats only");
opts.define("f|fields=s", "field selection list");
opts.define("d|divisions=d:4.0", "divisions per quarter note");
opts.define("r|raw=b", "raw analysis output");
opts.define("s|sum=b", "add attack pattern for all measures");
opts.define("c|codes=b", "convert measure sums to codes");
opts.define("C|table=b", "display the code table translations");
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: 6 Dec 2000" << 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");
binaryQ = opts.getBoolean("binary");
rawQ = opts.getBoolean("raw");
sumQ = opts.getBoolean("sum");
codeQ = opts.getBoolean("codes");
codeTableQ = opts.getBoolean("table");
qdiv = opts.getDouble("divisions");
generateFieldList(fields, opts.getString("fields"));
fieldsQ = opts.getBoolean("fields");
}
//////////////////////////////
//
// example -- example usage of the maxent program
//
void example(void) {
cout <<
" \n"
<< endl;
}
//////////////////////////////
//
// printAnalysis --
//
void printAnalysis(HumdrumFile& infile, Array<ArrayInt>& analysis,
Array<int>& line, Array<double>& beats, double divisions) {
int i, j;
int curr = 0;
Array<ArrayChar> codeTable;
if (codeQ || codeTableQ) {
generateCodeTable(codeTable, analysis);
}
if (appendQ) {
for (i=0; i<infile.getNumLines(); i++) {
switch (infile[i].getType()) {
case E_humrec_none:
case E_humrec_empty:
case E_humrec_global_comment:
case E_humrec_bibliography:
cout << infile[i] << "\n";
break;
case E_humrec_data_comment:
cout << infile[i] << "\t!\n";
break;
case E_humrec_data_kern_measure:
cout << infile[i] << "\t=\n";
break;
case E_humrec_interpretation:
if (strncmp(infile[i][0], "**", 2) == 0) {
cout << infile[i] << "\t**asum\n";
} else if (infile[i].equalFieldsQ() && strcmp(infile[i][0], "*-") == 0) {
cout << infile[i] << "\t*-\n";
} else if (strncmp(infile[i][0], "*M", 2) == 0) {
cout << infile[i] << "\t" << infile[i][0] << "\n";
} else {
cout << infile[i] << "\t*\n";
}
break;
case E_humrec_data:
if (curr < analysis.getSize() && i >= line[curr]) {
cout << infile[i] << "\t";
if (codeQ) {
cout << codeTable[curr].getBase();
} else {
printSum(analysis[curr]);
}
cout << "\n";
curr++;
} else {
cout << infile[i] << "\t.\n";
}
break;
default:
cout << infile[i] << "\n";
break;
}
}
} else if (rawQ) {
for (i=0; i<analysis.getSize(); i++) {
if (codeQ) {
cout << codeTable[i].getBase() << "\n";
} else {
for (j=0; j<analysis[i].getSize(); j++) {
cout << printDigit(analysis[i][j]);
}
cout << "\n";
}
}
} else if (sumQ) {
int maxsize = 0;
for (i=0; i<analysis.getSize(); i++) {
if (analysis[i].getSize() > maxsize) {
maxsize = analysis[i].getSize();
}
}
Array<int> summation(maxsize);
ZeroArray(summation);
int j;
for (i=0; i<analysis.getSize(); i++) {
for (j=0; j<analysis[i].getSize(); j++) {
summation[j] += analysis[i][j];
}
}
if (options.getArgCount() > 1) {
cout << CurrentFile << ":\t";
}
for (i=0; i<summation.getSize(); i++) {
cout << summation[i];
if (i < summation.getSize() - 1) {
cout << " ";
}
}
cout << endl;
// store counts in totalsum
for (i=0; i<summation.getSize(); i++) {
if (i < totalsum.getSize()) {
totalsum[i] += summation[i];
} else {
totalsum.append(summation[i]);
}
}
} else {
for (i=0; i<infile.getNumLines(); i++) {
switch (infile[i].getType()) {
case E_humrec_global_comment:
case E_humrec_bibliography:
cout << infile[i] << "\n";
break;
case E_humrec_data_kern_measure:
cout << infile[i][0] << "\n";
break;
case E_humrec_interpretation:
if (strncmp(infile[i][0], "**", 2) == 0) {
cout << "**asum\n";
} else if (infile[i].equalFieldsQ() && strcmp(infile[i][0], "*-") == 0) {
cout << "*-\n";
} else if (strncmp(infile[i][0], "*M", 2) == 0) {
cout << infile[i][0] << "\n";
} else {
// do nothing
}
break;
case E_humrec_data:
if (curr < line.getSize() && i >= line[curr]) {
if (codeQ) {
printSum(analysis[curr]);
} else {
cout << codeTable[curr].getBase();
}
cout << "\n";
curr++;
} else {
// do nothing
}
break;
case E_humrec_data_comment:
case E_humrec_none:
case E_humrec_empty:
default:
// do nothing
break;
}
}
}
}
//////////////////////////////
//
// generateCodeTable -- convert analysis to a list of unique sequence codes.
//
void generateCodeTable(Array& codeTable, Array& analysis) {
int i, j;
char empty = '\0';
char element;
Array<ArrayChar> temptable;
temptable.setSize(analysis.getSize());
codeTable.setSize(analysis.getSize());
for (i=0; i<analysis.getSize(); i++) {
codeTable[i].setSize(analysis[i].getSize());
temptable[i].setSize(analysis[i].getSize());
for (j=0; j<analysis[i].getSize(); j++) {
element = printDigit(analysis[i][j]);
if (binaryQ && (element != '0')) {
element = '1';
}
codeTable[i][j] = element;
temptable[i][j] = element;
}
codeTable[i].append(empty);
temptable[i].append(empty);
}
qsort(temptable.getBase(), temptable.getSize(), sizeof(ArrayChar),
stringcompare);
// count how many unique strings there are:
int count = 0;
if (temptable.getSize() > 0) {
count = 1;
}
for (i=1; i<temptable.getSize(); i++) {
if (strcmp(temptable[i].getBase(), temptable[i-1].getBase()) != 0) {
count++;
}
}
// cout << "!! Unique codes: " << count << endl;
// fill in the codeTable
Array<ArrayChar> uniqtable;
uniqtable.setSize(count);
uniqtable.allowGrowth(0);
int ncount = 0;
if (temptable.getSize() > 0) {
uniqtable[0] = temptable[0];
ncount = 1;
}
for (i=1; i<temptable.getSize(); i++) {
if (strcmp(temptable[i].getBase(), temptable[i-1].getBase()) != 0) {
uniqtable[ncount] = temptable[i];
ncount++;
}
}
// now that the codes are uniqued, sort them by the order in which
// they appear in the music.
int currentpos = 0;
int founditem = -1;
for (i=0; i<codeTable.getSize(); i++) {
founditem = findInList(uniqtable, codeTable[i]);
if (founditem > currentpos) {
switchArrays(uniqtable, currentpos, founditem);
founditem = currentpos;
currentpos++;
}
replaceWithCode(codeTable[i], founditem);
}
// print the uniq table if user requests it:
if (codeTableQ) {
for (i=0; i<uniqtable.getSize(); i++) {
cout << "!!!code:\t";
printBase26(i);
cout << "\t" << uniqtable[i].getBase() << "\n";
}
}
}
//////////////////////////////
//
// printBase26 --
//
void printBase26(int i) {
int digit;
if (i >= 26*26*26) {
digit = i/26*26*26;
cout << (char)('A' + digit - 1);
i -= digit * 26 * 26 * 26;
}
if (i >= 26*26) {
digit = i/26*26;
cout << (char)('A' + digit - 1);
i -= digit * 26 * 26;
}
if (i >= 26) {
digit = i/26;
cout << (char)('A' + digit - 1);
i -= digit * 26;
}
digit = i;
cout << (char)('A' + digit);
}
//////////////////////////////
//
// replaceWithCode --
//
void replaceWithCode(Array& tableitem, int founditem) {
char buffer[128] = {0};
int digit;
char schar[2] = {0};
if (founditem >= 26*26*26) {
digit = founditem/26*26*26;
schar[0] = 'A' + digit - 1;
strcat(buffer, schar);
founditem -= digit * 26 * 26 * 26;
}
if (founditem >= 26*26) {
digit = founditem/26*26;
schar[0] = 'A' + digit - 1;
strcat(buffer, schar);
founditem -= digit * 26 * 26;
}
if (founditem >= 26) {
digit = founditem/26;
schar[0] = 'A' + digit - 1;
strcat(buffer, schar);
founditem -= digit * 26;
}
digit = founditem;
schar[0] = 'A' + digit;
strcat(buffer, schar);
int length = strlen(buffer);
tableitem.setSize(length+1);
int i;
for (i=0; i<length+1; i++) {
tableitem[i] = buffer[i];
}
}
//////////////////////////////
//
// switchArrays --
//
void switchArrays(Array& codeTable, int currentpos, int founditem) {
if (founditem < 0) {
return;
}
ArrayChar temp = codeTable[currentpos];
codeTable[currentpos] = codeTable[founditem];
codeTable[founditem] = temp;
}
//////////////////////////////
//
// findInList --
//
int findInList(Array& codeTable, Array searchstring) {
int i;
for (i=0; i<codeTable.getSize(); i++) {
if (strcmp(codeTable[i].getBase(), searchstring.getBase()) == 0) {
return i;
}
}
return -1;
}
//////////////////////////////
//
// printSum --
//
void printSum(Array& analysis) {
int i;
for (i=0; i<analysis.getSize(); i++) {
cout << printDigit(analysis[i]);
}
}
//////////////////////////////
//
// usage -- gives the usage statement for the quality program
//
void usage(const char* command) {
cout <<
" \n"
<< endl;
}
//////////////////////////////
//
// generateFieldList -- taken from eised
//
void generateFieldList(Array& fieldList, const char* fieldstring) {
fieldList.setSize(100);
fieldList.setGrowth(100);
fieldList.setSize(0);
maxfield = 0;
int length = strlen(fieldstring);
char* buffer = new char[length+1];
strcpy(buffer, fieldstring);
int starti, stopi;
int temp;
int num;
char* ptr = strtok(buffer, " ,;:");
while (ptr != NULL) {
if (strchr(ptr, '-') != NULL) {
sscanf(ptr, "%d-%d", &starti, &stopi);
if (starti > stopi) {
temp = starti;
starti=stopi;
stopi = temp;
}
for (num=starti; num<=stopi; num++) {
fieldList.append(num);
}
if (num > maxfield) {
maxfield = num;
}
} else {
sscanf(ptr, "%d", &num);
fieldList.append(num);
if (num > maxfield) {
maxfield = num;
}
}
ptr = strtok(NULL, " ,;:");
}
fieldList.allowGrowth(0);
}
//////////////////////////////
//
// printDigit -- print digit in base 36 with cut off if too large
//
char printDigit(int number) {
if (number <= 0) {
return '0';
}
if (binaryQ) {
return '1';
}
if (number < 10) {
return '0' + number;
}
number = number - 10;
if (number >= 25) {
return 'Z';
}
return 'A' + number;
}
//////////////////////////////
//
// stringcompare --
//
int stringcompare(const void* a, const void* b) {
ArrayChar& A = *((ArrayChar*)a);
ArrayChar& B = *((ArrayChar*)b);
return strcmp(A.getBase(), B.getBase());
}
// md5sum: 1bf8337b37053a233ac3cf33d209e112 attacksum.cpp [20050403]