//
// Programmer: Craig Stuart Sapp <craig@ccrma.stanford.edu>
// Creation Date: Tue Mar 5 21:32:27 PST 2002
// Last Modified: Thu Sep 25 17:47:16 PDT 2003 (minor bug fixes)
// Filename: ...sig/examples/all/esac2hum.cpp
// Web Address: http://sig.sapp.org/examples/museinfo/humdrum/esac2hum.cpp
// Syntax: C++; museinfo
//
// Description: Converts an EsAC file into Humdrum.
//
#include "humdrum.h"
#include <string.h>
#include <ctype.h>
#include <stdio.h>
#include <math.h>
#ifndef OLDCPP
#include <iostream>
#include <fstream>
using namespace std;
#else
#include <iostream.h>
#include <fstream.h>
#endif
typedef struct { char c[260]; } char260;
class NoteData {
public:
NoteData(void) { clear(); }
void clear(void) { bar = pitch = phstart = phend = 0;
phnum = -1;
lyricerr = lyricnum = 0;
tiestart = tiecont = tieend = 0;
slstart = slend = 0;
num = denom = barnum = 0;
barinterp = 0; bardur = 0.0;
duration = 0.0; text[0] = '\0'; }
double duration;
int bar; int num;
int denom; int barnum;
double bardur; int barinterp;
int pitch; int lyricerr;
int phstart; int phend; int phnum;
int slstart; int slend; int lyricnum;
int tiestart; int tiecont; int tieend;
char text[32];
};
// function declarations:
void checkOptions(Options& opts, int argc, char** argv);
void example(void);
void usage(const char* command);
void convertEsacToHumdrum(const char* filename);
void getSong(Array<char260>& song, ifstream& infile,
int init);
void convertSong(Array<char260>& song, ostream& out);
void getKeyInfo(Array<char260>& song, char260& key,
double& mindur, int& tonic, char260& meter,
ostream& out);
void printNoteData(NoteData& data, int textQ, ostream& out);
void getNoteList(Array<char260>& song,
Array<NoteData>& songdata, double mindur,
int tonic);
void getMeterInfo(char260& meter, Array<int>& numerator,
Array<int>& denominator);
void postProcessSongData(Array<NoteData>& songdata,
Array<int>& numerator,Array<int>& denominator);
void printKeyInfo(Array<NoteData>& songdata, int tonic,
int textQ, ostream& out);
int getAccidentalMax(int a, int b, int c);
void printTitleInfo(Array<char260>& song, ostream& out);
void getLineRange(Array<char260>& song, const char* field,
int& start, int& stop);
void printChar(unsigned char c, ostream& out);
void printBibInfo(Array<char260>& song, ostream& out);
void printString(const char* string, ostream& out);
void printSpecialChars(ostream& out);
void placeLyrics(Array<char260>& song,
Array<NoteData>& songdata);
void placeLyricPhrase(Array<NoteData>& songdata,
Array<char260>& lyrics, int line);
void getLyrics(Array<char260>& lyrics, const char* buffer);
void cleanupLyrics(Array<char260>& lyrics);
void getFileContents(Array<char260>& array, const char* filename);
void chopExtraInfo(char260& holdbuffer);
// User interface variables:
Options options;
int debugQ = 0; // used with the --debug option
int verboseQ = 0; // used with the -v option
int splitQ = 0; // used with the -s option
int firstfilenum = 1; // used with the -f option
Array<char260> header; // used with the -h option
Array<char260> trailer; // used with the -t option
char fileextension[128] = {0}; // used with the -x option
char namebase[1024] = {0}; // used with the -s option
// Global variables:
Array<int> chartable; // used with printChars() and printSpecialChars()
int inputline = 0;
#define ND_NOTE 0 /* notes or rests + text and phrase markings */
#define ND_BAR 1 /* explicit barlines */
//////////////////////////////////////////////////////////////////////////
int main(int argc, char** argv) {
chartable.setSize(256);
chartable.zero();
// process the command-line options
checkOptions(options, argc, argv);
int i;
for (i=1; i<=options.getArgCount(); i++) {
convertEsacToHumdrum(options.getArg(i));
}
return 0;
}
//////////////////////////////////////////////////////////////////////////
//////////////////////////////
//
// convertEsacToHumdrum --
//
void convertEsacToHumdrum(const char* filename) {
#ifndef OLDCPP
ifstream infile(filename);
#else
ifstream infile(filename, ios::nocreate);
#endif
if (verboseQ) {
cout << "processing file " << filename << " ..." << endl;
}
if (!infile.is_open()) {
cerr << "Error: cannot open file: " << filename << endl;
exit(1);
}
Array<char260> song;
song.setSize(400);
song.setGrowth(400);
song.setSize(0);
int init = 0;
int filecounter = firstfilenum;
char outfilename[1024] = {0};
char numberstring[16] = {0};
ofstream outfile;
while (!infile.eof()) {
if (debugQ) {
cout << "Getting a song..." << endl;
}
getSong(song, infile, init);
if (debugQ) {
cout << "Got a song ..." << endl;
}
init = 1;
if (splitQ) {
strcpy(outfilename, namebase);
sprintf(numberstring, "%d", filecounter);
if (filecounter < 1000) {
strcat(outfilename, "0");
}
if (filecounter < 100) {
strcat(outfilename, "0");
}
if (filecounter < 10) {
strcat(outfilename, "0");
}
strcat(outfilename, numberstring);
strcat(outfilename, fileextension);
filecounter++;
#ifndef OLDCPP
outfile.open(outfilename);
#else
outfile.open(outfilename, ios::noreplace);
#endif
if (!outfile.is_open()) {
cout << "Error: cannot write to file: " << outfilename << endl;
}
convertSong(song, outfile);
outfile.close();
} else {
convertSong(song, cout);
}
}
}
//////////////////////////////
//
// getSong -- get a song from the ESac file
//
void getSong(Array& song, ifstream& infile, int init) {
static char260 holdbuffer;
song.setSize(0);
if (init) {
// do nothing holdbuffer has the CUT[] information
} else {
strcpy(holdbuffer.c, "");
while (!infile.eof() && strncmp(holdbuffer.c, "CUT[", 4) != 0) {
infile.getline(holdbuffer.c, 256, '\n');
if (verboseQ) {
cout << "INLINE: " << holdbuffer.c << endl;
}
}
if (infile.eof()) {
return;
}
}
song.setSize(1);
strcpy(song[0].c, holdbuffer.c);
infile.getline(holdbuffer.c, 256, '\n');
chopExtraInfo(holdbuffer);
inputline++;
if (verboseQ) {
cout << "INLINE: " << holdbuffer.c << endl;
}
while (!infile.eof() && strncmp(holdbuffer.c, "CUT[", 4) != 0) {
song.setSize(song.getSize()+1);
strcpy(song[song.getSize()-1].c, holdbuffer.c);
infile.getline(holdbuffer.c, 256, '\n');
chopExtraInfo(holdbuffer);
inputline++;
if (verboseQ) {
cout << "INLINE: " << holdbuffer.c << endl;
}
}
}
//////////////////////////////
//
// chopExtraInfo -- remove phrase number information from Luxembourg data.
//
void chopExtraInfo(char260& holdbuffer) {
int length = strlen(holdbuffer.c);
int i;
int spacecount = 0;
for (i=length-2; i>=0; i--) {
if (holdbuffer.c[i] == ' ') {
spacecount++;
if (spacecount > 10) {
holdbuffer.c[i] = '\0';
break;
}
} else {
spacecount = 0;
}
}
}
//////////////////////////////
//
// convertSong --
//
void convertSong(Array& song, ostream& out) {
int i;
if (verboseQ) {
for (i=0; i<song.getSize(); i++) {
out << song[i].c << "\n";
}
}
char260 key;
double mindur = 1.0;
char260 meter;
int tonic;
getKeyInfo(song, key, mindur, tonic, meter, out);
Array<NoteData> songdata;
songdata.setSize(1000);
songdata.setGrowth(1000);
songdata.setSize(0);
getNoteList(song, songdata, mindur, tonic);
placeLyrics(song, songdata);
Array<int> numerator;
Array<int> denominator;
getMeterInfo(meter, numerator, denominator);
postProcessSongData(songdata, numerator, denominator);
char buffer[32] = {0};
printTitleInfo(song, out);
out << "!!!id: " << key.c << "\n";
// check for presence of lyrics
int textQ = 0;
for (i=0; i<songdata.getSize(); i++) {
if (strcmp(songdata[i].text, "") != 0) {
textQ = 1;
break;
}
}
for (i=0; i<header.getSize(); i++) {
out << header[i].c << "\n";
}
out << "**kern";
if (textQ) {
out << "\t**text";
}
out << "\n";
printKeyInfo(songdata, tonic, textQ, out);
for (i=0; i<songdata.getSize(); i++) {
printNoteData(songdata[i], textQ, out);
}
out << "*-";
if (textQ) {
out << "\t*-";
}
out << "\n";
out << "!!!minrhy: " << Convert::durationToKernRhythm(buffer, mindur)<<"\n";
out << "!!!meter";
if (numerator.getSize() > 1) {
out << "s";
}
out << ": " << meter.c;
if (strcmp(meter.c, "frei") == 0 || strcmp(meter.c, "Frei") == 0) {
out << " [unmetered]";
} else if (strchr(meter.c, '/') == NULL) {
out << " interpreted as [";
for (i=0; i<numerator.getSize(); i++) {
out << numerator[i] << "/" << denominator[i];
if (i < numerator.getSize()-1) {
out << ", ";
}
}
out << "]";
}
out << "\n";
printBibInfo(song, out);
printSpecialChars(out);
for (i=0; i<songdata.getSize(); i++) {
if (songdata[i].lyricerr) {
out << "!!!RWG: Lyric placement mismatch "
<< "in phrase (too many syllables) " << songdata[i].phnum << " ["
<< key.c << "]\n";
break;
}
}
for (i=0; i<trailer.getSize(); i++) {
out << trailer[i].c << "\n";
}
if (!splitQ) {
out << "\n!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" << endl;
}
}
//////////////////////////////
//
// placeLyrics -- extract lyrics (if any) and place on correct notes
//
void placeLyrics(Array& song, Array& songdata) {
int start = -1;
int stop = -1;
int length;
int j;
getLineRange(song, "TXT", start, stop);
if (start == -1) {
// no TXT[] field, so don't do anything
return;
}
int line = 0;
Array<char260> lyrics;
char buffer[256] = {0};
for (line=0; line<=stop-start; line++) {
if (strlen(song[line+start].c) <= 4) {
cout << "Error: lyric line is too short!: "
<< song[line+start].c << endl;
exit(1);
}
strcpy(buffer, &(song[line+start].c[4]));
if (line == stop - start) {
length = strlen(buffer);
for (j=length-1; j>=0; j--) {
if (buffer[j] == ']') {
buffer[j] = '\0';
break;
}
}
}
if (strcmp(buffer, "") == 0) {
continue;
}
getLyrics(lyrics, buffer);
cleanupLyrics(lyrics);
placeLyricPhrase(songdata, lyrics, line);
}
}
//////////////////////////////
//
// cleanupLyrics -- add preceeding dashes, avoid starting *'s if any,
// and convert _'s to spaces.
//
void cleanupLyrics(Array& lyrics) {
int length;
int length2;
int i, j, m;
int lastsyl = 0;
for (i=0; i<lyrics.getSize(); i++) {
length = strlen(lyrics[i].c);
for (j=0; j<length; j++) {
if (lyrics[i].c[j] == '_') {
lyrics[i].c[j] = ' ';
}
}
if (i > 0) {
if (strcmp(lyrics[i].c, ".") != 0 &&
strcmp(lyrics[i].c, "") != 0 &&
strcmp(lyrics[i].c, "%") != 0 &&
strcmp(lyrics[i].c, "^") != 0 &&
strcmp(lyrics[i].c, "|") != 0 &&
strcmp(lyrics[i].c, " ") != 0 ) {
lastsyl = -1;
for (m=i-1; m>=0; m--) {
if (strcmp(lyrics[m].c, ".") != 0 &&
strcmp(lyrics[m].c, "") != 0 &&
strcmp(lyrics[m].c, "%") != 0 &&
strcmp(lyrics[i].c, "^") != 0 &&
strcmp(lyrics[m].c, "|") != 0 &&
strcmp(lyrics[m].c, " ") != 0 ) {
lastsyl = m;
break;
}
}
if (lastsyl >= 0) {
length2 = strlen(lyrics[lastsyl].c);
if (lyrics[lastsyl].c[length2-1] == '-') {
for (j=0; j<=length; j++) {
lyrics[i].c[length - j + 1] = lyrics[i].c[length - j];
}
lyrics[i].c[0] = '-';
}
}
}
}
// avoid *'s on the start of lyrics by placing a space before
// them if they exist.
if (lyrics[i].c[0] == '*') {
length = strlen(lyrics[i].c);
for (j=0; j<=length; j++) {
lyrics[i].c[length - j + 1] = lyrics[i].c[length - j];
}
lyrics[i].c[0] = ' ';
}
// avoid !'s on the start of lyrics by placing a space before
// them if they exist.
if (lyrics[i].c[0] == '!') {
length = strlen(lyrics[i].c);
for (j=0; j<=length; j++) {
lyrics[i].c[length - j + 1] = lyrics[i].c[length - j];
}
lyrics[i].c[0] = ' ';
}
}
}
///////////////////////////////
//
// getLyrics -- extract the lyrics from the text string.
//
void getLyrics(Array& lyrics, const char* buffer) {
lyrics.setSize(0);
int zero1 = 0;
char260 current;
int zero2 = 0;
zero2 = zero1 + zero2;
current.c[0] = '\0';
int length = strlen(buffer);
int i, j;
i = 0;
while (i<length) {
current.c[0] = '\0';
j = 0;
if (buffer[i] == ' ') {
strcpy(current.c, ".");
lyrics.append(current);
i++;
continue;
}
while (i < length && buffer[i] != ' ') {
current.c[j++] = buffer[i++];
}
current.c[j] = '\0';
lyrics.append(current);
i++;
}
}
//////////////////////////////
//
// placeLyricPhrase -- match lyrics from a phrase to the songdata.
//
void placeLyricPhrase(Array& songdata, Array& lyrics, int line) {
int i = 0;
int start = 0;
int found = 0;
if (lyrics.getSize() == 0) {
return;
}
// find the phrase to which the lyrics belongs
for (i=0; i<songdata.getSize(); i++) {
if (songdata[i].phnum == line) {
found = 1;
break;
}
}
start = i;
if (!found) {
cout << "Error: cannot find music for lyrics line " << line << endl;
cout << "Error near input data line: " << inputline << endl;
exit(1);
}
for (i=0; i<lyrics.getSize() && i+start < songdata.getSize(); i++) {
if (strcmp(lyrics[i].c, " ") == 0 || strcmp(lyrics[i].c, ".") == 0
|| strcmp(lyrics[i].c, "") == 0) {
if (songdata[i+start].pitch < 0) {
strcpy(lyrics[i].c, "%");
} else {
strcpy(lyrics[i].c, "|");
}
// strcpy(lyrics[i].c, ".");
}
strcpy(songdata[i+start].text, lyrics[i].c);
songdata[i+start].lyricnum = line;
if (line != songdata[i+start].phnum) {
songdata[i+start].lyricerr = 1; // lyric does not line up with music
}
}
}
//////////////////////////////
//
// printSpecialChars -- print high ASCII character table
//
void printSpecialChars(ostream& out) {
int i;
for (i=0; i<chartable.getSize(); i++) {
if (chartable[i]) {
switch (i) {
case 129: out << "!!!RNB" << ": symbol: ü = u umlaut (UTF-8: "
<< (char)0xc3 << (char)0xb3 << ")\n"; break;
case 130: out << "!!!RNB" << ": symbol: é= e acute (UTF-8: "
<< (char)0xc3 << (char)0xa9 << ")\n"; break;
case 132: out << "!!!RNB" << ": symbol: ä = a umlaut (UTF-8: "
<< (char)0xc3 << (char)0xa4 << ")\n"; break;
case 134: out << "!!!RNB" << ": symbol: $c = c acute (UTF-8: "
<< (char)0xc4 << (char)0x87 << ")\n"; break;
case 136: out << "!!!RNB" << ": symbol: $l = l slash (UTF-8: "
<< (char)0xc5 << (char)0x82 << ")\n"; break;
case 140: out << "!!!RNB" << ": symbol: î = i circumflex (UTF-8: "
<< (char)0xc3 << (char)0xaf << ")\n"; break;
case 141: out << "!!!RNB" << ": symbol: $X = Z acute (UTF-8: "
<< (char)0xc5 << (char)0xb9 << ")\n"; break;
case 142: out << "!!!RNB" << ": symbol: ä = a umlaut (UTF-8: "
<< (char)0xc3 << (char)0xa4 << ")\n"; break;
case 143: out << "!!!RNB" << ": symbol: $C = C acute (UTF-8: "
<< (char)0xc4 << (char)0x86 << ")\n"; break;
case 148: out << "!!!RNB" << ": symbol: ö = o umlaut (UTF-8: "
<< (char)0xc3 << (char)0xb6 << ")\n"; break;
case 151: out << "!!!RNB" << ": symbol: $S = S acute (UTF-8: "
<< (char)0xc5 << (char)0x9a << ")\n"; break;
case 152: out << "!!!RNB" << ": symbol: $s = s acute (UTF-8: "
<< (char)0xc5 << (char)0x9b << ")\n"; break;
case 156: out << "!!!RNB" << ": symbol: $s = s acute (UTF-8: "
<< (char)0xc5 << (char)0x9b << ")\n"; break;
case 157: out << "!!!RNB" << ": symbol: $L = L slash (UTF-8: "
<< (char)0xc5 << (char)0x81 << ")\n"; break;
case 159: out << "!!!RNB" << ": symbol: $vc = c hachek (UTF-8: "
<< (char)0xc4 << (char)0x8d << ")\n"; break;
case 162: out << "!!!RNB" << ": symbol: ó= o acute (UTF-8: "
<< (char)0xc3 << (char)0xb3 << ")\n"; break;
case 163: out << "!!!RNB" << ": symbol: ú= u acute (UTF-8: "
<< (char)0xc3 << (char)0xba << ")\n"; break;
case 165: out << "!!!RNB" << ": symbol: $a = a hook (UTF-8: "
<< (char)0xc4 << (char)0x85 << ")\n"; break;
case 169: out << "!!!RNB" << ": symbol: $e = e hook (UTF-8: "
<< (char)0xc4 << (char)0x99 << ")\n"; break;
case 171: out << "!!!RNB" << ": symbol: $y = z acute (UTF-8: "
<< (char)0xc5 << (char)0xba << ")\n"; break;
case 175: out << "!!!RNB" << ": symbol: $Z = Z dot (UTF-8: "
<< (char)0xc5 << (char)0xbb << ")\n"; break;
case 179: out << "!!!RNB" << ": symbol: $l = l slash (UTF-8: "
<< (char)0xc5 << (char)0x82 << ")\n"; break;
case 185: out << "!!!RNB" << ": symbol: $a = a hook (UTF-8: "
<< (char)0xc4 << (char)0x85 << ")\n"; break;
case 189: out << "!!!RNB" << ": symbol: $Z = Z dot (UTF-8: "
<< (char)0xc5 << (char)0xbb << ")\n"; break;
case 190: out << "!!!RNB" << ": symbol: $z = z dot (UTF-8: "
<< (char)0xc5 << (char)0xbc << ")\n"; break;
case 191: out << "!!!RNB" << ": symbol: $z = z dot (UTF-8: "
<< (char)0xc5 << (char)0xbc << ")\n"; break;
case 224: out << "!!!RNB" << ": symbol: Ó= O acute (UTF-8: "
<< (char)0xc3 << (char)0x93 << ")\n"; break;
case 225: out << "!!!RNB" << ": symbol: ß = sz ligature (UTF-8: "
<< (char)0xc3 << (char)0x9f << ")\n"; break;
case 0xdf: out << "!!!RNB" << ": symbol: ß = sz ligature (UTF-8: "
<< (char)0xc3 << (char)0x9f << ")\n"; break;
// Polish version:
// case 228: out << "!!!RNB" << ": symbol: $n = n acute (UTF-8: "
// << (char)0xc5 << (char)0x84 << ")\n"; break;
// Luxembourg version for some reason...:
case 228: out << "!!!RNB" << ": symbol: ä = a umlaut (UTF-8: "
<< (char)0xc5 << (char)0x84 << ")\n"; break;
case 230: out << "!!!RNB" << ": symbol: c = c\n"; break;
case 231: out << "!!!RNB" << ": symbol: $vs = s hachek (UTF-8: "
<< (char)0xc5 << (char)0xa1 << ")\n"; break;
case 234: out << "!!!RNB" << ": symbol: $e = e hook (UTF-8: "
<< (char)0xc4 << (char)0x99 << ")\n"; break;
case 241: out << "!!!RNB" << ": symbol: $n = n acute (UTF-8: "
<< (char)0xc5 << (char)0x84 << ")\n"; break;
case 243: out << "!!!RNB" << ": symbol: ó= o acute (UTF-8: "
<< (char)0xc3 << (char)0xb3 << ")\n"; break;
case 252: out << "!!!RNB" << ": symbol: ü = u umlaut (UTF-8: "
<< (char)0xc3 << (char)0xbc << ")\n"; break;
// default:
}
}
chartable[i] = 0;
}
}
//////////////////////////////
//
// printTitleInfo -- print the first line of the CUT[] field.
//
void printTitleInfo(Array& song, ostream& out) {
int start = -1;
int stop = -1;
getLineRange(song, "CUT", start, stop);
if (start == -1) {
cout << "Error: cannot find CUT[] field in song: " << song[0].c << endl;
exit(1);
}
char buffer[256] = {0};
strcpy(buffer, &(song[start].c[4]));
int length = strlen(buffer);
if (buffer[length-1] == ']') {
buffer[length-1] = '\0';
}
int i;
out << "!!!OTL: ";
length = strlen(buffer);
for (i=0; i<length; i++) {
printChar(buffer[i], out);
}
out << "\n";
}
//////////////////////////////
//
// printChar -- print text characters, translating high-bit data
// if required.
//
void printChar(unsigned char c, ostream& out) {
if (c < 128) {
out << c;
} else {
chartable[c]++;
switch (c) {
case 129: out << "ü"; break;
case 130: out << "é"; break;
case 132: out << "ä"; break;
case 134: out << "$c"; break;
case 136: out << "$l"; break;
case 140: out << "î"; break;
case 141: out << "$X"; break; // Z acute
case 142: out << "ä"; break; // ?
case 143: out << "$C"; break;
case 148: out << "ö"; break;
case 151: out << "$S"; break;
case 152: out << "$s"; break;
case 156: out << "$s"; break; // 1250 encoding
case 157: out << "$L"; break;
case 159: out << "$vc"; break; // Cech c with v accent
case 162: out << "ó"; break;
case 163: out << "ú"; break;
case 165: out << "$a"; break;
case 169: out << "$e"; break;
case 171: out << "$y"; break;
case 175: out << "$Z"; break; // 1250 encoding
case 179: out << "$l"; break; // 1250 encoding
case 185: out << "$a"; break; // 1250 encoding
case 189: out << "$Z"; break; // Z dot
case 190: out << "$z"; break; // z dot
case 191: out << "$z"; break; // 1250 encoding
case 224: out << "Ó"; break;
case 225: out << "ß"; break;
case 0xdf: out << "ß"; break;
// Polish version:
// case 228: out << "$n"; break;
// Luxembourg version (for some reason...)
case 228: out << "ä"; break;
case 230: out << "c"; break; // ?
case 231: out << "$vs"; break; // Cech s with v accent
case 234: out << "$e"; break; // 1250 encoding
case 241: out << "$n"; break; // 1250 encoding
case 243: out << "ó"; break; // 1250 encoding
case 252: out << "ü"; break;
default: out << c;
}
}
}
//////////////////////////////
//
// printKeyInfo --
//
void printKeyInfo(Array<NoteData>& songdata, int tonic, int textQ,
ostream& out) {
Array<int> pitches;
pitches.setSize(40);
pitches.zero();
int pitchsum = 0;
int pitchcount = 0;
int i;
for (i=0; i<songdata.getSize(); i++) {
if (songdata[i].pitch >= 0) {
pitches[songdata[i].pitch % 40]++;
pitchsum += Convert::base40ToMidiNoteNumber(songdata[i].pitch);
pitchcount++;
}
}
// generate a clef, choosing either treble or bass clef depending
// on the average pitch.
double averagepitch = pitchsum * 1.0 / pitchcount;
if (averagepitch > 60.0) {
out << "*clefG2";
if (textQ) {
out << "\t*clefG2";
}
out << "\n";
} else {
out << "*clefF4";
if (textQ) {
out << "\t*clefF4";
}
out << "\n";
}
// generate a key signature
Array<int> diatonic(7);
diatonic.zero();
diatonic[0] = getAccidentalMax(pitches[1], pitches[2], pitches[3]);
diatonic[1] = getAccidentalMax(pitches[7], pitches[8], pitches[9]);
diatonic[2] = getAccidentalMax(pitches[13], pitches[14], pitches[15]);
diatonic[3] = getAccidentalMax(pitches[18], pitches[19], pitches[20]);
diatonic[4] = getAccidentalMax(pitches[24], pitches[25], pitches[26]);
diatonic[5] = getAccidentalMax(pitches[30], pitches[31], pitches[32]);
diatonic[6] = getAccidentalMax(pitches[36], pitches[37], pitches[38]);
int flatcount = 0;
int sharpcount = 0;
int naturalcount = 0;
for (i=0; i<7; i++) {
switch (diatonic[i]) {
case -1: flatcount++; break;
case 0: naturalcount++; break;
case +1: sharpcount++; break;
}
}
char kbuf[32] = {0};
if (naturalcount == 7) {
// do nothing
} else if (flatcount > sharpcount) {
// print a flat key signature
if (diatonic[6] == -1) strcat(kbuf, "b-"); else goto keysigend;
if (diatonic[2] == -1) strcat(kbuf, "e-"); else goto keysigend;
if (diatonic[5] == -1) strcat(kbuf, "a-"); else goto keysigend;
if (diatonic[1] == -1) strcat(kbuf, "d-"); else goto keysigend;
if (diatonic[4] == -1) strcat(kbuf, "g-"); else goto keysigend;
if (diatonic[0] == -1) strcat(kbuf, "c-"); else goto keysigend;
if (diatonic[3] == -1) strcat(kbuf, "f-"); else goto keysigend;
} else {
// print a sharp key signature
if (diatonic[3] == +1) strcat(kbuf, "f#"); else goto keysigend;
if (diatonic[0] == +1) strcat(kbuf, "c#"); else goto keysigend;
if (diatonic[4] == +1) strcat(kbuf, "g#"); else goto keysigend;
if (diatonic[1] == +1) strcat(kbuf, "d#"); else goto keysigend;
if (diatonic[5] == +1) strcat(kbuf, "a#"); else goto keysigend;
if (diatonic[2] == +1) strcat(kbuf, "e#"); else goto keysigend;
if (diatonic[6] == +1) strcat(kbuf, "b#"); else goto keysigend;
}
keysigend:
out << "*k[" << kbuf << "]";
if (textQ) {
out << "\t*k[" << kbuf << "]";
}
out << "\n";
// look at the third scale degree above the tonic pitch
int minor = pitches[(tonic + 40 + 11) % 40];
int major = pitches[(tonic + 40 + 12) % 40];
char buffer[32] = {0};
if (minor > major) {
// minor key (or related mode)
out << "*" << Convert::base40ToKern(buffer, 40 * 4 + tonic) << ":";
if (textQ) {
out << "\t*" << Convert::base40ToKern(buffer, 40 * 4 + tonic) << ":";
}
out << "\n";
} else {
// major key (or related mode)
out << "*" << Convert::base40ToKern(buffer, 40 * 3 + tonic) << ":";
if (textQ) {
out << "\t*" << Convert::base40ToKern(buffer, 40 * 3 + tonic) << ":";
}
out << "\n";
}
}
//////////////////////////////
//
// getAccidentalMax --
//
int getAccidentalMax(int a, int b, int c) {
if (a > b && a > c) {
return -1;
} else if (c > a && c > b) {
return +1;
} else {
return 0;
}
}
//////////////////////////////
//
// postProcessSongData -- clean up data and do some interpreting.
//
void postProcessSongData(Array<NoteData>& songdata, Array<int>& numerator,
Array<int>& denominator) {
int i, j;
// move phrase start markers off of rests and onto the
// first note that it finds
for (i=0; i<songdata.getSize()-1; i++) {
if (songdata[i].pitch < 0 && songdata[i].phstart) {
songdata[i+1].phstart = songdata[i].phstart;
songdata[i].phstart = 0;
}
}
// move phrase ending markers off of rests and onto the
// previous note that it finds
for (i=songdata.getSize()-1; i>0; i--) {
if (songdata[i].pitch < 0 && songdata[i].phend) {
songdata[i-1].phend = songdata[i].phend;
songdata[i].phend = 0;
}
}
// examine barline information
double dur = 0.0;
for (i=songdata.getSize()-1; i>=0; i--) {
if (songdata[i].bar == 1) {
songdata[i].bardur = dur;
dur = songdata[i].duration;
} else {
dur += songdata[i].duration;
}
}
int barnum = 0;
double firstdur = 0.0;
if (numerator.getSize() == 1 && numerator[0] > 0) {
// handle single non-frei meter
songdata[0].num = numerator[0];
songdata[0].denom = denominator[0];
dur = 0;
double meterdur = 4.0 / denominator[0] * numerator[0];
for (i=0; i<songdata.getSize(); i++) {
if (songdata[i].bar) {
dur = 0.0;
} else {
dur += songdata[i].duration;
if (fabs(dur - meterdur) < 0.001) {
songdata[i].bar = 1;
songdata[i].barinterp = 1;
dur = 0.0;
}
}
}
// readjust measure beat counts
dur = 0.0;
for (i=songdata.getSize()-1; i>=0; i--) {
if (songdata[i].bar == 1) {
songdata[i].bardur = dur;
dur = songdata[i].duration;
} else {
dur += songdata[i].duration;
}
}
firstdur = dur;
// number the barlines
barnum = 0;
if (fabs(firstdur - meterdur) < 0.001) {
// music for first bar, next bar will be bar 2
barnum = 2;
} else {
barnum = 1;
// pickup-measure
}
for (i=0; i<songdata.getSize(); i++) {
if (songdata[i].bar == 1) {
songdata[i].barnum = barnum++;
}
}
} else if (numerator.getSize() == 1 && numerator[0] == -1) {
// handle free meter
// number the barline
firstdur = dur;
barnum = 1;
for (i=0; i<songdata.getSize(); i++) {
if (songdata[i].bar == 1) {
songdata[i].barnum = barnum++;
}
}
} else {
// handle multiple time signatures
// get the duration of each type of meter:
Array<double> meterdurs;
meterdurs.setSize(numerator.getSize());
for (i=0; i<meterdurs.getSize(); i++) {
meterdurs[i] = 4.0 / denominator[i] * numerator[i];
}
// measure beat counts:
dur = 0.0;
for (i=songdata.getSize()-1; i>=0; i--) {
if (songdata[i].bar == 1) {
songdata[i].bardur = dur;
dur = songdata[i].duration;
} else {
dur += songdata[i].duration;
}
}
firstdur = dur;
// interpret missing barlines
int currentmeter = 0;
// find first meter
for (i=0; i<numerator.getSize(); i++) {
if (fabs(firstdur - meterdurs[i]) < 0.001) {
songdata[0].num = numerator[i];
songdata[0].denom = denominator[i];
currentmeter = i;
}
}
// now handle the meters in the rest of the music...
int fnd = 0;
dur = 0;
for (i=0; i<songdata.getSize()-1; i++) {
if (songdata[i].bar) {
if (songdata[i].bardur != meterdurs[currentmeter]) {
// try to find the correct new meter
fnd = 0;
for (j=0; j<numerator.getSize(); j++) {
if (j == currentmeter) {
continue;
}
if (fabs(songdata[i].bardur - meterdurs[j]) < 0.001) {
songdata[i+1].num = numerator[j];
songdata[i+1].denom = denominator[j];
currentmeter = j;
fnd = 1;
}
}
if (!fnd) {
for (j=0; j<numerator.getSize(); j++) {
if (j == currentmeter) {
continue;
}
if (fabs(songdata[i].bardur/2.0 - meterdurs[j]) < 0.001) {
songdata[i+1].num = numerator[j];
songdata[i+1].denom = denominator[j];
currentmeter = j;
fnd = 1;
}
}
}
}
dur = 0.0;
} else {
dur += songdata[i].duration;
if (fabs(dur - meterdurs[currentmeter]) < 0.001) {
songdata[i].bar = 1;
songdata[i].barinterp = 1;
dur = 0.0;
}
}
}
// perhaps sum duration of measures again and search for error here?
// finally, number the barlines:
barnum = 1;
for (i=0; i<numerator.getSize(); i++) {
if (fabs(firstdur - meterdurs[i]) < 0.001) {
barnum = 2;
break;
}
}
for (i=0; i<songdata.getSize(); i++) {
if (songdata[i].bar == 1) {
songdata[i].barnum = barnum++;
}
}
}
}
//////////////////////////////
//
// getMeterInfo --
//
void getMeterInfo(char260& meter, Array<int>& numerator,
Array<int>& denominator) {
char buffer[256] = {0};
strcpy(buffer, meter.c);
numerator.setSize(0);
denominator.setSize(0);
int num = -1;
int denom = -1;
char* ptr;
ptr = strtok(buffer, " \t\n");
while (ptr != NULL) {
if (strcmp(ptr, "frei") == 0 || strcmp(ptr, "Frei") == 0) {
num = -1;
denom = -1;
numerator.append(num);
denominator.append(denom);
} else {
if (strchr(ptr, '/') != NULL) {
num = -1;
denom = 4;
sscanf(ptr, "%d/%d", &num, &denom);
numerator.append(num);
denominator.append(denom);
} else {
num = atoi(ptr);
denom = 4;
numerator.append(num);
denominator.append(denom);
}
}
ptr = strtok(NULL, " \t\n");
}
}
//////////////////////////////
//
// getLineRange -- get the staring line and ending line of a data
// field. Returns -1 if the data field was not found.
//
void getLineRange(Array<char260>& song, const char* field, int& start,
int& stop) {
char searchstring[32] = {0};
strcpy(searchstring, field);
strcat(searchstring, "[");
start = -1;
stop = -1;
int i;
for (i=0; i<song.getSize(); i++) {
if (strncmp(song[i].c, searchstring, 4) == 0) {
start = i;
if (strchr(song[i].c, ']') != NULL) {
stop = i;
break;
}
} else if (start >= 0 && strchr(song[i].c, ']') != NULL) {
stop = i;
break;
}
}
}
//////////////////////////////
//
// getNoteList -- get a list of the notes and rests and barlines in
// the MEL field.
//
void getNoteList(Array<char260>& song, Array<NoteData>& songdata, double mindur,
int tonic) {
songdata.setSize(0);
NoteData tempnote;
int melstart = -1;
int melstop = -1;
int i, j;
int octave = 0;
int degree = 0;
int accidental = 0;
double duration = mindur;
int bar = 0;
int tuplet = 0;
int major[8] = {-1, 0, 6, 12, 17, 23, 29, 35};
int oldstate = -1;
int state = -1;
int nextstate = -1;
int phend = 0;
int phnum = 0;
int phstart = 0;
int slend = 0;
int slstart = 0;
int tie = 0;
getLineRange(song, "MEL", melstart, melstop);
for (i=melstart; i<=melstop; i++) {
if (song[i].c[0] == '\0' || song[i].c[1] == '\0' ||
song[i].c[2] == '\0' || song[i].c[3] == '\0') {
cout << "Error: invalid line in MEL[]: " << song[i].c << endl;
exit(1);
}
j = 4;
phstart = 1;
phend = 0;
// Note Format: (+|-)*[0..7]_*\.*( )?
// ONADB
// Order of data: Octave, Note, Accidental, Duration, Barline
#define STATE_SLSTART -1
#define STATE_OCTAVE 0
#define STATE_NOTE 1
#define STATE_ACC 2
#define STATE_DUR 3
#define STATE_BAR 4
#define STATE_SLEND 5
while (j < 200 && song[i].c[j] != '\0') {
oldstate = state;
switch (song[i].c[j]) {
// Octave information:
case '-': octave--; state = STATE_OCTAVE; break;
case '+': octave++; state = STATE_OCTAVE; break;
// Duration information:
case '_': duration *= 2.0; state = STATE_DUR; break;
case '.': duration *= 1.5; state = STATE_DUR; break;
// Accidental information:
case 'b': accidental--; state = STATE_ACC; break;
case '#': accidental++; state = STATE_ACC; break;
// Note information:
case '0': case '1': case '2': case '3': case '4':
case '5': case '6': case '7':
degree = major[song[i].c[j] - '0'];
state = STATE_NOTE;
break;
case 'O':
degree = major[0];
state = STATE_NOTE;
break;
// Barline information:
case ' ':
state = STATE_BAR;
if (song[i].c[j+1] == ' ') {
bar = 1;
}
break;
// Other information:
case '{': slstart = 1; state = STATE_SLSTART; break;
case '}': slend = 1; state = STATE_SLEND; break;
case '(': tuplet = 1; break;
case ')': tuplet = 0; break;
case '/': break;
case ']': break;
// case '>': break; // unknown marker
// case '<': break; //
case '^': tie = 1; state = STATE_NOTE; break;
default : cout << "Error: unknown character " << song[i].c[j]
<< " on the line: " << song[i].c << endl;
exit(1);
}
j++;
switch (song[i].c[j]) {
case '-': case '+': nextstate = STATE_OCTAVE; break;
case 'O':
case '0': case '1': case '2': case '3': case '4':
case '5': case '6': case '7': nextstate = STATE_NOTE; break;
case 'b': case '#': nextstate = STATE_ACC; break;
case '_': case '.': nextstate = STATE_DUR; break;
case '{': nextstate = STATE_SLSTART; break;
case '}': nextstate = STATE_SLEND; break;
case '^': nextstate = STATE_NOTE; break;
case ' ':
if (song[i].c[j+1] == ' ') nextstate = STATE_BAR;
else if (song[i].c[j+1] == '/') nextstate = -2;
break;
case '\0':
phend = 1;
default: nextstate = -1;
}
if (nextstate < state ||
((nextstate == STATE_NOTE) && (state == nextstate))) {
tempnote.clear();
if (degree < 0) { // rest
tempnote.pitch = -999;
} else {
tempnote.pitch = degree + 40*(octave + 4) + accidental + tonic;
}
if (tie) {
tempnote.pitch = songdata[songdata.getSize()-1].pitch;
if (songdata[songdata.getSize()-1].tieend) {
songdata[songdata.getSize()-1].tiecont = 1;
songdata[songdata.getSize()-1].tieend = 0;
} else {
songdata[songdata.getSize()-1].tiestart = 1;
}
tempnote.tieend = 1;
}
tempnote.duration = duration;
tempnote.phend = phend;
tempnote.bar = bar;
tempnote.phstart = phstart;
tempnote.slstart = slstart;
tempnote.slend = slend;
if (nextstate == -2) {
tempnote.bar = 2;
tempnote.phend = 1;
}
tempnote.phnum = phnum;
songdata.append(tempnote);
duration = mindur;
degree = 0;
bar = 0;
tie = 0;
phend = 0;
phstart = 0;
slend = 0;
slstart = 0;
octave = 0;
accidental = 0;
if (nextstate == -2) {
return;
}
}
}
phnum++;
}
}
//////////////////////////////
//
// printNoteData --
//
void printNoteData(NoteData& data, int textQ, ostream& out) {
if (data.num > 0) {
out << "*M" << data.num << "/" << data.denom;
if (textQ) {
out << "\t*M" << data.num << "/" << data.denom;
}
out << "\n";
}
char buffer[32] = {0};
if (data.phstart == 1) {
out << "{";
}
if (data.slstart == 1) {
out << "(";
}
if (data.tiestart == 1) {
out << "[";
}
out << Convert::durationToKernRhythm(buffer, data.duration);
if (data.pitch < 0) {
out << "r";
} else {
out << Convert::base40ToKern(buffer, data.pitch);
}
if (data.tiecont == 1) {
out << "_";
}
if (data.tieend == 1) {
out << "]";
}
if (data.slend == 1) {
out << ")";
}
if (data.phend == 1) {
out << "}";
}
if (textQ) {
out << "\t";
if (data.phstart == 1) {
out << "{";
}
if (strcmp(data.text, "") == 0) {
if (data.pitch < 0) {
strcpy(data.text, "%");
} else {
strcpy(data.text, "|");
}
}
if (data.pitch < 0 && strchr(data.text, '%') == NULL) {
out << "%";
}
if (strcmp(data.text, " *") == 0) {
if (data.pitch < 0) {
strcpy(data.text, "%*");
} else {
strcpy(data.text, "|*");
}
}
if (strcmp(data.text, "^") == 0) {
strcpy(data.text, "|^");
}
printString(data.text, out);
if (data.phend == 1) {
out << "}";
}
}
out << "\n";
// print barline information
if (data.bar == 1) {
out << "=";
if (data.barnum > 0) {
out << data.barnum;
}
if (data.barinterp) {
out << "yy";
}
if (debugQ) {
if (data.bardur > 0.0) {
out << "[" << data.bardur << "]";
}
}
if (textQ) {
out << "\t";
out << "=";
if (data.barnum > 0) {
out << data.barnum;
}
if (data.barinterp) {
out << "yy";
}
if (debugQ) {
if (data.bardur > 0.0) {
out << "[" << data.bardur << "]";
}
}
}
out << "\n";
} else if (data.bar == 2) {
out << "==";
if (textQ) {
out << "\t==";
}
out << "\n";
}
}
//////////////////////////////
//
// getKeyInfo -- look for a KEY[] entry and extract the data.
//
void getKeyInfo(Array<char260>& song, char260& key, double& mindur,
int& tonic, char260& meter, ostream& out) {
int i;
for (i=0; i<song.getSize(); i++) {
if (strncmp(song[i].c, "KEY[", 4) == 0) {
key.c[0] = song[i].c[4]; // Letter
key.c[1] = song[i].c[5]; // Number
key.c[2] = song[i].c[6]; // Number
key.c[3] = song[i].c[7]; // Number
key.c[4] = song[i].c[8]; // Number
if (!isspace(song[i].c[9])) {
key.c[5] = song[i].c[9]; // optional letter (sometimes ' or ")
} else {
key.c[5] = '\0';
}
if (!isspace(song[i].c[10])) {
key.c[6] = song[i].c[9]; // illegal but possible extra letter
} else {
key.c[6] = '\0';
}
key.c[7] = '\0';
if (song[i].c[10] != ' ') {
out << "!! Warning key field is not complete" << endl;
}
mindur = (song[i].c[11] - '0') * 10 + (song[i].c[12] - '0');
mindur = 4.0 / mindur;
char260 tonicstr;
if (song[i].c[14] != ' ') {
tonicstr.c[0] = song[i].c[14];
if (tolower(song[i].c[15]) == 'b') {
tonicstr.c[1] = '-';
} else {
tonicstr.c[1] = song[i].c[15];
}
tonicstr.c[2] = '\0';
} else {
tonicstr.c[0] = song[i].c[15];
tonicstr.c[1] = '\0';
}
// convert German notation to English for note names
// Hopefully all references to B will mean English B-flat.
if (strcmp(tonicstr.c, "B") == 0) {
strcpy(tonicstr.c, "B-");
}
if (strcmp(tonicstr.c, "H") == 0) {
strcpy(tonicstr.c, "B");
}
tonic = Convert::kernToBase40(tonicstr.c);
if (tonic <= 0) {
cout << "Error: invalid tonic on line: " << song[i].c << endl;
exit(1);
}
tonic = tonic % 40;
strcpy(meter.c, &(song[i].c[17]));
int length = strlen(meter.c);
if (meter.c[length-1] != ']') {
cout << "Error with meter on line: " << song[i].c << endl;
exit(1);
} else {
meter.c[length-1] = '\0';
}
return;
}
}
cout << "Error: did not find a KEY field" << endl;
exit(1);
}
//////////////////////////////
//
// checkOptions --
//
void checkOptions(Options& opts, int argc, char* argv[]) {
opts.define("debug=b", "print debug information");
opts.define("v|verbose=b", "verbose output");
opts.define("h|header=s:", "Header filename for placement in output");
opts.define("t|trailer=s:", "Trailer filename for placement in output");
opts.define("s|split=s:file", "Split song info into separate files");
opts.define("x|extension=s:.krn", "Split filename extension");
opts.define("f|first=i:1", "Number of first split filename");
opts.define("author=b", "author of program");
opts.define("version=b", "compilation info");
opts.define("example=b", "example usages");
opts.define("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, March 2002" << endl;
exit(0);
} else if (opts.getBoolean("version")) {
cout << argv[0] << ", version: 5 March 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);
}
debugQ = opts.getBoolean("debug");
verboseQ = opts.getBoolean("verbose");
if (opts.getBoolean("header")) {
getFileContents(header, opts.getString("header"));
} else {
header.setSize(0);
}
if (opts.getBoolean("trailer")) {
getFileContents(trailer, opts.getString("trailer"));
} else {
trailer.setSize(0);
}
if (opts.getBoolean("split")) {
splitQ = 1;
}
strcpy(namebase, opts.getString("split"));
strcpy(fileextension, opts.getString("extension"));
firstfilenum = opts.getInteger("first");
}
///////////////////////////////
//
// getFileContents -- read a file into the array.
//
void getFileContents(Array& array, const char* filename) {
#ifndef OLDCPP
ifstream infile(filename);
#else
ifstream infile(filename, ios::nocreate);
#endif
array.setSize(100);
array.setGrowth(100);
array.setSize(0);
if (!infile.is_open()) {
cout << "Error: cannot open file: " << filename << endl;
exit(1);
}
char260 holdbuffer;
infile.getline(holdbuffer.c, 256, '\n');
while (!infile.eof()) {
array.append(holdbuffer);
infile.getline(holdbuffer.c, 256, '\n');
}
infile.close();
}
//////////////////////////////
//
// example --
//
void example(void) {
}
//////////////////////////////
//
// usage --
//
void usage(const char* command) {
}
//////////////////////////////
//
// printBibInfo --
//
void printBibInfo(Array& song, ostream& out) {
int i, j, m;
char buffer[32] = {0};
int start = -1;
int stop = -1;
int count = 0;
int length = 0;
char templine[256] = {0};
for (i=0; i<song.getSize(); i++) {
if (song[i].c[0] == '\0') {
continue;
}
if (song[i].c[0] != ' ') {
if (strlen(song[i].c) < 4 || song[i].c[3] != '[') {
out << "!! " << song[i].c << "\n";
continue;
}
strncpy(buffer, song[i].c, 3);
buffer[3] = '\0';
if (strcmp(buffer, "MEL") == 0) continue;
if (strcmp(buffer, "TXT") == 0) continue;
// if (strcmp(buffer, "KEY") == 0) continue;
getLineRange(song, buffer, start, stop);
// don't print CUT field if only one line. !!!OTL: will contain CUT[]
// if (strcmp(buffer, "CUT") == 0 && start == stop) continue;
buffer[0] = tolower(buffer[0]);
buffer[1] = tolower(buffer[1]);
buffer[2] = tolower(buffer[2]);
count = 1;
for (j=start; j<=stop; j++) {
if (strlen(song[j].c) < 4) {
continue;
}
if (stop - start == 0) {
strcpy(templine, &(song[j].c[4]));
length = strlen(templine);
for (m=length-1; m>=0; m--) {
if (templine[m] == ']') {
templine[m] = '\0';
break;
}
}
if (strcmp(templine, "") != 0) {
out << "!!!" << buffer << ": ";
printString(templine, out);
out << "\n";
}
} else if (j==start) {
out << "!!!" << buffer << count++ << ": ";
printString(&(song[j].c[4]), out);
out << "\n";
} else if (j==stop) {
strcpy(templine, &(song[j].c[4]));
length = strlen(templine);
for (m=length-1; m>=0; m--) {
if (templine[m] == ']') {
templine[m] = '\0';
break;
}
}
if (strcmp(templine, "") != 0) {
out << "!!!" << buffer << count++ << ": ";
printString(templine, out);
out << "\n";
}
} else {
out << "!!!" << buffer << count++ << ": ";
printString(&(song[j].c[4]), out);
out << "\n";
}
}
}
}
}
//////////////////////////////
//
// printString -- print characters in string.
//
void printString(const char* string, ostream& out) {
int i;
int length = strlen(string);
for (i=0; i<length; i++) {
printChar(string[i], out);
}
}
// md5sum: 220515a102abf046928e3dd0d590cd8d esac2hum.cpp [20050403]