//
// Programmer: Craig Stuart Sapp <craig@ccrma.stanford.edu>
// Creation Date: Thu Oct 20 20:52:45 PDT 2011
// Last Modified: Thu Oct 20 20:52:48 PDT 2011
// Filename: ...sig/examples/all/staff.cpp
// Web Address: http://sig.sapp.org/examples/museinfo/humdrum/staff.cpp
// Syntax: C++; museinfo
//
// Description: Extact music by staff indications.
//
#include "humdrum.h"
#include "PerlRegularExpression.h"
#ifndef OLDCPP
using namespace std;
#endif
///////////////////////////////////////////////////////////////////////////
// function declarations
void checkOptions(Options& opts, int argc, char* argv[]);
void example(void);
void usage(const char* command);
void processFile(HumdrumFile& infile, Array<int>& field);
void getStaves(Array<Array<Array<double> > >& staves,
HumdrumFile& infile);
void setStaffAssignment(Array<double>& states, const char* string);
void printStaves(Array<Array<Array<double> > >& staves);
void fillFieldData(Array<int>& field, Array<int>& subfield,
Array<int>& model, const char* fieldstring,
HumdrumFile& infile);
void processFieldEntry(Array<int>& field, Array<int>& subfield,
Array<int>& model, const char* string,
HumdrumFile& infile);
void removeDollarsFromString(Array<char>& buffer, int maxtrack);
int isMatch(int ss, Array<double>& list);
void printLineDuration(HumdrumFile& infile, int line);
void getMeasureDurations(Array<RationalNumber>& measuredur,
HumdrumFile& infile);
void addStaffEntry(Array<double> & states, double value);
void getMeasureStates(Array<Array<double> >& measurestates,
Array<Array<Array<double> > >& staves,
Array<RationalNumber>& mdur,
HumdrumFile& infile);
void printDuration(const RationalNumber& duration);
// global variables
Options options; // database for command-line arguments
const char* fieldstring = ""; // used with -s option
int debugQ = 0; // used with --debug option
///////////////////////////////////////////////////////////////////////////
int main(int argc, char* argv[]) {
HumdrumFile infile;
// process the command-line options
checkOptions(options, argc, argv);
const char* filename;
infile.clear();
// if no command-line arguments read data file from standard input
int numinputs = options.getArgCount();
if (numinputs < 1) {
infile.read(cin);
} else {
filename = options.getArg(1);
infile.read(options.getArg(1));
}
infile.analyzeRhythm("4");
Array<int> model;
Array<int> field;
Array<int> subfield;
fillFieldData(field, subfield, model, fieldstring, infile);
processFile(infile, field);
}
///////////////////////////////////////////////////////////////////////////
//////////////////////////////
//
// processFile --
//
void processFile(HumdrumFile& infile, Array& field) {
Array<Array<Array<double> > > staves;
getStaves(staves, infile);
if (debugQ) {
printStaves(staves);
}
Array<RationalNumber> measuredur;
getMeasureDurations(measuredur, infile);
Array<Array<double> > measurestates;
getMeasureStates(measurestates, staves, measuredur, infile);
PerlRegularExpression pre;
int i, j, s, ss;
int first = 0;
int found = 0;
int active;
for (i=0; i<infile.getNumLines(); i++) {
if (infile[i].isBibliographic()) {
cout << infile[i] << endl;
continue;
}
if (infile[i].isGlobalComment()) {
cout << infile[i] << endl;
continue;
}
if (infile[i].isEmpty()) {
cout << infile[i] << endl;
continue;
}
first = 0;
found = 0;
for (s=0; s<field.getSize(); s++) {
ss = field[s];
found = 0;
for (j=0; j<infile[i].getFieldCount(); j++) {
if (isMatch(ss, staves[i][j])) {
if (first == 0) {
first = 1;
} else {
cout << "\t";
}
cout << infile[i][j];
found = 1;
}
}
if (found) {
continue;
}
if (first == 0) {
first = 1;
} else {
cout << "\t";
}
if (infile[i].isMeasure()) {
cout << infile[i][0];
} else if (infile[i].isInterpretation()) {
if (strcmp(infile[i][0], "*-") == 0) {
cout << "*-";
} else if (pre.search(infile[i][0], "^\\*M\\d+/\\d+")) {
// time signature
cout << infile[i][0];
} else if (pre.search(infile[i][0], "^\\*met\\(")) {
// metrical symbol
cout << infile[i][0];
} else if (pre.search(infile[i][0], "^\\*MM\\d")) {
// tempo marker
cout << infile[i][0];
} else {
cout << "*";
}
} else if (infile[i].isLocalComment()) {
cout << "!";
} else {
active = isMatch(ss, measurestates[i]);
if (active) {
if (infile[i].getDuration() == 0.0) {
cout << ".";
} else {
printLineDuration(infile, i);
cout << "r";
}
} else {
// staff is not active in this measure, so add a
// full measure of rests.
if (measuredur[i] > 0) {
// this is the first data element in the measure
// place the rest here.
printDuration(measuredur[i]);
cout << "r";
} else {
// not the first data element in measure so
// just place a dot.
cout << ".";
}
}
}
}
cout << endl;
}
}
//////////////////////////////
//
// getMeasureStates -- return a list of staves which contain
// data within the current measure.
//
void getMeasureStates(Array<Array<double> >& measurestates,
Array<Array<Array<double> > >& staves, Array<RationalNumber>& mdur,
HumdrumFile& infile) {
int i, ii, j, k;
measurestates.setSize(mdur.getSize());
for (i=0; i<measurestates.getSize(); i++) {
measurestates[i].setSize(100);
measurestates[i].setSize(0);
}
for (i=0; i<mdur.getSize(); i++) {
if (mdur[i] == 0) {
continue;
}
for (ii=i; ii<mdur.getSize(); ii++) {
if (infile[ii].isMeasure()) {
i = ii;
break;
}
for (j=0; j<staves[ii].getSize(); j++) {
for (k=0; k<staves[ii][j].getSize(); k++) {
addStaffEntry(measurestates[i], staves[ii][j][k]);
}
}
}
}
for (i=1; i<measurestates.getSize(); i++) {
if (measurestates[i].getSize() > 0) {
continue;
}
if (measurestates[i-1].getSize() > 0) {
measurestates[i] = measurestates[i-1];
}
}
if (debugQ) {
cout << "STAVES BY MEASURE" << endl;
for (i=0; i<mdur.getSize(); i++) {
for (j=0; j<measurestates[i].getSize(); j++) {
cout << measurestates[i][j] << " ";
}
cout << endl;
}
cout << "========================" << endl;
}
}
//////////////////////////////
//
// addStaffEntry --
//
void addStaffEntry(Array & states, double value) {
int i;
for (i=0; i<states.getSize(); i++) {
if (states[i] == value) {
return;
}
}
states.append(value);
}
//////////////////////////////
//
// getMeasureDurations --
//
void getMeasureDurations(Array<RationalNumber>& measuredur,
HumdrumFile& infile) {
measuredur.setSize(infile.getNumLines());
measuredur.setAll(0);
RationalNumber rn;
int i;
int lastmi = -1;
int mi = -1;
int lastdatai = -1;
int datai = -1;
int datamark = 0;
for (i=0; i<infile.getNumLines(); i++) {
if (infile[i].isMeasure()) {
lastmi = mi;
mi = i;
datamark = 1;
continue;
} else if (infile[i].isData() && (infile[i].getDuration() != 0.0)) {
if (datamark) {
datai = i;
datamark = 0;
if ((mi > 0) && (lastmi > 0)) {
rn = infile[mi].getAbsBeatR() - infile[lastmi].getAbsBeatR();
if (lastdatai > 0) {
measuredur[lastdatai] = rn;
}
}
lastdatai = datai;
}
}
}
// process the last measure
if (datamark) {
datai = i;
datamark = 0;
if ((mi > 0) && (lastmi > 0)) {
rn = infile[mi].getAbsBeatR() - infile[lastmi].getAbsBeatR();
if (lastdatai > 0) {
measuredur[lastdatai] = rn;
}
}
lastdatai = datai;
}
if (debugQ) {
cout << "MEASURE DURATIONS" << endl;
for (i=0; i<infile.getNumLines(); i++) {
cout << measuredur[i] << endl;
}
cout << "=======================" << endl;
}
}
//////////////////////////////
//
// printLineDuration --
//
void printLineDuration(HumdrumFile& infile, int line) {
printDuration(infile[line].getDurationR());
}
//////////////////////////////
//
// printDuration --
//
void printDuration(const RationalNumber& duration) {
RationalNumber rn;
rn = duration;
rn /= 4;
if (rn.getNumerator() == 1) {
cout << rn.getDenominator();
} else if (rn.getNumerator() == 3) {
cout << rn.getDenominator()/2 << ".";
} else {
cout << rn.getNumerator() << "%" << rn.getDenominator();
}
}
//////////////////////////////
//
// isMatch -- returns a match if ss is in staves list.
//
int isMatch(int ss, Array& list) {
int i;
for (i=0; i<list.getSize(); i++) {
if (ss == (int)list[i]) {
return 1;
}
}
return 0;
}
//////////////////////////////
//
// printStaves --
//
void printStaves(Array > >& staves) {
int i, j, k;
for (i=0; i<staves.getSize(); i++) {
for (j=0; j<staves[i].getSize(); j++) {
if (staves[i][j].getSize() == 0) {
cout << ".";
} else {
for (k=0; k<staves[i][j].getSize(); k++) {
cout << staves[i][j][k];
if (k < staves[i][j].getSize()-1) {
cout << " ";
}
}
}
if (j<staves[i].getSize()-1) {
cout << "\t";
}
}
cout << "\n";
}
}
//////////////////////////////
//
// getStaves -- get a list of staff ownershipts for each spine cell.
//
void getStaves(Array > >& staves, HumdrumFile& infile) {
staves.setSize(infile.getNumLines());
int i, j;
for (i=0; i<infile.getNumLines(); i++) {
staves[i].setSize(infile[i].getFieldCount());
staves[i].allowGrowth(0);
for (j=0; j<staves[i].getSize(); j++) {
staves[i][j].setSize(0);
}
}
// keep track of the first line which contains a **staff marker in each spine
Array<int> firststaff;
firststaff.setSize(infile.getMaxTracks()+1);
firststaff.setAll(-1);
Array<Array<double> > states;
states.setSize(infile.getMaxTracks()+1);
for (i=0; i<states.getSize(); i++) {
states[i].setSize(0);
}
int track;
for (i=0; i<infile.getNumLines(); i++) {
if (infile[i].isInterpretation()) {
for (j=0; j<infile[i].getFieldCount(); j++) {
track = infile[i].getPrimaryTrack(j);
if (strncmp(infile[i][j], "*staff", strlen("*staff")) == 0) {
if (firststaff[track] == -1) {
firststaff[track] = i;
}
setStaffAssignment(states[track], infile[i][j]);
}
}
}
if (infile[i].isBibliographic()) {
continue;
}
if (infile[i].isGlobalComment()) {
continue;
}
if (infile[i].isEmpty()) {
continue;
}
for (j=0; j<infile[i].getFieldCount(); j++) {
track = infile[i].getPrimaryTrack(j);
staves[i][j] = states[track];
}
}
// at this point the staff assignments should work backwards from the
// first *staff marker upwards, so that the exclusive interpretations
// can be tied to a staff assignment.
int ttrack = -1;
int row = -1;
int k;
int line;
for (track = 1; track < firststaff.getSize(); track++) {
if (firststaff[track] < 0) {
continue;
}
line = firststaff[track];
// keep track of *staff entry
ttrack = -1;
row = -1;
for (j=0; j<staves[line].getSize(); j++) {
ttrack = infile[line].getPrimaryTrack(j);
if (ttrack != track) {
ttrack = -1;
row = -1;
continue;
} else {
row = j;
break;
}
}
if (ttrack < 0) {
continue;
}
if (row < 0) {
continue;
}
states[ttrack] = staves[line][row];
for (i=line-1; i>=0; i--) {
if (infile[i].isBibliographic()) {
continue;
}
if (infile[i].isGlobalComment()) {
continue;
}
if (infile[i].isEmpty()) {
continue;
}
for (j=0; j<infile[i].getFieldCount(); j++) {
k = infile[i].getPrimaryTrack(j);
if (k != ttrack) {
continue;
}
staves[i][j] = states[k];
}
}
}
}
//////////////////////////////
//
// setStaffAssignment --
//
void setStaffAssignment(Array& states, const char* string) {
PerlRegularExpression pre;
Array<Array<char> > pieces;
pre.getTokens(pieces, "[^\\dX.]+", string);
if (pieces.getSize() == 0) {
states.setSize(0);
states.allowGrowth(0);
return;
}
int i;
if (strncmp(pieces[0].getBase(), "X", 1) == 0) {
states.setSize(0);
states.allowGrowth(0);
return;
}
states.setSize(pieces.getSize());
int number;
for (i=0; i<pieces.getSize(); i++) {
// ignoring decimal part for now
number = atoi(pieces[i].getBase());
states[i] = number;
}
}
//////////////////////////////
//
// fillFieldData --
//
void fillFieldData(Array<int>& field, Array<int>& subfield, Array<int>& model,
const char* fieldstring, HumdrumFile& infile) {
int maxtrack = infile.getMaxTracks();
field.setSize(maxtrack);
field.setGrowth(maxtrack);
field.setSize(0);
subfield.setSize(maxtrack);
subfield.setGrowth(maxtrack);
subfield.setSize(0);
model.setSize(maxtrack);
model.setGrowth(maxtrack);
model.setSize(0);
PerlRegularExpression pre;
Array<char> buffer;
buffer.setSize(strlen(fieldstring)+1);
strcpy(buffer.getBase(), fieldstring);
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(field, subfield, model, pre.getSubmatch(), infile);
value = pre.search(buffer.getBase() + start, "^([^,]+,?)");
}
}
//////////////////////////////
//
// processFieldEntry --
// 3-6 expands to 3 4 5 6
// $ expands to maximum spine track
// $-1 expands to maximum spine track minus 1, etc.
//
void processFieldEntry(Array<int>& field, Array<int>& subfield,
Array<int>& model, const char* string, HumdrumFile& infile) {
int maxtrack = infile.getMaxTracks();
int modletter;
int subletter;
PerlRegularExpression pre;
Array<char> buffer;
buffer.setSize(strlen(string)+1);
strcpy(buffer.getBase(), string);
// 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());
int zero = 0;
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: \"" << string << "\""
<< " 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: \"" << string << "\""
<< " 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: \"" << string << "\""
<< " contains number too large at start: " << firstone << endl;
cerr << "Maximum number allowed is " << maxtrack << endl;
exit(1);
}
if (lastone > maxtrack) {
cerr << "Error: range token: \"" << string << "\""
<< " 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);
subfield.append(zero);
model.append(zero);
}
} else {
for (i=firstone; i<=lastone; i++) {
field.append(i);
subfield.append(zero);
model.append(zero);
}
}
} else if (pre.search(buffer.getBase(), "^(\\d+)([a-z]*)")) {
int value = strtol(pre.getSubmatch(1), NULL, 10);
modletter = 0;
subletter = 0;
if (strchr(pre.getSubmatch(2), 'a') != NULL) {
subletter = 'a';
}
if (strchr(pre.getSubmatch(), 'b') != NULL) {
subletter = 'b';
}
if (strchr(pre.getSubmatch(), 'c') != NULL) {
subletter = 'c';
}
if (strchr(pre.getSubmatch(), 'd') != NULL) {
modletter = 'd';
}
if (strchr(pre.getSubmatch(), 'n') != NULL) {
modletter = 'n';
}
if (strchr(pre.getSubmatch(), 'r') != NULL) {
modletter = 'r';
}
if ((value < 1) && (value != 0)) {
cerr << "Error: range token: \"" << string << "\""
<< " 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: \"" << string << "\""
<< " contains number too large at start: " << value << endl;
cerr << "Maximum number allowed is " << maxtrack << endl;
exit(1);
}
field.append(value);
if (value == 0) {
subfield.append(zero);
model.append(zero);
} else {
subfield.append(subletter);
model.append(modletter);
}
}
}
//////////////////////////////
//
// 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);
}
}
//////////////////////////////
//
// checkOptions -- validate and process command-line options.
//
void checkOptions(Options& opts, int argc, char* argv[]) {
opts.define("s|staves=s:", "List of staves to extract");
opts.define("debug=b", "Determine bad input line num");
opts.define("author=b", "Author of program");
opts.define("version=b", "Compilation info");
opts.define("example=b", "Example usages");
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, Oct 2011" << endl;
exit(0);
} else if (opts.getBoolean("version")) {
cout << argv[0] << ", version: 20 Oct 2011" << 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);
}
if (!opts.getBoolean("staves")) {
cerr << "Error: you must specify the -s option" << endl;
exit(1);
}
fieldstring = opts.getString("staves");
debugQ = opts.getBoolean("debug");
}
//////////////////////////////
//
// example -- example usage of the program
//
void example(void) {
cout <<
" \n"
<< endl;
}
//////////////////////////////
//
// usage -- gives the usage statement for the program
//
void usage(const char* command) {
cout <<
" \n"
<< endl;
}
// md5sum: 93a34f9674e91ccff8aa12b5f975c818 staff.cpp [20111105]