//
// Programmer: Craig Stuart Sapp <craig@ccrma.stanford.edu>
// Creation Date: Thu Nov 25 00:40:56 PST 2004
// Last Modified: Tue Apr 14 13:41:35 PDT 2009 (fixed interpretations when
// not appending to original data)
// Last Modified: Wed Jun 24 15:41:09 PDT 2009 (updated for GCC 4.4)
//
// Filename: ...sig/examples/all/tsroot.cpp
// Web Address: http://sig.sapp.org/examples/museinfo/humdrum/tsroot.cpp
// Syntax: C++; museinfo
//
// Description: Analyze the chord root analysis from Temperley &
// Sleator Melisma Music Analysis <i>harmony</i> program
// (2003 version) and optionally append to original **kern data.
//
// Requires the following ancillary programs:
// (1) meter -- Melisma meter program which adds Beat information to
// note data.
// (2) harmony -- Melisma root analysis program which adds chord
// analysis data to meter program output.
// (3) kern2melisma -- museinfo program which converts **kern data into
// melisma Note data.
// (4) harmony2humdrum -- PERL program which converts Melisma harmony
// program output into a temporary Humdrum data file.
//
// For roman-numeral analysis, two more programs are required:
// (5) key -- Melisma key analysis program
// (6) key2humdrum -- PERL program which converts Melisma Roman Numeral
// analysis into a temprary Humdrum data file.
//
// programs are expected to be in two directories:
//
// meldir = set with --meldir option. This is the location of the
// melisma music analyzer command-line programs (meter, harmony, key).
// midir = set with --midir option. This is the location of museinfo programs.
//
// key parameter file contents for roman numeral analysis:
//
// verbosity=2 <-- required for segment times
// default_profile_value = 1.5
// npc_or_tpc_profile=1
// scoring_mode = 1
// segment_beat_level=3
// beat_printout_level=2
// romnums=1 <-- required for roman numeral analysis
// romnum_type=0
// running=0
//
#include "humdrum.h"
#include "stdlib.h"
#include "string.h"
#include "time.h"
#include "stdio.h"
#ifndef OLDCPP
#include <fstream>
using namespace std;
#else
#include <fstream.h>
#endif
///////////////////////////////////////////////////////////////////////////
// function declarations
void checkOptions(Options& opts, int argc, char* argv[]);
void example(void);
void usage(const char* command);
void processFile(const char* filename, HumdrumFile& infile);
void analyzeTiming(HumdrumFile& infile, Array<double>& timings,
Array<double>& tempo);
void getAnalysisTimes(Array<int>& analysistimes,
HumdrumFile& rootanalysis);
void printAnalysis(HumdrumFile& rootanalysis,
HumdrumFile& romananalysis,
HumdrumFile& infile);
int getClosestRootLine(Array<int>& analysistimes, int targettime);
void getDataLineTimings(Array<int>& linetimes, HumdrumFile& infile);
const char* getMatchRoot(int matchline, HumdrumFile& hfile);
void createParameterFile(const char* parametertmpname);
void getRomanAnalysisTimes(Array<int>& romantimes,
HumdrumFile& romananalysis);
void getRomanAnalysisLines(Array<int>& romanlines,
Array<int>& romantimes,
Array<int>& linetimes,
HumdrumFile& romananalysis);
const char* getRomanData(HumdrumFile& romananalysis,
Array<int>& romanlines, int target);
void printRomanKey(HumdrumFile& romananalysis,
Array<int>& romanlines, int target,
HumdrumRecord& aRecord);
double dtempo = 120.0;
// global variables
Options options; // database for command-line arguments
int debugQ = 0; // used with --debug option
int appendQ = 0; // used with -a option
int prependQ = 0; // used with -p option
int verboseQ = 0; // used with -v option
int harmonyQ = 0; // used with -h option
const char* tmpdir = "/tmp"; // used with --tmpdir option
const char* meldir = "/usr/ccarh/melisma/bin"; // used with --meldir option
const char* midir = "/var/www/websites/museinfo/bin"; // used with --midir
///////////////////////////////////////////////////////////////////////////
int main(int argc, char* argv[]) {
#ifndef VISUAL
srand48(time(NULL)*12345); // seed rand num generator with current time
#else
srand(time(NULL)*12345);
#endif
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) {
cout << "Error: you must supply at least one input fileme" << endl;
exit(1);
} else {
filename = options.getArg(1);
infile.read(options.getArg(1));
}
processFile(filename, infile);
}
///////////////////////////////////////////////////////////////////////////
//////////////////////////////
//
// processFile --
//
void processFile(const char* filename, HumdrumFile& infile) {
// first convert the file to melisma format
if (verboseQ) {
cout << "The filename is: " << filename << endl;
}
char buffer[4096] = {0};
const char *ptr = NULL;
ptr = strrchr(filename, '/');
if (ptr != NULL) {
strcpy(buffer, ptr+1);
} else {
strcpy(buffer, filename);
}
if (verboseQ) {
cout << "The filename base is: " << buffer << endl;
}
#ifndef VISUAL
int randval = lrand48();
#else
int randval = rand();
#endif
if (verboseQ) {
cout << "Random value is: " << randval << endl;
cout << "Creating temporary Melisma file: " << endl;
cout << "\t" << tmpdir << "/" << buffer << "." << randval << endl;
}
char commandbuffer[20000] = {0};
char tmpname[1024] = {0}; // output from the harmony2humdrum command
char tmpname2[1024] = {0}; // output from the key2humdrum command
char parametertmpname[1024] = {0}; // key parameter file
char harmonytmpname[1024] = {0};
sprintf(tmpname, "%s/%s.%d", tmpdir, buffer, randval);
sprintf(harmonytmpname, "%s/%s-%s.%d", tmpdir, buffer, "harmony", randval);
sprintf(tmpname2, "%s/%s-%s.%d", tmpdir, buffer, "roman", randval);
sprintf(parametertmpname, "%s/%s-%s.%d", tmpdir, buffer, "parameters", randval);
if (harmonyQ) {
createParameterFile(parametertmpname);
sprintf(commandbuffer,
"%s/kern2melisma %s | egrep -v '(Reference|Comment|Info)' | %s/meter | %s/harmony > %s; %s/harmony2humdrum %s > %s; %s/key -p %s %s | %s/key2humdrum > %s",
midir, filename, meldir, meldir, harmonytmpname, meldir,
harmonytmpname, tmpname,
meldir, parametertmpname, harmonytmpname, meldir, tmpname2);
} else {
sprintf(commandbuffer,
"%s/kern2melisma %s | egrep -v '(Reference|Comment|Info)' | %s/meter | %s/harmony | %s/harmony2humdrum > %s",
midir, filename, meldir, meldir, meldir, tmpname);
}
if (verboseQ) {
cout << "COMMAND: " << commandbuffer << endl;
}
int status = system(commandbuffer);
if (status < 0) {
cout << "ERROR: command not successful:\n" << commandbuffer << endl;
exit(1);
}
// read in the newly created Humdrum file:
HumdrumFile rootanalysis;
rootanalysis.read(tmpname);
HumdrumFile romananalysis;
if (harmonyQ) {
romananalysis.read(tmpname2);
}
printAnalysis(rootanalysis, romananalysis, infile);
// delete temporary files
if (harmonyQ) {
sprintf(commandbuffer, "rm -f %s", tmpname);
if (system(commandbuffer) == -1) {
exit(-1);
}
sprintf(commandbuffer, "rm -f %s", tmpname2);
if (system(commandbuffer) == -1) {
exit(-1);
}
sprintf(commandbuffer, "rm -f %s", harmonytmpname);
if (system(commandbuffer) == -1) {
exit(-1);
}
sprintf(commandbuffer, "rm -f %s", parametertmpname);
if (system(commandbuffer) == -1) {
exit(-1);
}
} else {
sprintf(commandbuffer, "rm -f %s", tmpname);
if (system(commandbuffer) == -1) {
exit(-1);
}
}
}
//////////////////////////////
//
// createParameterFile --
//
void createParameterFile(const char* parametertmpname) {
ofstream pfile(parametertmpname);
if (!pfile.is_open()) {
cout << "Error: cannot write parameter file\n";
exit(1);
}
pfile <<
"verbosity=2\n"
"default_profile_value = 1.5\n"
"npc_or_tpc_profile=1\n"
"scoring_mode = 1\n"
"segment_beat_level=3\n"
"beat_printout_level=2\n"
"romnums=1\n"
"romnum_type=0\n"
"running=0\n"
;
pfile.close();
}
//////////////////////////////
//
// printAnalysis -- match Melismla Music Analyzer output back to Humdrum data.
//
void printAnalysis(HumdrumFile& rootanalysis, HumdrumFile& romananalysis,
HumdrumFile& infile) {
const char* romandatum = ".";
Array<int> analysistimes;
getAnalysisTimes (analysistimes, rootanalysis);
Array<int> linetimes;
getDataLineTimings(linetimes, infile);
Array<int> romantimes;
Array<int> romanlines;
if (harmonyQ) {
getRomanAnalysisTimes(romantimes, romananalysis);
getRomanAnalysisLines(romanlines, romantimes, linetimes, romananalysis);
}
const char* matchroot;
int matchline;
int i;
for (i=0; i<infile.getNumLines(); i++) {
switch (infile[i].getType()) {
case E_humrec_data_comment:
if (prependQ) {
if (harmonyQ) cout << "!\t";
cout << "!\t";
}
if (prependQ || appendQ) {
cout << infile[i];
}
if (appendQ) {
if (harmonyQ) cout << "\t!";
cout << "\t!";
}
cout << endl;
break;
case E_humrec_data_kern_measure:
if (prependQ) {
if (harmonyQ) cout << infile[i][0] << "\t";
cout << infile[i][0] << "\t";
}
if (prependQ || appendQ) {
cout << infile[i];
} else {
cout << infile[i][0];
if (harmonyQ) {
cout << "\t" << infile[i][0];
}
}
if (appendQ) {
if (harmonyQ) cout << "\t" << infile[i][0];
cout << "\t" << infile[i][0];
}
cout << endl;
break;
case E_humrec_interpretation:
if (prependQ) {
if (strcmp(infile[i][0], "*-") == 0) {
if (harmonyQ) cout << "*-\t";
cout << "*-\t";
} else if (strncmp(infile[i][0], "**", 2) == 0) {
if (harmonyQ) cout << "**tshrm\t";
cout << "**tsroot\t";
} else if (infile[i].equalFieldsQ()) {
if ((!infile[i].isSpineManipulator(0)) &&
(strchr(infile[i][0], ':') == NULL) ) {
if (harmonyQ) cout << infile[i][0] << "\t";
cout << infile[i][0] << "\t";
} else {
if (harmonyQ) cout << "*\t";
cout << "*\t";
}
} else {
if (harmonyQ) cout << "*\t";
cout << "*\t";
}
}
if (prependQ || appendQ) {
cout << infile[i];
} else {
if (strncmp(infile[i][0], "**", 2) == 0) {
if (harmonyQ) {
cout << "**tsharm\t";
}
cout << "**tsroot";
} else if (strcmp(infile[i][0], "*-") == 0) {
if (harmonyQ) {
cout << "*-\t";
}
cout << "*-";
} else if (infile[i].equalFieldsQ()) {
if (!infile[i].isSpineManipulator(0)) {
if (harmonyQ) cout << infile[i][0] << "\t";
cout << infile[i][0];
} else {
if (harmonyQ) cout << "*\t";
cout << "*";
}
} else {
if (harmonyQ) cout << "*\t";
cout << "*";
}
}
if (appendQ) {
if (strcmp(infile[i][0], "*-") == 0) {
if (harmonyQ) cout << "\t*-";
cout << "\t*-";
} else if (strncmp(infile[i][0], "**", 2) == 0) {
if (harmonyQ) cout << "**tshrm\t";
cout << "**tsroot\t";
} else if (infile[i].equalFieldsQ()) {
if ((!infile[i].isSpineManipulator(0)) &&
(strchr(infile[i][0], ':') == NULL) ) {
if (harmonyQ) cout << "\t" << infile[i][0];
cout << "\t" << infile[i][0];
} else {
if (harmonyQ) cout << "\t*";
cout << "\t*";
}
} else {
if (harmonyQ) cout << "\t*";
cout << "\t*";
}
}
cout << endl;
break;
case E_humrec_data:
if (harmonyQ) {
printRomanKey(romananalysis, romanlines, i, infile[i]);
}
// matchline = linetimes[i];
// temporary fix for 12/8 meter analysis problem:
// matchline = getClosestRootLine(analysistimes, linetimes[i]*2);
matchline = getClosestRootLine(analysistimes, linetimes[i]);
matchroot = getMatchRoot(matchline, rootanalysis);
if (harmonyQ) {
romandatum = getRomanData(romananalysis, romanlines, i);
}
if (prependQ) {
if (harmonyQ) cout << romandatum << "\t";
cout << matchroot;
cout << "\t";
}
if (prependQ || appendQ) {
cout << infile[i];
} else {
if (harmonyQ) cout << romandatum << "\t";
cout << matchroot;
}
if (appendQ) {
if (harmonyQ) cout << "\t" << romandatum;
cout << "\t";
cout << matchroot;
}
cout << endl;
break;
case E_humrec_none:
case E_humrec_empty:
case E_humrec_global_comment:
case E_humrec_bibliography:
default:
cout << infile[i] << endl;
}
}
if (harmonyQ && debugQ) {
cout << romananalysis;
cout << endl;
}
}
//////////////////////////////
//
// printRomanKey --
//
void printRomanKey(HumdrumFile& romananalysis, Array<int>& romanlines,
int target, HumdrumRecord& aRecord) {
int i;
int j;
for (i=0; i<romanlines.getSize(); i++) {
if (romananalysis[i].getType() != E_humrec_interpretation) {
continue;
}
if (target < romanlines[i]) {
return;
}
if (target > romanlines[i]) {
continue;
}
// target == romanlines[i]: assume first column contains data
if (strchr(romananalysis[i][0], ':') == NULL) {
continue;
}
// found a key interpretation which should be printed
if (prependQ) {
cout << romananalysis[i][0] << "\t";
cout << romananalysis[i][0] << "\t";
}
if (prependQ || appendQ) {
for (j=0; j<aRecord.getFieldCount(); j++) {
cout << "*";
if (j < aRecord.getFieldCount() - 1) {
cout << "\t";
}
}
} else {
if (harmonyQ) {
cout << romananalysis[i][0] << "\t";
}
cout << romananalysis[i][0];
}
if (appendQ) {
cout << "\t" << romananalysis[i][0];
cout << "\t" << romananalysis[i][0];
}
cout << "\n";
romanlines[i] = -1; // make sure the that key will not be printed again
break;
}
}
//////////////////////////////
//
// getRomanData --
//
const char* getRomanData(HumdrumFile& romananalysis, Array<int>& romanlines,
int target) {
int i;
for (i=0; i<romanlines.getSize(); i++) {
if (romananalysis[i].getType() != E_humrec_data) {
continue;
}
if (target < romanlines[i]) {
return ".";
}
if (target > romanlines[i]) {
continue;
}
// target == romanlines[i]: assume first column contains data
if (strcmp(romananalysis[i][0], "|") == 0) {
continue;
}
return romananalysis[i][0];
}
// most likely some sort of error if got to here
return ".";
}
//////////////////////////////
//
// getRomanAnalysisLines --
//
void getRomanAnalysisLines(Array<int>& romanlines, Array<int>& romantimes,
Array<int>& linetimes, HumdrumFile& romananalysis) {
romanlines.setSize(romantimes.getSize());
romanlines.setAll(-1);
int i;
for (i=0; i<romantimes.getSize(); i++) {
romanlines[i] = getClosestRootLine(linetimes, romantimes[i]);
}
if (debugQ) {
for (i=0; i<romananalysis.getNumLines(); i++) {
cout << romananalysis[i] << "\t" << romantimes[i]
<< "\t" << romanlines[i] << "\n";
}
}
}
//////////////////////////////
//
// getRomanAnalysisTimes --
//
void getRomanAnalysisTimes(Array& romantimes, HumdrumFile& romananalysis) {
romantimes.setSize(romananalysis.getNumLines());
romantimes.setAll(-1);
int i, j;
long timeval;
for (i=0; i<romananalysis.getNumLines(); i++) {
if (romananalysis[i].getType() != E_humrec_data) {
continue;
}
for (j=0; j<romananalysis[i].getFieldCount(); j++) {
if (strcmp(romananalysis[i].getExInterp(j), "**time") != 0) {
continue;
}
if (strcmp(romananalysis[i][j], ".") == 0) {
romantimes[i] = -100;
} else {
timeval = strtol(romananalysis[i][j], (char**)NULL, 10);
romantimes[i] = timeval;
}
}
}
int k;
int count = 0;
int countother = 0;
int endingtime = 0;
double increment = 0.0;
for (i=0; i<romantimes.getSize(); i++) {
if (romantimes[i] >= 0) {
count = 0;
countother = 0;
endingtime = -1;
j = i+1;
while (j < romantimes.getSize()) {
if (romantimes[j] < -10) {
count++;
} else if (romantimes[j] < 0) {
countother++;
} else {
endingtime = romantimes[j];
break;
}
j++;
}
if (count > 0) {
increment = 1.0*(endingtime - romantimes[i])/count;
} else {
increment = 0.0;
}
count = 0;
k = i+1;
while (k<romantimes.getSize()) {
if (romantimes[k] < -10) {
romantimes[k] = (int)(romantimes[i] + increment * count + 0.5);
count++;
} else if (romantimes[k] >= 0) {
break;
}
k++;
}
i = j-1;
}
}
for (i=romantimes.getSize()-2; i>=0; i--) {
if (romantimes[i] == -1) {
romantimes[i] = romantimes[i+1];
}
}
for (i=1; i<romantimes.getSize(); i++) {
if (romantimes[i] == -1) {
romantimes[i] = romantimes[i-1];
}
}
// for (i=0; i<romananalysis.getNumLines(); i++) {
// cout << romananalysis[i] << "\t" << romantimes[i] << "\n";
// }
}
//////////////////////////////
//
// getMatchRoot -- return the **tsroot spine info on the given line;
//
const char* getMatchRoot(int matchline, HumdrumFile& hfile) {
int i;
if (hfile.getNumLines() <= matchline) {
return "X";
} else if (matchline < 0) {
return "r";
} else if (hfile[matchline].getType() != E_humrec_data) {
return "Z";
}
for (i=0; i<hfile[i].getFieldCount(); i++) {
if (strcmp("**tsroot", hfile[matchline].getExInterp(i)) == 0) {
return hfile[matchline][i];
}
}
// **tsroot data not found
return "W";
}
//////////////////////////////
//
// getClosestRootLine -- get the nearst root analysis line number
// given the input time (which is rounded to the nearest 5 ms).
// Returns -1 if there is no nearest time value.
//
int getClosestRootLine(Array& analysistimes, int targettime) {
int i;
int diff = 0;
int mindiff = 100000;
int mindiffline = -1;
for (i=0; i<analysistimes.getSize(); i++) {
diff = targettime - analysistimes[i];
if (diff < 0) {
diff = -diff;
}
if (diff < 5) {
return i;
}
if (diff < mindiff) {
mindiff = diff;
mindiffline = i;
}
}
if (mindiff < 20) {
return mindiffline;
} else {
return -mindiff;
}
}
//////////////////////////////
//
// getAnalysisTimes -- get the analysis times from the data
//
void getAnalysisTimes(Array& analysistimes, HumdrumFile& rootanalysis) {
int i;
analysistimes.setSize(rootanalysis.getNumLines());
analysistimes.allowGrowth(0);
analysistimes.setAll(-10000);
int j;
int datavalue = 0;
for (i=0; i<rootanalysis.getNumLines(); i++) {
if (rootanalysis[i].getType() == E_humrec_data) {
for (j=0; j<rootanalysis[i].getFieldCount(); j++) {
if (strcmp("**time", rootanalysis[i].getExInterp(j)) == 0) {
// found a **time data record on the line, so store the data
datavalue = atoi(rootanalysis[i][j]);
analysistimes[i] = datavalue;
}
}
}
}
}
//////////////////////////////
//
// checkOptions -- validate and process command-line options.
//
void checkOptions(Options& opts, int argc, char* argv[]) {
opts.define("r|roman=b", "add roman numeral analysis as well");
opts.define("v|verbose=b", "display intermediate command");
opts.define("a|append=b", "append analysis to right side of input data");
opts.define("p|prepend=b", "prepend analysis to left side of input data");
opts.define("tmpdir=s:/tmp", "temporary directory for intermediate output");
opts.define("meldir=s:/usr/ccarh/melisma/bin", "melisma command dir.");
opts.define("midir=s:/var/www/websites/museinfo/bin", "museinfo command dir.");
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, Nov 2004" << endl;
exit(0);
} else if (opts.getBoolean("version")) {
cout << argv[0] << ", version: 23 Nov 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);
}
debugQ = opts.getBoolean("debug");
tmpdir = opts.getString("tmpdir");
meldir = opts.getString("meldir");
midir = opts.getString("midir");
appendQ = opts.getBoolean("append");
prependQ = opts.getBoolean("prepend");
harmonyQ = opts.getBoolean("roman");
if (prependQ) {
appendQ = 0;
}
}
//////////////////////////////
//
// 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;
}
//////////////////////////////
//
// analyzeTiming -- determing the absolute time position of each
// line in the file. (Borrowed from the addtime program).
//
void analyzeTiming(HumdrumFile& infile, Array<double>& timings,
Array<double>& tempo) {
infile.analyzeRhythm("4");
timings.setSize(infile.getNumLines());
timings.setAll(0.0);
tempo.setSize(infile.getNumLines());
tempo.setAll(dtempo);
tempo[0] = dtempo;
double currtempo = dtempo;
double input = 0.0;
int count;
int i;
for (i=1; i<infile.getNumLines(); i++) {
// check for new tempo...
if (strncmp(infile[i][0], "*MM", 3) == 0) {
count = sscanf(infile[i][0], "*MM%lf", &input);
if (count == 1) {
currtempo = input;
if (i > 0) {
tempo[i-1] = currtempo;
}
tempo[i] = currtempo;
}
}
tempo[i] = currtempo;
timings[i] = timings[i-1] + (infile[i].getAbsBeat() -
infile[i-1].getAbsBeat()) * 60.0/currtempo;
}
}
//////////////////////////////
//
// getDataLineTimings -- borrowed and modified from addtime program.
//
void getDataLineTimings(Array& linetimes, HumdrumFile& infile) {
Array<double> timings;
Array<double> tempo;
analyzeTiming(infile, timings, tempo);
linetimes.setSize(infile.getNumLines());
linetimes.allowGrowth(0);
linetimes.setAll(-5000);
int i;
int offset = 0;
for (i=0; i<infile.getNumLines(); i++) {
switch (infile[i].getType()) {
case E_humrec_data:
linetimes[i] = (int)((timings[i] + offset + 0.0005) * 1000);
break;
case E_humrec_global_comment:
case E_humrec_bibliography:
case E_humrec_none:
case E_humrec_empty:
case E_humrec_data_comment:
case E_humrec_data_measure:
case E_humrec_data_interpretation:
default:
break;
}
}
}
// md5sum: eba16e614e3343bbd91df5abe111f924 tsroot.cpp [20090626]