//
// Programmer: Craig Stuart Sapp <craig@ccrma.stanford.edu>
// Creation Date: Wed May 22 19:58:17 PDT 2002
// Last Modified: Wed May 22 19:58:22 PDT 2002
// Filename: ...sig/examples/all/tetra.cpp
// Web Address: http://sig.sapp.org/examples/museinfo/humdrum/tetra.cpp
// Syntax: C++; museinfo
//
// Description: Identify melodic ascending/decending tetrachords in
// each spine.
// Repeated internal note in a tetrachord are not
// considered as creating a tetrachord.
// will only look at the first note of a chord.
//
#include "humdrum.h"
#include <string.h>
#include <time.h>
#include <ctype.h>
//////////////////////////////////////////////////////////////////////////
class TetraUnit {
public:
TetraUnit(void) { clear(); };
void clear(void) {
pitch = spine = line = type = direction = position = 0;
};
int type; // M=major; R=minor; N=natural; H=harmonic
int direction; // 1=ascending, -1=decending
int position; // 1=first note of tetrachord, etc.
int line; // line number in file for data.
int spine; // spine to which analysis belongs.
int pitch; // pitch for the particular note
};
ostream& operator<<(ostream& out, TetraUnit& tetra) {
if (tetra.direction == 1) {
out << (char)tetra.type << tetra.position;
} else {
out << (char)tolower(tetra.type) << tetra.position;
}
return out;
}
typedef Array<TetraUnit> TetraArray;
typedef Array<TetraArray> ArrayTetraArray;
typedef Array<int> ArrayInt;
//////////////////////////////////////////////////////////////////////////
// function declarations:
void checkOptions(Options& opts, int argc, char** argv);
void example(void);
void usage(const char* command);
void generateAnalysis(HumdrumFile& infile,
Array<ArrayTetraArray>& tetraAnalysis,
Array<int>& primaryTracks);
void printAnalysis(HumdrumFile& infile,
Array<ArrayTetraArray>& tetraAnalysis,
Array<int>& primaryTracks);
void getPitchArray(HumdrumFile& infile, Array<ArrayInt>& pitches,
Array<ArrayInt>& lines,
Array<ArrayInt>& spines,
Array<int>& primaryTracks);
int convertTrackToIndex(int track, Array<int>& primaryTracks);
void findTetraChords(Array<TetraArray>& tetras, Array<int>& pitches,
Array<int>& lines, Array<int>& spines);
void identifyTetraChord(Array<TetraArray>& tetras,
Array<int>& lines, Array<int>& spines,
int currline, Array<int>& pitches, int note1,
int note2, int note3, int note4);
int intcompare(const void* a, const void* b);
// User interface variables:
Options options;
int debugQ = 0; // used with --debug option
int rawQ = 0; // used with -r option
int binaryQ = 0; // used with -b option
int appendQ = 0; // used with -a option
int pitchQ = 0;; // used with -p option
//////////////////////////////////////////////////////////////////////////
int main(int argc, char** argv) {
// process the command-line options
checkOptions(options, argc, argv);
HumdrumFile infile;
infile.read(options.getArg(1));
Array<ArrayTetraArray> tetraAnalysis;
Array<int> primaryTracks;
generateAnalysis(infile, tetraAnalysis, primaryTracks);
printAnalysis(infile, tetraAnalysis, primaryTracks);
return 0;
}
//////////////////////////////////////////////////////////////////////////
//////////////////////////////
//
// generateAnalysis --
//
void generateAnalysis(HumdrumFile& infile,
Array<ArrayTetraArray>& tetraAnalysis, Array<int>& primaryTracks) {
Array<ArrayInt> pitches;
Array<ArrayInt> lines;
Array<ArrayInt> spines;
getPitchArray(infile, pitches, lines, spines, primaryTracks);
int i;
int j;
if (pitchQ) {
for (i=0; i<pitches.getSize(); i++) {
cout << i << ":\t";
for (j=0; j<pitches[i].getSize(); j++) {
cout << 12 + Convert::base40ToMidiNoteNumber(pitches[i][j]) << " ";
}
cout << endl;
}
exit(1);
}
tetraAnalysis.setSize(pitches.getSize());
for (i=0; i<pitches.getSize(); i++) {
findTetraChords(tetraAnalysis[i], pitches[i], lines[i], spines[i]);
}
}
//////////////////////////////
//
// findTetraChords --
//
void findTetraChords(Array<TetraArray>& tetras, Array<int>& pitches,
Array<int>& lines, Array<int>& spines) {
int i;
tetras.setSize(pitches.getSize());
for (i=0; i<tetras.getSize(); i++) {
tetras[i].setSize(0);
}
Array<int> base12;
base12.setSize(pitches.getSize());
for (i=0; i<pitches.getSize(); i++) {
if (pitches[i] > 0) {
base12[i] = Convert::base40ToMidiNoteNumber(pitches[i]);
} else {
base12[i] = -1; // rest
}
}
for (i=0; i<base12.getSize()-4; i++) {
identifyTetraChord(tetras, lines, spines, i, pitches,
base12[i], base12[i+1], base12[i+2], base12[i+3]);
}
}
//////////////////////////////
//
// identifyTetraChord --
//
// M Major tetrachord 2-2-1 C-D-E-F
// R Minor tetrachord 2-1-2 C-D-Ef-F
// N Natural tetrachord 1-2-2 C-Df-Ef-F
// H Harmonic tetrachord 1-3-1 C-Df-E-F
//
//
void identifyTetraChord(Array<TetraArray>& tetras, Array<int>& lines,
Array<int>& spines, int currline, Array<int>& pitches, int note1,
int note2, int note3, int note4) {
TetraUnit tetra;
int n[4];
n[0] = note1; n[1] = note2; n[2] = note3; n[3] = note4;
qsort(n, 4, sizeof(int), intcompare);
// min to max must be a perfect fourth:
if (n[3]-n[0] != 5) {
return;
}
// no duplicate numbers allowed:
if (n[1] == n[0] || n[2] == n[1] || n[3] == n[2]) {
return;
}
int type = 'X';
if (n[1] - n[0] == 2 && n[2] - n[1] == 2) type = 'M';
else if (n[1] - n[0] == 2 && n[2] - n[1] == 1) type = 'R';
else if (n[1] - n[0] == 1 && n[2] - n[1] == 2) type = 'N';
else if (n[1] - n[0] == 1 && n[2] - n[1] == 3) type = 'H';
int mapping[4] = {0};
if (note1 == n[0]) mapping[0] = 1;
else if (note1 == n[1]) mapping[0] = 2;
else if (note1 == n[2]) mapping[0] = 3;
else if (note1 == n[3]) mapping[0] = 4;
if (note2 == n[0]) mapping[1] = 1;
else if (note2 == n[1]) mapping[1] = 2;
else if (note2 == n[2]) mapping[1] = 3;
else if (note2 == n[3]) mapping[1] = 4;
if (note3 == n[0]) mapping[2] = 1;
else if (note3 == n[1]) mapping[2] = 2;
else if (note3 == n[2]) mapping[2] = 3;
else if (note3 == n[3]) mapping[2] = 4;
if (note4 == n[0]) mapping[3] = 1;
else if (note4 == n[1]) mapping[3] = 2;
else if (note4 == n[2]) mapping[3] = 3;
else if (note4 == n[3]) mapping[3] = 4;
int direction = 0;
if (mapping[0] < mapping[3]) {
direction = +1;
} else {
direction = -1;
}
// type; // M=major; R=minor; N=natural; H=harmonic
// direction; // 1=ascending, -1=decending
// position; // 1=first note of tetrachord, etc.
// line; // line number in file for data.
// spine; // spine to which analysis belongs.
// pitch; // pitch for the particular note
int j;
for (j=0; j<4; j++) {
tetra.clear();
tetra.direction = direction;
tetra.type = type;
tetra.position = mapping[j];
tetra.line = lines[currline+j];
tetra.spine = spines[currline+j];
tetra.pitch = pitches[currline+j];
tetras[currline+j].append(tetra);
}
}
//////////////////////////////
//
// getPitchArray --
//
void getPitchArray(HumdrumFile& infile, Array<ArrayInt>& pitches,
Array<ArrayInt>& lines, Array<ArrayInt>& spines,
Array<int>& primaryTracks) {
int index;
int pitch;
int track;
primaryTracks.setSize(0);
int i;
int j;
for (i=0; i<infile.getNumLines(); i++) {
if (strncmp(infile[i][0], "**", 2) == 0) {
for (j=0; j<infile[i].getFieldCount(); j++) {
if (strcmp(infile[i].getExInterp(j), "**kern") == 0) {
primaryTracks.appendcopy(j+1);
}
}
if (primaryTracks.getSize() == 0) {
cout << "Error: no **kern data in file" << endl;
exit(1);
}
if (debugQ) {
cout << "There are " << primaryTracks.getSize()
<< "**kern spines" << endl;
}
pitches.setSize(primaryTracks.getSize());
lines.setSize(primaryTracks.getSize());
spines.setSize(primaryTracks.getSize());
for (j=0; j<pitches.getSize(); j++) {
pitches[j].setSize(infile.getNumLines());
pitches[j].setSize(0);
lines[j].setSize(infile.getNumLines());
lines[j].setSize(0);
spines[j].setSize(infile.getNumLines());
spines[j].setSize(0);
}
}
if (!infile[i].isData()) {
continue;
}
for (j=0; j<infile[i].getFieldCount(); j++) {
if (strcmp(infile[i].getExInterp(j), "**kern") != 0) {
continue;
}
if (strchr(infile[i][j], ']') != NULL) {
// ignore ending notes in ties
continue;
}
if (strchr(infile[i][j], '_') != NULL) {
// ignore continuing notes in ties
continue;
}
if (strcmp(infile[i][j], ".") == 0) {
// ignore null tokens
continue;
}
track = infile[i].getPrimaryTrack(j);
index = convertTrackToIndex(track, primaryTracks);
pitch = Convert::kernToBase40(infile[i][j]);
pitches[index].append(pitch);
lines[index].append(i);
spines[index].append(track);
}
}
}
//////////////////////////////
//
// convertTrackToIndex --
//
int convertTrackToIndex(int track, Array& primaryTracks) {
int output = -1;
int i;
for (i=0; i<primaryTracks.getSize(); i++) {
if (track == primaryTracks[i]) {
output = i;
break;
}
}
return output;
}
//////////////////////////////
//
// printAnalysis --
//
void printAnalysis(HumdrumFile& infile,
Array<ArrayTetraArray>& tetraAnalysis,
Array<int>& primaryTracks) {
int i; int j; int k;
int count;
int index;
Array<int> currentline;
currentline.setSize(tetraAnalysis.getSize());
currentline.setAll(0);
for (i=0; i<infile.getNumLines(); i++) {
switch (infile[i].getType()) {
case E_humrec_data_comment:
for (j=0; j<infile[i].getFieldCount(); j++) {
if ((j<infile[i].getFieldCount() - 1) &&
infile[i].getPrimaryTrack(j) ==
infile[i].getPrimaryTrack(j+1)) {
cout << infile[i][j] << "\t";
continue;
}
if (strcmp(infile[i].getExInterp(j), "**kern") == 0) {
cout << infile[i][j] << "\t" << "!";
} else {
cout << infile[i][j];
}
if (j<infile[i].getFieldCount()-1) {
cout << "\t";
}
}
cout << "\n";
break;
case E_humrec_data_kern_measure:
for (j=0; j<infile[i].getFieldCount(); j++) {
if ((j<infile[i].getFieldCount() - 1) &&
infile[i].getPrimaryTrack(j) ==
infile[i].getPrimaryTrack(j+1)) {
cout << infile[i][j] << "\t";
continue;
}
if (strcmp(infile[i].getExInterp(j), "**kern") == 0) {
cout << infile[i][j] << "\t" << infile[i][0];
} else {
cout << infile[i][j];
}
if (j<infile[i].getFieldCount()-1) {
cout << "\t";
}
}
cout << "\n";
break;
case E_humrec_interpretation:
for (j=0; j<infile[i].getFieldCount(); j++) {
if ((j<infile[i].getFieldCount() - 1) &&
infile[i].getPrimaryTrack(j) ==
infile[i].getPrimaryTrack(j+1)) {
cout << infile[i][j] << "\t";
continue;
}
if (strcmp(infile[i].getExInterp(j), "**kern") == 0) {
if (strncmp(infile[i][0], "**", 2) == 0) {
cout << infile[i][j] << "\t" << "**tetra";
} else if (strcmp(infile[i][0], "*-") == 0) {
cout << infile[i][j] << "\t" << "*-";
} else {
cout << infile[i][j] << "\t" << "*";
}
} else {
cout << infile[i][j];
}
if (j<infile[i].getFieldCount()-1) {
cout << "\t";
}
}
cout << "\n";
break;
case E_humrec_data:
for (j=0; j<infile[i].getFieldCount(); j++) {
if ((j<infile[i].getFieldCount() - 1) &&
infile[i].getPrimaryTrack(j) ==
infile[i].getPrimaryTrack(j+1)) {
cout << infile[i][j] << "\t";
continue;
}
if (strcmp(infile[i].getExInterp(j), "**kern") == 0) {
index = convertTrackToIndex(infile[i].getPrimaryTrack(j),
primaryTracks);
cout << infile[i][j] << "\t";
// print analysis here if any
if (currentline[index] >= tetraAnalysis[index].getSize()) {
count = 0;
} else {
count = tetraAnalysis[index][currentline[index]].getSize();
}
if ((count > 0) &&
(tetraAnalysis[index][currentline[index]][0].line > i)) {
cout << ".";
} else if ((count > 0) &&
(tetraAnalysis[index][currentline[index]][0].line==i)){
if (binaryQ) {
cout << "x";
} else {
for (k=0; k<count; k++) {
cout << tetraAnalysis[index][currentline[index]][k];
if (k < count - 1) {
cout << " ";
}
}
}
currentline[index]++;
} else {
cout << ".";
currentline[index]++;
}
} else {
cout << infile[i][j];
}
if (j<infile[i].getFieldCount()-1) {
cout << "\t";
}
}
cout << "\n";
/* index = convertTrackToIndex(track, primaryTracks);
int index;
Array<int> currentline;
currentline.setSize(tetraAnalysis.getSize());
currentlines.setAll(0);
*/
break;
case E_humrec_none:
case E_humrec_empty:
case E_humrec_global_comment:
case E_humrec_bibliography:
default:
cout << infile[i] << "\n";
break;
}
}
/* for (i=0; i<tetraAnalysis.getSize(); i++) {
cout << i << ":" << endl;
for (j=0; j<tetraAnalysis[i].getSize(); j++) {
for (k=0; k<tetraAnalysis[i][j].getSize(); k++) {
cout << tetraAnalysis[i][j][k] << " ";
}
cout << endl;
}
cout << endl;
}
*/
}
//////////////////////////////
//
// checkOptions --
//
void checkOptions(Options& opts, int argc, char* argv[]) {
opts.define("debug=b", "print debug information");
opts.define("r|raw=b", "print only raw data");
opts.define("a|append=b", "append analysis to input data");
opts.define("b|binary=b", "print x if note is part of tetrachord");
opts.define("p|pitch=b", "display pitch sequences only");
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, May 2002" << endl;
exit(0);
} else if (opts.getBoolean("version")) {
cout << argv[0] << ", version: 22 May 2002" << 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);
}
rawQ = opts.getBoolean("raw");
appendQ = opts.getBoolean("append");
binaryQ = opts.getBoolean("binary");
pitchQ = opts.getBoolean("pitch");
debugQ = opts.getBoolean("debug");
}
//////////////////////////////
//
// example --
//
void example(void) {
}
//////////////////////////////
//
// usage --
//
void usage(const char* command) {
}
//////////////////////////////
//
// intcompare -- compare two integers for ordering
//
int intcompare(const void* a, const void* b) {
if (*((int*)a) < *((int*)b)) {
return -1;
} else if (*((int*)a) > *((int*)b)) {
return 1;
} else {
return 0;
}
}
// md5sum: ec1096b0b68e1c672ad8147e6c7b0e6c tetra.cpp [20050403]