//
// Programmer:    Craig Stuart Sapp <craig@ccrma.stanford.edu>
// Creation Date: Sat Aug  8 12:24:49 PDT 2015
// Last Modified: Fri Mar 28 11:08:41 PDT 2025
// Filename:      min/humlib.cpp
// URL:           https://github.com/craigsapp/humlib/blob/master/min/humlib.cpp
// Syntax:        C++11
// vim:           ts=3
//
// Description:   Source file for humlib library.
//
/*
https://github.com/craigsapp/humlib
Copyright (c) 2015-2021 Craig Stuart Sapp
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice, this
   list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
   and the following disclaimer in the documentation and/or other materials
   provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

*/

#include "humlib.h"

namespace hum {



//////////////////////////////
//
// Convert::majorScaleBase40 -- Return the base-40 scale degree
//     tonic-intervals for each  note in a major scale.  The input is the
//     base-40 pitch-class of the root.  The default input is 0, which
//     will return a list of the intervals for each scale degree to the
//     tonic of the key.
//

vector<int> Convert::majorScaleBase40(void) {
	return {0, 6, 12, 17, 23, 29, 35};
}



//////////////////////////////
//
// Convert::minorHScaleBase40 -- Return the base-40 scale degree
//     tonic-intervals for each  note in a harmonic minor scale.  The input
//     is the base-40 pitch-class of the root.  The default input is 0, which
//     will return a list of the intervals for each scale degree to the
//     tonic of the key.
//

vector<int> Convert::minorHScaleBase40(void) {
	return {0, 6, 11, 17, 23, 28, 35};
}



//////////////////////////////
//
// Convert::keyToBase40 -- convert a Humdrum **kern key designation into
//    a base-40 integer.  Positive values are for major keys and negative
//    values are for minor keys.  (C-double-flat major is 40 rather than 0).
//    Returns 0 if no legitimate key was found.
//

int Convert::keyToBase40(const string& key) {
	string token;
	auto loc = key.find(":");
	if (loc != std::string::npos) {
		token = key.substr(0, loc);
	} else {
		token = key;
	}

	int base40 = Convert::kernToBase40(token);
	if (base40 < 0)  {
		return 0;
	}

	if (base40 >= 160) {
		base40 = -(base40 % 40);
		if (base40 == 0) {
			base40 = -40;
		}
	} else {
		base40 = base40 % 40;
		if (base40 == 0) {
			base40 = 40;
		}
	}
	return base40;
}



//////////////////////////////
//
// Convert::keyToInversion -- Extract the inversion from a **harm token.
//    Root position is 0, first inversion is 1, etc. up to 6th inversion
//    for 13th chords.
//

int Convert::keyToInversion(const string& harm) {
	for (char ch : harm) {
		if ((ch >= 'a') && (ch <= 'g')) {
			return ch - 'a';
		}
	}
	return 0;
}



//////////////////////////////
//
// Convert::chromaticAlteration -- Return the sum of "#" minus "-" in the string.
//

int Convert::chromaticAlteration(const string& content) {
	int sum = 0;
	for (char ch : content) {
		switch (ch) {
			case '#': sum++; break;
			case '-': sum--; break;
		}
	}
	return sum;
}



//////////////////////////////
//
// Convert::makeAdjustedKeyRootAndMode --
//

void Convert::makeAdjustedKeyRootAndMode(const string& secondary, int& keyroot,
		int& keymode) {

	vector<int> majorkey = Convert::majorScaleBase40();
	vector<int> minorkey = Convert::minorHScaleBase40();

	vector<string> roots;
	HumRegex hre;
	hre.split(roots, secondary, "/");

	for (int i=0; i<(int)roots.size(); i++) {
		string piece = roots[(int)roots.size() - i - 1];
		int number = Convert::romanNumeralToInteger(piece);
		if (number == 0) {
			continue;
		} else if (number > 7) {
			number = (number - 1) % 7;
		} else {
			number -= 1;
		}
		if (keymode == 0) { // major key
			keyroot += majorkey[number];
		} else {
			keyroot += minorkey[number];
		}
		int alteration = chromaticAlteration(piece);
		keyroot += alteration;
		if ((!piece.empty()) && isupper(piece[0])) {
			keymode = 0; // major
		} else {
			keymode = 1; // minor
		}
	}

	keyroot = keyroot % 40;
}



//////////////////////////////
//
// Convert::harmToBase40 -- Convert a **harm chord into a list of
//   pitch classes contained in the chord.  The output is a vector
//   that contains the root pitch class in the first slot, then
//   the successive chord tones after that.  If the vector is empty
//   then there was some sort of syntax error in the **harm token.
//   The bass note is placed in the 3rd octave and other pitch classes
//   in the chord are placed in the 4th octave.
//

vector<int> Convert::harmToBase40(const string& harm, const string& key) {
	int keyroot = Convert::keyToBase40(key);
	int keymode = 0; // major key
	if (keyroot < 0) {
		keyroot = -keyroot;
		keymode = 1; // minor key
	}
	return harmToBase40(harm, keyroot, keymode);
}


vector<int> Convert::harmToBase40(const string& harm, int keyroot, int keymode) {
	// Create a tonic-interval list of the scale-degrees:
	vector<int> degrees;
	if (keymode == 1) {
		degrees = Convert::minorHScaleBase40();
	} else {
		degrees = Convert::majorScaleBase40();
	}

	// Remove any **recip prefixed to token:
	string newharm = harm;
	HumRegex hre;
	if (hre.search(harm, R"(^[{}\d%._\][]+(.*))")) {
		newharm = hre.getMatch(1);
	}

	// Remove alternate chord labels:
	string single;
	auto loc = newharm.find('[');
	if (loc != string::npos) {
		single = newharm.substr(0, loc);
	} else {
		single = newharm;
	}

	// Split off secondary dominant qualifications
	string cbase;     // base chord
	string secondary; // secondary chord qualifiers
	loc = single.find("/");
	if (loc != string::npos) {
		cbase = single.substr(0, loc);
		secondary = single.substr(loc+1, string::npos);
	} else {
		cbase = single;
	}

	// Calculate interval offset for secondary dominants:
	int newkeyroot = keyroot;
	int newkeymode = keymode;
	if (!secondary.empty()) {
		makeAdjustedKeyRootAndMode(secondary, newkeyroot, newkeymode);
	}

	int rootdeg = -1; // chord root scale degree in key
	// int degalt = 0;   // degree alteration

	vector<char> chars(256, 0);
	for (auto ch : cbase) {
		chars[ch]++;
	}

	rootdeg = -1; // invalid scale degree
	// degalt = chars['#'] - chars['-'];

	int vcount = chars['V'] + chars['v'];
	int icount = chars['I'] + chars['i'];

	if (vcount == 1) {
		switch (icount) {
			case 0: rootdeg = 4; break; // V
			case 1:
				if (cbase.find("IV") != string::npos) {
					rootdeg = 3; break; // IV
				} else if (cbase.find("iv") != string::npos) {
					rootdeg = 3; break; // iv
				} else {
					rootdeg = 5; break; // VI/vi
				}
			case 2: rootdeg = 6; break; // VII
			case 3: rootdeg = 0; break; // VIII (I)
		}
	} else {
		switch (icount) {
			case 0:  // N, Fr, Gn, Lt, Tr
				if (chars['N']) {
					// Neapolitan (flat-second scale degree)
					rootdeg = 1; // -II
					// degalt += -1; // -II
				} else if (chars['L'] || chars['F'] || chars['G']) {
					// augmented 6th chord on -VII
					rootdeg = 5;
					// fixed to -VI of major scale:
					if (newkeymode == 0) { // major
						// degalt += -1;
					} else { // minor
						// already at -VI in minor
						// degalt += 0;
					}
				}
				break;
			case 1: rootdeg = 0; break; // I
			case 2: rootdeg = 1; break; // II
			case 3: rootdeg = 2; break; // III
		}
	}

	int inversion = Convert::keyToInversion(single);
	vector<int> output;

	if (rootdeg < 0) {
		return output;
	}

	int root = degrees.at(rootdeg) + newkeyroot;
	output.push_back(root);

	int int3  = -1;
	int int5  = 23;  // assume a perfect 5th
	int int7  = -1;
	int int9  = -1;
	// int int11 = -1;
	// int int13 = -1;

	// determine the third's interval
	if (chars['i'] || chars['v']) {
		// minor third
		int3 = 11;
	} else if (chars['I'] || chars['V']) {
		// major third
		int3 = 12;
	} else if (chars['N']) {
		// neapolitan (major triad)
		int3 = 12;
		int5 = 23;
	} else if (chars['G']) {
		// german aug. 6th chord
		int3 = 12;
		int5 = 23;
		int7 = 30; // technically on 6th
	} else if (chars['L']) {
		// Italian aug. 6th chord
		int3 = 12;
		int5 = -1;
		int7 = 30; // technically on 6th
	} else if (chars['F']) {
		// French aug. 6th chord
		int3 = 12;
		int5 = 18; // technically on 4th
		int7 = 30; // technically on 6th
	}

	// determine the fifth's interval
	if (chars['o']) { // diminished
		int5 = 22;
	}
	if (chars['+']) { // augmented
		int5 = 24;
	}

	if (int3 > 0) {
		output.push_back(int3 + output[0]);
	}
	if (int5 > 0) {
		output.push_back(int5 + output[0]);
	}


	///// determine higher chord notes

	// determine the seventh
	if (chars['7']) {
		int7 = degrees.at((rootdeg + 6) % 7) - degrees.at(rootdeg);
		if (int7 < 0) {
			int7 += 40;
		}
		if (hre.search(cbase, "(A+|D+|M|m)7")) {
			string quality = hre.getMatch(1);
			if (quality == "M") {
				int7 = 35;
			} else if (quality == "m") {
				int7 = 34;
			} else if (quality[0] == 'D') {
				int7 = 34 - (int)quality.size();
			} else if (quality[0] == 'A') {
				int7 = 35 + (int)quality.size();
			}
		}
		output.push_back(int7 % 40 + output[0]);
	}

	// determine the 9th
	if (chars['9']) {
		HumRegex hre;
		int9 = degrees.at((rootdeg + 1) % 7) - degrees.at(rootdeg);
		if (int9 < 0) {
			int9 += 40;
		}
		if (hre.search(cbase, "(A+|D+|M|m)9")) {
			string quality = hre.getMatch(1);
			if (quality == "M") {
				int9 = 46;
			} else if (quality == "m") {
				int9 = 45;
			} else if (quality[0] == 'D') {
				int9 = 45 - (int)quality.size();
			} else if (quality[0] == 'A') {
				int9 = 46 + (int)quality.size();
			}
		}
		output.push_back(int9 + output[0]);
	}


	// add inverion
	if (inversion < (int)output.size()) {
		output[inversion] = output[inversion] % 40 + 3 * 40;
	}

	int oct = 4;
	int lastvalue = -1;
	for (int i=0; i<(int)output.size(); i++) {
		if (i != inversion) {
			output[i] = output[i] % 40 + oct * 40;
			if (output[i] < lastvalue) {
				output[i] += 40;
			}
			if (output[i] < lastvalue) {
				output[i] += 40;
			}
			lastvalue = output[i];
		} else {
		}
	}

	return output;

}





//////////////////////////////
//
// Convert::getInstrumentList -- List from https://bit.ly/humdrum-instrument-codes
//

vector<pair<string, string> > Convert::getInstrumentList(void) {
	return vector<pair<string, string> > {
		{"accor",	"klav"},
		{"alto",	"vox"},
		{"anvil",	"idio"},
		{"archl",	"str"},
		{"armon",	"ww"},
		{"arpa",	"str"},
		{"bagpI",	"ww"},
		{"bagpS",	"ww"},
		{"banjo",	"str"},
		{"bansu",	"ww"},
		{"barit",	"vox"},
		{"baset",	"ww"},
		{"bass",	"vox"},
		{"bdrum",	"idio"},
		{"bguit",	"str"},
		{"biwa",	"str"},
		{"brush",	"idio"},
		{"bscan",	"vox"},
		{"bspro",	"vox"},
		{"bugle",	"bras"},
		{"calam",	"ww"},
		{"calpe",	"ww"},
		{"calto",	"vox"},
		{"campn",	"idio"},
		{"cangl",	"ww"},
		{"canto",	"vox"},
		{"caril",	"idio"},
		{"castr",	"vox"},
		{"casts",	"idio"},
		{"cbass",	"str"},
		{"cello",	"str"},
		{"cemba",	"klav"},
		{"cetra",	"str"},
		{"chain",	"idio"},
		{"chcym",	"idio"},
		{"chime",	"idio"},
		{"chlma",	"ww"},
		{"chlms",	"ww"},
		{"chlmt",	"ww"},
		{"clap",	"idio"},
		{"clara",	"ww"},
		{"clarb",	"ww"},
		{"claro",	"ww"},
		{"clarp",	"ww"},
		{"clars",	"ww"},
		{"clave",	"idio"},
		{"clavi",	"klav"},
		{"clest",	"klav"},
		{"clrno",	"bras"},
		{"colsp",	"vox"},
		{"conga",	"idio"},
		{"cor",	"bras"},
		{"cornm",	"ww"},
		{"corno",	"ww"},
		{"cornt",	"bras"},
		{"coro",	"vox"},
		{"crshc",	"idio"},
		{"ctenor",	"vox"},
		{"ctina",	"klav"},
		{"drmsp",	"vox"},
		{"drum",	"idio"},
		{"drumP",	"idio"},
		{"dulc",	"str"},
		{"eguit",	"str"},
		{"fag_c",	"ww"},
		{"fagot",	"ww"},
		{"false",	"vox"},
		{"fdrum",	"idio"},
		{"feme",	"vox"},
		{"fife",	"ww"},
		{"fingc",	"idio"},
		{"flex",	"idio"},
		{"flt",	"ww"},
		{"flt_a",	"ww"},
		{"flt_b",	"ww"},
		{"fltda",	"ww"},
		{"fltdb",	"ww"},
		{"fltdn",	"ww"},
		{"fltds",	"ww"},
		{"fltdt",	"ww"},
		{"flugh",	"bras"},
		{"forte",	"klav"},
		{"gen",	"gen"},
		{"genB",	"gen"},
		{"genT",	"gen"},
		{"glock",	"idio"},
		{"gong",	"idio"},
		{"guitr",	"str"},
		{"hammd",	"klav"},
		{"hbell",	"idio"},
		{"heck",	"ww"},
		{"heltn",	"vox"},
		{"hichi",	"ww"},
		{"hurdy",	"str"},
		{"kitv",	"str"},
		{"klav",	"klav"},
		{"kokyu",	"str"},
		{"komun",	"str"},
		{"koto",	"str"},
		{"kruma",	"ww"},
		{"krumb",	"ww"},
		{"krums",	"ww"},
		{"krumt",	"ww"},
		{"lion",	"idio"},
		{"liuto",	"str"},
		{"lyrsp",	"vox"},
		{"lyrtn",	"vox"},
		{"male",	"vox"},
		{"mando",	"str"},
		{"marac",	"idio"},
		{"marim",	"idio"},
		{"mbari",	"vox"},
		{"mezzo",	"vox"},
		{"nfant",	"vox"},
		{"nokan",	"ww"},
		{"oboe",	"ww"},
		{"oboeD",	"ww"},
		{"ocari",	"ww"},
		{"ondes",	"klav"},
		{"ophic",	"bras"},
		{"organ",	"klav"},
		{"oud",	"str"},
		{"paila",	"idio"},
		{"panpi",	"ww"},
		{"pbell",	"idio"},
		{"pguit",	"str"},
		{"physh",	"klav"},
		{"piano",	"klav"},
		{"piatt",	"idio"},
		{"picco",	"ww"},
		{"pipa",	"str"},
		{"piri",	"ww"},
		{"porta",	"klav"},
		{"psalt",	"str"},
		{"qin",	"str"},
		{"quinto",	"vox"},
		{"quitr",	"str"},
		{"rackt",	"ww"},
		{"ratch",	"idio"},
		{"ratl",	"idio"},
		{"rebec",	"str"},
		{"recit",	"vox"},
		{"reedo",	"klav"},
		{"rhode",	"klav"},
		{"ridec",	"idio"},
		{"sarod",	"str"},
		{"sarus",	"ww"},
		{"saxA",	"ww"},
		{"saxB",	"ww"},
		{"saxC",	"ww"},
		{"saxN",	"ww"},
		{"saxR",	"ww"},
		{"saxS",	"ww"},
		{"saxT",	"ww"},
		{"sbell",	"idio"},
		{"sdrum",	"idio"},
		{"serp",	"bras"},
		{"sesto",	"vox"},
		{"shaku",	"ww"},
		{"shami",	"str"},
		{"sheng",	"ww"},
		{"sho",	"ww"},
		{"siren",	"idio"},
		{"sitar",	"str"},
		{"slap",	"idio"},
		{"soprn",	"vox"},
		{"spok",	"vox"},
		{"spokF",	"vox"},
		{"spokM",	"vox"},
		{"spshc",	"idio"},
		{"steel",	"idio"},
		{"stim",	"vox"},
		{"stimA",	"vox"},
		{"stimB",	"vox"},
		{"stimC",	"vox"},
		{"stimR",	"vox"},
		{"stimS",	"vox"},
		{"strdr",	"idio"},
		{"sxhA",	"bras"},
		{"sxhB",	"bras"},
		{"sxhC",	"bras"},
		{"sxhR",	"bras"},
		{"sxhS",	"bras"},
		{"sxhT",	"bras"},
		{"synth",	"klav"},
		{"tabla",	"idio"},
		{"tambn",	"idio"},
		{"tambu",	"str"},
		{"tanbr",	"str"},
		{"tblok",	"idio"},
		{"tdrum",	"idio"},
		{"tenor",	"vox"},
		{"timpa",	"idio"},
		{"tiorb",	"str"},
		{"tom",	"idio"},
		{"trngl",	"idio"},
		{"tromP",	"bras"},
		{"troma",	"bras"},
		{"tromb",	"bras"},
		{"tromp",	"bras"},
		{"tromt",	"bras"},
		{"trumB",	"bras"},
		{"tuba",	"bras"},
		{"tubaB",	"bras"},
		{"tubaC",	"bras"},
		{"tubaT",	"bras"},
		{"tubaU",	"bras"},
		{"ukule",	"str"},
		{"vibra",	"idio"},
		{"vina",	"str"},
		{"viola",	"str"},
		{"violb",	"str"},
		{"viold",	"str"},
		{"viole",	"str"},
		{"violn",	"str"},
		{"violp",	"str"},
		{"viols",	"str"},
		{"violt",	"str"},
		{"vox",	"vox"},
		{"wblok",	"idio"},
		{"xylo",	"idio"},
		{"zithr",	"str"},
		{"zurna",	"ww"}
	};
}





//////////////////////////////
//
// Convert::isKernRest -- Returns true if the input string represents
//   a **kern rest.
//

bool Convert::isKernRest(const string& kerndata) {
	if (kerndata.find("r") != string::npos) {
		return true;
	} else {
		return false;
	}
}



//////////////////////////////
//
// Convert::isKernNote -- Returns true if the input string represents
//   a **kern note (i.e., token with a pitch, not a null token or a rest).
//

bool Convert::isKernNote(const string& kerndata) {
	if (isKernRest(kerndata)) {
		return false;
	}
	char ch;
	for (int i=0; i < (int)kerndata.size(); i++) {
		ch = std::tolower(kerndata[i]);
		if ((ch >= 'a') && (ch <= 'g')) {
			return true;
		}
	}
	return false;
}



//////////////////////////////
//
// Convert::isKernSecondaryTiedNote -- Returns true if the input string
//   represents a **kern note (i.e., token with a pitch,
//   not a null token or a rest) and has a '_' or ']' character.
//

bool Convert::isKernSecondaryTiedNote(const string& kerndata) {
	char ch;
	if (!Convert::isKernNote(kerndata)) {
		return false;
	}
	for (int i=0; i < (int)kerndata.size(); i++) {
		ch = std::tolower(kerndata[i]);
		if ((ch == '_') || (ch == ']')) {
			return true;
		}
	}
	return false;
}



//////////////////////////////
//
// Convert::isKernNoteAttack -- Returns true if the input string
//   represents a **kern note (not null or rest) and is not a
//   secondary tied note.
//

bool Convert::isKernNoteAttack(const string& kerndata) {
	char ch;
	if (!Convert::isKernNote(kerndata)) {
		return false;
	}
	for (int i=0; i < (int)kerndata.size(); i++) {
		ch = std::tolower(kerndata[i]);
		if ((ch == '_') || (ch == ']')) {
			return false;
		}
	}
	return true;
}



//////////////////////////////
//
// Convert::hasKernSlurStart -- Returns true if the input string
//   has a '('.
//

bool Convert::hasKernSlurStart(const string& kerndata) {
	return kerndata.find('(') != string::npos;
}



//////////////////////////////
//
// Convert::hasKernSlurEnd -- Returns true if the input string
//   has a ')'.
//

bool Convert::hasKernSlurEnd(const string& kerndata) {
	return kerndata.find(')') != string::npos;
}



//////////////////////////////
//
// Convert::hasKernPhraseStart -- Returns true if the input string
//   has a '{'.
//

bool Convert::hasKernPhraseStart(const string& kerndata) {
	return kerndata.find('{') != string::npos;
}



//////////////////////////////
//
// Convert::hasKernPhraseEnd -- Returns true if the input string
//   has a '}'.
//

bool Convert::hasKernPhraseEnd(const string& kerndata) {
	return kerndata.find('}') != string::npos;
}



//////////////////////////////
//
// Convert::getKernBeamStartElisionLevel -- Returns the number of
//   '&' characters before the given 'L' character in a kern token.
//   Returns -1 if no 'L' character in string.
//

int Convert::getKernBeamStartElisionLevel(const string& kerndata, int index) {
	bool foundBeamStart = false;
	int output = 0;
	int count = 0;
	int target = index + 1;
	for (int i=0; i<(int)kerndata.size(); i++) {
		char ch = kerndata[i];
		if (ch == 'L') {
			count++;
		}
		if (count == target) {
			foundBeamStart = true;
			for (int j=i-1; j>=0; j--) {
				ch = kerndata[j];
				if (ch == '&') {
					output++;
				} else {
					break;
				}
			}
			break;
		}
	}
	if (!foundBeamStart) {
		return -1;
	} else {
		return output;
	}
}



//////////////////////////////
//
// Convert::getKernBeamEndElisionLevel -- Returns the number of
//   '&' characters before the last 'J' character in a kern token.
//   Returns -1 if no 'J' character in string.
//

int Convert::getKernBeamEndElisionLevel(const string& kerndata, int index) {
	bool foundBeamEnd = false;
	int output = 0;
	int count = 0;
	int target = index + 1;
	for (int i=0; i<(int)kerndata.size(); i++) {
		char ch = kerndata[i];
		if (ch == 'J') {
			count++;
		}
		if (count == target) {
			foundBeamEnd = true;
			for (int j=i-1; j>=0; j--) {
				ch = kerndata[j];
				if (ch == '&') {
					output++;
				} else {
					break;
				}
			}
			break;
		}
	}
	if (!foundBeamEnd) {
		return -1;
	} else {
		return output;
	}
}



//////////////////////////////
//
// Convert::getKernSlurStartElisionLevel -- Returns the number of
//   '&' characters before the given '(' character in a kern token.
//   Returns -1 if no '(' character in string.
//

int Convert::getKernSlurStartElisionLevel(const string& kerndata, int index) {
	bool foundSlurStart = false;
	int output = 0;
	int count = 0;
	int target = index + 1;
	for (int i=0; i<(int)kerndata.size(); i++) {
		char ch = kerndata[i];
		if (ch == '(') {
			count++;
		}
		if (count == target) {
			foundSlurStart = true;
			for (int j=i-1; j>=0; j--) {
				ch = kerndata[j];
				if (ch == '&') {
					output++;
				} else {
					break;
				}
			}
			break;
		}
	}
	if (!foundSlurStart) {
		return -1;
	} else {
		return output;
	}
}



//////////////////////////////
//
// Convert::getKernSlurEndElisionLevel -- Returns the number of
//   '&' characters before the last ')' character in a kern token.
//   Returns -1 if no ')' character in string.
//

int Convert::getKernSlurEndElisionLevel(const string& kerndata, int index) {
	bool foundSlurEnd = false;
	int output = 0;
	int count = 0;
	int target = index + 1;
	for (int i=0; i<(int)kerndata.size(); i++) {
		char ch = kerndata[i];
		if (ch == ')') {
			count++;
		}
		if (count == target) {
			foundSlurEnd = true;
			for (int j=i-1; j>=0; j--) {
				ch = kerndata[j];
				if (ch == '&') {
					output++;
				} else {
					break;
				}
			}
			break;
		}
	}
	if (!foundSlurEnd) {
		return -1;
	} else {
		return output;
	}
}



//////////////////////////////
//
// Convert::getKernPhraseStartElisionLevel -- Returns the number of
//   '&' characters before the given '{' character in a kern token.
//   Returns -1 if no '{' character in string.
//

int Convert::getKernPhraseStartElisionLevel(const string& kerndata, int index) {
	bool foundPhraseStart = false;
	int output = 0;
	int count = 0;
	int target = index + 1;
	for (int i=0; i<(int)kerndata.size(); i++) {
		char ch = kerndata[i];
		if (ch == '{') {
			count++;
		}
		if (count == target) {
			foundPhraseStart = true;
			for (int j=i-1; j>=0; j--) {
				ch = kerndata[j];
				if (ch == '&') {
					output++;
				} else {
					break;
				}
			}
			break;
		}
	}
	if (!foundPhraseStart) {
		return -1;
	} else {
		return output;
	}
}



//////////////////////////////
//
// Convert::getKernPhraseEndElisionLevel -- Returns the number of
//   '&' characters before the last '}' character in a kern token.
//   Returns -1 if no '}' character in string.
//

int Convert::getKernPhraseEndElisionLevel(const string& kerndata, int index) {
	bool foundPhraseEnd = false;
	int output = 0;
	int count = 0;
	int target = index + 1;
	for (int i=0; i<(int)kerndata.size(); i++) {
		char ch = kerndata[i];
		if (ch == '}') {
			count++;
		}
		if (count == target) {
			foundPhraseEnd = true;
			for (int j=i-1; j>=0; j--) {
				ch = kerndata[j];
				if (ch == '&') {
					output++;
				} else {
					break;
				}
			}
			break;
		}
	}
	if (!foundPhraseEnd) {
		return -1;
	} else {
		return output;
	}
}



//////////////////////////////
//
// Convert::getKernPitchAttributes --
//    pc         = pitch class
//    numacc     = numeric accidental (-1=flat, 0=natural, 1=sharp)
//    explicit   = force showing of accidental
//    oct        = octave number (middle C = 4)
//    base40     = base-40 enumeration of pitch (valid if abs(numacc) <= 2)
//

string Convert::getKernPitchAttributes(const string& kerndata) {
	int accid = kernToAccidentalCount(kerndata);
	string output = "";

	output += " dpc=\"";
	output += kernToDiatonicUC(kerndata);
	output += "\"";

	output += " numacc=\"";
	output += to_string(accid);
	output += "\"";

	if (kerndata.find('n') != string::npos) {
		output += " explicit =\"true\"";
	}

	output += " oct=\"";
	output += to_string(kernToOctaveNumber(kerndata));
	output += "\"";

	if (abs(accid) <= 2) {
		output += " base40=\"";
		output += to_string(kernToBase40(kerndata));
		output += "\"";
	}

	return output;
}



//////////////////////////////
//
// Convert::hasKernStemDirection -- Returns true if a stem direction in data; otherwise,
//    return false.  If true, then '/' means stem up, and '\\' means stem down.
//

char Convert::hasKernStemDirection(const string& kerndata) {
	for (int i=0; i<(int)kerndata.size(); i++) {
		if (kerndata[i] == '/') {
			return '/';
		}
		if (kerndata[i] == '\\') {
			return '\\';
		}
	}
	return '\0';
}



//////////////////////////////
//
// Convert::kernToRecip -- Extract only the **recip data from **kern data.
//

string Convert::kernToRecip(const std::string& kerndata) {
	string output;
	output.reserve(kerndata.size());
	for (int i=0; i<(int)kerndata.size(); i++) {
		if (kerndata.at(i) == ' ') {
			// only process the first subtoken
			break;
		}
		switch (kerndata.at(i)) {
			case '0':
			case '1':
			case '2':
			case '3':
			case '4':
			case '5':
			case '6':
			case '7':
			case '8':
			case '9':
			case '.':   // augmentation dot
			case '%':   // rational rhythms
			case 'q':   // grace note (zero duration)
				output += kerndata.at(i);
		}
	}
	return output;
}


string Convert::kernToRecip(HTp token) {
	return Convert::kernToRecip((string)*token);
}



//////////////////////////////
//
// Convert::kernKeyToNumber -- convert a kern key signature into an integer.
//      For example: *k[f#] == +1, *k[b-e-] == -2, *k[] == 0
//      Input string is expected to be in the form *k[] with the
//      accidentals inside the brackets with no spaces.
//

int Convert::kernKeyToNumber(const string& aKernString) {
	int count = 0;
	int length = (int)aKernString.size();
	int start = 0;
	int sign = 1;

	if ((length == 0) || (aKernString.find("[]") != std::string::npos)) {
		return 0;
	}

	for (int i=0; i<length; i++) {
		if (start) {
			if (aKernString[i] == ']') {
				break;
			} else if (aKernString[i] == '-') {
				sign = -1;
			}
			count++;
		}
		else if (aKernString[i] == '[') {
			start = 1;
		}
	}

	return sign * count/2;
}



//////////////////////////////
//
// Convert::kernToDuration -- returns the kern rhythm's duration, using
//	1.0 as the duration of a quarter note (rhythm=4).
// if the kern token has a "q" then assume that it is a grace note
// and return a duration of zero.
//

HumNum Convert::kernToDuration(const string& aKernString) {
	HumNum zero(0,1);
	HumRegex hre;

	// check for grace notes
	if ((aKernString.find('q') != std::string::npos) ||
			(aKernString.find('Q') != std::string::npos)) {
		return zero;
	}

	// check for dots to modify rhythm
	// also add an exit if a space is found, so that dots
	// from multiple notes in chord notes do not get accidentally
	// get counted together (input to this function should be a
	// single note, but chords may accidentally be sent to this
	// function instead).
	int dotcount = 0;
	for (int i=0; i<(int)aKernString.size(); i++) {
		if (aKernString[i] == '.') {
			dotcount++;
		}
		if (aKernString[i] == ' ') {
			break;
		}
	}

	// parse special rhythms which can't be represented in
	// classical **kern definition.  A non-standard rhythm
	// consists of two numbers separated by any character.
	if (hre.search(aKernString, "(\\d+)[^\\d](\\d+)")) {
		int rtop = stoi(hre.getMatch(1));
		int rbot = stoi(hre.getMatch(2));
		HumNum original(rbot, rtop);  // duration is inverse
		HumNum output(rbot, rtop);    // duration is inverse
		original *= 4;  // adjust to quarter note count;
		output *= 4;    // adjust to quarter note count;
		for (int i=0; i<dotcount; i++) {
			output += original / (int)(pow(2.0, (double)(i+1)));
		}
		return output;
	}

	int index = 0;
	while ((index < (int)aKernString.size()) && !isdigit(aKernString[index])) {
		index++;
	}
	if (index >= (int)aKernString.size()) {
		// no rhythm data found
		return zero;
	}

	// should now be at start of kern rhythm
	int orhythm = 0;
	while ((index < (int)aKernString.size()) && isdigit(aKernString[index])) {
		orhythm *= 10;
		orhythm += aKernString[index] - '0';
		index++;
	}

	HumNum oduration(0,1);
	if ((aKernString.find('0') != std::string::npos) &&
		 (aKernString.find('1') == std::string::npos) &&
		 (aKernString.find('2') == std::string::npos) &&
		 (aKernString.find('3') == std::string::npos) &&
		 (aKernString.find('4') == std::string::npos) &&
		 (aKernString.find('5') == std::string::npos) &&
		 (aKernString.find('6') == std::string::npos) &&
		 (aKernString.find('7') == std::string::npos) &&
		 (aKernString.find('8') == std::string::npos) &&
		 (aKernString.find('9') == std::string::npos)    ) {
		if (aKernString.find("0000000000") != std::string::npos) { // exotic rhythm
			oduration = 4096;
		} else if (aKernString.find("000000000") != std::string::npos) { // exotic rhythm
			oduration = 2048;
		} else if (aKernString.find("00000000") != std::string::npos) { // exotic rhythm
			oduration = 1024;
		} else if (aKernString.find("0000000") != std::string::npos) { // exotic rhythm
			oduration = 512;
		} else if (aKernString.find("000000") != std::string::npos) { // exotic rhythm
			oduration = 256;
		} else if (aKernString.find("00000") != std::string::npos) { // exotic rhythm
			oduration = 128;
		} else if (aKernString.find("0000") != std::string::npos) { // exotic rhythm
			oduration = 64;
		} else if (aKernString.find("000") != std::string::npos) { // 000 = maxima
			oduration = 32;
		} else if (aKernString.find("00") != std::string::npos) {  // 00 = long
			oduration = 16;
		} else { // 0 == breve
			oduration = 8;
		}

	} else {
		// now know everything to create a duration
		if (orhythm == 0) {
			oduration = 8;
		} else {
			oduration = 4;
			oduration /= orhythm;
		}
	}

	HumNum duration = oduration;
	for (int i=0; i<dotcount; i++) {
		duration += oduration / (int)(pow(2.0, (double)(i+1)));
	}

	return duration;
}





//////////////////////////////
//
// Convert::getLcm -- Return the Least Common Multiple of a list of numbers.
//

int Convert::getLcm(const vector<int>& numbers) {
	if (numbers.size() == 0) {
		return 1;
	}
	int output = numbers[0];
	for (int i=1; i<(int)numbers.size(); i++) {
		output = (output * numbers[i]) / getGcd(output, numbers[i]);
	}
	return output;
}



//////////////////////////////
//
// Convert::getGcd -- Return the Greatest Common Divisor of two numbers.
//

int Convert::getGcd(int a, int b) {
	if (b == 0) {
		return a;
	}
	int c = a % b;
	a = b;
	int output = getGcd(a, c);
	return output;
}



//////////////////////////////
//
// Convert::primeFactors -- Return a list of prime factors of a number.
//

void Convert::primeFactors(vector<int>& output, int n) {
	output.clear();
	while (n%2 == 0) {
		output.push_back(2);
		n = n >> 1;
	}
	for (int i=3; i <= sqrt(n); i += 2) {
		while (n%i == 0) {
			output.push_back(i);
			n = n/i;
		}
	}
	if (n > 2) {
		output.push_back(n);
	}
}



//////////////////////////////
//
// Convert::nearIntQuantize -- avoid small deviations from integer values.
//    devault value: delta = 0.00001
//

double Convert::nearIntQuantize(double value, double delta) {
	if ((value + delta) - int(value+delta)  < delta*2) {
		value = (int)(value+delta);
	}
	return value;
}



//////////////////////////////
//
// Convert::significantDigits --
//

double Convert::significantDigits(double value, int digits) {
	double scale = pow(10, digits);
	return (int(value * scale + 0.5))/scale;
}



//////////////////////////////
//
// Convert::isNaN -- needed due to compiler differences.
//

bool Convert::isNaN(double value) {
	union { uint64_t u; double f; } ieee754;
	ieee754.f = value;
	return ( (unsigned)(ieee754.u >> 32) & 0x7fffffff ) +
           ( (unsigned)ieee754.u != 0 ) > 0x7ff00000;
}



//////////////////////////////
//
// Convert::isPowerOfTwo --
//

bool Convert::isPowerOfTwo(int value) {
	if (value < 0) {
		return (-value & (-value - 1)) == 0;
	} else if (value > 0) {
		return (value & (value - 1)) == 0;
	} else {
		return false;
	}
}



//////////////////////////////
//
// Convert::pearsonCorrelation --
//

double Convert::pearsonCorrelation(const vector<double>& x, const vector<double>& y) {
	double sumx  = 0.0;
	double sumy  = 0.0;
	double sumco = 0.0;
	double meanx = x[0];
	double meany = y[0];

	int size = (int)x.size();
	if ((int)y.size() < size) {
		size = (int)y.size();
	}

	for (int i=2; i<=size; i++) {
		double sweep = (i-1.0) / i;
		double deltax = x[i-1] - meanx;
		double deltay = y[i-1] - meany;
		sumx  += deltax * deltax * sweep;
		sumy  += deltay * deltay * sweep;
		sumco += deltax * deltay * sweep;
		meanx += deltax / i;
		meany += deltay / i;
	}

	double popsdx = sqrt(sumx / size);
	double popsdy = sqrt(sumy / size);
	double covxy  = sumco / size;

	return covxy / (popsdx * popsdy);
}



//////////////////////////////
//
// Convert::standardDeviation --
//

double Convert::standardDeviation(const vector<double>& x) {
	double sum = 0.0;
	for (int i=0; i<(int)x.size(); i++) {
		sum += x[i];
	}
	double mean = sum / x.size();
	double variance = 0.0;
	for (int i=0; i<(int)x.size(); i++) {
		variance += pow(x[i] - mean, 2);
	}
	variance = variance / x.size();
	return sqrt(variance);
}


double Convert::standardDeviation(const vector<int>& x) {
	double sum = 0.0;
	for (int i=0; i<(int)x.size(); i++) {
		sum += x[i];
	}
	double mean = sum / x.size();
	double variance = 0.0;
	for (int i=0; i<(int)x.size(); i++) {
		variance += pow(x[i] - mean, 2);
	}
	variance = variance / x.size();
	return sqrt(variance);
}



//////////////////////////////
//
// Convert::standardDeviationSample -- Similar to Convert::standardDeviation,
//     but divide by (size-1) rather than (size).
//

double Convert::standardDeviationSample(const vector<double>& x) {
	double sum = 0.0;
	for (int i=0; i<(int)x.size(); i++) {
		sum += x[i];
	}
	double mean = sum / x.size();
	double variance = 0.0;
	for (int i=0; i<(int)x.size(); i++) {
		variance += pow(x[i] - mean, 2);
	}
	variance = variance / ((int)x.size()-1);
	return sqrt(variance);
}


double Convert::standardDeviationSample(const vector<int>& x) {
	double sum = 0.0;
	for (int i=0; i<(int)x.size(); i++) {
		sum += x[i];
	}
	double mean = sum / x.size();
	double variance = 0.0;
	for (int i=0; i<(int)x.size(); i++) {
		variance += pow(x[i] - mean, 2);
	}
	variance = variance / ((int)x.size()-1);
	return sqrt(variance);
}



//////////////////////////////
//
// Convert::mean -- calculate the mean (average) of a list of numbers.
//

double Convert::mean(const std::vector<double>& x) {
	double output = 0.0;
	for (int i=0; i<(int)x.size(); i++) {
		output += x[i];
	}
	return output / (int)x.size();
}


double Convert::mean(const std::vector<int>& x) {
	double output = 0.0;
	for (int i=0; i<(int)x.size(); i++) {
		output += x[i];
	}
	return output / (int)x.size();
}



//////////////////////////////
//
// Convert::coefficientOfVariationPopulation -- Standard deviation divided by
//    mean.  From: Patel, Iversen & Rosenberg (2006): Comparing the
//    rhythm and melody of speech and music: The case of British
//    English and French.  JASA 119(5), May 2006, pp. 3034-3047.
//

double Convert::coefficientOfVariationPopulation(const std::vector<double>& x) {
	double sd = Convert::standardDeviation(x);
	double mean = Convert::mean(x);
	return sd / mean;
}



//////////////////////////////
//
// Convert::coefficientOfVariationSample -- Standard deviation divided by
//    mean.  From: Patel, Iversen & Rosenberg (2006): Comparing the
//    rhythm and melody of speech and music: The case of British
//    English and French.  JASA 119(5), May 2006, pp. 3034-3047.
//

double Convert::coefficientOfVariationSample(const std::vector<double>& x) {
	double sd = Convert::standardDeviationSample(x);
	double mean = Convert::mean(x);
	return sd / mean;
}



//////////////////////////////
//
// Convert::nPvi -- normalized pairwise variablity index.
//    See: Linguistic: Grabe & Lowe 2002.
//    See: Daniele & Patel 2004.
//    See: Patel, Iversen & Rosenberg (2006): Comparing the
//    rhythm and melody of speech and music: The case of British
//    English and French.  JASA 119(5), May 2006, pp. 3034-3047.
//

double Convert::nPvi(const std::vector<double>& x) {
	double output = 0.0;
	for (int i=0; i<(int)x.size() - 1; i++) {
		output += fabs((x[i] - x[i+1]) / (x[i] + x[i+1]));
	}
	output *= 200.0 / ((int)x.size() - 1);
	return output;
}



//////////////////////////////
//
// Convert::romanNumeralToInteger -- Convert a roman numeral into an integer.
//

int Convert::romanNumeralToInteger(const string& roman) {
	int rdigit;
	int sum = 0;
	char previous='_';
	for (int i=(int)roman.length()-1; i>=0; i--) {
		switch (roman[i]) {
			case 'I': case 'i': rdigit =    1; break;
			case 'V': case 'v': rdigit =    5; break;
			case 'X': case 'x': rdigit =   10; break;
			case 'L': case 'l': rdigit =   50; break;
			case 'C': case 'c': rdigit =  100; break;
			case 'D': case 'd': rdigit =  500; break;
			case 'M': case 'm': rdigit = 1000; break;
			default:  rdigit =   -1;
		}
		if (rdigit < 0) {
			continue;
		} else if (rdigit < sum && (roman[i] != previous)) {
			sum -= rdigit;
		} else {
			sum += rdigit;
		}
		previous = roman[i];
	}

	return sum;
}





//////////////////////////////
//
// Convert::isMensRest -- Returns true if the input string represents
//   a **mens rest.
//

bool Convert::isMensRest(const string& mensdata) {
	return mensdata.find('r') != std::string::npos;
}



//////////////////////////////
//
// Convert::isMensNote -- Returns true if the input string represents
//   a **mens note (i.e., token with a pitch, not a null token or a rest).
//

bool Convert::isMensNote(const string& mensdata) {
	char ch;
	for (int i=0; i < (int)mensdata.size(); i++) {
		ch = std::tolower(mensdata[i]);
		if ((ch >= 'a') && (ch <= 'g')) {
			return true;
		}
	}
	return false;
}



//////////////////////////////
//
// Convert::hasLigatureBegin -- Returns true if the input string
//   has a '<' or '[' character.
//

bool Convert::hasLigatureBegin(const string& mensdata) {
	return hasRectaLigatureBegin(mensdata) || hasObliquaLigatureBegin(mensdata);
}



//////////////////////////////
//
// Convert::hasRectaLigatureBegin --
//

bool Convert::hasRectaLigatureBegin(const string& mensdata) {
	return mensdata.find('[') != std::string::npos;
}



//////////////////////////////
//
// Convert::hasObliquaLigatureBegin --
//

bool Convert::hasObliquaLigatureBegin(const string& mensdata) {
	return mensdata.find('<') != std::string::npos;
}



//////////////////////////////
//
// Convert::hasLigatureEnd --
//

bool Convert::hasLigatureEnd(const string& mensdata) {
	return hasRectaLigatureEnd(mensdata) || hasObliquaLigatureEnd(mensdata);
}



//////////////////////////////
//
// Convert::hasRectaLigatureEnd -- Returns true if the input string
//   has a ']'.
//

bool Convert::hasRectaLigatureEnd(const string& mensdata) {
	return mensdata.find(']') != std::string::npos;
}



//////////////////////////////
//
// Convert::hasObliquaLigatureEnd -- Returns true if the input string
//   has a '>'.
//

bool Convert::hasObliquaLigatureEnd(const string& mensdata) {
	return mensdata.find('>') != std::string::npos;
}



//////////////////////////////
//
// Convert::getMensStemDir -- Returns the stem direction of **mens
//   notes.  These are the same as **kern pitches.
//      / = stem up, return +1
//      \ = stemp down, return -1
//      otherwise return 0 for no stem information
//

bool Convert::getMensStemDirection(const string& mensdata) {
	if (mensdata.find('/') != std::string::npos) {
		return +1;
	} else if (mensdata.find('\\') != std::string::npos) {
		return +1;
	} else {
		return 0;
	}
}



//////////////////////////////
//
// Convert::mensToDuration --
//   X = maxima (octuple whole note)
//   L = long  (quadruple whole note)
//   S = breve (double whole note)
//   s = semi-breve (whole note)
//   M = minim (half note)
//   m = semi-minim (quarter note)
//   U = fusa (eighth note)
//   u = semifusa (sixteenth note)
//
//   p = perfect (dotted)
//   i = imperfect (not-dotted)
//
// Still have to deal with coloration (triplets)
//
// Default value: scale = 4 (convert to quarter note units)
//                separator = " " (space between chord notes)
//

HumNum Convert::mensToDuration(const string& mensdata, HumNum scale,
		const string& separator) {
	HumNum output(0);
   bool perfect = false;
   // bool imperfect = true;
	for (int i=0; i<(int)mensdata.size(); i++) {
		if (mensdata[i] == 'p') {
			perfect = true;
			// imperfect = false;
		}
		if (mensdata[i] == 'i') {
			perfect = false;
			// imperfect = true;
		}

		// units are in whole notes, but scaling will probably
		// convert to quarter notes (default
		switch (mensdata[i]) {
			case 'X': output = 8; break;              // octuple whole note
			case 'L': output = 4; break;              // quadruple whole note
			case 'S': output = 2; break;              // double whole note
			case 's': output = 1; break;              // whole note
			case 'M': output.setValue(1, 2);  break;  // half note
			case 'm': output.setValue(1, 4);  break;  // quarter note
			case 'U': output.setValue(1, 8);  break;  // eighth note
			case 'u': output.setValue(1, 16); break;  // sixteenth note
		}

		if (mensdata.compare(i, separator.size(), separator) == 0) {
			// only get duration of first note in chord
			break;
		}
	}

	if (perfect) {
		output *= 3;
		output /= 2;
	}
	output *= scale;
	return output;
}



//////////////////////////////
//
// Convert::mensToDurationNoDots -- The imperfect duration of the **mens rhythm.
//

HumNum Convert::mensToDurationNoDots(const string& mensdata, HumNum scale,
		const string& separator) {
	HumNum output(0);

	for (int i=0; i<(int)mensdata.size(); i++) {
		switch (mensdata[i]) {
			case 'X': output = 8; break;              // octuple whole note
			case 'L': output = 4; break;              // quadruple whole note
			case 'S': output = 2; break;              // double whole note
			case 's': output = 1; break;              // whole note
			case 'M': output.setValue(1, 2);  break;  // half note
			case 'm': output.setValue(1, 4);  break;  // quarter note
			case 'U': output.setValue(1, 8);  break;  // eighth note
			case 'u': output.setValue(1, 16); break;  // sixteenth note
		}
		if (mensdata.compare(i, separator.size(), separator) == 0) {
			// only get duration of first note in chord
			break;
		}
	}
	output *= scale;

	return output;
}



//////////////////////////////
//
// Convert::mensToRecip --
//

string Convert::mensToRecip(const string& mensdata, HumNum scale, const string& separator) {
	HumNum duration = Convert::mensToDuration(mensdata, scale, separator);
	return Convert::durationToRecip(duration);
}



//////////////////////////////
//
// Convert::mensToRecip -- Convert from **mens rhythmic levels according to alteration/perfections/imperfection and
//      divisions of rhythmic levels based on prevailing mensuration.
//

string Convert::mensToRecip(char rhythm, bool altera, bool perfecta, bool imperfecta,
		int maximodus, int modus, int tempus, int prolatio) {

	double minim_units;


	double maxima_def = 16;
	double longa_def = 8;
	double brevis_def = 4;
	double semibrevis_def = 2;
	double minima_def = 1;
	double semiminima_def = 0.5;
	double fusa_def = 0.25;
	double semifusa_def = 0.125;

	prolatio  = prolatio  == 2 ? 2 : 3;
	tempus    = tempus    == 2 ? 2 : 3;
	modus     = modus     == 2 ? 2 : 3;
	maximodus = maximodus == 2 ? 2 : 3;

	semibrevis_def = prolatio;
	brevis_def     = semibrevis_def * tempus;
	longa_def      = brevis_def     * modus;
	maxima_def     = longa_def      * maximodus;

	if (rhythm == 'X') {
		if (perfecta) { minim_units = longa_def * 3; }
		else if (imperfecta) { minim_units = longa_def * 2; }
		else { minim_units = maxima_def; }
	}
	else if (rhythm == 'L') {
		if (perfecta) { minim_units = brevis_def * 3; }
		else if (imperfecta) { minim_units = brevis_def * 2; }
		else if (altera) { minim_units = longa_def * 2; }
		else { minim_units = longa_def; }
	}
	else if (rhythm == 'S') {
		if (perfecta) { minim_units = semibrevis_def * 3; }
		else if (imperfecta) { minim_units = semibrevis_def * 2; }
		else if (altera) { minim_units = brevis_def * 2; }
		else { minim_units = brevis_def; }
	}
	else if (rhythm == 's') {
		if (perfecta) { minim_units = minima_def * 3; }
		else if (imperfecta) { minim_units = minima_def * 2; }
		else if (altera) { minim_units = semibrevis_def * 2; }
		else { minim_units = semibrevis_def; }
	}
	else if (rhythm == 'M') {
		if (perfecta) { minim_units = minima_def * 3 / 2; }
		else if (altera) { minim_units = minima_def * 2; }
		else { minim_units = minima_def; }
	}
	else if (rhythm == 'm') {
		if (perfecta) { minim_units = semiminima_def * 3 / 2; }
		else { minim_units = semiminima_def; }
	}
	else if (rhythm == 'U') {
		if (perfecta) { minim_units = fusa_def * 3 / 2; }
		else { minim_units = fusa_def; }
	}
	else if (rhythm == 'u') {
		if (perfecta) { minim_units = semifusa_def * 3 / 2; }
		else { minim_units = semifusa_def; }
	}
	else { cerr << "UNKNOWN RHYTHM: " << rhythm << endl; return ""; }

	switch ((int)(minim_units * 10000)) {
		case 1250:     return "16";   break;   // sixteenth note
		case 1875:     return "16.";  break;   // dotted sixteenth note
		case 2500:     return "8";    break;   // eighth note
		case 3750:     return "8.";   break;   // dotted eighth note
		case 5000:     return "4";    break;   // quarter note
		case 7500:     return "4.";   break;   // dotted quarter note
		case 10000:    return "2";    break;   // half note
		case 15000:    return "2.";   break;   // dotted half note
		case 20000:    return "1";    break;   // whole note
		case 30000:    return "1.";   break;   // dotted whole note
		case 40000:    return "0";    break;   // breve note
		case 60000:    return "0.";   break;   // dotted breve note
		case 90000:    return "2%9";  break;   // or ["0.", "1."];
		case 80000:    return "00";   break;   // long note
		case 120000:   return "00.";  break;   // dotted long note
		case 180000:   return "1%9";  break;   // or ["00.", "0."];
		case 270000:   return "2%27"; break;   // or ["0.", "1.", "0.", "1.", "0.", "1."];
		case 160000:   return "000";  break;   // maxima note
		case 240000:   return "000."; break;   // dotted maxima note
		case 360000:   return "1%18"; break;   // or ["000.", "00."];
		case 540000:   return "1%27"; break;   // or ["00.", "0.", "00.", "0.", "00.", "0."];
		case 810000:   return "2%81"; break;   // or ["00.", "0.", "00.", "0.", "00.", "0.", "0.", "1.", "0.", "1.", "0.", "1."];
		default:
			cerr << "Error: unknown minim_units: " << minim_units << endl;
	}

	return "";
}




//////////////////////////////
//
// Convert::mensToDuration -- Convert from **mens rhythmic levels according to alteration/perfections/imperfection and
//      divisions of rhythmic levels based on prevailing mensuration.
//

HumNum Convert::mensToDuration(HTp menstok) {
	int rlev = menstok->getValueInt("auto", "mensuration", "levels");
	if (rlev < 2222) {
		cerr << "Warning: cannot find mensuration levels for token " << menstok << endl;
		rlev = 2222;
	}
	return Convert::mensToDuration(*menstok, rlev);
}

HumNum Convert::mensToDuration(HTp menstok, const std::string& mettok) {
	int rlev = Convert::metToMensurationLevels(mettok);
	return Convert::mensToDuration(*menstok, rlev);
}


HumNum Convert::mensToDuration(const string& menstok, int rlev) {
	if (rlev < 2222) {
		rlev = 2222;
	}
	int maximodus = (rlev / 1000) % 10;
	int modus     = (rlev / 100)  % 10;
	int tempus    = (rlev / 10)   % 10;
	int prolation =  rlev         % 10;
	bool altera = false;
	bool perfecta = false;
	bool imperfecta = false;

	if (menstok.find("+") != string::npos) {
		altera = true;
	}
	if (menstok.find("p") != string::npos) {
		perfecta = true;
	}
	if (menstok.find("i") != string::npos) {
		imperfecta = true;
	}
	HumRegex hre;
	if (!hre.search(menstok, "([XLSsMmUu])")) {
		// invalid note/rest rhythm
		return 0;
	}
	string rhythm = hre.getMatch(1);
	char rchar = rhythm[0];

	// check for redundant perfection/imperfection?
	return Convert::mensToDuration(rchar, altera, perfecta, imperfecta, maximodus, modus, tempus, prolation);

}

HumNum Convert::mensToDuration(char rhythm, bool altera, bool perfecta, bool imperfecta,
		int maximodus, int modus, int tempus, int prolatio) {

	HumNum minim_units;

	HumNum maxima_def(16,1);
	HumNum longa_def(8,1);
	HumNum brevis_def(4,1);
	HumNum semibrevis_def(2,1);
	HumNum minima_def(1,1);
	HumNum semiminima_def(1,2);
	HumNum fusa_def(1, 4);
	HumNum semifusa_def(1, 8);

	prolatio  = prolatio  == 2 ? 2 : 3;
	tempus    = tempus    == 2 ? 2 : 3;
	modus     = modus     == 2 ? 2 : 3;
	maximodus = maximodus == 2 ? 2 : 3;

	semibrevis_def = prolatio;
	brevis_def     = semibrevis_def * tempus;
	longa_def      = brevis_def     * modus;
	maxima_def     = longa_def      * maximodus;

	if (rhythm == 'X') {
		if      (perfecta)   { minim_units = longa_def * 3; }
		else if (imperfecta) { minim_units = longa_def * 2; }
		else                 { minim_units = maxima_def; }
	}
	else if (rhythm == 'L') {
		if      (perfecta)   { minim_units = brevis_def * 3; }
		else if (imperfecta) { minim_units = brevis_def * 2; }
		else if (altera)     { minim_units = longa_def  * 2; }
		else                 { minim_units = longa_def; }
	}
	else if (rhythm == 'S') {
		if      (perfecta)   { minim_units = semibrevis_def * 3; }
		else if (imperfecta) { minim_units = semibrevis_def * 2; }
		else if (altera)     { minim_units = brevis_def     * 2; }
		else                 { minim_units = brevis_def; }
	}
	else if (rhythm == 's') {
		if      (perfecta)   { minim_units = minima_def     * 3; }
		else if (imperfecta) { minim_units = minima_def     * 2; }
		else if (altera)     { minim_units = semibrevis_def * 2; }
		else                 { minim_units = semibrevis_def; }
	}
	else if (rhythm == 'M') {
		if      (perfecta) { minim_units = minima_def * 3 / 2; }
		else if (altera)   { minim_units = minima_def * 2; }
		else               { minim_units = minima_def; }
	}
	else if (rhythm == 'm') {
		if (perfecta) { minim_units = semiminima_def * 3 / 2; }
		else          { minim_units = semiminima_def; }
	}
	else if (rhythm == 'U') {
		if (perfecta) { minim_units = fusa_def * 3 / 2; }
		else          { minim_units = fusa_def; }
	}
	else if (rhythm == 'u') {
		if (perfecta) { minim_units = semifusa_def * 3 / 2; }
		else          { minim_units = semifusa_def; }
	}
	else { cerr << "UNKNOWN RHYTHM: " << rhythm << endl; return 0; }

	HumNum quarter_units = minim_units * 2;
	return quarter_units;
}



//////////////////////////////
//
// HumdrumInput::metToMensurationLevels --
//		Return value has four digits that are either 2 or 3 (almost
//    always, but currently always as output from this function):
//    	WXYZ
//    W = number of longs in a maxima (maximodus)
//    X = number of breves in a long (modus)
//    Y = number of semibreves in a breve (tempus)
//    Z = number of minims in a semibreve (prolation)
//
// See page 17 of: https://digital.library.unt.edu/ark:/67531/metadc86/m1/40
// For levels for basic mensuration signs:
//
//    sign  maximodus modus tempus prolation
//    O3    3         3     3      2          (breve = tactus)
//    C3    2         2     3      2          (breve = tactus)
//    O2    2         3     2      2          (breve = tactus)
//    C2    2         2     2      2          (breve = tactus)
//    O     2         2     3      2          (semibreve = tactus)
//    C     2         2     2      2          (semibreve = tactus)
//    O.    2         2     3      3          (semibreve = tactus)
//    C.    2         2     2      3          (semibreve = tactus)
//

int Convert::metToMensurationLevels(const string& metsig) {
	// Default divisions are 2 (matching modern system):
	int maximodus = 2;
	int modus     = 2;
	int tempus    = 2;
	int prolation = 2;

	// Explicit values for rhythmic levels:
	int emaximodus = 0;
	int emodus     = 0;
	int etempus    = 0;
	int eprolation = 0;

	HumRegex hre;

	// Check for explicit rhythmic level divisions:
	if (hre.search(metsig, "^\\*?met\\(.*?\\)_(\\d)(\\d)(\\d)(\\d)")) {
		emaximodus = hre.getMatchInt(1);
		emodus     = hre.getMatchInt(2);
		etempus    = hre.getMatchInt(3);
		eprolation = hre.getMatchInt(4);
		// Limit to two or three divisions per level:
		emaximodus = emaximodus == 3 ? 3 : 2;
		emodus     = emodus     == 3 ? 3 : 2;
		etempus    = etempus    == 3 ? 3 : 2;
		eprolation = eprolation == 3 ? 3 : 2;
		return emaximodus * 1000 + emodus * 100 + etempus * 10 + eprolation;
	}

	// Check for incomplete rhythmic levels:
	if (hre.search(metsig, "^\\*?met\\(.*?\\)_(\\d)(\\d)(\\d)")) {
		emaximodus = hre.getMatchInt(1);
		emodus     = hre.getMatchInt(2);
		etempus    = hre.getMatchInt(3);
	} else if (hre.search(metsig, "^\\*?met\\(.*?\\)_(\\d)(\\d)")) {
		emaximodus = hre.getMatchInt(1);
		emodus     = hre.getMatchInt(2);
	} else if (hre.search(metsig, "^\\*?met\\(.*?\\)_(\\d)")) {
		emaximodus = hre.getMatchInt(1);
	}

	// Get implicit rhythmic levels based on mensuration sign:
	if (!hre.search(metsig, "^\\*?met\\((.+?)\\)")) {
		// Null mensuration sign or strange problem.
		maximodus = emaximodus == 3 ? 3 : 2;
		modus     = emodus     == 3 ? 3 : 2;
		tempus    = etempus    == 3 ? 3 : 2;
		prolation = eprolation == 3 ? 3 : 2;
		return maximodus * 1000 + modus * 100 + tempus * 10 + prolation;
	}
	string mensur = hre.getMatch(1);

	if (mensur == "C") {
		maximodus = 2; modus = 2; tempus = 2; prolation = 2;
	} else if (mensur == "C|") {
		maximodus = 2; modus = 2; tempus = 2; prolation = 2;
	} else if (mensur == "C.") {
		maximodus = 2; modus = 2; tempus = 2; prolation = 3;
	} else if (mensur == "C.|") {
		maximodus = 2; modus = 2; tempus = 2; prolation = 3;
	} else if (mensur == "C2") {
		maximodus = 2; modus = 2; tempus = 2; prolation = 2;
	} else if (mensur == "C3") {
		maximodus = 2; modus = 2; tempus = 3; prolation = 2;
	} else if (mensur == "O") {
		maximodus = 2; modus = 2; tempus = 3; prolation = 2;
	} else if (mensur == "O|") {
		maximodus = 2; modus = 2; tempus = 3; prolation = 2;
	} else if (mensur == "O.") {
		maximodus = 2; modus = 2; tempus = 3; prolation = 3;
	} else if (mensur == "O.|") {
		maximodus = 2; modus = 2; tempus = 3; prolation = 3;
	} else if (mensur == "O2") {
		maximodus = 2; modus = 3; tempus = 2; prolation = 2;
	} else if (mensur == "O3") {
		maximodus = 3; modus = 3; tempus = 3; prolation = 2;
	} else if (mensur == "O|3") {
		maximodus = 3; modus = 3; tempus = 3; prolation = 2;
	} else if (mensur == "C|3/2") {
		maximodus = 2; modus = 2; tempus = 2; prolation = 2;
		// rscale: 3/2
	} else {
		// Others to implement include:
		// *met(c) == this is a modern metric signature (common time)
		// *met(c|) == this is a modern metric signature (cut time)
		// *met(Cr)
      // *met(3) == this is problematic because it will depend on the
		// previous mensuration sign, such as *met(C|), which means that
		// *met(3) is *met(C|3/2) which is 50% faster than *met(C|), having
		// three semibreves in the time of two original ones.  Such
		// "sequialtera" mensurations are not dealt with yet.
		cerr << "Warning: do not understand mensuration " << mensur << endl;
		return maximodus * 1000 + modus * 100 + tempus * 10 + prolation;
	}

	maximodus = emaximodus != 0 ? emaximodus : maximodus;
	modus     = emodus     != 0 ? emodus     : modus;
	tempus    = etempus    != 0 ? etempus    : tempus;
	prolation = eprolation != 0 ? eprolation : prolation;

	maximodus = maximodus == 3 ? 3 : 2;
	modus     = modus     == 3 ? 3 : 2;
	tempus    = tempus    == 3 ? 3 : 2;
	prolation = prolation == 3 ? 3 : 2;

	return maximodus * 1000 + modus * 100 + tempus * 10 + prolation;
}






//////////////////////////////
//
// Convert::museToBase40 -- Convert a MuseData pitch into base-40 representation.
//

int Convert::museToBase40(const string& pitchString) {
   string temp = pitchString;
   int octave;
   int i = (int)temp.size() - 1;
   while (i >= 0 && !isdigit(temp[i])) {
      i--;
   }

   if (i <= 0) {
      cerr << "Error: could not find octave in string: " << pitchString << endl;
		cerr << "Assigning to octave 4" << endl;
      octave = 4;
   } else {
   	octave = temp[i] - '0';
	}
   temp.resize(i);

	for (int i=0; i<(int)temp.size(); i++) {
		if (temp[i] == 'f') {
			temp[i] = '-';
		}
	}
	int kb40 = Convert::kernToBase40(temp);
	if (kb40 < 0) {
		return kb40;
	}
   return kb40 % 40 + 40 * octave;
}



//////////////////////////////
//
// Convert::musePitchToKernPitch --
//

string Convert::musePitchToKernPitch(const string& museInput) {
   return base40ToKern(museToBase40(museInput));
}



//////////////////////////////
//
// Convert::museClefToKernClef --
//

string Convert::museClefToKernClef(const string& mclef) {
	if (mclef == "4") {         // treble clef
		return "*clefG2";
	} else if (mclef == "22") { // bass clef
		return "*clefF4";
	} else if (mclef == "13") { // alto clef
		return "*clefC3";
	} else if (mclef == "12") { // tenor clef
		return "*clefC4";
	} else if (mclef == "15") { // soprano clef
		return "*clefC1";
	} else if (mclef == "14") { // mezzo-soprano clef
		return "*clefC2";
	} else if (mclef == "14") { // baritone clef
		return "*clefC5";
	} else if (mclef == "5") { // French violin clef
		return "*clefG1";
	} else if (mclef == "3") {
		return "*clefG3";
	} else if (mclef == "2") {
		return "*clefG4";
	} else if (mclef == "1") {
		return "*clefG5";
	} else if (mclef == "25") {
		return "*clefF1";
	} else if (mclef == "24") {
		return "*clefF2";
	} else if (mclef == "23") {
		return "*clefF3";
	} else if (mclef == "21") {
		return "*clefF5";
	} else if (mclef == "35") {
		return "*clefGv1";
	} else if (mclef == "34") {  // vocal tenor clef
		return "*clefGv2";
	} else if (mclef == "33") {
		return "*clefGv3";
	} else if (mclef == "32") {
		return "*clefGv3";
	} else if (mclef == "31") {
		return "*clefGv5";
	}
	// percussion clef?
	// return unknown clef:
	return "*";
}



//////////////////////////////
//
// Convert::museKeySigToKernKeySig --
//

string Convert::museKeySigToKernKeySig(const string& mkeysig) {
	if (mkeysig == "0") {
		return "*k[]";
	} else if (mkeysig == "1") {
		return "*k[f#]";
	} else if (mkeysig == "-1") {
		return "*k[b-]";
	} else if (mkeysig == "2") {
		return "*k[f#c#]";
	} else if (mkeysig == "-2") {
		return "*k[b-e-]";
	} else if (mkeysig == "3") {
		return "*k[f#c#g#]";
	} else if (mkeysig == "-3") {
		return "*k[b-e-a-]";
	} else if (mkeysig == "4") {
		return "*k[f#c#g#d#]";
	} else if (mkeysig == "-4") {
		return "*k[b-e-a-d-]";
	} else if (mkeysig == "5") {
		return "*k[f#c#g#d#a#]";
	} else if (mkeysig == "-5") {
		return "*k[b-e-a-d-g-]";
	} else if (mkeysig == "6") {
		return "*k[f#c#g#d#a#e#]";
	} else if (mkeysig == "-6") {
		return "*k[b-e-a-d-g-c-]";
	} else if (mkeysig == "7") {
		return "*k[f#c#g#d#a#e#b#]";
	} else if (mkeysig == "-7") {
		return "*k[b-e-a-d-g-c-f-]";
	}
	return "*";
}



//////////////////////////////
//
// Convert::museTimeSigToKernTimeSig --
//

string Convert::museTimeSigToKernTimeSig(const string& mtimesig) {
	if (mtimesig == "11/0") {
		return "*M3/1";    // "*met(O)
	} else if (mtimesig == "1/1") {
		return "*M4/4";    // "*met(c)
	} else if (mtimesig == "0/0") {
		return "*M2/2";    // "*met(c)
	} else if (mtimesig == "12/0") {
		return "";    // "*met(O:)
	} else if (mtimesig == "21/0") {
		return "";    // "*met(O.)
	} else if (mtimesig == "22/0") {
		return "";    // "*met(O;)
	} else if (mtimesig == "31/0") {
		return "*M2/1";    // "*met(C)
	} else if (mtimesig == "41/0") {
		return "";    // "*met(C.)
	} else if (mtimesig == "42/0") {
		return "";    // "*met(C.3/2)
	} else if (mtimesig == "43/0") {
		return "";    // "*met(C.3/8)
	} else if (mtimesig == "51/0") {
		return "";    // "*met(Cr)
	} else if (mtimesig == "52/0") {
		return "";    // "*met(Cr|)
	} else if (mtimesig == "61/0") {
		return "*M2/1"; // "*met(C|)
	} else if (mtimesig == "62/0") {
		return "";     // "*met(C|/2)
	} else if (mtimesig == "63/0") {
		return "";     // "*met(C|.)
	} else if (mtimesig == "71/0") {
		return "";     // "*met(C2)
	} else if (mtimesig == "72/0") {
		return "";     // "*met(C2/3)
	} else if (mtimesig == "81/0") {
		return "";     // "*met(O2)
	} else if (mtimesig == "82/0") {
		return "";     // "*met(O3/2)
	} else if (mtimesig == "91/0") {
		return "*M3/1"; // "*met(O|)
	} else if (mtimesig == "92/0") {
		return ""; // "*met(O|3)
	} else if (mtimesig == "93/0") {
		return ""; // "*met(O|3/2)
	} else if (mtimesig == "101/0") {
		return ""; // "*met(C|3)
	} else if (mtimesig == "102/0") {
		return ""; // "*met(3)
	} else if (mtimesig == "103/0") {
		return ""; // "*met(3/2)
	} else if (mtimesig == "104/0") {
		return ""; // "*met(C|/3)
	} else if (mtimesig == "105/0") {
		return ""; // "*met(C3)
	} else if (mtimesig == "106/0") {
		return ""; // "*met(O/3)
	} else if (mtimesig == "111/0") {
		return ""; // "*met(C|2)
	} else if (mtimesig == "112/0") {
		return ""; // "*met(2)
	} else if (mtimesig == "121/0") {
		return ""; // "*met(Oo)
	}
	string output = "*M" + mtimesig;
	return output;
}



//////////////////////////////
//
// Convert::museMeterSigToKernMeterSig --
//

string Convert::museMeterSigToKernMeterSig(const string& mtimesig) {
	if (mtimesig == "11/0") {
		return "*met(O)";
	} else if (mtimesig == "1/1") {
		return "*met(c)";
	} else if (mtimesig == "0/0") {
		return "*met(c)";
	} else if (mtimesig == "12/0") {
		return "*met(O:)";
	} else if (mtimesig == "21/0") {
		return "*met(O.)";
	} else if (mtimesig == "22/0") {
		return "*met(O;)";
	} else if (mtimesig == "31/0") {
		return "*met(C)";
	} else if (mtimesig == "41/0") {
		return "*met(C.)";
	} else if (mtimesig == "42/0") {
		return "*met(C.3/2)";
	} else if (mtimesig == "43/0") {
		return "*met(C.3/8)";
	} else if (mtimesig == "51/0") {
		return "*met(Cr)";
	} else if (mtimesig == "52/0") {
		return "*met(Cr|)";
	} else if (mtimesig == "61/0") {
		return "*met(C|)";
	} else if (mtimesig == "62/0") {
		return "*met(C|/2)";
	} else if (mtimesig == "63/0") {
		return "*met(C|.)";
	} else if (mtimesig == "71/0") {
		return "*met(C2)";
	} else if (mtimesig == "72/0") {
		return "*met(C2/3)";
	} else if (mtimesig == "81/0") {
		return "*met(O2)";
	} else if (mtimesig == "82/0") {
		return "*met(O3/2)";
	} else if (mtimesig == "91/0") {
		return "*met(O|)";
	} else if (mtimesig == "92/0") {
		return "*met(O|3)";
	} else if (mtimesig == "93/0") {
		return "*met(O|3/2)";
	} else if (mtimesig == "101/0") {
		return "*met(C|3)";
	} else if (mtimesig == "102/0") {
		return "*met(3)";
	} else if (mtimesig == "103/0") {
		return "*met(3/2)";
	} else if (mtimesig == "104/0") {
		return "*met(C|/3)";
	} else if (mtimesig == "105/0") {
		return "*met(C3)";
	} else if (mtimesig == "106/0") {
		return "*met(O/3)";
	} else if (mtimesig == "111/0") {
		return "*met(C|2)";
	} else if (mtimesig == "112/0") {
		return "*met(2)";
	} else if (mtimesig == "121/0") {
		return "*met(Oo)";
	}
	return "";
}



//////////////////////////////
//
// Convert::museFiguredBassToKernFiguredBass --
//

string Convert::museFiguredBassToKernFiguredBass(const string& mfb) {
	string output;
	for (int i=0; i<(int)mfb.size(); i++) {
		if (mfb[i] == 'b') { // blank spot in figure stack
			output += 'X';
		} else if (mfb[i] == 'f') { // flat
			output += '-';
		} else if (mfb[i] == 'x') { // sharp
			output += '#';
		} else if ((mfb[i] == '&') && (i < (int)mfb.size()-1) && (mfb[i+1] == '0')) {
			output += ":";
			i++;
		} else if ((mfb[i] == '/')) {  // assuming slash means flat
			output += "-/";
		} else if ((mfb[i] == '\\')) { // assuming slash means sharp
			output += "#/";
		} else if ((mfb[i] == '+')) {  // assuming slash means sharp
			output += "#|";
		} else if (isdigit(mfb[i]) && (i < (int)mfb.size() - 1) && (mfb[i+1] == '#')) {
			output += mfb[i];
			output += mfb[i+1];
			output += 'r';
			i++;
		} else if (isdigit(mfb[i]) && (i < (int)mfb.size() - 1) && (mfb[i+1] == 'f')) {
			output += mfb[i];
			output += '-';
			output += 'r';
			i++;
		} else if (isdigit(mfb[i]) && (i < (int)mfb.size() - 1) && (mfb[i+1] == 'n')) {
			output += mfb[i];
			output += mfb[i+1];
			output += 'r';
			i++;
		} else {
			output += mfb[i];
		}
	}
	return output;
}





//////////////////////////////
//
// Convert::kernToScientificPitch -- Convert a **kern pitch to
//   ScientificPitch notation, which is the diatonic letter name,
//   followed by a possible accidental, then an optional separator
//   string, and finally the octave number.  A string representing a
//   chord can be given to this function, and the output will return
//   a list of the pitches in the chord, separated by a space.
// default value: flat      = "b"
// default value: sharp     = "#"
// default value: separator = ""
//

string Convert::kernToScientificPitch(const string& kerndata,
		string flat, string sharp, string separator) {
	vector<string> subtokens = Convert::splitString(kerndata);
	string output;

	for (int i=0; i<(int)subtokens.size(); i++) {
		char diatonic   = Convert::kernToDiatonicUC(subtokens[i]);
		int accidental = Convert::kernToAccidentalCount(subtokens[i]);
		int octave     = Convert::kernToOctaveNumber(subtokens[i]);
		if ((i > 0) && (i < (int)subtokens.size()-1)) {
			output += " ";
		}
		output += diatonic;
		for (int j=0; j<abs(accidental); j++) {
			output += (accidental < 0 ? flat : sharp);
		}
		output += separator;
		output += to_string(octave);
	}

	return output;
}



//////////////////////////////
//
// Convert::kernToDiatonicPC -- Convert a kern token into a diatonic
//    note pitch-class where 0="C", 1="D", ..., 6="B".  -1000 is returned
//    if the note is rest, and -2000 if there is no pitch information in the
//    input string. Only the first subtoken in the string is considered.
//

int Convert::kernToDiatonicPC(const string& kerndata) {
	for (int i=0; i<(int)kerndata.size(); i++) {
		if (kerndata[i] == ' ') {
			break;
		}
		if (kerndata[i] == 'r') {
			return -1000;
		}
		switch (kerndata[i]) {
			case 'A': case 'a': return 5;
			case 'B': case 'b': return 6;
			case 'C': case 'c': return 0;
			case 'D': case 'd': return 1;
			case 'E': case 'e': return 2;
			case 'F': case 'f': return 3;
			case 'G': case 'g': return 4;
		}
	}
	return -2000;
}



//////////////////////////////
//
// Convert::kernToDiatonicUC -- Convert a kern token into a diatonic
//    note pitch-class.  "R" is returned if the note is rest, and
//    "X" is returned if there is no pitch name in the string.
//    Only the first subtoken in the string is considered.
//

char Convert::kernToDiatonicUC(const string& kerndata) {
	for (int i=0; i<(int)kerndata.size(); i++) {
		if (kerndata[i] == ' ') {
			break;
		}
		if (kerndata[i] == 'r') {
			return 'R';
		}
		if (('A' <= kerndata[i]) && (kerndata[i] <= 'G')) {
			return kerndata[i];
		}
		if (('a' <= kerndata[i]) && (kerndata[i] <= 'g')) {
			return toupper(kerndata[i]);
		}
	}
	return 'X';
}



//////////////////////////////
//
// Convert::kernToDiatonicLC -- Similar to kernToDiatonicUC, but
//    the returned pitch name is lower case.
//

char Convert::kernToDiatonicLC(const string& kerndata) {
	return tolower(Convert::kernToDiatonicUC(kerndata));
}



//////////////////////////////
//
// Convert::kernToAccidentalCount -- Convert a kern token into a count
//    of accidentals in the first subtoken.  Sharps are assigned to the
//    value +1 and flats to -1.  So a double sharp is +2 and a double
//    flat is -2.  Only the first subtoken in the string is considered.
//    Cases such as "#-" should not exist, but in this case the return
//    value will be 0.
//

int Convert::kernToAccidentalCount(const string& kerndata) {
	int output = 0;
	for (int i=0; i<(int)kerndata.size(); i++) {
		if (kerndata[i] == ' ') {
			break;
		}
		if (kerndata[i] == '-') {
			output--;
		}
		if (kerndata[i] == '#') {
			output++;
		}
	}
	return output;
}



//////////////////////////////
//
// Convert::kernToOctaveNumber -- Convert a kern token into an octave number.
//    Middle C is the start of the 4th octave. -1000 is returned if there
//    is not pitch in the string.  Only the first subtoken in the string is
//    considered.
//

int Convert::kernToOctaveNumber(const string& kerndata) {
	int uc = 0;
	int lc = 0;
	if (kerndata == ".") {
		return -1000;
	}
	for (int i=0; i<(int)kerndata.size(); i++) {
		if (kerndata[i] == ' ') {
			break;
		}
		if (kerndata[i] == 'r') {
			return -1000;
		}
		uc += ('A' <= kerndata[i]) && (kerndata[i] <= 'G') ? 1 : 0;
		lc += ('a' <= kerndata[i]) && (kerndata[i] <= 'g') ? 1 : 0;
	}
	if ((uc > 0) && (lc > 0)) {
		// invalid pitch description
		return -1000;
	}
	if (uc > 0) {
		return 4 - uc;
	} else if (lc > 0) {
		return 3 + lc;
	} else {
		return -1000;
	}
}



//////////////////////////////
//
// Convert::kernToBase40PC -- Convert **kern pitch to a base-40 pitch class.
//    Will ignore subsequent pitches in a chord.
//

int Convert::kernToBase40PC(const string& kerndata) {
	int diatonic = Convert::kernToDiatonicPC(kerndata);
	if (diatonic < 0) {
		return diatonic;
	}
	int accid  = Convert::kernToAccidentalCount(kerndata);
	int output = -1000;
	switch (diatonic) {
		case 0: output =  0; break;
		case 1: output =  6; break;
		case 2: output = 12; break;
		case 3: output = 17; break;
		case 4: output = 23; break;
		case 5: output = 29; break;
		case 6: output = 35; break;
	}
	output += accid;
	return output + 2;     // +2 to make c-flat-flat bottom of octave.
}



//////////////////////////////
//
// Convert::kernToBase40 -- Convert **kern pitch to a base-40 integer.
//    Will ignore subsequent pitches in a chord.
//

int Convert::kernToBase40(const string& kerndata) {
	string trimmed = Convert::trimWhiteSpace(kerndata);
	int pc = Convert::kernToBase40PC(trimmed);
	if (pc < 0) {
		return pc;
	}
	int octave   = Convert::kernToOctaveNumber(trimmed);
	return pc + 40 * octave;
}



//////////////////////////////
//
// Convert::kernToBase12PC -- Convert **kern pitch to a base-12 pitch-class.
//   C=0, C#/D-flat=1, D=2, etc.  Will return -1 instead of 11 for C-, and
//   will return 12 instead of 0 for B#.
//

int Convert::kernToBase12PC(const string& kerndata) {
	int diatonic = Convert::kernToDiatonicPC(kerndata);
	if (diatonic < 0) {
		return diatonic;
	}
	int accid    = Convert::kernToAccidentalCount(kerndata);
	int output = -1000;
	switch (diatonic) {
		case 0: output =  0; break;
		case 1: output =  2; break;
		case 2: output =  4; break;
		case 3: output =  5; break;
		case 4: output =  7; break;
		case 5: output =  9; break;
		case 6: output = 11; break;
	}
	output += accid;
	return output;
}



//////////////////////////////
//
// Convert::kernToBase12 -- Convert **kern pitch to a base-12 integer.
//     (middle C = 48).
//

int Convert::kernToBase12(const string& kerndata) {
	int pc = Convert::kernToBase12PC(kerndata);
	int octave = Convert::kernToOctaveNumber(kerndata);
	return pc + 12 * octave;
}



//////////////////////////////
//
// Convert::base40ToKern -- Convert Base-40 integer pitches into
//   **kern pitch representation.
//

string Convert::base40ToKern(int b40) {
	int octave     = b40 / 40;
	int accidental = Convert::base40ToAccidental(b40);
	int diatonic   = Convert::base40ToDiatonic(b40) % 7;
	char base = 'a';
	switch (diatonic) {
		case 0: base = 'c'; break;
		case 1: base = 'd'; break;
		case 2: base = 'e'; break;
		case 3: base = 'f'; break;
		case 4: base = 'g'; break;
		case 5: base = 'a'; break;
		case 6: base = 'b'; break;
	}
	if (octave < 4) {
		base = toupper(base);
	}
	int repeat = 0;
	if (octave > 4) {
		repeat = octave - 4;
	} else if (octave < 3) {
		repeat = 3 - octave;
	}
	if (repeat > 12) {
		cerr << "Error: unreasonable octave value: " << octave << " for " << b40 << endl;
		exit(1);
	}
	string output;
	output += base;
	for (int i=0; i<repeat; i++) {
		output += base;
	}
	if (accidental == 0) {
		return output;
	}
	if (accidental > 0) {
		for (int i=0; i<accidental; i++) {
			output += '#';
		}
	} else if (accidental < 0) {
		for (int i=0; i<-accidental; i++) {
			output += '-';
		}
	}

	return output;
}



//////////////////////////////
//
// Convert::base40ToDiatonic -- find the diatonic pitch of the
//   given base-40 pitch.  Output pitch classes: 0=C, 1=D, 2=E,
//   3=F, 4=G, 5=A, 6=B.  To this the diatonic octave is added.
//   To get only the diatonic pitch class, mod by 7: (% 7).
//   Base-40 pitches are not allowed, and the algorithm will have
//   to be adjusted to allow them.  Currently any negative base-40
//   value is presumed to be a rest and not processed.
//

int Convert::base40ToDiatonic(int b40) {
	int chroma = b40 % 40;
	int octaveoffset = (b40 / 40) * 7;
	if (b40 < 0) {
		return -1;   // rest;
	}
	switch (chroma) {
		case 0: case 1: case 2: case 3: case 4:      // C-- to C##
			return 0 + octaveoffset;
		case 6: case 7: case 8: case 9: case 10:     // D-- to D##
			return 1 + octaveoffset;
		case 12: case 13: case 14: case 15: case 16: // E-- to E##
			return 2 + octaveoffset;
		case 17: case 18: case 19: case 20: case 21: // F-- to F##
			return 3 + octaveoffset;
		case 23: case 24: case 25: case 26: case 27: // G-- to G##
			return 4 + octaveoffset;
		case 29: case 30: case 31: case 32: case 33: // A-- to A##
			return 5 + octaveoffset;
		case 35: case 36: case 37: case 38: case 39: // B-- to B##
			return 6 + octaveoffset;
	}

	// found an empty slot, so return rest:
	return -1;
}



//////////////////////////////
//
// Convert::base40ToMidiNoteNumber --
//

int Convert::base40ToMidiNoteNumber(int b40) {
	// +1 since middle-C octave is 5 in MIDI:
	int octave     = b40 / 40 + 1;
	int accidental = Convert::base40ToAccidental(b40);
	int diatonicpc = Convert::base40ToDiatonic(b40) % 7;
	switch (diatonicpc) {
		case 0: return octave * 12 +  0 + accidental;
		case 1: return octave * 12 +  2 + accidental;
		case 2: return octave * 12 +  4 + accidental;
		case 3: return octave * 12 +  5 + accidental;
		case 4: return octave * 12 +  7 + accidental;
		case 5: return octave * 12 +  9 + accidental;
		case 6: return octave * 12 + 11 + accidental;
		default: return -1000; // can't deal with negative pitches
	}
}



//////////////////////////////
//
// Convert::base40ToAccidental -- +1 = 1 sharp, +2 = double sharp, 0 = natural
//	-1 = 1 flat, -2 = double flat
//

int Convert::base40ToAccidental(int b40) {
	if (b40 < 0) {
		// not considering low pitches.  If so then the mod operator
		// below whould need fixing.
		return 0;
	}

	switch (b40 % 40) {
		case 0:	return -2;      // C-double-flat
		case 1:	return -1;      // C-flat
		case 2:	return  0;      // C
		case 3:	return  1;      // C-sharp
		case 4:	return  2;      // C-double-sharp
		case 5:	return 1000;
		case 6:	return -2;
		case 7:	return -1;
		case 8:	return  0;      // D
		case 9:	return  1;
		case 10:	return  2;
		case 11:	return 1000;
		case 12:	return -2;
		case 13:	return -1;
		case 14:	return  0;      // E
		case 15:	return  1;
		case 16:	return  2;
		case 17:	return -2;
		case 18:	return -1;
		case 19:	return  0;      // F
		case 20:	return  1;
		case 21:	return  2;
		case 22:	return 1000;
		case 23:	return -2;
		case 24:	return -1;
		case 25:	return  0;      // G
		case 26:	return  1;
		case 27:	return  2;
		case 28:	return 1000;
		case 29:	return -2;
		case 30:	return -1;
		case 31:	return  0;      // A
		case 32:	return  1;
		case 33:	return  2;
		case 34:	return 1000;
		case 35:	return -2;
		case 36:	return -1;
		case 37:	return  0;      // B
		case 38:	return  1;
		case 39:	return  2;
	}

	return 0;
}



///////////////////////////////
//
// Convert::kernToMidiNoteNumber -- Convert **kern to MIDI note number
//    (middle C = 60).  Middle C is assigned to octave 5 rather than
//    octave 4 for the kernToBase12() function.
//

int Convert::kernToMidiNoteNumber(const string& kerndata) {
	int pc = Convert::kernToBase12PC(kerndata);
	int octave = Convert::kernToOctaveNumber(kerndata);
	return pc + 12 * (octave + 1);
}



//////////////////////////////
//
// Convert::kernToBase7 -- Convert **kern pitch to a base-7 integer.
//    This is a diatonic pitch class with C=0, D=1, ..., B=6.
//

int Convert::kernToBase7(const string& kerndata) {
	int diatonic = Convert::kernToDiatonicPC(kerndata);
	if (diatonic < 0) {
		return diatonic;
	}
	int octave = Convert::kernToOctaveNumber(kerndata);
	return diatonic + 7 * octave;;
}



//////////////////////////////
//
// Convert::pitchToWbh -- Convert a given diatonic pitch class and
//   accidental adjustment into an integer.  The diatonic pitch class
//   is C=0, D=1, E=2, F=3, G=4, A=5, B=6. "acc" is the accidental
//   count: -2=double flat, -1=double flat, 0 natural, +1=sharp, etc.
//   "octave" is the octave number, with middle-C being the start of
//   octave 4.  //   "maxacc" is the maximum accidental which defines
//    the base:
//    maxacc = 2 -> Base-40.
//    maxacc = n -> Base (n*2+1)*7 + 5.
//

int Convert::pitchToWbh(int dpc, int acc, int octave, int maxacc) {
	if (dpc > 6) {
		// allow for pitch-classes expressed as ASCII characters:
		dpc = tolower(dpc) - 'a' + 5;
		dpc = dpc % 7;
	}
	int output = -1000;
	switch (dpc) {
		case 0: output = maxacc;            break;
		case 1: output =  3  * maxacc + 2;  break;
		case 2: output =  5  * maxacc + 4;  break;
		case 3: output =  7  * maxacc + 5;  break;
		case 4: output =  9  * maxacc + 7;  break;
		case 5: output =  11 * maxacc + 9;  break;
		case 6: output =  13 * maxacc + 11; break;
	}
	if (output < 0) {
		return output;
	}
	return (output + acc) + (7 * (maxacc * 2 + 1) + 5) * octave;
}



//////////////////////////////
//
// Convert::wbhToPitch -- Convert an integer-based pitch into
//    a diatonic pitch class, accidental alteration and octave number
//   The output diatonic pitch classes are 0=C, 1=D, 2=E, 3=F, 4=G, 5=A, 6=B.
//   "acc" is the accidental count: -2=double flat, -1=double flat,
//   0 natural, +1=sharp, etc.
//   "octave" is the octave number, with middle-C being the start of
//   octave 4.
//   "maxacc" is the maximum accidental which defines
//    the base:
//    maxacc = 2 -> Base-40.
//    maxacc = n -> Base (n*2+1)*7 + 5.
//    This valus must match the the analogous value used in PitchToWbh().
//

void Convert::wbhToPitch(int& dpc, int& acc, int& octave, int maxacc,
		int wbh) {
	int cwidth = maxacc * 2 + 1;
	int base = 7 * cwidth + 5;
	octave = wbh / base;
	int pc = wbh % base;

	// test for C diatonic pitch:
	int pctest = cwidth;
	if (pc < pctest) {
		dpc = 0;
		acc = pc - pctest + maxacc + 1;
		return;
	}

	// test for D diatonic pitch
	pctest += 1 + cwidth;
	if (pc < pctest) {
		dpc = 1;
		acc = pc - pctest + maxacc + 1;
		return;
	}

	// test for E diatonic pitch
	pctest += 1 + cwidth;
	if (pc < pctest) {
		dpc = 2;
		acc = pc - pctest + maxacc + 1;
		return;
	}

	// test for F diatonic pitch
	pctest += cwidth;
	if (pc < pctest) {
		dpc = 3;
		acc = pc - pctest + maxacc + 1;
		return;
	}

	// test for G diatonic pitch
	pctest += 1 + cwidth;
	if (pc < pctest) {
		dpc = 4;
		acc = pc - pctest + maxacc + 1;
		return;
	}

	// test for A diatonic pitch
	pctest += 1 + cwidth;
	if (pc < pctest) {
		dpc = 5;
		acc = pc - pctest + maxacc + 1;
		return;
	}

	// test for B diatonic pitch
	pctest += 1 + cwidth;
	if (pc < pctest) {
		dpc = 6;
		acc = pc - pctest + maxacc + 1;
		return;
	}

	// if acc in any of the above tests is +3/-3, then there was an
	// accidental overflow (overflow of the accidental).
}



//////////////////////////////
//
// Convert::kernClefToBaseline -- returns the diatonic pitch
//    of the bottom line on the staff.
//

int Convert::kernClefToBaseline(HTp input) {
	return kernClefToBaseline((string)*input);
}


int Convert::kernClefToBaseline(const string& input) {
	string clefname;
	if (input.compare(0, 5, "*clef") == 0) {
		clefname = input.substr(5);
	} else if (input.compare(0, 4, "clef") == 0) {
		clefname = input.substr(4);
	} else {
		cerr << "Error in Convert::kernClefToBaseline: " << input << endl;
		return -1000;
	}

	if (clefname == "G2") {                        // treble clef
		return Convert::kernToBase7("e");
	} else if (clefname == "F4") {                 // bass clef
		return Convert::kernToBase7("GG");
	} else if (clefname == "C3") {                 // alto clef
		return Convert::kernToBase7("F");
	} else if (clefname == "C4") {                 // tenor clef
		return Convert::kernToBase7("D");
	} else if (clefname == "Gv2") {                // vocal tenor clef
		return Convert::kernToBase7("E");

	// rest of C clef possibilities:
	} else if (clefname == "C1") {                 // soprano clef
		return Convert::kernToBase7("c");
	} else if (clefname == "C2") {                 // mezzo-soprano clef
		return Convert::kernToBase7("A");
	} else if (clefname == "C5") {                 // baritone clef
		return Convert::kernToBase7("BB");

	// rest of G clef possibilities:
	} else if (clefname == "G1") {                 // French-violin clef
		return Convert::kernToBase7("g");
	} else if (clefname == "G3") {
		return Convert::kernToBase7("c");
	} else if (clefname == "G4") {
		return Convert::kernToBase7("A");
	} else if (clefname == "G5") {
		return Convert::kernToBase7("F");

	// rest of F clef possibilities:
	} else if (clefname == "F1") {
		return Convert::kernToBase7("F");
	} else if (clefname == "F2") {
		return Convert::kernToBase7("D");
	} else if (clefname == "F3") {
		return Convert::kernToBase7("BB");
	} else if (clefname == "F5") {
		return Convert::kernToBase7("EE");

	// rest of G clef down an octave possibilities:
	} else if (clefname == "Gv1") {
		return Convert::kernToBase7("G");
	} else if (clefname == "Gv3") {
		return Convert::kernToBase7("C");
	} else if (clefname == "Gv4") {
		return Convert::kernToBase7("AA");
	} else if (clefname == "Gv5") {
		return Convert::kernToBase7("FF");

	// F clef down an octave possibilities:
	} else if (clefname == "Fv1") {
		return Convert::kernToBase7("FF");
	} else if (clefname == "Fv2") {
		return Convert::kernToBase7("DD");
	} else if (clefname == "Fv3") {
		return Convert::kernToBase7("BBB");
	} else if (clefname == "Fv4") {
		return Convert::kernToBase7("GGG");
	} else if (clefname == "Fv5") {
		return Convert::kernToBase7("EEE");

	// C clef down an octave possibilities:
	} else if (clefname == "Cv1") {
		return Convert::kernToBase7("C");
	} else if (clefname == "Cv2") {
		return Convert::kernToBase7("AA");
	} else if (clefname == "Cv3") {
		return Convert::kernToBase7("FF");
	} else if (clefname == "Cv4") {
		return Convert::kernToBase7("DD");
	} else if (clefname == "Cv5") {
		return Convert::kernToBase7("BBB");

	// G clef up an octave possibilities:
	} else if (clefname == "G^1") {
		return Convert::kernToBase7("gg");
	} else if (clefname == "G^2") {
		return Convert::kernToBase7("ee");
	} else if (clefname == "G^3") {
		return Convert::kernToBase7("cc");
	} else if (clefname == "G^4") {
		return Convert::kernToBase7("a");
	} else if (clefname == "G^5") {
		return Convert::kernToBase7("f");

	// F clef up an octave possibilities:
	} else if (clefname == "F^1") {
		return Convert::kernToBase7("f");
	} else if (clefname == "F^2") {
		return Convert::kernToBase7("d");
	} else if (clefname == "F^3") {
		return Convert::kernToBase7("B");
	} else if (clefname == "F^4") {
		return Convert::kernToBase7("G");
	} else if (clefname == "F^5") {
		return Convert::kernToBase7("E");

	// C clef up an octave possibilities:
	} else if (clefname == "C^1") {
		return Convert::kernToBase7("cc");
	} else if (clefname == "C^2") {
		return Convert::kernToBase7("a");
	} else if (clefname == "C^3") {
		return Convert::kernToBase7("f");
	} else if (clefname == "C^4") {
		return Convert::kernToBase7("d");
	} else if (clefname == "C^5") {
		return Convert::kernToBase7("B");

	// there are also two octaves down (*clefGvv2) and two octaves up (*clefG^^2)
	} else {
		// but just use treble clef if don't know what the clef it by this point
		return Convert::kernToBase7("e");
	}
}



//////////////////////////////
//
// Convert::base40ToTrans -- convert a base-40 interval into
//    a trans program's diatonic/chromatic alteration marker
//

string Convert::base40ToTrans(int base40) {
	int sign = 1;
	int chroma;
	int octave;
	if (base40 < 0) {
		sign = -1;
		chroma = -base40 % 40;
		octave = -base40 / 40;
	} else {
		sign = +1;
		chroma = base40 % 40;
		octave = base40 / 40;
	}

	int cval = 0;
	int dval = 0;

	switch (chroma * sign) {
		case   0: dval=0;  cval=0;   break; // C -> C
		case   1: dval=0;  cval=1;   break; // C -> C#
		case   2: dval=0;  cval=2;   break; // C -> C##
		case   4: dval=1;  cval=0;   break; // C -> D--
		case   5: dval=1;  cval=1;   break; // C -> D-
		case   6: dval=1;  cval=2;   break; // C -> D
		case   7: dval=1;  cval=3;   break; // C -> D#
		case   8: dval=1;  cval=4;   break; // C -> D##
		case  10: dval=2;  cval=2;   break; // C -> E--
		case  11: dval=2;  cval=3;   break; // C -> E-
		case  12: dval=2;  cval=4;   break; // C -> E
		case  13: dval=2;  cval=5;   break; // C -> E#
		case  14: dval=2;  cval=6;   break; // C -> E##
		case  15: dval=3;  cval=3;   break; // C -> F--
		case  16: dval=3;  cval=4;   break; // C -> F-
		case  17: dval=3;  cval=5;   break; // C -> F
		case  18: dval=3;  cval=6;   break; // C -> F#
		case  19: dval=3;  cval=7;   break; // C -> F##
		case  21: dval=4;  cval=5;   break; // C -> G--
		case  22: dval=4;  cval=6;   break; // C -> G-
		case  23: dval=4;  cval=7;   break; // C -> G
		case  24: dval=4;  cval=8;   break; // C -> G#
		case  25: dval=4;  cval=9;   break; // C -> G##
		case  27: dval=5;  cval=7;   break; // C -> A--
		case  28: dval=5;  cval=8;   break; // C -> A-
		case  29: dval=5;  cval=9;   break; // C -> A
		case  30: dval=5;  cval=10;  break; // C -> A#
		case  31: dval=5;  cval=11;  break; // C -> A##
		case  33: dval=6;  cval=9;   break; // C -> B--
		case  34: dval=6;  cval=10;  break; // C -> B-
		case  35: dval=6;  cval=11;  break; // C -> B
		case  36: dval=6;  cval=12;  break; // C -> B#
		case  37: dval=6;  cval=13;  break; // C -> B##
		case  38: dval=7;  cval=10;  break; // C -> c--
		case  39: dval=7;  cval=11;  break; // C -> c-
		case  -1: dval=-0; cval=-1;  break; // c -> c-
		case  -2: dval=-0; cval=-2;  break; // c -> c--
		case  -3: dval=-1; cval=1;   break; // c -> B##
		case  -4: dval=-1; cval=-0;  break; // c -> B#
		case  -5: dval=-1; cval=-1;  break; // c -> B
		case  -6: dval=-1; cval=-2;  break; // c -> B-
		case  -7: dval=-1; cval=-3;  break; // c -> B--
		case  -9: dval=-2; cval=-1;  break; // c -> A##
		case -10: dval=-2; cval=-2;  break; // c -> A#
		case -11: dval=-2; cval=-3;  break; // c -> A
		case -12: dval=-2; cval=-4;  break; // c -> A-
		case -13: dval=-2; cval=-5;  break; // c -> A-
		case -15: dval=-3; cval=-3;  break; // c -> G##
		case -16: dval=-3; cval=-4;  break; // c -> G#
		case -17: dval=-3; cval=-5;  break; // c -> G
		case -18: dval=-3; cval=-6;  break; // c -> G-
		case -19: dval=-3; cval=-7;  break; // c -> G--
		case -21: dval=-4; cval=-5;  break; // c -> F##
		case -22: dval=-4; cval=-6;  break; // c -> F#
		case -23: dval=-4; cval=-7;  break; // c -> F
		case -24: dval=-4; cval=-8;  break; // c -> F-
		case -25: dval=-4; cval=-9;  break; // c -> F--
		case -26: dval=-5; cval=-6;  break; // c -> E##
		case -27: dval=-5; cval=-7;  break; // c -> E#
		case -28: dval=-5; cval=-8;  break; // c -> E
		case -29: dval=-5; cval=-9;  break; // c -> E-
		case -30: dval=-5; cval=-10; break; // c -> E--
		case -32: dval=-6; cval=-8;  break; // c -> D##
		case -33: dval=-6; cval=-9;  break; // c -> D#
		case -34: dval=-6; cval=-10; break; // c -> D
		case -35: dval=-6; cval=-11; break; // c -> D-
		case -36: dval=-6; cval=-12; break; // c -> D--
		case -38: dval=-7; cval=-10; break; // c -> C##
		case -39: dval=-7; cval=-11; break; // c -> C#
		default:
			dval=0; cval=0;
	}

	if (octave > 0) {
		dval = dval + sign * octave * 7;
		cval = cval + sign * octave * 12;
	}

	string output = "d";
	output += to_string(dval);
	output += "c";
	output += to_string(cval);

	return output;
}



//////////////////////////////
//
// Convert::base40ToIntervalAbbr --
//

string Convert::base40ToIntervalAbbr(int base40interval) {
	if (base40interval < -1000) {
		return "r";
	}

	string output;
	if (base40interval < 0) {
		output = "-";
		base40interval = -base40interval;
	}

	// Add chromatic prefix
	switch (base40interval % 40) {
		case  0: output += "p"   ; break;  // C
		case  1: output += "a"   ; break;  // C#
		case  2: output += "aa"  ; break;  // C##
		case  3: output += "X"   ; break;  // X
		case  4: output += "d"   ; break;  // D--
		case  5: output += "m"   ; break;  // D-
		case  6: output += "M"   ; break;  // D
		case  7: output += "a"   ; break;  // D#
		case  8: output += "aa"  ; break;  // D##
		case  9: output += "X"   ; break;  // X
		case 10: output += "d"   ; break;  // E--
		case 11: output += "m"   ; break;  // E-
		case 12: output += "M"   ; break;  // E
		case 13: output += "a"   ; break;  // E#
		case 14: output += "aa"  ; break;  // E##
		case 15: output += "dd"  ; break;  // F--
		case 16: output += "d"   ; break;  // F-
		case 17: output += "p"   ; break;  // F
		case 18: output += "a"   ; break;  // F#
		case 19: output += "aa"  ; break;  // F##
		case 20: output += "X"   ; break;  // X
		case 21: output += "dd"  ; break;  // G--
		case 22: output += "d"   ; break;  // G-
		case 23: output += "p"   ; break;  // G
		case 24: output += "a"   ; break;  // G#
		case 25: output += "aa"  ; break;  // G##
		case 26: output += "X"   ; break;  // X
		case 27: output += "d"   ; break;  // A--
		case 28: output += "m"   ; break;  // A-
		case 29: output += "M"   ; break;  // A
		case 30: output += "a"   ; break;  // A#
		case 31: output += "aa"  ; break;  // A##
		case 32: output += "X"   ; break;  // X
		case 33: output += "d"   ; break;  // B--
		case 34: output += "m"   ; break;  // B-
		case 35: output += "M"   ; break;  // B
		case 36: output += "a"   ; break;  // B#
		case 37: output += "aa"  ; break;  // B##
		case 38: output += "dd"  ; break;  // C--
		case 39: output += "d"   ; break;  // C-
	}

	// Add base-7 number
	char buffer2[32] = {0};
	int diatonic = Convert::base40IntervalToDiatonic(base40interval)+1;
	snprintf(buffer2, 32, "%d", diatonic);
	output += buffer2;

	return output;
}



//////////////////////////////
//
// Convert::base40IntervalToDiatonic -- convert a base40 interval
//    into a diatonic interval (excluding the chromatic alteration)
//

int Convert::base40IntervalToDiatonic(int base40interval) {
   int sign = 1;
   if (base40interval < 0) {
      sign = -1;
      base40interval = -base40interval;
   }
   int octave = base40interval / 40;
   base40interval = base40interval % 40;

   int diatonic = 0;
   switch (base40interval) {
      case  0: diatonic = 0; break;  // C
      case  1: diatonic = 0; break;  // C#
      case  2: diatonic = 0; break;  // C##

      case  3: diatonic = 1000; break;  // blank

      case  4: diatonic = 1; break;  // D--
      case  5: diatonic = 1; break;  // D-
      case  6: diatonic = 1; break;  // D
      case  7: diatonic = 1; break;  // D#
      case  8: diatonic = 1; break;  // D##

      case  9: diatonic = 1000; break;  // blank

      case 10: diatonic = 2; break;  // E--
      case 11: diatonic = 2; break;  // E-
      case 12: diatonic = 2; break;  // E
      case 13: diatonic = 2; break;  // E#
      case 14: diatonic = 2; break;  // E##

      case 15: diatonic = 3; break;  // F--
      case 16: diatonic = 3; break;  // F-
      case 17: diatonic = 3; break;  // F
      case 18: diatonic = 3; break;  // F#
      case 19: diatonic = 3; break;  // F##

      case 20: diatonic = 1000; break;  // blank

      case 21: diatonic = 4; break;  // G--
      case 22: diatonic = 4; break;  // G-
      case 23: diatonic = 4; break;  // G
      case 24: diatonic = 4; break;  // G#
      case 25: diatonic = 4; break;  // G##

      case 26: diatonic = 1000; break;  // blank

      case 27: diatonic = 5; break;  // A--
      case 28: diatonic = 5; break;  // A-
      case 29: diatonic = 5; break;  // A
      case 30: diatonic = 5; break;  // A#
      case 31: diatonic = 5; break;  // A##

      case 32: diatonic = 1000; break;  // blank

      case 33: diatonic = 6; break;  // B--
      case 34: diatonic = 6; break;  // B-
      case 35: diatonic = 6; break;  // B
      case 36: diatonic = 6; break;  // B#
      case 37: diatonic = 6; break;  // B##

      case 38: diatonic = 0; break;  // C--
      case 39: diatonic = 0; break;  // C-
   }

   return sign * (diatonic + octave * 7);
}



//////////////////////////////
//
// Convert::transToBase40 -- convert the Humdrum Toolkit program
//     trans's binomial notation for intervals into base-40.
//  The input can be in three formats:
//     d1c2      == no prepended text on information
//     *Trd1c2   == Transposition interpretation marker prefixed
//     *ITrd1c2  == Instrumental transposition marker prefixed
//

int Convert::transToBase40(const string& input) {
	int dval = 0;
	int cval = 0;
	if (sscanf(input.c_str(), "d%dc%d", &dval, &cval) != 2) {
		if (sscanf(input.c_str(), "*Trd%dc%d", &dval, &cval) != 2) {
			if (sscanf(input.c_str(), "*ITrd%dc%d", &dval, &cval) != 2) {
			   // cerr << "Cannot find correct information" << endl;
			   return 0;
			}
		}
	}

	int dsign = 1;
	// int csign = 1;
	if (dval < 0) {
		dsign = -1;
	}
	// if (cval < 0) {
	//    csign = -1;
	// }

	int doctave = dsign * dval / 7;
	// int coctave = csign * cval / 12;

	int base = 0;

		  if ((dval==0)  && (cval==0))   { base =	 0; }
	else if ((dval==0)  && (cval==1))   { base =	 1; }
	else if ((dval==0)  && (cval==2))   { base =	 2; }
	else if ((dval==1)  && (cval==0))   { base =	 4; }
	else if ((dval==1)  && (cval==1))   { base =	 5; }
	else if ((dval==1)  && (cval==2))   { base =	 6; }
	else if ((dval==1)  && (cval==3))   { base =	 7; }
	else if ((dval==1)  && (cval==4))   { base =	 8; }
	else if ((dval==2)  && (cval==2))   { base =	 10; }
	else if ((dval==2)  && (cval==3))   { base =	 11; }
	else if ((dval==2)  && (cval==4))   { base =	 12; }
	else if ((dval==2)  && (cval==5))   { base =	 13; }
	else if ((dval==2)  && (cval==6))   { base =	 14; }
	else if ((dval==3)  && (cval==3))   { base =	 15; }
	else if ((dval==3)  && (cval==4))   { base =	 16; }
	else if ((dval==3)  && (cval==5))   { base =	 17; }
	else if ((dval==3)  && (cval==6))   { base =	 18; }
	else if ((dval==3)  && (cval==7))   { base =	 19; }
	else if ((dval==4)  && (cval==5))   { base =	 21; }
	else if ((dval==4)  && (cval==6))   { base =	 22; }
	else if ((dval==4)  && (cval==7))   { base =	 23; }
	else if ((dval==4)  && (cval==8))   { base =	 24; }
	else if ((dval==4)  && (cval==9))   { base =	 25; }
	else if ((dval==5)  && (cval==7))   { base =	 27; }
	else if ((dval==5)  && (cval==8))   { base =	 28; }
	else if ((dval==5)  && (cval==9))   { base =	 29; }
	else if ((dval==5)  && (cval==10))  { base =	 30; }
	else if ((dval==5)  && (cval==11))  { base =	 31; }
	else if ((dval==6)  && (cval==9))   { base =	 33; }
	else if ((dval==6)  && (cval==10))  { base =	 34; }
	else if ((dval==6)  && (cval==11))  { base =	 35; }
	else if ((dval==6)  && (cval==12))  { base =	 36; }
	else if ((dval==6)  && (cval==13))  { base =	 37; }
	else if ((dval==7)  && (cval==10))  { base =	 38; }
	else if ((dval==7)  && (cval==11))  { base =	 38; }
	else if ((dval==-0) && (cval==-0))  { base =	 -0; }
	else if ((dval==-0) && (cval==-1))  { base =	 -1; }
	else if ((dval==-0) && (cval==-2))  { base =	 -2; }
	else if ((dval==-1) && (cval==1))   { base =	 -3; }
	else if ((dval==-1) && (cval==-0))  { base =	 -4; }
	else if ((dval==-1) && (cval==-1))  { base =	 -5; }
	else if ((dval==-1) && (cval==-2))  { base =	 -6; }
	else if ((dval==-1) && (cval==-3))  { base =	 -7; }
	else if ((dval==-2) && (cval==-1))  { base =	 -9; }
	else if ((dval==-2) && (cval==-2))  { base =	-10; }
	else if ((dval==-2) && (cval==-3))  { base =	-11; }
	else if ((dval==-2) && (cval==-4))  { base =	-12; }
	else if ((dval==-2) && (cval==-5))  { base =	-13; }
	else if ((dval==-3) && (cval==-3))  { base =	-15; }
	else if ((dval==-3) && (cval==-4))  { base =	-16; }
	else if ((dval==-3) && (cval==-5))  { base =	-17; }
	else if ((dval==-3) && (cval==-6))  { base =	-18; }
	else if ((dval==-3) && (cval==-7))  { base =	-19; }
	else if ((dval==-4) && (cval==-5))  { base =	-21; }
	else if ((dval==-4) && (cval==-6))  { base =	-22; }
	else if ((dval==-4) && (cval==-7))  { base =	-23; }
	else if ((dval==-4) && (cval==-8))  { base =	-24; }
	else if ((dval==-4) && (cval==-9))  { base =	-25; }
	else if ((dval==-5) && (cval==-6))  { base =	-26; }
	else if ((dval==-5) && (cval==-7))  { base =	-27; }
	else if ((dval==-5) && (cval==-8))  { base =	-28; }
	else if ((dval==-5) && (cval==-9))  { base =	-29; }
	else if ((dval==-5) && (cval==-10)) { base =	-30; }
	else if ((dval==-6) && (cval==-8))  { base =	-32; }
	else if ((dval==-6) && (cval==-9))  { base =	-33; }
	else if ((dval==-6) && (cval==-10)) { base =	-34; }
	else if ((dval==-6) && (cval==-11)) { base =	-35; }
	else if ((dval==-6) && (cval==-12)) { base =	-36; }
	else if ((dval==-7) && (cval==-10)) { base =	-38; }
	else if ((dval==-7) && (cval==-11)) { base =	-39; }
	else { // some error occurred or accidentals out of range
		// cerr << "Problem occured in transToBase40()" << endl;
		base = 0;
	}

	base += 40 * doctave * dsign;

	return base;
}



//////////////////////////////
//
// Convert::base40IntervalToLineOfFifths -- 0 => 0 (unison),
//    Perfect Fifth => 1, Major second => 2 (two fifths up), etc.
//

int Convert::base40IntervalToLineOfFifths(int base40interval) {
	base40interval += 4000;
	base40interval = base40interval % 40;

	switch (base40interval) {
		case 0:    return   0;     // C
		case 1:    return   7;     // C#
		case 2:    return  14;     // C##
		case 3:    return 100;     // X
		case 4:    return -12;     // D--
		case 5:    return  -5;     // D-
		case 6:    return   2;     // D
		case 7:    return   9;     // D#
		case 8:    return  16;     // D##
		case 9:    return 100;     // X
		case 10:   return -10;     // E--
		case 11:   return  -3;     // E-
		case 12:   return   4;     // E
		case 13:   return  11;     // E#
		case 14:   return  18;     // E##
		case 15:   return -15;     // F--
		case 16:   return  -8;     // F-
		case 17:   return  -1;     // F
		case 18:   return   6;     // F#
		case 19:   return  13;     // F##
		case 20:   return 100;     // X
		case 21:   return -13;     // G--
		case 22:   return  -6;     // G-
		case 23:   return   1;     // G
		case 24:   return   8;     // G#
		case 25:   return  15;     // G##
		case 26:   return 100;     // X
		case 27:   return -11;     // A--
		case 28:   return  -4;     // A-
		case 29:   return   3;     // A
		case 30:   return  10;     // A#
		case 31:   return  17;     // A##
		case 32:   return 100;     // X
		case 33:   return  -9;     // B--
		case 34:   return  -2;     // B-
		case 35:   return   5;     // B
		case 36:   return  12;     // B#
		case 37:   return  19;     // B##
		case 38:   return -14;     // C--
		case 39:   return  -7;     // C-
		default:   return 100;     // X
	}

	return 100;
}



//////////////////////////////
//
// Convert::keyNumberToKern -- reverse of kernKeyToNumber.
//

string Convert::keyNumberToKern(int number) {
	switch (number) {
		case -7: return "*k[b-e-a-d-g-c-f-]";
		case -6: return "*k[b-e-a-d-g-c-]";
		case -5: return "*k[b-e-a-d-g-]";
		case -4: return "*k[b-e-a-d-]";
		case -3: return "*k[b-e-a-]";
		case -2: return "*k[b-e-]";
		case -1: return "*k[b-]";
		case  0: return "*k[]";
		case +1: return "*k[f#]";
		case +2: return "*k[f#c#]";
		case +3: return "*k[f#c#g#]";
		case +4: return "*k[f#c#g#d#]";
		case +5: return "*k[f#c#g#d#a#]";
		case +6: return "*k[f#c#g#d#a#e#]";
		case +7: return "*k[f#c#g#d#a#e#b#]";
		default: return "*k[]";
	}
}



//////////////////////////////
//
// Convert::base7ToBase40 -- Convert a base7 value to a base-40 value
//   (without accidentals).  Negative values are not allowed, but not
//   checked for.
//

int Convert::base7ToBase40(int base7) {
	int octave = base7 / 7;
	int b7pc = base7 % 7;
	int b40pc = 0;
	switch (b7pc) {
		case 0: b40pc =  0; break; // C
		case 1: b40pc =  6; break; // D
		case 2: b40pc = 12; break; // E
		case 3: b40pc = 17; break; // F
		case 4: b40pc = 23; break; // G
		case 5: b40pc = 29; break; // A
		case 6: b40pc = 35; break; // B
	}
	return octave * 40 + 2 + b40pc;
}



//////////////////////////////
//
// Convert::base7ToBase12 -- convert diatonic pitch class with optional
//    chromatic alteration into Base-12 (MIDI note number).
//

int Convert::base7ToBase12(int aPitch, int alter) {
	if (aPitch <= 0) {
		return -1;
	}

	int octave = aPitch / 7;
	int chroma = aPitch % 7;
	int output = 0;
	switch (chroma) {
		case 0:  output =  0;  break;   // 0 = C -> 0
		case 1:  output =  2;  break;   // 1 = D -> 2
		case 2:  output =  4;  break;   // 2 = E -> 4
		case 3:  output =  5;  break;   // 3 = F -> 5
		case 4:  output =  7;  break;   // 4 = G -> 7
		case 5:  output =  9;  break;   // 5 = A -> 9
		case 6:  output = 11;  break;   // 6 = B -> 11
		default: output = 0;
	}
	// need to add 1 to octave since C4 is MIDI note 60 which is 5 * 12:
	return output + 12 * (octave+1) + alter;
}



//////////////////////////////
//
// Convert::kernToStaffLocation -- 0 = bottom line of staff, 1 = next space higher,
//     2 = second line of staff, etc.  -1 = space below bottom line.
//

int Convert::kernToStaffLocation(HTp token, HTp clef) {
	if (clef == NULL) {
		return Convert::kernToStaffLocation(*token, "");
	} else {
		return Convert::kernToStaffLocation(*token, *clef);
	}
}


int Convert::kernToStaffLocation(HTp token, const string& clef) {
	return Convert::kernToStaffLocation(*token, clef);
}


int Convert::kernToStaffLocation(const string& token, const string& clef) {
	int offset = 0;
	HumRegex hre;
	if (hre.search(clef, "clef([GFC])([v^]*)(\\d+)")) {
		string letter = hre.getMatch(1);
		string vcaret = hre.getMatch(2);
		int line = hre.getMatchInt(3);
		int octadj = 0;
		if (!vcaret.empty()) {
			for (int i=0; i<(int)vcaret.size(); i++) {
				if (vcaret[i] == '^') {
					octadj--;
				} else if (vcaret[i] == 'v') {
					octadj++;
				}
			}
		}
		if (letter == "F") {
			offset = 14 + 4;
		} else if (letter == "C") {
			offset = 28;
		} else {
			offset = 28 + 4;
		}
		offset += (line - 1)  * 2;
		offset += octadj * 7;
	} else {
		// pretend clefG2:
		offset = 28 + 2;
	}

	int diatonic = Convert::kernToBase7(token);
	return diatonic - offset;
}



//////////////////////////////
//
// Convert::base12ToKern -- Convert MIDI note numbers to Kern pitches.
//     It might be nice to also add a reference key to minimize
//     diatonic pitch errors (for example 61 in A-flat major is probably
//     a D-flat, but in B-major it is probably a C-sharp.
//

string Convert::base12ToKern(int aPitch) {
	int octave = aPitch / 12 - 1;
	if (octave > 12 || octave < -1) {
		cerr << "Error: unreasonable octave value: " << octave << endl;
		cerr << "For base-12 input pitch " << aPitch << endl;
		return "c";
	}
	int chroma = aPitch % 12;
	string output;

	switch (chroma) {
		case 0:  output = "c";  break;
		case 1:  output = "c#"; break;
		case 2:  output = "d";  break;
		case 3:  output = "e-"; break;
		case 4:  output = "e";  break;
		case 5:  output = "f";  break;
		case 6:  output = "f#"; break;
		case 7:  output = "g";  break;
		case 8:  output = "g#"; break;
		case 9:  output = "a";  break;
		case 10: output = "b-"; break;
		case 11: output = "b";  break;
	}

	if (octave >= 4) {
		output[0] = std::tolower(output[0]);
	} else {
		output[0] = std::toupper(output[0]);
	}
	int repeat = 0;
	switch (octave) {
		case 4:  repeat = 0; break;
		case 5:  repeat = 1; break;
		case 6:  repeat = 2; break;
		case 7:  repeat = 3; break;
		case 8:  repeat = 4; break;
		case 9:  repeat = 5; break;
		case 3:  repeat = 0; break;
		case 2:  repeat = 1; break;
		case 1:  repeat = 2; break;
		case 0:  repeat = 3; break;
		case -1: repeat = 4; break;
		default:
			cerr << "Error: unknown octave value: " << octave << endl;
			cerr << "for base-12 pitch: " << aPitch << endl;
			return "c";
	}
	if (repeat == 0) {
		return output;
	}

	string rstring;
	for (int i=0; i<repeat; i++) {
		rstring += output[0];
	}
	output = rstring + output;
	return output;
}



//////////////////////////////
//
// Convert::base12ToPitch -- Convert MIDI note numbers to **pitch format.
//     This is a diatonic pitch class followed by an octave number.
//     It might be nice to also add a reference key to minimize
//     diatonic pitch errors (for example 61 in A-flat major is probably
//     a D-flat, but in B-major it is probably a C-sharp.
//

string Convert::base12ToPitch(int aPitch) {
	int octave = aPitch / 12 - 1;
	if (octave > 12 || octave < -1) {
		cerr << "Error: unreasonable octave value: " << octave << endl;
		cerr << "For base-12 input pitch: " << aPitch << endl;
		return "C4";
	}
	int chroma = aPitch % 12;
	string output;
	switch (chroma) {
		case 0:  output = "C";  break;
		case 1:  output = "C#"; break;
		case 2:  output = "D";  break;
		case 3:  output = "E-"; break;
		case 4:  output = "E";  break;
		case 5:  output = "F";  break;
		case 6:  output = "F#"; break;
		case 7:  output = "G";  break;
		case 8:  output = "G#"; break;
		case 9:  output = "A";  break;
		case 10: output = "B-"; break;
		case 11: output = "B";  break;
	}
	output += to_string(octave);
	return output;
}



//////////////////////////////
//
// Convert::base12ToBase40 -- assume fixed accidentals.
//

int Convert::base12ToBase40(int aPitch) {
	int octave = aPitch / 12 - 1;
	int chroma = aPitch % 12;

	int output = 0;
	switch (chroma) {
		case  0: output =  2;  break;   // 0  = C
		case  1: output =  3;  break;   // 1  = C#
		case  2: output =  8;  break;   // 2  = D
		case  3: output = 13;  break;   // 3  = E-
		case  4: output = 14;  break;   // 4  = E
		case  5: output = 19;  break;   // 5  = F
		case  6: output = 20;  break;   // 6  = F#
		case  7: output = 25;  break;   // 7  = G
		case  8: output = 30;  break;   // 8  = A-
		case  9: output = 31;  break;   // 9  = A
		case 10: output = 36;  break;   // 10 = B-
		case 11: output = 37;  break;   // 11 = B
		default: output =  2;  break;   // give up and set to C
	}
	output = output + 40 * octave;
	return output;
}




//////////////////////////////
//
// Convert::getReferenceKeyMeaning --
//

string Convert::getReferenceKeyMeaning(HTp token) {
	string text = token->getText();
	return Convert::getReferenceKeyMeaning(text);
}

string Convert::getReferenceKeyMeaning(const string& token) {
	string key;
	string keybase;
	string translation;
	string language;
	string number;
	HumRegex hre;
	if (hre.search(token, "^!!!+\\s*([^:]+)\\s*:")) {
		key = hre.getMatch(1);
	}
	if (key.empty()) {
		return "";
	}
	if (islower(key[0])) {
		// non-standard reference record.
		return "";
	}

	// extract language information
	if (hre.search(key, "^([^@])+@@([^@]+)$")) {
		key      = hre.getMatch(1);
		language = hre.getMatch(2);
	} else if (hre.search(key, "^([^@])+@([^@]+)$")) {
		key         = hre.getMatch(1);
		translation = hre.getMatch(2);
	}

	// extract number qualifier
	if (hre.search(key, "^(.*)(\\d+)$")) {
		key     = hre.getMatch(1);
		number  = hre.getMatch(2);
	}

	if (key.empty()) {
		return "";
	}

	string meaning;
	switch (key[0]) {
		case 'A':	// analytic information
			if      (key == "ACO") { meaning = "Collection designation"; }
			else if (key == "AFR") { meaning = "Form designation"; }
			else if (key == "AGN") { meaning = "genre designation"; }
			else if (key == "AST") { meaning = "Syle/period"; }
			else if (key == "AMD") { meaning = "Mode classification"; }
			else if (key == "AMT") { meaning = "Meter classification"; }
			else if (key == "AIN") { meaning = "Instrumentation"; }
			else if (key == "ARE") { meaning = "Geographical region of origin"; }
			else if (key == "ARL") { meaning = "Origin coordinates"; }
			break;

		case 'C':	// composer-base reference records
			if      (key == "COM") { meaning = "Composer"; }
			else if (key == "CDT") { meaning = "Composer's dates"; }
			else if (key == "CNT") { meaning = "Composer's nationality"; }
			else if (key == "COA") { meaning = "Attributed composer"; }
			else if (key == "COS") { meaning = "Suspected composer"; }
			else if (key == "COL") { meaning = "Composer's stage name"; }
			else if (key == "COC") { meaning = "Composer's corporate name"; }
			else if (key == "CBL") { meaning = "Composer's birth location"; }
			else if (key == "CDL") { meaning = "Composer's death location"; }
			break;

		case 'E':	// electronic editor
			if      (key == "EED") { meaning = "Electronic editor"; }
			else if (key == "ENC") { meaning = "Electronic encoder"; }
			else if (key == "END") { meaning = "Electronic encoding date"; }
			else if (key == "EMD") { meaning = "Modification description"; }
			else if (key == "EEV") { meaning = "Electronic edition version"; }
			else if (key == "EFL") { meaning = "Electronic file number"; }
			else if (key == "EST") { meaning = "Encoding status"; }
			break;

		case 'G':	// group information
			if      (key == "GTL") { meaning = "Group title"; }
			else if (key == "GAW") { meaning = "Associated work"; }
			else if (key == "GCO") { meaning = "Collection designation"; }
			break;

		case 'H':	// historical information
			if      (key == "HAO") { meaning = "Aural history"; }
			else if (key == "HTX") { meaning = "Vocal text translation"; }
			break;

		case 'L':	// lyricist/librettis/arranger/orchestrator reference records
			if      (key == "LYR") { meaning = "Lyricist"; }
			else if (key == "LIB") { meaning = "Librettist"; }
			else if (key == "LOR") { meaning = "Orchestrator"; }
			break;

		case 'M':	// performance information
			if      (key == "MPN") { meaning = "Performer"; }
			else if (key == "MPS") { meaning = "Suspected performer"; }
			else if (key == "MGN") { meaning = "Performance group name"; }
			else if (key == "MRD") { meaning = "Performance date"; }
			else if (key == "MLC") { meaning = "Performance location"; }
			else if (key == "MCN") { meaning = "Conductor"; }
			else if (key == "MPD") { meaning = "Premier date"; }
			break;

		case 'O':	// work (opus) information
			if      (key == "OTL") { meaning = "Work title"; }
			else if (key == "OTP") { meaning = "Popular title"; }
			else if (key == "OTA") { meaning = "Alternative title"; }
			else if (key == "OPR") { meaning = "Parent-work title"; }
			else if (key == "OAC") { meaning = "Act number"; }
			else if (key == "OSC") { meaning = "Scene number"; }
			else if (key == "OMV") { meaning = "Movement number"; }
			else if (key == "OMD") { meaning = "Movement designation"; }
			else if (key == "OPS") { meaning = "Opus number"; }
			else if (key == "ONM") { meaning = "Work number in opus"; }
			else if (key == "OVM") { meaning = "Volume number"; }
			else if (key == "ODE") { meaning = "Dedicatee"; }
			else if (key == "OCO") { meaning = "Commission"; }
			else if (key == "OCL") { meaning = "Collector"; }
			else if (key == "OCL") { meaning = "Free-form note"; }
			else if (key == "OCY") { meaning = "Composition country"; }
			else if (key == "OPC") { meaning = "Composition city"; }
			break;

		case 'P':	// publication information
			if      (key == "PUB") { meaning = "Publication status"; }
			else if (key == "PPR") { meaning = "First publisher"; }
			else if (key == "PTL") { meaning = "Publication title"; }
			else if (key == "PDT") { meaning = "Publication date"; }
			else if (key == "PPP") { meaning = "Publication location"; }
			else if (key == "PC#") { meaning = "Publication catalog number"; }
			else if (key == "SCT") { meaning = "Scholarly catalog abbreviation and number"; }
			else if (key == "SCA") { meaning = "Scholarly catalog unabbreviated name"; }
			else if (key == "SMS") { meaning = "Manuscript source name"; }
			else if (key == "SML") { meaning = "Manuscript location"; }
			else if (key == "SMA") { meaning = "Manuscript access"; }
			break;

		case 'R':
			// recording information
			if      (key == "RTL") { meaning = "Recording Title"; }
			else if (key == "RMM") { meaning = "Manufacturer"; }
			else if (key == "RC#") { meaning = "Catalog number"; }
			else if (key == "RRD") { meaning = "Recording release date"; }
			else if (key == "RLC") { meaning = "Recording location"; }
			else if (key == "RNP") { meaning = "Record producer"; }
			else if (key == "RDT") { meaning = "Recording date"; }
			else if (key == "RT#") { meaning = "Recording track number"; }
			// representation information
			else if (key == "RLN") { meaning = "ASCII language setting"; }
			else if (key == "RDF") { meaning = "User-defined signifiers"; }
			else if (key == "RDT") { meaning = "Encoding date"; }
			else if (key == "RNB") { meaning = "Encoding note"; }
			else if (key == "RWG") { meaning = "Encoding warning"; }
			break;

		case 'T':	// translator
			if      (key == "TRN") { meaning = "Translator"; }
			break;

		case 'V':	// version
			if      (key == "VTS") { meaning = "Data checksum"; }
			break;

		case 'Y':	// copyright information
			if      (key == "YEP") { meaning = "Publisher of electronic edition"; }
			else if (key == "YEC") { meaning = "Electronic edition copyright"; }
			else if (key == "YER") { meaning = "Electronic edition release year"; }
			else if (key == "YEM") { meaning = "Copyright message"; }
			else if (key == "YEC") { meaning = "Country of copyright"; }
			else if (key == "YOR") { meaning = "Original document"; }
			else if (key == "YOO") { meaning = "Original edition owner"; }
			else if (key == "YOY") { meaning = "Original edition copyright year"; }
			else if (key == "YOE") { meaning = "Original edition editor"; }
			break;
	}

	if (!number.empty()) {
		meaning += " #" + number;
	}

	if (!language.empty()) {
		meaning += ", original language " + Convert::getLanguageName(language);
	} else if (!translation.empty()) {
		meaning += ", translated into " + Convert::getLanguageName(language);
	}

	return meaning;
}



//////////////////////////////
//
// Convert::getLanguageName --
//

string Convert::getLanguageName(const string& abbreviation) {
	string code;
	for (int i=0; i<(int)abbreviation.size(); i++) {
		if (abbreviation[i] == '@') {
			continue;
		}
		code.push_back(toupper(abbreviation[i]));
	}

	if (code.size() == 2) {
		// ISO 639-1 language codes

		if (code == "AA") { return "Afar"; }
		if (code == "AB") { return "Abkhazian"; }
		if (code == "AE") { return "Avestan"; }
		if (code == "AF") { return "Afrikaans"; }
		if (code == "AK") { return "Akan"; }
		if (code == "AM") { return "Amharic"; }
		if (code == "AN") { return "Aragonese"; }
		if (code == "AR") { return "Arabic"; }
		if (code == "AS") { return "Assamese"; }
		if (code == "AV") { return "Avaric"; }
		if (code == "AY") { return "Aymara"; }
		if (code == "AZ") { return "Azerbaijani"; }
		if (code == "BA") { return "Bashkir"; }
		if (code == "BE") { return "Belarusian"; }
		if (code == "BG") { return "Bulgarian"; }
		if (code == "BH") { return "Bihari languages"; }
		if (code == "BI") { return "Bislama"; }
		if (code == "BM") { return "Bambara"; }
		if (code == "BN") { return "Bengali"; }
		if (code == "BO") { return "Tibetan"; }
		if (code == "BR") { return "Breton"; }
		if (code == "BS") { return "Bosnian"; }
		if (code == "CA") { return "Catalan"; }
		if (code == "CE") { return "Chechen"; }
		if (code == "CH") { return "Chamorro"; }
		if (code == "CO") { return "Corsican"; }
		if (code == "CR") { return "Cree"; }
		if (code == "CS") { return "Czech"; }
		if (code == "CS") { return "Czech"; }
		if (code == "CU") { return "Church Slavic"; }
		if (code == "CV") { return "Chuvash"; }
		if (code == "CY") { return "Welsh"; }
		if (code == "CY") { return "Welsh"; }
		if (code == "DA") { return "Danish"; }
		if (code == "DE") { return "German"; }
		if (code == "DV") { return "Divehi"; }
		if (code == "DZ") { return "Dzongkha"; }
		if (code == "EE") { return "Ewe"; }
		if (code == "EL") { return "Greek, Modern (1453-)"; }
		if (code == "EN") { return "English"; }
		if (code == "EO") { return "Esperanto"; }
		if (code == "ES") { return "Spanish"; }
		if (code == "ET") { return "Estonian"; }
		if (code == "EU") { return "Basque"; }
		if (code == "EU") { return "Basque"; }
		if (code == "FA") { return "Persian"; }
		if (code == "FF") { return "Fulah"; }
		if (code == "FI") { return "Finnish"; }
		if (code == "FJ") { return "Fijian"; }
		if (code == "FO") { return "Faroese"; }
		if (code == "FR") { return "French"; }
		if (code == "FY") { return "Western Frisian"; }
		if (code == "GA") { return "Irish"; }
		if (code == "GD") { return "Gaelic"; }
		if (code == "GL") { return "Galician"; }
		if (code == "GN") { return "Guarani"; }
		if (code == "GU") { return "Gujarati"; }
		if (code == "GV") { return "Manx"; }
		if (code == "HA") { return "Hausa"; }
		if (code == "HE") { return "Hebrew"; }
		if (code == "HI") { return "Hindi"; }
		if (code == "HO") { return "Hiri Motu"; }
		if (code == "HR") { return "Croatian"; }
		if (code == "HT") { return "Haitian"; }
		if (code == "HU") { return "Hungarian"; }
		if (code == "HY") { return "Armenian"; }
		if (code == "HZ") { return "Herero"; }
		if (code == "IA") { return "Interlingua"; }
		if (code == "ID") { return "Indonesian"; }
		if (code == "IE") { return "Interlingue"; }
		if (code == "IG") { return "Igbo"; }
		if (code == "II") { return "Sichuan Yi"; }
		if (code == "IK") { return "Inupiaq"; }
		if (code == "IO") { return "Ido"; }
		if (code == "IS") { return "Icelandic"; }
		if (code == "IT") { return "Italian"; }
		if (code == "IU") { return "Inuktitut"; }
		if (code == "JA") { return "Japanese"; }
		if (code == "JV") { return "Javanese"; }
		if (code == "KA") { return "Georgian"; }
		if (code == "KG") { return "Kongo"; }
		if (code == "KI") { return "Kikuyu"; }
		if (code == "KJ") { return "Kuanyama"; }
		if (code == "KK") { return "Kazakh"; }
		if (code == "KL") { return "Greenlandic"; }
		if (code == "KM") { return "Central Khmer"; }
		if (code == "KN") { return "Kannada"; }
		if (code == "KO") { return "Korean"; }
		if (code == "KR") { return "Kanuri"; }
		if (code == "KS") { return "Kashmiri"; }
		if (code == "KU") { return "Kurdish"; }
		if (code == "KV") { return "Komi"; }
		if (code == "KW") { return "Cornish"; }
		if (code == "KY") { return "Kirghiz"; }
		if (code == "LA") { return "Latin"; }
		if (code == "LB") { return "Luxembourgish"; }
		if (code == "LG") { return "Ganda"; }
		if (code == "LI") { return "Limburgan"; }
		if (code == "LN") { return "Lingala"; }
		if (code == "LO") { return "Lao"; }
		if (code == "LT") { return "Lithuanian"; }
		if (code == "LU") { return "Luba-Katanga"; }
		if (code == "LV") { return "Latvian"; }
		if (code == "MG") { return "Malagasy"; }
		if (code == "MH") { return "Marshallese"; }
		if (code == "MI") { return "Maori"; }
		if (code == "MK") { return "Macedonian"; }
		if (code == "MK") { return "Macedonian"; }
		if (code == "ML") { return "Malayalam"; }
		if (code == "MN") { return "Mongolian"; }
		if (code == "MR") { return "Marathi"; }
		if (code == "MS") { return "Malay"; }
		if (code == "MT") { return "Maltese"; }
		if (code == "MY") { return "Burmese"; }
		if (code == "MY") { return "Burmese"; }
		if (code == "NA") { return "Nauru"; }
		if (code == "NB") { return "Bokmål, Norwegian"; }
		if (code == "ND") { return "Ndebele, North"; }
		if (code == "NE") { return "Nepali"; }
		if (code == "NG") { return "Ndonga"; }
		if (code == "NL") { return "Dutch"; }
		if (code == "NL") { return "Dutch"; }
		if (code == "NN") { return "Norwegian Nynorsk"; }
		if (code == "NO") { return "Norwegian"; }
		if (code == "NR") { return "Ndebele, South"; }
		if (code == "NV") { return "Navajo"; }
		if (code == "NY") { return "Chichewa"; }
		if (code == "OC") { return "Occitan (post 1500)"; }
		if (code == "OJ") { return "Ojibwa"; }
		if (code == "OM") { return "Oromo"; }
		if (code == "OR") { return "Oriya"; }
		if (code == "OS") { return "Ossetian"; }
		if (code == "PA") { return "Panjabi"; }
		if (code == "PI") { return "Pali"; }
		if (code == "PL") { return "Polish"; }
		if (code == "PS") { return "Pushto"; }
		if (code == "PT") { return "Portuguese"; }
		if (code == "QU") { return "Quechua"; }
		if (code == "RM") { return "Romansh"; }
		if (code == "RN") { return "Rundi"; }
		if (code == "RO") { return "Romanian"; }
		if (code == "RU") { return "Russian"; }
		if (code == "RW") { return "Kinyarwanda"; }
		if (code == "SA") { return "Sanskrit"; }
		if (code == "SC") { return "Sardinian"; }
		if (code == "SD") { return "Sindhi"; }
		if (code == "SE") { return "Northern Sami"; }
		if (code == "SG") { return "Sango"; }
		if (code == "SI") { return "Sinhala"; }
		if (code == "SL") { return "Slovenian"; }
		if (code == "SM") { return "Samoan"; }
		if (code == "SN") { return "Shona"; }
		if (code == "SO") { return "Somali"; }
		if (code == "SQ") { return "Albanian"; }
		if (code == "SR") { return "Serbian"; }
		if (code == "SS") { return "Swati"; }
		if (code == "ST") { return "Sotho, Southern"; }
		if (code == "SU") { return "Sundanese"; }
		if (code == "SV") { return "Swedish"; }
		if (code == "SW") { return "Swahili"; }
		if (code == "TA") { return "Tamil"; }
		if (code == "TE") { return "Telugu"; }
		if (code == "TG") { return "Tajik"; }
		if (code == "TH") { return "Thai"; }
		if (code == "TI") { return "Tigrinya"; }
		if (code == "TK") { return "Turkmen"; }
		if (code == "TL") { return "Tagalog"; }
		if (code == "TN") { return "Tswana"; }
		if (code == "TO") { return "Tonga (Tonga Islands)"; }
		if (code == "TR") { return "Turkish"; }
		if (code == "TS") { return "Tsonga"; }
		if (code == "TT") { return "Tatar"; }
		if (code == "TW") { return "Twi"; }
		if (code == "TY") { return "Tahitian"; }
		if (code == "UG") { return "Uighur"; }
		if (code == "UK") { return "Ukrainian"; }
		if (code == "UR") { return "Urdu"; }
		if (code == "UZ") { return "Uzbek"; }
		if (code == "VE") { return "Venda"; }
		if (code == "VI") { return "Vietnamese"; }
		if (code == "VO") { return "Volapük"; }
		if (code == "WA") { return "Walloon"; }
		if (code == "WO") { return "Wolof"; }
		if (code == "XH") { return "Xhosa"; }
		if (code == "YI") { return "Yiddish"; }
		if (code == "YO") { return "Yoruba"; }
		if (code == "ZA") { return "Zhuang"; }
		if (code == "ZH") { return "Chinese"; }
		if (code == "ZU") { return "Zulu"; }

	} else if (code.size() == 3) {
		// ISO 639-2 language codes

		if (code == "AAR") { return "Afar"; }
		if (code == "ABK") { return "Abkhazian"; }
		if (code == "ACE") { return "Achinese"; }
		if (code == "ACH") { return "Acoli"; }
		if (code == "ADA") { return "Adangme"; }
		if (code == "ADY") { return "Adyghe"; }
		if (code == "AFA") { return "Afro-Asiatic languages"; }
		if (code == "AFH") { return "Afrihili"; }
		if (code == "AFR") { return "Afrikaans"; }
		if (code == "AIN") { return "Ainu"; }
		if (code == "AKA") { return "Akan"; }
		if (code == "AKK") { return "Akkadian"; }
		if (code == "ALB") { return "Albanian"; }
		if (code == "ALE") { return "Aleut"; }
		if (code == "ALG") { return "Algonquian languages"; }
		if (code == "ALT") { return "Southern Altai"; }
		if (code == "AMH") { return "Amharic"; }
		if (code == "ANG") { return "English, Old (ca.450-1100)"; }
		if (code == "ANP") { return "Angika"; }
		if (code == "APA") { return "Apache languages"; }
		if (code == "ARA") { return "Arabic"; }
		if (code == "ARC") { return "Aramaic (700-300 BCE)"; }
		if (code == "ARG") { return "Aragonese"; }
		if (code == "ARM") { return "Armenian"; }
		if (code == "ARN") { return "Mapudungun"; }
		if (code == "ARP") { return "Arapaho"; }
		if (code == "ART") { return "Artificial languages"; }
		if (code == "ARW") { return "Arawak"; }
		if (code == "ASM") { return "Assamese"; }
		if (code == "AST") { return "Asturian"; }
		if (code == "ATH") { return "Athapascan languages"; }
		if (code == "AUS") { return "Australian languages"; }
		if (code == "AVA") { return "Avaric"; }
		if (code == "AVE") { return "Avestan"; }
		if (code == "AWA") { return "Awadhi"; }
		if (code == "AYM") { return "Aymara"; }
		if (code == "AZE") { return "Azerbaijani"; }
		if (code == "BAD") { return "Banda languages"; }
		if (code == "BAI") { return "Bamileke languages"; }
		if (code == "BAK") { return "Bashkir"; }
		if (code == "BAL") { return "Baluchi"; }
		if (code == "BAM") { return "Bambara"; }
		if (code == "BAN") { return "Balinese"; }
		if (code == "BAQ") { return "Basque"; }
		if (code == "BAQ") { return "Basque"; }
		if (code == "BAS") { return "Basa"; }
		if (code == "BAT") { return "Baltic languages"; }
		if (code == "BEJ") { return "Beja"; }
		if (code == "BEL") { return "Belarusian"; }
		if (code == "BEM") { return "Bemba"; }
		if (code == "BEN") { return "Bengali"; }
		if (code == "BER") { return "Berber languages"; }
		if (code == "BHO") { return "Bhojpuri"; }
		if (code == "BIH") { return "Bihari languages"; }
		if (code == "BIK") { return "Bikol"; }
		if (code == "BIN") { return "Bini"; }
		if (code == "BIS") { return "Bislama"; }
		if (code == "BLA") { return "Siksika"; }
		if (code == "BNT") { return "Bantu languages"; }
		if (code == "BOD") { return "Tibetan"; }
		if (code == "BOS") { return "Bosnian"; }
		if (code == "BRA") { return "Braj"; }
		if (code == "BRE") { return "Breton"; }
		if (code == "BTK") { return "Batak languages"; }
		if (code == "BUA") { return "Buriat"; }
		if (code == "BUG") { return "Buginese"; }
		if (code == "BUL") { return "Bulgarian"; }
		if (code == "BUR") { return "Burmese"; }
		if (code == "BUR") { return "Burmese"; }
		if (code == "BYN") { return "Blin"; }
		if (code == "CAD") { return "Caddo"; }
		if (code == "CAI") { return "Central American Indian languages"; }
		if (code == "CAR") { return "Galibi Carib"; }
		if (code == "CAT") { return "Catalan"; }
		if (code == "CAU") { return "Caucasian languages"; }
		if (code == "CEB") { return "Cebuano"; }
		if (code == "CEL") { return "Celtic languages"; }
		if (code == "CES") { return "Czech"; }
		if (code == "CES") { return "Czech"; }
		if (code == "CHA") { return "Chamorro"; }
		if (code == "CHB") { return "Chibcha"; }
		if (code == "CHE") { return "Chechen"; }
		if (code == "CHG") { return "Chagatai"; }
		if (code == "CHI") { return "Chinese"; }
		if (code == "CHK") { return "Chuukese"; }
		if (code == "CHM") { return "Mari"; }
		if (code == "CHN") { return "Chinook jargon"; }
		if (code == "CHO") { return "Choctaw"; }
		if (code == "CHP") { return "Chipewyan"; }
		if (code == "CHR") { return "Cherokee"; }
		if (code == "CHU") { return "Church Slavic"; }
		if (code == "CHV") { return "Chuvash"; }
		if (code == "CHY") { return "Cheyenne"; }
		if (code == "CMC") { return "Chamic languages"; }
		if (code == "CNR") { return "Montenegrin"; }
		if (code == "COP") { return "Coptic"; }
		if (code == "COR") { return "Cornish"; }
		if (code == "COS") { return "Corsican"; }
		if (code == "CPE") { return "Creoles and pidgins, English based"; }
		if (code == "CPF") { return "Creoles and pidgins, French-based"; }
		if (code == "CPP") { return "Creoles and pidgins, Portuguese-based"; }
		if (code == "CRE") { return "Cree"; }
		if (code == "CRH") { return "Crimean Tatar"; }
		if (code == "CRP") { return "Creoles and pidgins"; }
		if (code == "CSB") { return "Kashubian"; }
		if (code == "CUS") { return "Cushitic languages"; }
		if (code == "CYM") { return "Welsh"; }
		if (code == "CYM") { return "Welsh"; }
		if (code == "CZE") { return "Czech"; }
		if (code == "CZE") { return "Czech"; }
		if (code == "DAK") { return "Dakota"; }
		if (code == "DAN") { return "Danish"; }
		if (code == "DAR") { return "Dargwa"; }
		if (code == "DAY") { return "Land Dayak languages"; }
		if (code == "DEL") { return "Delaware"; }
		if (code == "DEN") { return "Slave (Athapascan)"; }
		if (code == "DEU") { return "German"; }
		if (code == "DGR") { return "Dogrib"; }
		if (code == "DIN") { return "Dinka"; }
		if (code == "DIV") { return "Divehi"; }
		if (code == "DOI") { return "Dogri"; }
		if (code == "DRA") { return "Dravidian languages"; }
		if (code == "DSB") { return "Lower Sorbian"; }
		if (code == "DUA") { return "Duala"; }
		if (code == "DUM") { return "Dutch, Middle (ca.1050-1350)"; }
		if (code == "DUT") { return "Dutch"; }
		if (code == "DUT") { return "Dutch"; }
		if (code == "DYU") { return "Dyula"; }
		if (code == "DZO") { return "Dzongkha"; }
		if (code == "EFI") { return "Efik"; }
		if (code == "EGY") { return "Egyptian (Ancient)"; }
		if (code == "EKA") { return "Ekajuk"; }
		if (code == "ELL") { return "Greek, Modern (1453-)"; }
		if (code == "ELX") { return "Elamite"; }
		if (code == "ENG") { return "English"; }
		if (code == "ENM") { return "English, Middle (1100-1500)"; }
		if (code == "EPO") { return "Esperanto"; }
		if (code == "EST") { return "Estonian"; }
		if (code == "EUS") { return "Basque"; }
		if (code == "EUS") { return "Basque"; }
		if (code == "EWE") { return "Ewe"; }
		if (code == "EWO") { return "Ewondo"; }
		if (code == "FAN") { return "Fang"; }
		if (code == "FAO") { return "Faroese"; }
		if (code == "FAS") { return "Persian"; }
		if (code == "FAT") { return "Fanti"; }
		if (code == "FIJ") { return "Fijian"; }
		if (code == "FIL") { return "Filipino"; }
		if (code == "FIN") { return "Finnish"; }
		if (code == "FIU") { return "Finno-Ugrian languages"; }
		if (code == "FON") { return "Fon"; }
		if (code == "FRA") { return "French"; }
		if (code == "FRE") { return "French"; }
		if (code == "FRM") { return "French, Middle (ca.1400-1600)"; }
		if (code == "FRO") { return "French, Old (842-ca.1400)"; }
		if (code == "FRR") { return "Northern Frisian"; }
		if (code == "FRS") { return "Eastern Frisian"; }
		if (code == "FRY") { return "Western Frisian"; }
		if (code == "FUL") { return "Fulah"; }
		if (code == "FUR") { return "Friulian"; }
		if (code == "GAA") { return "Ga"; }
		if (code == "GAY") { return "Gayo"; }
		if (code == "GBA") { return "Gbaya"; }
		if (code == "GEM") { return "Germanic languages"; }
		if (code == "GEO") { return "Georgin"; }
		if (code == "GER") { return "German"; }
		if (code == "GEZ") { return "Geez"; }
		if (code == "GIL") { return "Gilbertese"; }
		if (code == "GLA") { return "Gaelic"; }
		if (code == "GLE") { return "Irish"; }
		if (code == "GLG") { return "Galician"; }
		if (code == "GLV") { return "Manx"; }
		if (code == "GMH") { return "German, Middle High (ca.1050-1500)"; }
		if (code == "GOH") { return "German, Old High (ca.750-1050)"; }
		if (code == "GON") { return "Gondi"; }
		if (code == "GOR") { return "Gorontalo"; }
		if (code == "GOT") { return "Gothic"; }
		if (code == "GRB") { return "Grebo"; }
		if (code == "GRC") { return "Greek, Ancient (to 1453)"; }
		if (code == "GRE") { return "Greek"; }
		if (code == "GRN") { return "Guarani"; }
		if (code == "GSW") { return "Swiss German"; }
		if (code == "GUJ") { return "Gujarati"; }
		if (code == "GWI") { return "Gwich'in"; }
		if (code == "HAI") { return "Haida"; }
		if (code == "HAT") { return "Haitian"; }
		if (code == "HAU") { return "Hausa"; }
		if (code == "HAW") { return "Hawaiian"; }
		if (code == "HEB") { return "Hebrew"; }
		if (code == "HER") { return "Herero"; }
		if (code == "HIL") { return "Hiligaynon"; }
		if (code == "HIM") { return "Himachali languages"; }
		if (code == "HIN") { return "Hindi"; }
		if (code == "HIT") { return "Hittite"; }
		if (code == "HMN") { return "Hmong"; }
		if (code == "HMO") { return "Hiri Motu"; }
		if (code == "HRV") { return "Croatian"; }
		if (code == "HSB") { return "Upper Sorbian"; }
		if (code == "HUN") { return "Hungarian"; }
		if (code == "HUP") { return "Hupa"; }
		if (code == "HYE") { return "Armenian"; }
		if (code == "IBA") { return "Iban"; }
		if (code == "IBO") { return "Igbo"; }
		if (code == "ICE") { return "Icelandic"; }
		if (code == "IDO") { return "Ido"; }
		if (code == "III") { return "Sichuan Yi"; }
		if (code == "IJO") { return "Ijo languages"; }
		if (code == "IKU") { return "Inuktitut"; }
		if (code == "ILE") { return "Interlingue"; }
		if (code == "ILO") { return "Iloko"; }
		if (code == "INA") { return "Interlingua)"; }
		if (code == "INC") { return "Indic languages"; }
		if (code == "IND") { return "Indonesian"; }
		if (code == "INE") { return "Indo-European languages"; }
		if (code == "INH") { return "Ingush"; }
		if (code == "IPK") { return "Inupiaq"; }
		if (code == "IRA") { return "Iranian languages"; }
		if (code == "IRO") { return "Iroquoian languages"; }
		if (code == "ISL") { return "Icelandic"; }
		if (code == "ITA") { return "Italian"; }
		if (code == "JAV") { return "Javanese"; }
		if (code == "JBO") { return "Lojban"; }
		if (code == "JPN") { return "Japanese"; }
		if (code == "JPR") { return "Judeo-Persian"; }
		if (code == "JRB") { return "Judeo-Arabic"; }
		if (code == "KAA") { return "Kara-Kalpak"; }
		if (code == "KAB") { return "Kabyle"; }
		if (code == "KAC") { return "Kachin"; }
		if (code == "KAL") { return "Greenlandic"; }
		if (code == "KAM") { return "Kamba"; }
		if (code == "KAN") { return "Kannada"; }
		if (code == "KAR") { return "Karen languages"; }
		if (code == "KAS") { return "Kashmiri"; }
		if (code == "KAT") { return "Georgian"; }
		if (code == "KAU") { return "Kanuri"; }
		if (code == "KAW") { return "Kawi"; }
		if (code == "KAZ") { return "Kazakh"; }
		if (code == "KBD") { return "Kabardian"; }
		if (code == "KHA") { return "Khasi"; }
		if (code == "KHI") { return "Khoisan languages"; }
		if (code == "KHM") { return "Central Khmer"; }
		if (code == "KHO") { return "Khotanese"; }
		if (code == "KIK") { return "Kikuyu"; }
		if (code == "KIN") { return "Kinyarwanda"; }
		if (code == "KIR") { return "Kirghiz"; }
		if (code == "KMB") { return "Kimbundu"; }
		if (code == "KOK") { return "Konkani"; }
		if (code == "KOM") { return "Komi"; }
		if (code == "KON") { return "Kongo"; }
		if (code == "KOR") { return "Korean"; }
		if (code == "KOS") { return "Kosraean"; }
		if (code == "KPE") { return "Kpelle"; }
		if (code == "KRC") { return "Karachay-Balkar"; }
		if (code == "KRL") { return "Karelian"; }
		if (code == "KRO") { return "Kru languages"; }
		if (code == "KRU") { return "Kurukh"; }
		if (code == "KUA") { return "Kuanyama"; }
		if (code == "KUM") { return "Kumyk"; }
		if (code == "KUR") { return "Kurdish"; }
		if (code == "KUT") { return "Kutenai"; }
		if (code == "LAD") { return "Ladino"; }
		if (code == "LAH") { return "Lahnda"; }
		if (code == "LAM") { return "Lamba"; }
		if (code == "LAO") { return "Lao"; }
		if (code == "LAT") { return "Latin"; }
		if (code == "LAV") { return "Latvian"; }
		if (code == "LEZ") { return "Lezghian"; }
		if (code == "LIM") { return "Limburgan"; }
		if (code == "LIN") { return "Lingala"; }
		if (code == "LIT") { return "Lithuanian"; }
		if (code == "LOL") { return "Mongo"; }
		if (code == "LOZ") { return "Lozi"; }
		if (code == "LTZ") { return "Luxembourgish"; }
		if (code == "LUA") { return "Luba-Lulua"; }
		if (code == "LUB") { return "Luba-Katanga"; }
		if (code == "LUG") { return "Ganda"; }
		if (code == "LUI") { return "Luiseno"; }
		if (code == "LUN") { return "Lunda"; }
		if (code == "LUO") { return "Luo (Kenya and Tanzania)"; }
		if (code == "LUS") { return "Lushai"; }
		if (code == "MAC") { return "Macedonian"; }
		if (code == "MAC") { return "Macedonian"; }
		if (code == "MAD") { return "Madurese"; }
		if (code == "MAG") { return "Magahi"; }
		if (code == "MAH") { return "Marshallese"; }
		if (code == "MAI") { return "Maithili"; }
		if (code == "MAK") { return "Makasar"; }
		if (code == "MAL") { return "Malayalam"; }
		if (code == "MAN") { return "Mandingo"; }
		if (code == "MAO") { return "Maori"; }
		if (code == "MAP") { return "Austronesian languages"; }
		if (code == "MAR") { return "Marathi"; }
		if (code == "MAS") { return "Masai"; }
		if (code == "MAY") { return "Malay"; }
		if (code == "MDF") { return "Moksha"; }
		if (code == "MDR") { return "Mandar"; }
		if (code == "MEN") { return "Mende"; }
		if (code == "MGA") { return "Irish, Middle (900-1200)"; }
		if (code == "MIC") { return "Mi'kmaq"; }
		if (code == "MIN") { return "Minangkabau"; }
		if (code == "MIS") { return "Uncoded languages"; }
		if (code == "MKD") { return "Macedonian"; }
		if (code == "MKD") { return "Macedonian"; }
		if (code == "MKH") { return "Mon-Khmer languages"; }
		if (code == "MLG") { return "Malagasy"; }
		if (code == "MLT") { return "Maltese"; }
		if (code == "MNC") { return "Manchu"; }
		if (code == "MNI") { return "Manipuri"; }
		if (code == "MNO") { return "Manobo languages"; }
		if (code == "MOH") { return "Mohawk"; }
		if (code == "MON") { return "Mongolian"; }
		if (code == "MOS") { return "Mossi"; }
		if (code == "MRI") { return "Maori"; }
		if (code == "MSA") { return "Malay"; }
		if (code == "MUL") { return "Multiple languages"; }
		if (code == "MUN") { return "Munda languages"; }
		if (code == "MUS") { return "Creek"; }
		if (code == "MWL") { return "Mirandese"; }
		if (code == "MWR") { return "Marwari"; }
		if (code == "MYA") { return "Burmese"; }
		if (code == "MYA") { return "Burmese"; }
		if (code == "MYN") { return "Mayan languages"; }
		if (code == "MYV") { return "Erzya"; }
		if (code == "NAH") { return "Nahuatl languages"; }
		if (code == "NAI") { return "North American Indian languages"; }
		if (code == "NAP") { return "Neapolitan"; }
		if (code == "NAU") { return "Nauru"; }
		if (code == "NAV") { return "Navajo"; }
		if (code == "NBL") { return "Ndebele, South"; }
		if (code == "NDE") { return "Ndebele, North"; }
		if (code == "NDO") { return "Ndonga"; }
		if (code == "NDS") { return "Low German"; }
		if (code == "NEP") { return "Nepali"; }
		if (code == "NEW") { return "Nepal Bhasa"; }
		if (code == "NIA") { return "Nias"; }
		if (code == "NIC") { return "Niger-Kordofanian languages"; }
		if (code == "NIU") { return "Niuean"; }
		if (code == "NLD") { return "Dutch"; }
		if (code == "NLD") { return "Dutch"; }
		if (code == "NNO") { return "Norwegian Nynorsk"; }
		if (code == "NOB") { return "Bokmål, Norwegian"; }
		if (code == "NOG") { return "Nogai"; }
		if (code == "NON") { return "Norse, Old"; }
		if (code == "NOR") { return "Norwegian"; }
		if (code == "NQO") { return "N'Ko"; }
		if (code == "NSO") { return "Pedi"; }
		if (code == "NUB") { return "Nubian languages"; }
		if (code == "NWC") { return "Classical Newari"; }
		if (code == "NYA") { return "Chichewa"; }
		if (code == "NYM") { return "Nyamwezi"; }
		if (code == "NYN") { return "Nyankole"; }
		if (code == "NYO") { return "Nyoro"; }
		if (code == "NZI") { return "Nzima"; }
		if (code == "OCI") { return "Occitan (post 1500)"; }
		if (code == "OJI") { return "Ojibwa"; }
		if (code == "ORI") { return "Oriya"; }
		if (code == "ORM") { return "Oromo"; }
		if (code == "OSA") { return "Osage"; }
		if (code == "OSS") { return "Ossetian"; }
		if (code == "OTA") { return "Turkish, Ottoman (1500-1928)"; }
		if (code == "OTO") { return "Otomian languages"; }
		if (code == "PAA") { return "Papuan languages"; }
		if (code == "PAG") { return "Pangasinan"; }
		if (code == "PAL") { return "Pahlavi"; }
		if (code == "PAM") { return "Pampanga"; }
		if (code == "PAN") { return "Panjabi"; }
		if (code == "PAP") { return "Papiamento"; }
		if (code == "PAU") { return "Palauan"; }
		if (code == "PEO") { return "Persian, Old (ca.600-400 B.C.)"; }
		if (code == "PER") { return "Persian"; }
		if (code == "PHI") { return "Philippine languages"; }
		if (code == "PHN") { return "Phoenician"; }
		if (code == "PLI") { return "Pali"; }
		if (code == "POL") { return "Polish"; }
		if (code == "PON") { return "Pohnpeian"; }
		if (code == "POR") { return "Portuguese"; }
		if (code == "PRA") { return "Prakrit languages"; }
		if (code == "PRO") { return "Provençal, Old (to 1500)"; }
		if (code == "PUS") { return "Pushto"; }
		if (code == "QUE") { return "Quechua"; }
		if (code == "RAJ") { return "Rajasthani"; }
		if (code == "RAP") { return "Rapanui"; }
		if (code == "RAR") { return "Rarotongan"; }
		if (code == "ROA") { return "Romance languages"; }
		if (code == "ROH") { return "Romansh"; }
		if (code == "ROM") { return "Romany"; }
		if (code == "RON") { return "Romanian"; }
		if (code == "RUM") { return "Romanian"; }
		if (code == "RUN") { return "Rundi"; }
		if (code == "RUP") { return "Aromanian"; }
		if (code == "RUS") { return "Russian"; }
		if (code == "SAD") { return "Sandawe"; }
		if (code == "SAG") { return "Sango"; }
		if (code == "SAH") { return "Yakut"; }
		if (code == "SAI") { return "South American Indian languages"; }
		if (code == "SAL") { return "Salishan languages"; }
		if (code == "SAM") { return "Samaritan Aramaic"; }
		if (code == "SAN") { return "Sanskrit"; }
		if (code == "SAS") { return "Sasak"; }
		if (code == "SAT") { return "Santali"; }
		if (code == "SCN") { return "Sicilian"; }
		if (code == "SCO") { return "Scots"; }
		if (code == "SEL") { return "Selkup"; }
		if (code == "SEM") { return "Semitic languages"; }
		if (code == "SGA") { return "Irish, Old (to 900)"; }
		if (code == "SGN") { return "Sign Languages"; }
		if (code == "SHN") { return "Shan"; }
		if (code == "SID") { return "Sidamo"; }
		if (code == "SIN") { return "Sinhala"; }
		if (code == "SIO") { return "Siouan languages"; }
		if (code == "SIT") { return "Sino-Tibetan languages"; }
		if (code == "SLA") { return "Slavic languages"; }
		if (code == "SLO") { return "Slovak"; }
		if (code == "SLV") { return "Slovenian"; }
		if (code == "SMA") { return "Southern Sami"; }
		if (code == "SME") { return "Northern Sami"; }
		if (code == "SMI") { return "Sami languages"; }
		if (code == "SMJ") { return "Lule Sami"; }
		if (code == "SMN") { return "Inari Sami"; }
		if (code == "SMO") { return "Samoan"; }
		if (code == "SMS") { return "Skolt Sami"; }
		if (code == "SNA") { return "Shona"; }
		if (code == "SND") { return "Sindhi"; }
		if (code == "SNK") { return "Soninke"; }
		if (code == "SOG") { return "Sogdian"; }
		if (code == "SOM") { return "Somali"; }
		if (code == "SON") { return "Songhai languages"; }
		if (code == "SOT") { return "Sotho, Southern"; }
		if (code == "SPA") { return "Spanish"; }
		if (code == "SQI") { return "Albanian"; }
		if (code == "SRD") { return "Sardinian"; }
		if (code == "SRN") { return "Sranan Tongo"; }
		if (code == "SRP") { return "Serbian"; }
		if (code == "SRR") { return "Serer"; }
		if (code == "SSA") { return "Nilo-Saharan languages"; }
		if (code == "SSW") { return "Swati"; }
		if (code == "SUK") { return "Sukuma"; }
		if (code == "SUN") { return "Sundanese"; }
		if (code == "SUS") { return "Susu"; }
		if (code == "SUX") { return "Sumerian"; }
		if (code == "SWA") { return "Swahili"; }
		if (code == "SWE") { return "Swedish"; }
		if (code == "SYC") { return "Classical Syriac"; }
		if (code == "SYR") { return "Syriac"; }
		if (code == "TAH") { return "Tahitian"; }
		if (code == "TAI") { return "Tai languages"; }
		if (code == "TAM") { return "Tamil"; }
		if (code == "TAT") { return "Tatar"; }
		if (code == "TEL") { return "Telugu"; }
		if (code == "TEM") { return "Timne"; }
		if (code == "TER") { return "Tereno"; }
		if (code == "TET") { return "Tetum"; }
		if (code == "TGK") { return "Tajik"; }
		if (code == "TGL") { return "Tagalog"; }
		if (code == "THA") { return "Thai"; }
		if (code == "TIB") { return "Tibetian"; }
		if (code == "TIG") { return "Tigre"; }
		if (code == "TIR") { return "Tigrinya"; }
		if (code == "TIV") { return "Tiv"; }
		if (code == "TKL") { return "Tokelau"; }
		if (code == "TLH") { return "Klingon"; }
		if (code == "TLI") { return "Tlingit"; }
		if (code == "TMH") { return "Tamashek"; }
		if (code == "TOG") { return "Tonga (Nyasa)"; }
		if (code == "TON") { return "Tonga (Tonga Islands)"; }
		if (code == "TPI") { return "Tok Pisin"; }
		if (code == "TSI") { return "Tsimshian"; }
		if (code == "TSN") { return "Tswana"; }
		if (code == "TSO") { return "Tsonga"; }
		if (code == "TUK") { return "Turkmen"; }
		if (code == "TUM") { return "Tumbuka"; }
		if (code == "TUP") { return "Tupi languages"; }
		if (code == "TUR") { return "Turkish"; }
		if (code == "TUT") { return "Altaic languages"; }
		if (code == "TVL") { return "Tuvalu"; }
		if (code == "TWI") { return "Twi"; }
		if (code == "TYV") { return "Tuvinian"; }
		if (code == "UDM") { return "Udmurt"; }
		if (code == "UGA") { return "Ugaritic"; }
		if (code == "UIG") { return "Uighur"; }
		if (code == "UKR") { return "Ukrainian"; }
		if (code == "UMB") { return "Umbundu"; }
		if (code == "UND") { return "Undetermined"; }
		if (code == "URD") { return "Urdu"; }
		if (code == "UZB") { return "Uzbek"; }
		if (code == "VAI") { return "Vai"; }
		if (code == "VEN") { return "Venda"; }
		if (code == "VIE") { return "Vietnamese"; }
		if (code == "VOL") { return "Volapük"; }
		if (code == "VOT") { return "Votic"; }
		if (code == "WAK") { return "Wakashan languages"; }
		if (code == "WAL") { return "Wolaitta"; }
		if (code == "WAR") { return "Waray"; }
		if (code == "WAS") { return "Washo"; }
		if (code == "WEL") { return "Welsh"; }
		if (code == "WEL") { return "Welsh"; }
		if (code == "WEN") { return "Sorbian languages"; }
		if (code == "WLN") { return "Walloon"; }
		if (code == "WOL") { return "Wolof"; }
		if (code == "XAL") { return "Kalmyk"; }
		if (code == "XHO") { return "Xhosa"; }
		if (code == "YAO") { return "Yao"; }
		if (code == "YAP") { return "Yapese"; }
		if (code == "YID") { return "Yiddish"; }
		if (code == "YOR") { return "Yoruba"; }
		if (code == "YPK") { return "Yupik languages"; }
		if (code == "ZAP") { return "Zapotec"; }
		if (code == "ZBL") { return "Blissymbols"; }
		if (code == "ZEN") { return "Zenaga"; }
		if (code == "ZGH") { return "Moroccan"; }
		if (code == "ZHA") { return "Zhuang"; }
		if (code == "ZHO") { return "Chinese"; }
		if (code == "ZND") { return "Zande languages"; }
		if (code == "ZUL") { return "Zulu"; }
		if (code == "ZUN") { return "Zuni"; }
		if (code == "ZZA") { return "Zaza"; }
	}
	return code;
}





//////////////////////////////
//
// Convert::recipToDuration -- Convert **recip rhythmic values into
//     rational number durations in terms of quarter notes.  For example "4"
//     will be converted to 1, "4." to 3/2 (1+1/2).  The second parameter
//     is a scaling factor which can change the rhythmic value's base duration.
//     Giving a scale of 1 will return the duration in whole note units, so
//     "4" will return a value of 1/4 (one quarter of a whole note).  Using
//     3/2 will give the duration in terms of dotted-quarter note units.
//     The third parameter is the sub-token separate.  For example if the input
//     string contains a space, anything after the first space will be ignored
//     when extracting the string.  **kern data which also includes the pitch
//     along with the rhythm can also be given and will be ignored.
// default value: scale = 4 (duration in terms of quarter notes)
// default value: separator = " " (sub-token separator)
//

HumNum Convert::recipToDuration(string* recip, HumNum scale,
		const string& separator) {
	return Convert::recipToDuration(*recip, scale, separator);
}


HumNum Convert::recipToDuration(const string& recip, HumNum scale,
		const string& separator) {
	size_t loc;
	loc = recip.find(separator);
	string subtok;
	if (loc != string::npos) {
		subtok = recip.substr(0, loc);
	} else {
		subtok = recip;
	}

	loc = recip.find('q');
	if (loc != string::npos) {
		// grace note, ignore printed rhythm
		HumNum zero(0);
		return zero;
	}

	int dotcount = 0;
	int i;
	int numi = -1;
	for (i=0; i<(int)subtok.size(); i++) {
		if (subtok[i] == '.') {
			dotcount++;
		}
		if ((numi < 0) && isdigit(subtok[i])) {
			numi = i;
		}
	}
	loc = subtok.find("%");
	int numerator = 1;
	int denominator = 1;
	HumNum output;
	if (loc != string::npos) {
		// reciprocal rhythm
		numerator = 1;
		denominator = subtok[numi++] - '0';
		while ((numi<(int)subtok.size()) && isdigit(subtok[numi])) {
			denominator = denominator * 10 + (subtok[numi++] - '0');
		}
		if ((loc + 1 < subtok.size()) && isdigit(subtok[loc+1])) {
			int xi = (int)loc + 1;
			numerator = subtok[xi++] - '0';
			while ((xi<(int)subtok.size()) && isdigit(subtok[xi])) {
				numerator = numerator * 10 + (subtok[xi++] - '0');
			}
		}
		output.setValue(numerator, denominator);
	} else if (numi < 0) {
		// no rhythm found
		HumNum zero(0);
		return zero;
	} else if (subtok[numi] == '0') {
		// 0-symbol
		int zerocount = 1;
		for (i=numi+1; i<(int)subtok.size(); i++) {
			if (subtok[i] == '0') {
				zerocount++;
			} else {
				break;
			}
		}
		numerator = (int)pow(2, zerocount);
		output.setValue(numerator, 1);
	} else {
		// plain rhythm
		denominator = subtok[numi++] - '0';
		while ((numi<(int)subtok.size()) && isdigit(subtok[numi])) {
			denominator = denominator * 10 + (subtok[numi++] - '0');
		}
		output.setValue(1, denominator);
	}

	if (dotcount <= 0) {
		return output * scale;
	}

	int bot = (int)pow(2.0, dotcount);
	int top = (int)pow(2.0, dotcount + 1) - 1;
	HumNum factor(top, bot);
	return output * factor * scale;
}



//////////////////////////////
//
// Convert::recipToDurationIgnoreGrace -- Similar to recipToDuration(), but
//     do not set grace notes to a zero duration, but rather give their
//     visual duration.
// default value: scale = 4 (duration in terms of quarter notes)
// default value: separator = " " (sub-token separator)
//

HumNum Convert::recipToDurationIgnoreGrace(string* recip, HumNum scale,
		const string& separator) {
	return Convert::recipToDurationIgnoreGrace(*recip, scale, separator);
}


HumNum Convert::recipToDurationIgnoreGrace(const string& recip, HumNum scale,
		const string& separator) {
	size_t loc;
	loc = recip.find(separator);
	string subtok;
	if (loc != string::npos) {
		subtok = recip.substr(0, loc);
	} else {
		subtok = recip;
	}

	int dotcount = 0;
	int i;
	int numi = -1;
	for (i=0; i<(int)subtok.size(); i++) {
		if (subtok[i] == '.') {
			dotcount++;
		}
		if ((numi < 0) && isdigit(subtok[i])) {
			numi = i;
		}
	}
	loc = subtok.find("%");
	int numerator = 1;
	int denominator = 1;
	HumNum output;
	if (loc != string::npos) {
		// reciprocal rhythm
		numerator = 1;
		denominator = subtok[numi++] - '0';
		while ((numi<(int)subtok.size()) && isdigit(subtok[numi])) {
			denominator = denominator * 10 + (subtok[numi++] - '0');
		}
		if ((loc + 1 < subtok.size()) && isdigit(subtok[loc+1])) {
			int xi = (int)loc + 1;
			numerator = subtok[xi++] - '0';
			while ((xi<(int)subtok.size()) && isdigit(subtok[xi])) {
				numerator = numerator * 10 + (subtok[xi++] - '0');
			}
		}
		output.setValue(numerator, denominator);
	} else if (numi < 0) {
		// no rhythm found
		HumNum zero(0);
		return zero;
	} else if (subtok[numi] == '0') {
		// 0-symbol
		int zerocount = 1;
		for (i=numi+1; i<(int)subtok.size(); i++) {
			if (subtok[i] == '0') {
				zerocount++;
			} else {
				break;
			}
		}
		numerator = (int)pow(2, zerocount);
		output.setValue(numerator, 1);
	} else {
		// plain rhythm
		denominator = subtok[numi++] - '0';
		while ((numi<(int)subtok.size()) && isdigit(subtok[numi])) {
			denominator = denominator * 10 + (subtok[numi++] - '0');
		}
		output.setValue(1, denominator);
	}

	if (dotcount <= 0) {
		return output * scale;
	}

	int bot = (int)pow(2.0, dotcount);
	int top = (int)pow(2.0, dotcount + 1) - 1;
	HumNum factor(top, bot);
	return output * factor * scale;
}



//////////////////////////////
//
// Convert::recipToDurationNoDots -- Same as recipToDuration(), but ignore
//   any augmentation dots.
//

HumNum Convert::recipToDurationNoDots(string* recip, HumNum scale,
		const string& separator) {
	return Convert::recipToDurationNoDots(*recip, scale, separator);
}


HumNum Convert::recipToDurationNoDots(const string& recip, HumNum scale,
		const string& separator) {
	string temp = recip;
	std::replace(temp.begin(), temp.end(), '.', 'Z');
	return Convert::recipToDuration(temp, scale, separator);
}


//////////////////////////////
//
// Convert::durationToRecip -- Duration input is in units of quarter notes,
//     since the default value for scale is 1/4.
//

string Convert::durationToRecip(HumNum duration, HumNum scale) {
	duration *= scale;
	if (duration.getNumerator() == 1) {
		// simple rhythm (integer divisions of the whole note)
		return to_string(duration.getDenominator());
	}
	if (duration.getDenominator() == 1) {
		if (duration.getNumerator() == 2) {
			return "0";  // breve
		} else if (duration.getNumerator() == 3) {
			return "0."; // dotted breve
		} else if (duration.getNumerator() == 4) {
			return "00";  // long
		} else if (duration.getNumerator() == 6) {
			return "00."; // dotted long
		} else if (duration.getNumerator() == 8) {
			return "000";  // maxima
		} else if (duration.getNumerator() == 12) {
			return "000."; // dotted maxima
		}
	}
	if (duration.getNumerator() == 0) {
		// grace note
		return "q";
	}

	// now decide if the rhythm can be represented simply with one dot.
	HumNum test1dot = (duration * 2) / 3;
	if (test1dot.getNumerator() == 1) {
		// single dot works
		// string output = to_string(test1dot.getDenominator() * 2);
		string output = to_string(test1dot.getDenominator());
		output += ".";
		return output;
	}

	// now decide if the rhythm can be represented simply with two dots.
	HumNum test2dot = (duration * 4) / 7;
	if (test2dot.getNumerator() == 1) {
		// double dot works
		string output = to_string(test2dot.getDenominator());
		output += "..";
		return output;
	}

	// now decide if the rhythm can be represented simply with three dots.
	HumNum test3dot = (duration * 8) / 15;
	if (test3dot.getNumerator() == 1) {
		// single dot works
		string output = to_string(test3dot.getDenominator());
		output += "...";
		return output;
	}

	// duration required more than three dots or is not simple,
	// so assume that it is not simple:
	string output = to_string(duration.getDenominator());
	output += "%";
	output += to_string(duration.getNumerator());
	return output;
}



//////////////////////////////
//
// Convert::durationFloatToRecip -- not allowed to have more than
//	three rhythmic dots
//	default value: timebase = 1;
//

string Convert::durationFloatToRecip(double input, HumNum timebase) {
	string output;

   double testinput = input;
   double basic = 4.0 / input * timebase.getFloat();
   double diff = basic - (int)basic;

   if (diff > 0.998) {
      diff = 1.0 - diff;
      basic += diff;
   }

	// do power of two checks instead
   if (input == 0.0625)  { output = "64"; return output; }
   if (input == 0.125)   { output = "32"; return output; }
   if (input == 0.25)    { output = "16"; return output; }
   if (input == 0.5)  { output = "8";    return output; }
   if (input == 1.0)  { output = "4";    return output; }
   if (input == 2.0)  { output = "2";    return output; }
   if (input == 4.0)  { output = "1";    return output; }
   if (input == 8.0)  { output = "0";    return output; }
   if (input == 12.0) { output = "0.";   return output; }
   if (input == 16.0) { output = "00";   return output; }
   if (input == 24.0) { output = "00.";  return output; }
   if (input == 32.0) { output = "000";  return output; }
   if (input == 48.0) { output = "000."; return output; }

   // special case for triplet whole notes:
   if (fabs(input - (4.0 * 2.0 / 3.0)) < 0.0001) {
		return "3%2";
   }

   // special case for triplet breve notes:
   if (fabs(input - (4.0 * 4.0 / 3.0)) < 0.0001) {
		return "3%4";
   }

   // special case for 9/8 full rests
   if (fabs(input - (4.0 * 9.0 / 8.0)) < 0.0001) {
		return "8%9";
   }

   // special case for 9/2 full-measure rest
   if (fabs(input - 18.0) < 0.0001) {
		return "2%9";
   }

   // handle special rounding cases primarily for SCORE which
   // only stores 4 digits for a duration
   if (input == 0.0833) {
      // triplet 32nd note, which has a real duration of 0.0833333 etc.
		return "48";
   }

   if (diff < 0.002) {
		output += to_string((int)basic);
   } else {
      testinput = input / 3.0 * 2.0;
      basic = 4.0 / testinput;
      diff = basic - (int)basic;
      if (diff < 0.002) {
			output += to_string((int)basic);
			output += ".";
      } else {
         testinput = input / 7.0 * 4.0;
         basic = 4.0 / testinput;
         diff = basic - (int)basic;
         if (diff < 0.002) {
				output += to_string((int)basic);
            output += "..";
         } else {
            testinput = input / 15.0 * 4.0;
            basic = 2.0 / testinput;
            diff = basic - (int)basic;
            if (diff < 0.002) {
					output += to_string((int)basic);
               output += "...";
            } else {
					// Don't know what it could be so echo as a grace note.
					output += "q";
					output += to_string(input);
            }
         }
      }
   }

   return output;
}



//////////////////////////////
//
// Convert::timeSigToDurationInQuarters -- Convert a **kern time signature
//   into the duration of the measure for that time signature.
//   output units are in quarter notes.
//   Example: 6/8 => 3 quarters
//   Example: 3/4 => 3 quarters
//   Example: 3/8 => 3/2 quarters
//

HumNum Convert::timeSigToDurationInQuarter(HTp token) {
	HumRegex hre;
	if (!token->isTimeSignature()) {
		return 0;
	}
	// Handle extended **recip for denominator later...
	if (!hre.search(token, "^\\*M(\\d+)/(\\d+)")) {
		return 0;
	}
	int top = hre.getMatchInt(1);
	int bot = hre.getMatchInt(2);
	HumNum output = 4;
	output /= bot;
	output *= top;
	return output;
}





//////////////////////////////
//
// Convert::replaceOccurrences -- Similar to s// regular expressions
//    operator.  This function replaces the search string in the source
//    string with the replace string.
//

void Convert::replaceOccurrences(string& source, const string& search,
		const string& replace) {
	for (int loc=0; ; loc += (int)replace.size()) {
		loc = (int)source.find(search, loc);
		if (loc == (int)string::npos) {
			break;
		}
		source.erase(loc, search.length());
		source.insert(loc, replace);
	}
}



//////////////////////////////
//
// Convert::splitString -- Splits a string into a list of strings
//   separated by the given character.  Empty strings will be generated
//   if the separator occurs at the start/end of the input string, and
//   if two or more separates are adjacent to each other.
// default value: separator = ' ';
//

vector<string> Convert::splitString(const string& data, char separator) {
	stringstream ss(data);
	string key;
	vector<string> output;
	while (getline(ss, key, separator)) {
		output.push_back(key);
	}
	if (output.size() == 0) {
		output.push_back(data);
	}
	return output;
}



//////////////////////////////
//
// Convert::repeatString -- Returns a string which repeats the given
//   pattern by the given count.
//

string Convert::repeatString(const string& pattern, int count) {
	string output;
	for (int i=0; i<count; i++) {
		output += pattern;
	}
	return output;
}


//////////////////////////////
//
// Convert::encodeXml -- Encode a string for XML printing.  Ampersands
//    get converted to &amp;, < to &lt; > to &gt;, " to &quot; and
//    ' to &apos;.
//

string Convert::encodeXml(const string& input) {
	string output;
	output.reserve(input.size()*2);
	for (int i=0; i<(int)input.size(); i++) {
		switch (input[i]) {
			case '&':  output += "&amp;";   break;
			case '<':  output += "&lt;";    break;
			case '>':  output += "&gt;";    break;
			case '"':  output += "&quot;";  break;
			case '\'': output += "&apos;";  break;
			default:   output += input[i];
		}
	}
	return output;
}



//////////////////////////////
//
// Convert::getHumNumAttributes -- Returns XML attributes for a HumNum
//   number.  First @float which gives the floating-point representation.
//   If the number has a fractional part, then also add @ratfrac with the
//   fractional representation of the non-integer portion number.
//

string Convert::getHumNumAttributes(const HumNum& num) {
	string output;
	if (num.isInteger()) {
		output += " float=\"" + to_string(num.getNumerator()) + "\"";
	} else {
		stringstream sstr;
		sstr << num.toFloat();
		output += " float=\"" + sstr.str() + "\"";
	}
	if (!num.isInteger()) {
		HumNum rem = num.getRemainder();
		output += " ratfrac=\"" + to_string(rem.getNumerator()) +
				+ "/" + to_string(rem.getDenominator()) + "\"";
	}
	return output;
}



//////////////////////////////
//
// Convert::trimWhiteSpace -- remove spaces, tabs and/or newlines
//     from the beginning and end of input string.
//

string Convert::trimWhiteSpace(const string& input) {
	string s = input;
	s.erase(s.begin(), std::find_if(s.begin(), s.end(),
			[](int c) {return !std::isspace(c);}));
	s.erase(std::find_if(s.rbegin(), s.rend(),
			[](int c) {return !std::isspace(c);}).base(), s.end());
	return s;
}



//////////////////////////////
//
// Convert::startsWith --
//

bool Convert::startsWith(const string& input, const string& searchstring) {
	return input.compare(0, searchstring.size(), searchstring) == 0;
}


/////////////////////////////
//
// Convert::contains -- Returns true if the character or string
//    is found in the string.
//

bool Convert::contains(const string& input, const string& pattern) {
	return input.find(pattern) != string::npos;
}

bool Convert::contains(const string& input, char pattern) {
	return input.find(pattern) != string::npos;
}

bool Convert::contains(string* input, const string& pattern) {
	return Convert::contains(*input, pattern);
}

bool Convert::contains(string* input, char pattern) {
	return Convert::contains(*input, pattern);
}


//////////////////////////////
//
// Convert::makeBooleanTrackList -- Given a string
//   such as "1,2,3" and a max track of 5, then
//   create a vector with contents:
//      0:false, 1:true, 2:true, 3:true, 4:false, 5:false.
//   The 0 track is not used, and the two tracks not specified
//   in the string are set to false.  Special abbreviations:
//     $ = maxtrack
//     $1 = maxtrack - 1
//     $2 = maxtrack - 2
//     etc.
//   Ranges can be given, such as 1-3 instead of 1,2,3
//

void Convert::makeBooleanTrackList(vector<bool>& spinelist,
		 const string& spinestring, int maxtrack) {
   spinelist.resize(maxtrack+1);

	if (spinestring.size() == 0) {
		fill(spinelist.begin()+1, spinelist.end(), true);
		return;
	}
	fill(spinelist.begin(), spinelist.end(), false);

   string buffer = spinestring;;
	vector<string> entries;
	string separator = "[^\\d\\$-]+";
   HumRegex hre;

	// create an initial list of values:
	hre.split(entries, buffer, separator);

	// Now process each token in the extracted list:
	int val = -1;
	int val2 = -1;
	bool range = false;
	string tbuff;
	for (int i=0; i<(int)entries.size(); i++) {

		if (hre.search(entries[i], "\\$(\\d*)")) {
			if (hre.getMatch(1).size() == 0) {
				tbuff = to_string(maxtrack);
			} else {
				val = hre.getMatchInt(1);
				tbuff = to_string(maxtrack - val);
			}
			hre.replaceDestructive(entries[i], tbuff, "\\$\\d*");
		}

		range = false;
		if (entries[i].find('-') != string::npos) {
			range = true;
			// check for second $ abbreviation at end of range:
			if (hre.search(entries[i], "\\$(\\d*)")) {
				if (hre.getMatch(1).size() == 0) {
					tbuff = to_string(maxtrack);
				} else {
					val = hre.getMatchInt(1);
					tbuff = to_string(maxtrack - val);
				}
				hre.replaceDestructive(entries[i], tbuff, "\\$\\d*");
			}
			if (entries[i].back() == '$') {
				entries[i].pop_back();
				entries[i] += to_string(maxtrack);
			}
			// extract second value
			if (hre.search(entries[i], "-(\\d+)")) {
				val2 = hre.getMatchInt(1);
			} else {
				range = false;
			}
		}


		// get first value:
		if (hre.search(entries[i], "(\\d+)")) {
			val = stoi(hre.getMatch(1));
		}
		if (range) {
			int direction = 1;
			if (val > val2) {
				direction = -1;
			}
			for (int j=val; j != val2; j += direction) {
				if ((j > 0) && (j < maxtrack + 1)) {
					spinelist[j] = true;
				}
			}
			if ((val2 > 0) && (val2 < maxtrack + 1)) {
				spinelist[val2] = true;
			}
		} else {
			// not a range
			if ((val > 0) && (val < maxtrack+1)) {
				spinelist[val] = true;
			}
		}
	}
}



//////////////////////////////
//
// Convert::extractIntegerList -- Convert a list such as 1-4 into the vector 1,2,3,4.
//   $ (or %) can be used to represent the maximum value, so if the input
//   is 1-$ (or 1-%), and the maximum should be 5, then the output will be a
//   vector 1,2,3,4,5.  In addition commas can be used to generate non-consecutive
//   sequences, and adding a number after the $/% sign means to subtract that
//   value from the maximum.  So if the string is 1,$-$2 and the maximum is 5,
//   then the vector will be 1,5,4,3.  Notice that ranges can be reversed to
//   place the sequence in reverse order, such as $-1 with a maximum of 5 will
//   result in the vector 5,4,3,2,1.  This function does not expect negative
//   values.
//

std::vector<int> Convert::extractIntegerList(const std::string& input, int maximum) {
	std::vector<int> output;
	if (maximum < 0) {
		maximum = 0;
	}
	if (maximum < 1000) {
		output.reserve(maximum);
	} else {
		output.reserve(1000);
	}
	HumRegex hre;
	string buffer = input;
	hre.replaceDestructive(buffer, "", "\\s", "gs");
	int start = 0;
	string tempstr;
	vector<int> tempdata;
	while (hre.search(buffer,  start, "^([^,]+,?)")) {
		tempdata.clear();
		processSegmentEntry(tempdata, hre.getMatch(1), maximum);
		start += hre.getMatchEndIndex(1);
		output.insert(output.end(), tempdata.begin(), tempdata.end());
	}
	return output;
}



//////////////////////////////
//
// Convert::processSegmentEntry --
//   3-6 expands to 3 4 5 6
//   $   expands to maximum file number
//   $-1 expands to maximum file number minus 1, etc.
//

void Convert::processSegmentEntry(vector<int>& field,
		const string& astring, int maximum) {

	HumRegex hre;
	string buffer = astring;

	// remove any comma left at end of input astring (or anywhere else)
	hre.replaceDestructive(buffer, "", ",", "g");

	// first remove $ symbols and replace with the correct values
	removeDollarsFromString(buffer, maximum);

	if (hre.search(buffer, "^(\\d+)-(\\d+)$")) {
		int firstone = hre.getMatchInt(1);
		int lastone  = hre.getMatchInt(2);

		if ((firstone < 1) && (firstone != 0)) {
			cerr << "Error: range token: \"" << astring << "\""
				  << " contains too small a number at start: " << firstone << endl;
			cerr << "Minimum number allowed is " << 1 << endl;
			return;
		}
		if ((lastone < 1) && (lastone != 0)) {
			cerr << "Error: range token: \"" << astring << "\""
				  << " contains too small a number at end: " << lastone << endl;
			cerr << "Minimum number allowed is " << 1 << endl;
			return;
		}
		if (firstone > maximum) {
			cerr << "Error: range token: \"" << astring << "\""
				  << " contains number too large at start: " << firstone << endl;
			cerr << "Maximum number allowed is " << maximum << endl;
			return;
		}
		if (lastone > maximum) {
			cerr << "Error: range token: \"" << astring << "\""
				  << " contains number too large at end: " << lastone << endl;
			cerr << "Maximum number allowed is " << maximum << endl;
			return;
		}

		if (firstone > lastone) {
			for (int i=firstone; i>=lastone; i--) {
				field.push_back(i);
			}
		} else {
			for (int i=firstone; i<=lastone; i++) {
				field.push_back(i);
			}
		}
	} else if (hre.search(buffer, "^(\\d+)")) {
		int value = hre.getMatchInt(1);
		if ((value < 1) && (value != 0)) {
			cerr << "Error: range token: \"" << astring << "\""
				  << " contains too small a number at end: " << value << endl;
			cerr << "Minimum number allowed is " << 1 << endl;
			return;
		}
		if (value > maximum) {
			cerr << "Error: range token: \"" << astring << "\""
				  << " contains number too large at start: " << value << endl;
			cerr << "Maximum number allowed is " << maximum << endl;
			return;
		}
		field.push_back(value);
	}
}



//////////////////////////////
//
// Convert::removeDollarsFromString -- substitute $ sign for maximum file count.
//

void Convert::removeDollarsFromString(string& buffer, int maximum) {
	HumRegex hre;
	string buf2 = to_string(maximum);
	if (hre.search(buffer, "[%$]$")) {
		hre.replaceDestructive(buffer, buf2, "[$%]$");
	} else if (hre.search(buffer, "[%$](?![\\d-])")) {
		// don't know how this case could happen, however...
		hre.replaceDestructive(buffer, buf2, "[%$](?![\\d-])", "g");
	} else if (hre.search(buffer, "[%$]$0")) {
		// replace $0 with maximum (used for reverse orderings)
		hre.replaceDestructive(buffer, buf2, "[%$]0", "g");
	} else if (hre.search(buffer, "^[%$]-")) {
		// replace $ with maximum at start of string
		hre.replaceDestructive(buffer, buf2, "^[%$]", "");
	}

	while (hre.search(buffer, "[%$](\\d+)")) {
		int value2 = maximum - abs(hre.getMatchInt(1));
		buf2 = to_string(value2);
		hre.replaceDestructive(buffer, buf2, "[%$]\\d+");
	}
}



//////////////////////////////
//
// Convert::generateRandomId -- using characters 0-9, A-Z, a-z with the
//     given length.
//

string Convert::generateRandomId(int length) {
    const string characters = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
    std::random_device rd;  // Non-deterministic generator
    std::mt19937 gen(rd()); // Seed the generator
    std::uniform_int_distribution<> distr(0, characters.size() - 1);
    string randomId;
    std::generate_n(std::back_inserter(randomId), length, [&]() {
        return characters[distr(gen)];
    });
    return randomId;
}






//////////////////////////////
//
// Convert::tempoNameToMm -- Guess what the MM tempo should be given
//    a tempo name.  Returns 0 if no guess is made.
//
// Also add cases where there is a tempo marking, such as [quarter] = 132
// in the input text.
//

int Convert::tempoNameToMm (const string& name, int bot, int top) {
	HumRegex hre;
	int output = -1;

	if      (hre.search(name, "larghissimo"      , "i")) { output = 24;  }
	else if (hre.search(name, "adagissimo"       , "i")) { output = 35;  }
	else if (hre.search(name, "all.*molto"       , "i")) { output = 146; }
	else if (hre.search(name, "all.*vivace"      , "i")) { output = 144; }
	else if (hre.search(name, "all.*moderato"    , "i")) { output = 116; }
	else if (hre.search(name, "all.*fuoco"       , "i")) { output = 138; }
	else if (hre.search(name, "all.*presto"      , "i")) { output = 160; }
	else if (hre.search(name, "grave"            , "i")) { output = 40;  }
	else if (hre.search(name, "largo"            , "i")) { output = 45;  }
	else if (hre.search(name, "lento?"           , "i")) { output = 50;  }
	else if (hre.search(name, "larghetto"        , "i")) { output = 63;  }
	else if (hre.search(name, "adagio"           , "i")) { output = 70;  }
	else if (hre.search(name, "adagietto"        , "i")) { output = 74;  }
	else if (hre.search(name, "andantino"        , "i")) { output = 90;  }
	else if (hre.search(name, "marcia moderato"  , "i")) { output = 85;  }
	else if (hre.search(name, "andante moderato" , "i")) { output = 92;  }
	else if (hre.search(name, "allegretto"       , "i")) { output = 116; }
	else if (hre.search(name, "rasch"            , "i")) { output = 128; }
	else if (hre.search(name, "vivo"             , "i")) { output = 152; }
	else if (hre.search(name, "vif"              , "i")) { output = 152; }
	else if (hre.search(name, "vivace"           , "i")) { output = 164; }
	else if (hre.search(name, "schnell"          , "i")) { output = 164; }
	else if (hre.search(name, "vivacissimo"      , "i")) { output = 172; }
	else if (hre.search(name, "allegrissimo"     , "i")) { output = 176; }
	else if (hre.search(name, "moderato"         , "i")) { output = 108; }
	else if (hre.search(name, "andante"          , "i")) { output = 88;  }
	else if (hre.search(name, "presto"           , "i")) { output = 180; }
	else if (hre.search(name, "allegro"          , "i")) { output = 128; }
	else if (hre.search(name, "prestissimo"      , "i")) { output = 208; }
	else if (hre.search(name, "bewegt"           , "i")) { output = 144; }
	else if (hre.search(name, "all(?!a)"         , "i")) { output = 128; }

	if (output <= 0) {
		return output;
	}

	if (hre.search(name, "ma non troppo", "i") || hre.search(name, "non tanto")) {
		if (output > 100) {
			output = int(output * 0.93 + 0.5);
		} else {
			output = int(output / 0.93 + 0.5);
		}
	}

	if (bot == 2) {
		output = int(output * 1.75 + 0.5);
	} else if (bot == 1) {
		output = int (output * 3.0 + 0.5);
	} else if ((bot == 8) && (top % 3 == 0)) {
		output = int(output * 1.5 + 0.5);
	} else if (bot == 8) {
		output = int(output * 0.75 + 0.5);
	} else if ((bot == 16) && (top % 3 == 0)) {
		output = int(output * 1.5 / 2.0 + 0.5);
	} else if (bot == 16) {
		output = int(output / 2.0 + 0.5);
	} else if ((bot == 32) && (top % 3 == 0)) {
		output = int(output * 1.5 / 4.0 + 0.5);
	} else if (bot == 32) {
		output = int(output / 4.0 + 0.5);
	}

	if ((bot == 2) && (top % 3 == 0)) {
		output = int(output * 1.5 + 0.5);
	}

	return output;
}




//////////////////////////////
//
// GridMeasure::GridMeasure -- Constructor.
//

GridMeasure::GridMeasure(HumGrid* owner) {
	m_owner = owner;
	m_style = MeasureStyle::Plain;
}



//////////////////////////////
//
// GridMeasure::~GridMeasure -- Deconstructor.
//

GridMeasure::~GridMeasure(void) {
	for (auto it = this->begin(); it != this->end(); it++) {
		if (*it) {
			delete *it;
			*it = NULL;
		}
	}
}



//////////////////////////////
//
// GridMeasure::appendGlobalLayout --
//

GridSlice* GridMeasure::appendGlobalLayout(const string& tok, HumNum timestamp) {
	GridSlice* gs = new GridSlice(this, timestamp, SliceType::GlobalLayouts, 1);
	gs->addToken(tok, 0, 0, 0);
	gs->setDuration(0);
	this->push_back(gs);
	return gs;
}



//////////////////////////////
//
// GridSlice::addGraceToken -- Add a grace note token at the given
//   gracenumber grace note line before the data line at the given
//   timestamp.
//

GridSlice* GridMeasure::addGraceToken(const string& tok, HumNum timestamp,
	int part, int staff, int voice, int maxstaff, int gracenumber) {
	if (gracenumber < 1) {
		cerr << "ERROR: gracenumber " << gracenumber << " has to be larger than 0" << endl;
		return NULL;
	}

	GridSlice* gs = NULL;
	// GridSlice* datatarget = NULL;
	auto iterator = this->begin();
	if (this->empty()) {
		// add a new GridSlice to an empty list or at end of list if timestamp
		// is after last entry in list.
		gs = new GridSlice(this, timestamp, SliceType::GraceNotes, maxstaff);
		gs->addToken(tok, part, staff, voice);
		this->push_back(gs);
	} else if (timestamp > this->back()->getTimestamp()) {

		// Grace note needs to be added at the end of a measure:
		auto it2 = this->end();
		it2--;
		int counter = 0;
		while (it2 != this->end()) {
			if ((*it2)->isGraceSlice()) {
				counter++;
				if (counter == gracenumber) {
					// insert grace note into this slice
					(*it2)->addToken(tok, part, staff, voice);
					return *it2;
				}
			} else if ((*it2)->isLayoutSlice()) {
				// skip over any layout paramter lines.
				it2--;
				continue;
			} else if ((*it2)->isDataSlice()) {
				// insert grace note after this note
				gs = new GridSlice(this, timestamp, SliceType::GraceNotes, maxstaff);
				gs->addToken(tok, part, staff, voice);
				it2++;
				this->insert(it2, gs);
				return gs;
			}
			it2--;
		}
		return NULL;

	} else {
		// search for existing line with same timestamp on a data slice:

		while (iterator != this->end()) {
			if (timestamp < (*iterator)->getTimestamp()) {
				cerr << "STRANGE CASE 2 IN GRIDMEASURE::ADDGRACETOKEN" << endl;
				cerr << "\tGRACE TIMESTAMP: " << timestamp << endl;
				cerr << "\tTEST  TIMESTAMP: " << (*iterator)->getTimestamp() << endl;
				return NULL;
			}
			if ((*iterator)->isDataSlice()) {
				if ((*iterator)->getTimestamp() == timestamp) {
					// found dataslice just before graceslice(s)
					// datatarget = *iterator;
					break;
				}
			}
			iterator++;
		}

		auto it2 = iterator;
		it2--;
		int counter = 0;
		while (it2 != this->end()) {
			if ((*it2)->isGraceSlice()) {
				counter++;
				if (counter == gracenumber) {
					// insert grace note into this slice
					(*it2)->addToken(tok, part, staff, voice);
					return *it2;
				}
			} else if ((*it2)->isLayoutSlice()) {
				// skip over any layout paramter lines.
				it2--;
				continue;
			} else if ((*it2)->isDataSlice()) {
				// insert grace note after this note
				gs = new GridSlice(this, timestamp, SliceType::GraceNotes, maxstaff);
				gs->addToken(tok, part, staff, voice);
				it2++;
				this->insert(it2, gs);
				return gs;
			}
			it2--;
		}

		// grace note should be added at start of measure
		gs = new GridSlice(this, timestamp, SliceType::GraceNotes, maxstaff);
		gs->addToken(tok, part, staff, voice);
		this->insert(this->begin(), gs);

	}

	return NULL;
}



//////////////////////////////
//
// GridMeasure::addDataToken -- Add a data token in the data slice at the given
//    timestamp (or create a new data slice at that timestamp), placing the
//    token at the specified part, staff, and voice index.
//

GridSlice* GridMeasure::addDataToken(const string& tok, HumNum timestamp,
		int part, int staff, int voice, int maxstaff) {
	GridSlice* gs = NULL;
	if (this->empty() || (this->back()->getTimestamp() < timestamp)) {
		// add a new GridSlice to an empty list or at end of list if timestamp
		// is after last entry in list.
		gs = new GridSlice(this, timestamp, SliceType::Notes, maxstaff);
		gs->addToken(tok, part, staff, voice);
		this->push_back(gs);
	} else {
		// search for existing line with same timestamp and the same slice type
		GridSlice* target = NULL;
		auto iterator = this->begin();
		while (iterator != this->end()) {
			if ((timestamp == (*iterator)->getTimestamp()) && ((*iterator)->isGraceSlice())) {
				iterator++;
				continue;
			}
			if ((timestamp == (*iterator)->getTimestamp()) && ((*iterator)->isMeasureSlice())) {
				iterator++;
				continue;
			}
			if ((!(*iterator)->isDataSlice()) && (timestamp >= (*iterator)->getTimestamp())) {
				iterator++;
				continue;
			} else if ((*iterator)->getTimestamp() == timestamp) {
				target = *iterator;
				target->addToken(tok, part, staff, voice);
				gs = target;
				break;
			} else if (timestamp < (*iterator)->getTimestamp()) {
				gs = new GridSlice(this, timestamp, SliceType::Notes, maxstaff);
				gs->addToken(tok, part, staff, voice);
				this->insert(iterator, gs);
				break;
			}
			iterator++;
		}

		if (iterator == this->end()) {
			// Couldn't find a place for the lef, so place at end of measure.
			gs = new GridSlice(this, timestamp, SliceType::Notes, maxstaff);
			gs->addToken(tok, part, staff, voice);
			this->insert(iterator, gs);
		}
	}

	return gs;
}



//////////////////////////////
//
// GridMeasure::addTempoToken -- Add a tempo token in the data slice at
//    the given timestamp (or create a new tempo slice at that timestamp), placing the
//    token at the specified part, staff, and voice index.
//

GridSlice* GridMeasure::addTempoToken(const string& tok, HumNum timestamp,
		int part, int staff, int voice, int maxstaff) {
	GridSlice* gs = NULL;
	if (this->empty() || (this->back()->getTimestamp() < timestamp)) {
		// add a new GridSlice to an empty list or at end of list if timestamp
		// is after last entry in list.
		gs = new GridSlice(this, timestamp, SliceType::Tempos, maxstaff);
		gs->addToken(tok, part, staff, voice);
		this->push_back(gs);
	} else {
		// search for existing line with same timestamp and the same slice type
		GridSlice* target = NULL;
		auto iterator = this->begin();
		while (iterator != this->end()) {
			if (((*iterator)->getTimestamp() == timestamp) && (*iterator)->isTempoSlice()) {
				target = *iterator;
				target->addToken(tok, part, staff, voice);
				break;
			} else if (((*iterator)->getTimestamp() == timestamp) && (*iterator)->isDataSlice()) {
				// found the correct timestamp, but no clef slice at the timestamp
				// so add the clef slice before the data slice (eventually keeping
				// track of the order in which the other non-data slices should be placed).
				gs = new GridSlice(this, timestamp, SliceType::Tempos, maxstaff);
				gs->addToken(tok, part, staff, voice);
				this->insert(iterator, gs);
				break;
			} else if ((*iterator)->getTimestamp() > timestamp) {
				gs = new GridSlice(this, timestamp, SliceType::Tempos, maxstaff);
				gs->addToken(tok, part, staff, voice);
				this->insert(iterator, gs);
				break;
			}
			iterator++;
		}

		if (iterator == this->end()) {
			// Couldn't find a place for the key signature, so place at end of measure.
			gs = new GridSlice(this, timestamp, SliceType::Tempos, maxstaff);
			gs->addToken(tok, part, staff, voice);
			this->insert(iterator, gs);
		}

	}
	return gs;
}



//////////////////////////////
//
// GridMeasure::addTempoToken --
//

GridSlice* GridMeasure::addTempoToken(GridSlice* slice, int partindex,
		const string& tempo) {
	auto iter = this->rbegin();
	if (iter == this->rend()) {
		// something strange happened: expecting at least one item in measure.
		return slice;
	}

	auto previous = iter;
	previous++;
	while (previous != this->rend()) {
		if ((*previous)->isLayoutSlice()) {
			GridPart* gp = (*previous)->at(partindex);
			GridStaff* gs = gp->at(0);
			GridVoice* gv;
			if (gs->size() == 0) {
				gv = new GridVoice;
				gs->push_back(gv);
			}
			gv = gs->at(0);
			if (gv) {
				if (gv->getToken() == NULL) {
					// create a token with text
					HTp newtoken = new HumdrumToken(tempo);
					gv->setToken(newtoken);
					return slice;
				} else if (*gv->getToken() == "*") {
					// replace token with text
					HTp newtoken = new HumdrumToken(tempo);
					gv->setToken(newtoken);
					return slice;
				}
			} else {
				previous++;
				continue;
			}
		} else {
			break;
		}
		previous++;
	}

	auto insertpoint = previous.base();
	GridSlice* newslice = new GridSlice(this, (*iter)->getTimestamp(), SliceType::Layouts);
	newslice->initializeBySlice(*iter);
	this->insert(insertpoint, newslice);
	HTp newtoken = new HumdrumToken(tempo);
	if (newslice->at(partindex)->at(0)->size() == 0) {
		GridVoice* v = new GridVoice;
		newslice->at(partindex)->at(0)->push_back(v);
	}
	newslice->at(partindex)->at(0)->at(0)->setToken(newtoken);
	return newslice;
}



//////////////////////////////
//
// GridMeasure::addTimeSigToken -- Add a time signature token in the data slice at
//    the given timestamp (or create a new timesig slice at that timestamp), placing the
//    token at the specified part, staff, and voice index.
//

GridSlice* GridMeasure::addTimeSigToken(const string& tok, HumNum timestamp,
		int part, int staff, int voice, int maxstaff) {
	GridSlice* gs = NULL;
	if (this->empty() || (this->back()->getTimestamp() < timestamp)) {
		// add a new GridSlice to an empty list or at end of list if timestamp
		// is after last entry in list.
		gs = new GridSlice(this, timestamp, SliceType::TimeSigs, maxstaff);
		gs->addToken(tok, part, staff, voice);
		this->push_back(gs);
	} else {
		// search for existing line with same timestamp and the same slice type
		GridSlice* target = NULL;
		auto iterator = this->begin();
		while (iterator != this->end()) {
			if (((*iterator)->getTimestamp() == timestamp) && (*iterator)->isTimeSigSlice()) {
				target = *iterator;
				target->addToken(tok, part, staff, voice);
				break;
			} else if (((*iterator)->getTimestamp() == timestamp) && (*iterator)->isDataSlice()) {
				// found the correct timestamp, but no clef slice at the timestamp
				// so add the clef slice before the data slice (eventually keeping
				// track of the order in which the other non-data slices should be placed).
				gs = new GridSlice(this, timestamp, SliceType::TimeSigs, maxstaff);
				gs->addToken(tok, part, staff, voice);
				this->insert(iterator, gs);
				break;
			} else if ((*iterator)->getTimestamp() > timestamp) {
				gs = new GridSlice(this, timestamp, SliceType::TimeSigs, maxstaff);
				gs->addToken(tok, part, staff, voice);
				this->insert(iterator, gs);
				break;
			}
			iterator++;
		}

		if (iterator == this->end()) {
			// Couldn't find a place for the key signature, so place at end of measure.
			gs = new GridSlice(this, timestamp, SliceType::TimeSigs, maxstaff);
			gs->addToken(tok, part, staff, voice);
			this->insert(iterator, gs);
		}

	}
	return gs;
}



//////////////////////////////
//
// GridMeasure::addMeterSigToken -- Add a meter signature token in the data slice at
//    the given timestamp (or create a new timesig slice at that timestamp), placing the
//    token at the specified part, staff, and voice index.
//
//    To do:
//      The meter signtature should occur immediately after a time signature line.
//

GridSlice* GridMeasure::addMeterSigToken(const string& tok, HumNum timestamp,
		int part, int staff, int voice, int maxstaff) {
	GridSlice* gs = NULL;
	if (this->empty() || (this->back()->getTimestamp() < timestamp)) {
		// add a new GridSlice to an empty list or at end of list if timestamp
		// is after last entry in list.
		gs = new GridSlice(this, timestamp, SliceType::MeterSigs, maxstaff);
		gs->addToken(tok, part, staff, voice);
		this->push_back(gs);
	} else {
		// search for existing line with same timestamp and the same slice type
		GridSlice* target = NULL;
		auto iterator = this->begin();
		while (iterator != this->end()) {
			if (((*iterator)->getTimestamp() == timestamp) && (*iterator)->isMeterSigSlice()) {
				target = *iterator;
				target->addToken(tok, part, staff, voice);
				break;
			} else if (((*iterator)->getTimestamp() == timestamp) && (*iterator)->isDataSlice()) {
				// found the correct timestamp, but no clef slice at the timestamp
				// so add the clef slice before the data slice (eventually keeping
				// track of the order in which the other non-data slices should be placed).
				gs = new GridSlice(this, timestamp, SliceType::MeterSigs, maxstaff);
				gs->addToken(tok, part, staff, voice);
				this->insert(iterator, gs);
				break;
			} else if ((*iterator)->getTimestamp() > timestamp) {
				gs = new GridSlice(this, timestamp, SliceType::MeterSigs, maxstaff);
				gs->addToken(tok, part, staff, voice);
				this->insert(iterator, gs);
				break;
			}
			iterator++;
		}

		if (iterator == this->end()) {
			// Couldn't find a place for the key signature, so place at end of measure.
			gs = new GridSlice(this, timestamp, SliceType::MeterSigs, maxstaff);
			gs->addToken(tok, part, staff, voice);
			this->insert(iterator, gs);
		}

	}
	return gs;
}


//////////////////////////////
//
// GridMeasure::addKeySigToken -- Add a key signature  token in a key sig slice at
//    the given timestamp (or create a new keysig slice at that timestamp), placing the
//    token at the specified part, staff, and voice index.
//

GridSlice* GridMeasure::addKeySigToken(const string& tok, HumNum timestamp,
		int part, int staff, int voice, int maxstaff) {
	GridSlice* gs = NULL;
	if (this->empty() || (this->back()->getTimestamp() < timestamp)) {
		// add a new GridSlice to an empty list or at end of list if timestamp
		// is after last entry in list.
		gs = new GridSlice(this, timestamp, SliceType::KeySigs, maxstaff);
		gs->addToken(tok, part, staff, voice);
		this->push_back(gs);
	} else {
		// search for existing line with same timestamp and the same slice type
		GridSlice* target = NULL;
		auto iterator = this->begin();
		while (iterator != this->end()) {
			if (((*iterator)->getTimestamp() == timestamp) && (*iterator)->isKeySigSlice()) {
				target = *iterator;
				target->addToken(tok, part, staff, voice);
				break;
			} else if (((*iterator)->getTimestamp() == timestamp) && (*iterator)->isDataSlice()) {
				// found the correct timestamp, but no clef slice at the timestamp
				// so add the clef slice before the data slice (eventually keeping
				// track of the order in which the other non-data slices should be placed).
				gs = new GridSlice(this, timestamp, SliceType::KeySigs, maxstaff);
				gs->addToken(tok, part, staff, voice);
				this->insert(iterator, gs);
				break;
			} else if ((*iterator)->getTimestamp() > timestamp) {
				gs = new GridSlice(this, timestamp, SliceType::KeySigs, maxstaff);
				gs->addToken(tok, part, staff, voice);
				this->insert(iterator, gs);
				break;
			}
			iterator++;
		}

		if (iterator == this->end()) {
			// Couldn't find a place for the key signature, so place at end of measure.
			gs = new GridSlice(this, timestamp, SliceType::KeySigs, maxstaff);
			gs->addToken(tok, part, staff, voice);
			this->insert(iterator, gs);
		}

	}
	return gs;
}




//////////////////////////////
//
// GridMeasure::addLabelToken -- Add an instrument label token in a label slice at
//    the given timestamp (or create a new label slice at that timestamp), placing the
//    token at the specified part, staff, and voice index.
//

GridSlice* GridMeasure::addLabelToken(const string& tok, HumNum timestamp,
		int part, int staff, int voice, int maxpart, int maxstaff) {
	GridSlice* gs = NULL;
	if (this->empty() || (this->back()->getTimestamp() < timestamp)) {
		// add a new GridSlice to an empty list or at end of list if timestamp
		// is after last entry in list.
		gs = new GridSlice(this, timestamp, SliceType::Labels, maxpart);
		gs->addToken(tok, part, maxstaff-1, voice);
		this->push_back(gs);
	} else {
		// search for existing line with same timestamp and the same slice type
		GridSlice* target = NULL;
		auto iterator = this->begin();
		while (iterator != this->end()) {
			if (((*iterator)->getTimestamp() == timestamp) && (*iterator)->isLabelSlice()) {
				target = *iterator;
				target->addToken(tok, part, maxstaff-1, voice);
				break;
			}
			iterator++;
		}
		if (iterator == this->end()) {
			// Couldn't find a place for the label abbreviation line, so place at end of measure.
			gs = new GridSlice(this, timestamp, SliceType::Labels, maxpart);
			gs->addToken(tok, part, maxstaff-1, voice);
			this->insert(this->begin(), gs);
		}
	}
	return gs;
}



//////////////////////////////
//
// GridMeasure::addLabelAbbrToken -- Add an instrument label token in a label slice at
//    the given timestamp (or create a new label slice at that timestamp), placing the
//    token at the specified part, staff, and voice index.
//

GridSlice* GridMeasure::addLabelAbbrToken(const string& tok, HumNum timestamp,
		int part, int staff, int voice, int maxpart, int maxstaff) {
	GridSlice* gs = NULL;
	if (this->empty() || (this->back()->getTimestamp() < timestamp)) {
		// add a new GridSlice to an empty list or at end of list if timestamp
		// is after last entry in list.
		gs = new GridSlice(this, timestamp, SliceType::LabelAbbrs, maxpart);
		gs->addToken(tok, part, maxstaff-1, voice);
		this->push_back(gs);
	} else {
		// search for existing line with same timestamp and the same slice type
		GridSlice* target = NULL;
		auto iterator = this->begin();
		while (iterator != this->end()) {
			if (((*iterator)->getTimestamp() == timestamp) && (*iterator)->isLabelAbbrSlice()) {
				target = *iterator;
				target->addToken(tok, part, maxstaff-1, voice);
				break;
			}
			iterator++;
		}
		if (iterator == this->end()) {
			// Couldn't find a place for the label abbreviation line, so place at end of measure.
			gs = new GridSlice(this, timestamp, SliceType::LabelAbbrs, maxpart);
			gs->addToken(tok, part, maxstaff-1, voice);
			this->insert(this->begin(), gs);
		}
	}
	return gs;
}



//////////////////////////////
//
// GridMeasure::addTransposeToken -- Add a transposition token in the data slice at
//    the given timestamp (or create a new transposition slice at that timestamp), placing
//    the token at the specified part, staff, and voice index.
//
//    Note: should placed after clef if present and no other transpose slice at
//    same time.
//

GridSlice* GridMeasure::addTransposeToken(const string& tok, HumNum timestamp,
		int part, int staff, int voice, int maxstaff) {
	GridSlice* gs = NULL;
	if (this->empty() || (this->back()->getTimestamp() < timestamp)) {
		// add a new GridSlice to an empty list or at end of list if timestamp
		// is after last entry in list.
		gs = new GridSlice(this, timestamp, SliceType::Transpositions, maxstaff);
		gs->addToken(tok, part, staff, voice);
		this->push_back(gs);
	} else {
		// search for existing line with same timestamp and the same slice type
		GridSlice* target = NULL;
		auto iterator = this->begin();
		while (iterator != this->end()) {
			if (((*iterator)->getTimestamp() == timestamp) && (*iterator)->isTransposeSlice()) {
				target = *iterator;
				target->addToken(tok, part, staff, voice);
				break;
			} else if (((*iterator)->getTimestamp() == timestamp) && (*iterator)->isDataSlice()) {
				// found the correct timestamp, but no clef slice at the timestamp
				// so add the clef slice before the data slice (eventually keeping
				// track of the order in which the other non-data slices should be placed).
				gs = new GridSlice(this, timestamp, SliceType::Transpositions, maxstaff);
				gs->addToken(tok, part, staff, voice);
				this->insert(iterator, gs);
				break;
			} else if ((*iterator)->getTimestamp() > timestamp) {
				gs = new GridSlice(this, timestamp, SliceType::Transpositions, maxstaff);
				gs->addToken(tok, part, staff, voice);
				this->insert(iterator, gs);
				break;
			}
			iterator++;
		}

		if (iterator == this->end()) {
			// Couldn't find a place for the key signature, so place at end of measure.
			gs = new GridSlice(this, timestamp, SliceType::Transpositions, maxstaff);
			gs->addToken(tok, part, staff, voice);
			this->insert(iterator, gs);
		}

	}
	return gs;
}



//////////////////////////////
//
// GridMeasure::addClefToken -- Add a clef token in the data slice at the given
//    timestamp (or create a new clef slice at that timestamp), placing the
//    token at the specified part, staff, and voice index.
//

GridSlice* GridMeasure::addClefToken(const string& tok, HumNum timestamp,
		int part, int staff, int voice, int maxstaff) {
	GridSlice* gs = NULL;
	if (this->empty() || (this->back()->getTimestamp() < timestamp)) {
		// add a new GridSlice to an empty list or at end of list if timestamp
		// is after last entry in list.
		gs = new GridSlice(this, timestamp, SliceType::Clefs, maxstaff);
		gs->addToken(tok, part, staff, voice);
		this->push_back(gs);
	} else {
		// search for existing line with same timestamp and the same slice type
		GridSlice* target = NULL;
		auto iterator = this->begin();
		while (iterator != this->end()) {
			if (((*iterator)->getTimestamp() == timestamp) && (*iterator)->isClefSlice()) {
				target = *iterator;
				target->addToken(tok, part, staff, voice);
				break;
			} else if (((*iterator)->getTimestamp() == timestamp) && (*iterator)->isDataSlice()) {
				// found the correct timestamp, but no clef slice at the timestamp
				// so add the clef slice before the data slice (eventually keeping
				// track of the order in which the other non-data slices should be placed).
				gs = new GridSlice(this, timestamp, SliceType::Clefs, maxstaff);
				gs->addToken(tok, part, staff, voice);
				this->insert(iterator, gs);
				break;
			} else if ((*iterator)->getTimestamp() > timestamp) {
				gs = new GridSlice(this, timestamp, SliceType::Clefs, maxstaff);
				gs->addToken(tok, part, staff, voice);
				this->insert(iterator, gs);
				break;
			}
			iterator++;
		}

		if (iterator == this->end()) {
			// Couldn't find a place for the key signature, so place at end of measure.
			gs = new GridSlice(this, timestamp, SliceType::Clefs, maxstaff);
			gs->addToken(tok, part, staff, voice);
			this->insert(iterator, gs);
		}
	}

	return gs;
}



//////////////////////////////
//
// GridMeasure::addBarlineToken -- Add a barline token in the data slice at the given
//    timestamp (or create a new barline slice at that timestamp), placing the
//    token at the specified part, staff, and voice index.
//

GridSlice* GridMeasure::addBarlineToken(const string& tok, HumNum timestamp,
		int part, int staff, int voice, int maxstaff) {
	GridSlice* gs = NULL;
	if (this->empty() || (this->back()->getTimestamp() < timestamp)) {
		// add a new GridSlice to an empty list or at end of list if timestamp
		// is after last entry in list.
		gs = new GridSlice(this, timestamp, SliceType::Measures, maxstaff);
		gs->addToken(tok, part, staff, voice);
		this->push_back(gs);
	} else {
		// search for existing line with same timestamp and the same slice type
		GridSlice* target = NULL;
		auto iterator = this->begin();
		while (iterator != this->end()) {
			if (((*iterator)->getTimestamp() == timestamp) && (*iterator)->isMeasureSlice()) {
				target = *iterator;
				target->addToken(tok, part, staff, voice);
				break;
			} else if (((*iterator)->getTimestamp() == timestamp) && (*iterator)->isDataSlice()) {
				// found the correct timestamp, but no barline slice at the timestamp
				// so add the clef slice before the data slice (eventually keeping
				// track of the order in which the other non-data slices should be placed).
				gs = new GridSlice(this, timestamp, SliceType::Measures, maxstaff);
				gs->addToken(tok, part, staff, voice);
				this->insert(iterator, gs);
				break;
			} else if ((*iterator)->getTimestamp() > timestamp) {
				gs = new GridSlice(this, timestamp, SliceType::Measures, maxstaff);
				gs->addToken(tok, part, staff, voice);
				this->insert(iterator, gs);
				break;
			}
			iterator++;
		}

		if (iterator == this->end()) {
			// Couldn't find a place for the key signature, so place at end of measure.
			gs = new GridSlice(this, timestamp, SliceType::Measures, maxstaff);
			gs->addToken(tok, part, staff, voice);
			this->insert(iterator, gs);
		}
	}

	return gs;
}



//////////////////////////////
//
// GridMeasure::setKernBar -- set the token for the barline.
//

void GridMeasure::setKernBar(const string& kernBar) {
	m_kernBar = kernBar;
}



//////////////////////////////
//
// GridMeasure::addFiguredBass --
//
GridSlice* GridMeasure::addFiguredBass(HTp token, HumNum timestamp, int part, int maxstaff) {
	GridSlice* gs = NULL;
	bool processed = false;

	if (this->empty() || (this->back()->getTimestamp() < timestamp)) {
		// add a new GridSlice to an empty list or at end of list if timestamp
		// is after last entry in list.
		gs = new GridSlice(this, timestamp, SliceType::Notes, maxstaff);
		int staff = 0;
		int voice = 0;
		string null = ".";
		gs->addToken(null, part, staff, voice);
		gs->at(part)->setFiguredBass(token);
		this->push_back(gs);
		processed = true;
	} else {
		// search for existing line with same timestamp and the same slice type
		GridSlice* target = NULL;
		auto iterator = this->begin();
		while (iterator != this->end()) {
			if (((*iterator)->getTimestamp() == timestamp) && (*iterator)->isDataSlice()) {
				target = *iterator;
				target->at(part)->setFiguredBass(token);
				processed = true;
				break;
			} else if ((*iterator)->getTimestamp() > timestamp) {
				// Need to add figured bass data where there are note notes,
				// so add an emtpy data line and add the figure bass contnet.
				gs = new GridSlice(this, timestamp, SliceType::Notes, maxstaff);
				int staff = 0;
				int voice = 0;
				string null = ".";
				gs->addToken(null, part, staff, voice);
				gs->at(part)->setFiguredBass(token);
				this->insert(iterator, gs);
				processed = true;
				break;
			}
			iterator++;
		}
	}


	if ((!processed) && (!this->empty()) && (this->back()->getTimestamp() == timestamp)) {
		// This case is related to putting figures on the first note in a measure
		// but the note is not yet there, but the key signature/meter/clef etc. have already
		// been added.
		gs = new GridSlice(this, timestamp, SliceType::Notes, maxstaff);
		int staff = 0;
		int voice = 0;
		string null = ".";
		gs->addToken(null, part, staff, voice);
		gs->at(part)->setFiguredBass(token);
		this->push_back(gs);
		processed = true;
	}

	if (!processed) {
		cerr << "Error: could not insert figured bass: " << token << endl;
	} else {
		HumGrid* hg = getOwner();
		if (hg) {
			hg->setFiguredBassPresent(part);
		}
	}

	return gs;
}



//////////////////////////////
//
// GridMeasure::addFiguredBass --
//
GridSlice* GridMeasure::addFiguredBass(const string& tok, HumNum timestamp, int part, int maxstaff) {
	GridSlice* gs = NULL;
	bool processed = false;

	if (this->empty() || (this->back()->getTimestamp() < timestamp)) {

		// add a new GridSlice to an empty list or at end of list if timestamp
		// is after last entry in list.
		gs = new GridSlice(this, timestamp, SliceType::Notes, maxstaff);
		int staff = 0;
		int voice = 0;
		string null = ".";
		gs->addToken(null, part, staff, voice);
		gs->at(part)->setFiguredBass(tok);
		this->push_back(gs);
		processed = true;
	} else {
		// search for existing line with same timestamp and the same slice type
		GridSlice* target = NULL;
		auto iterator = this->begin();
		while (iterator != this->end()) {
			if (((*iterator)->getTimestamp() == timestamp) && (*iterator)->isDataSlice()) {
				target = *iterator;
				target->at(part)->setFiguredBass(tok);
				processed = true;
				break;
			} else if ((*iterator)->getTimestamp() > timestamp) {
				// Need to add figured bass data where there are note notes,
				// so add an emtpy data line and add the figure bass contnet.
				gs = new GridSlice(this, timestamp, SliceType::Notes, maxstaff);
				int staff = 0;
				int voice = 0;
				string null = ".";
				gs->addToken(null, part, staff, voice);
				gs->at(part)->setFiguredBass(tok);
				this->insert(iterator, gs);
				processed = true;
				break;
			}
			iterator++;
		}
	}

	if ((!processed) && (!this->empty()) && (this->back()->getTimestamp() == timestamp)) {
		// This case is related to putting figures on the first note in a measure
		// but the note is not yet there, but the key signature/meter/clef etc. have already
		// been added.
		gs = new GridSlice(this, timestamp, SliceType::Notes, maxstaff);
		int staff = 0;
		int voice = 0;
		string null = ".";
		gs->addToken(null, part, staff, voice);
		gs->at(part)->setFiguredBass(tok);
		this->push_back(gs);
		processed = true;
	}

	if (!processed) {
		cerr << "Error: could not inser figured bass: " << tok << endl;
	} else {
		HumGrid* hg = getOwner();
		if (hg) {
			hg->setFiguredBassPresent(part);
		}
	}

	return gs;
}



//////////////////////////////
//
// GridMeasure::addGlobalComment -- Add a global comment at the given
//    timestamp (before any data line at the same timestamp).  Suppress
//    adding the comment if it matches to another global comment at the
//    same timestamp with the same text.
//

GridSlice* GridMeasure::addGlobalComment(const string& tok, HumNum timestamp) {
	GridSlice* gs = NULL;
	if (this->empty() || (this->back()->getTimestamp() < timestamp)) {
		// add a new GridSlice to an empty list or at end of list if timestamp
		// is after last entry in list.
		gs = new GridSlice(this, timestamp, SliceType::GlobalComments, 1);
		gs->addToken(tok, 0, 0, 0);
		this->push_back(gs);
	} else {
		// search for existing data line (or any other type)  with same timestamp
		auto iterator = this->begin();
		while (iterator != this->end()) {
			// does it need to be before data slice or any slice?
			// if (((*iterator)->getTimestamp() == timestamp) && (*iterator)->isDataSlice()) {
			if ((*iterator)->getTimestamp() == timestamp) {
				// found the correct timestamp on a data slice, so add the global comment
				// before the data slice.  But don't add if the previous
				// grid slice is a global comment with the same text.
				if ((iterator != this->end()) && (*iterator)->isGlobalComment()) {
					if (tok == (*iterator)->at(0)->at(0)->at(0)->getToken()->getText()) {
						// do not insert duplicate global comment
						gs = *iterator;
						break;
					}
				}
				gs = new GridSlice(this, timestamp, SliceType::GlobalComments, 1);
				gs->addToken(tok, 0, 0, 0);
				this->insert(iterator, gs);
				break;
			} else if ((*iterator)->getTimestamp() > timestamp) {
				gs = new GridSlice(this, timestamp, SliceType::GlobalComments, 1);
				gs->addToken(tok, 0, 0, 0);
				this->insert(iterator, gs);
				break;
			}
			iterator++;
		}
	}
	return gs;
}



//////////////////////////////
//
// GridMeasure::transferTokens --
//    default value: startbarnum = 0
//

bool GridMeasure::transferTokens(HumdrumFile& outfile, bool recip,
		bool addbar, int startbarnum) {

	// If the last data slice duration is zero, then calculate
	// the true duration from the duration of the measure.
	if (this->size() > 0) {
		GridSlice* slice = back();
		if (slice->isMeasureSlice() && (this->size() >= 2)) {
			auto ending = this->end();
			--ending;
			--ending;
			while ((ending != this->begin()) && (!(*ending)->isDataSlice())) {
				--ending;
			}
			slice = *ending;
		} else {
			slice = NULL;
		}
		if ((slice != NULL) && slice->isDataSlice()
				&& (slice->getDuration() == 0)) {
			HumNum mts  = getTimestamp();
			HumNum mdur = getDuration();
			HumNum sts  = slice->getTimestamp();
			HumNum slicedur = (mts + mdur) - sts;
			slice->setDuration(slicedur);
		}
	}

	bool founddata = false;
	bool addedbar = false;

	for (auto it : *this) {
		if (it->isInvalidSlice()) {
			// ignore slices to be removed from output (used for
			// removing redundant clef slices).
			continue;
		}
		if (it->isDataSlice()) {
			founddata = true;
		}
		if (it->isLayoutSlice()) {
			// didn't actually find data, but barline should
			// not cross this line.
			founddata = true;
		}
		if (it->isManipulatorSlice()) {
			// didn't acutally find data, but the barline should
			// be placed before any manipulator (a spine split), since
			// that is more a property of the data than of the header
			// interpretations.
			founddata = true;
		}
		if (founddata && addbar && !addedbar) {
			if (getDuration() == 0) {
				// do nothing
			} else {
				if (startbarnum) {
					appendInitialBarline(outfile, startbarnum);
				} else {
					appendInitialBarline(outfile);
				}
				addedbar = true;
			}
		}
		it->transferTokens(outfile, recip);
	}
	return true;
}



//////////////////////////////
//
// GridMeasure::appendInitialBarline -- The barline will be
//    duplicated to all spines later.
//

void GridMeasure::appendInitialBarline(HumdrumFile& infile, int startbarline) {
	(void)startbarline; // suppress compiler warnings about variable not being used
	if (infile.getLineCount() == 0) {
		// strange case which should never happen.
		return;
	}
	if (getMeasureNumber() > 0) {
		startbarline = getMeasureNumber();
	}
	int fieldcount = infile.back()->getFieldCount();
	HLp line = new HumdrumLine;
	string tstring = "=";
//	if (startbarline) {
//		tstring += to_string(startbarline);
//	} else {
//		tstring += "1";
//	}
	// probably best not to start with an invisible barline since
	// a plain barline would not be shown before the first measure anyway.
	// tstring += "-";
	HTp token;
	for (int i=0; i<fieldcount; i++) {
		token = new HumdrumToken(tstring);
		line->appendToken(token);
	}
	infile.push_back(line);
}



//////////////////////////////
//
// GridMeasure::getOwner --
//

HumGrid* GridMeasure::getOwner(void) {
	return m_owner;
}



//////////////////////////////
//
// GridMeasure::setOwner --
//

void GridMeasure::setOwner(HumGrid* owner) {
	m_owner = owner;
}



//////////////////////////////
//
// GridMeasure::setDuration --
//

void GridMeasure::setDuration(HumNum duration) {
	m_duration = duration;
}



//////////////////////////////
//
// GridMeasure::getDuration --
//

HumNum GridMeasure::getDuration(void) {
	return m_duration;
}



//////////////////////////////
//
// GridMeasure::getTimestamp --
//

HumNum GridMeasure::getTimestamp(void) {
	return m_timestamp;
}



//////////////////////////////
//
// GridMeasure::setTimestamp --
//

void GridMeasure::setTimestamp(HumNum timestamp) {
	m_timestamp = timestamp;
}



//////////////////////////////
//
// GridMeasure::getTimeSigDur --
//

HumNum GridMeasure::getTimeSigDur(void) {
	return m_timesigdur;
}



//////////////////////////////
//
// GridMeasure::setTimeSigDur --
//

void GridMeasure::setTimeSigDur(HumNum duration) {
	m_timesigdur = duration;
}



//////////////////////////////
//
// GridMeasure::addLayoutParameter -- Add a layout line at a particular timestamp
//

void GridMeasure::addLayoutParameter(HumNum timestamp, int partindex,
		int staffindex, const string& locomment) {
	auto iter = this->rbegin();
	if (iter == this->rend()) {
		// no items in measure yet, so add
		cerr << "DEAL WITH THIS LAYOUT COMMAND" << endl;
		return;
	}
	GridPart* part;
	GridStaff* staff;
	GridVoice* voice;

	auto previous = iter;
	previous++;
	while (previous != this->rend()) {
		if ((*previous)->isLayoutSlice()) {
			part = (*previous)->at(partindex);
			staff = part->at(0);
			if (staff->size() == 0) {
				GridVoice* v = new GridVoice;
				staff->push_back(v);
			}
			voice = staff->at(0);
			if (voice) {
				if (voice->getToken() == NULL) {
					// create a token with text
					HTp newtoken = new HumdrumToken(locomment);
					voice->setToken(newtoken);
					return;
				} else if (*voice->getToken() == "!") {
					// replace token with text
					HTp newtoken = new HumdrumToken(locomment);
					voice->setToken(newtoken);
					return;
				}
			} else {
				previous++;
				continue;
			}
		} else {
			break;
		}
		previous++;
	}

	auto insertpoint = previous.base();
	GridSlice* newslice = new GridSlice(this, (*iter)->getTimestamp(), SliceType::Layouts);
	newslice->initializeBySlice(*iter);
	this->insert(insertpoint, newslice);
	HTp newtoken = new HumdrumToken(locomment);
	if (newslice->at(partindex)->at(0)->size() == 0) {
		GridVoice* v = new GridVoice;
		newslice->at(partindex)->at(0)->push_back(v);
	}
	newslice->at(partindex)->at(0)->at(0)->setToken(newtoken);
}



//////////////////////////////
//
// GridMeasure::addLayoutParameter --
//

void GridMeasure::addLayoutParameter(GridSlice* slice, int partindex,
		const string& locomment) {
	auto iter = this->rbegin();
	if (iter == this->rend()) {
		// something strange happened: expecting at least one item in measure.
		return;
	}
	GridPart* part;
	GridStaff* staff;
	GridVoice* voice;

	auto previous = iter;
	previous++;
	while (previous != this->rend()) {
		if ((*previous)->isLayoutSlice()) {
			part = (*previous)->at(partindex);
			staff = part->at(0);
			if (staff->size() == 0) {
				GridVoice* v = new GridVoice;
				staff->push_back(v);
			}
			voice = staff->at(0);
			if (voice) {
				if (voice->getToken() == NULL) {
					// create a token with text
					HTp newtoken = new HumdrumToken(locomment);
					voice->setToken(newtoken);
					return;
				} else if (*voice->getToken() == "!") {
					// replace token with text
					HTp newtoken = new HumdrumToken(locomment);
					voice->setToken(newtoken);
					return;
				}
			} else {
				previous++;
				continue;
			}
		} else {
			break;
		}
		previous++;
	}

	auto insertpoint = previous.base();
	GridSlice* newslice = new GridSlice(this, (*iter)->getTimestamp(), SliceType::Layouts);
	newslice->initializeBySlice(*iter);
	this->insert(insertpoint, newslice);
	HTp newtoken = new HumdrumToken(locomment);
	if (newslice->at(partindex)->at(0)->size() == 0) {
		GridVoice* v = new GridVoice;
		newslice->at(partindex)->at(0)->push_back(v);
	}
	newslice->at(partindex)->at(0)->at(0)->setToken(newtoken);
}



//////////////////////////////
//
// GridMeasure::addInterpretationAfter -- Add an interpretation line after the
//      given slice, (which is probably a note slice).
//

void GridMeasure::addInterpretationAfter(GridSlice* slice, int partindex,
		int staffindex, int voiceindex, const string& interpretation, HumNum timestamp) {
	HumNum targettime = slice->getTimestamp();

	auto iter = this->rbegin();
	if (iter == this->rend()) {
		// something strange happened: expecting at least one item in measure.
		return;
	}
	// GridPart* part;
	// GridStaff* staff;
	// GridVoice* voice;

	auto previous = iter;
	// auto last = previous;
	previous++;
	HumNum ptime;
	HumNum newtargettime;
	if (previous != this->rend()) {
		ptime = (*previous)->getTimestamp();
		newtargettime = ptime;
	} else {
		ptime = targettime;
		newtargettime = targettime;
	}

	if (ptime == targettime) {

		auto nextone = previous;
		nextone--;
		if ((nextone != this->rend()) && ((*nextone)->isInterpretationSlice())) {
			GridPart* gp = (*nextone)->at(partindex);
			GridStaff* gs = gp->at(staffindex);
			// only place in voice=0 for now:
			if (gs->size() == 0) {
				GridVoice* gv = new GridVoice;
				gs->push_back(gv);
			}
			HTp token = gs->at(0)->getToken();
			if (token == NULL) {
				gs->at(0)->setToken(interpretation);
				return;
			} else if (*token == "*") {
				gs->at(0)->setToken(interpretation);
				return;
			}
		}
	}

	if (ptime <= targettime) {
		// Insert slice at end of measure.
		GridSlice* newslice = new GridSlice(this, timestamp, SliceType::_Interpretation);
		newslice->initializeBySlice(slice);
		this->push_back(newslice);
		HTp newtoken = new HumdrumToken(interpretation);
		if (newslice->at(partindex)->at(0)->size() == 0) {
			GridVoice* v = new GridVoice;
			newslice->at(partindex)->at(0)->push_back(v);
		}
		newslice->at(partindex)->at(0)->at(0)->setToken(newtoken);
		return;
	}
}



//////////////////////////////
//
// GridMeasure::addInterpretationBefore -- Add an interpretation line before the
//      given slice, (which is probably a note slice).
//

void GridMeasure::addInterpretationBefore(GridSlice* slice, int partindex, int staffindex, int voiceindex,
		const string& interpretation) {
	auto iter = this->rbegin();
	if (iter == this->rend()) {
		// something strange happened: expecting at least one item in measure.
		return;
	}

	auto previous = iter;
	previous++;

	// Try to insert new token in the current interpretation
	// line if there is a spot that is empty.  This should usually
	// work, but there could be times when the interpretation line
	// has a specific submeaning that will not match the inserted
	// interpretation.
	if ((previous != this->rend()) && (*previous)->isInterpretationSlice()) {
		GridPart* gp = (*previous)->at(partindex);
		GridStaff* gs = gp->at(0);
		GridVoice* gv = NULL;
		if (gs->empty()) {
			gv = new GridVoice;
			gs->push_back(gv);
		}
		// only allowing at index 0 voice for now:
		// And is asuumed now to be non-null.
		gv = gs->at(0);
		HTp token = gv->getToken();
		if (!token) {
			gv->setToken(interpretation);
			return;
		} else if (token->isNull()) {
			gv->setToken(interpretation);
			return;
		}
	}

	// Could not insert interpretation on interpretation line immediatly before
	// the assumed starting data line, so insert a new interpretation slice just before
	// the data slice.
	auto insertpoint = previous.base();
	GridSlice* newslice = new GridSlice(this, (*iter)->getTimestamp(), SliceType::_Interpretation);
	newslice->initializeBySlice(*iter);
	this->insert(insertpoint, newslice);
	HTp newtoken = new HumdrumToken(interpretation);
	if (newslice->at(partindex)->at(0)->size() == 0) {
		GridVoice* v = new GridVoice;
		newslice->at(partindex)->at(0)->push_back(v);
	}
	newslice->at(partindex)->at(0)->at(0)->setToken(newtoken);
}




//////////////////////////////
//
// GridMeasure::addDynamicsLayoutParameters --
//

void GridMeasure::addDynamicsLayoutParameters(GridSlice* slice, int partindex,
		const string& locomment) {
	auto iter = this->rbegin();
	if (iter == this->rend()) {
		// something strange happened: expecting at least one item in measure.
		return;
	}
	GridPart* part;

	while ((iter != this->rend()) && (*iter != slice)) {
		iter++;
	}

	if (*iter != slice) {
		// cannot find owning line.
		return;
	}

	auto previous = iter;
	previous++;
	while (previous != this->rend()) {
		if ((*previous)->isLayoutSlice()) {
			part = (*previous)->at(partindex);
			if ((part->getDynamics() == NULL) || (*part->getDynamics() == "!")) {
				HTp token = new HumdrumToken(locomment);
				part->setDynamics(token);
				return;
			} else {
				previous++;
				continue;
			}
		} else {
			break;
		}
	}

	auto insertpoint = previous.base();
	GridSlice* newslice = new GridSlice(this, (*iter)->getTimestamp(), SliceType::Layouts);
	newslice->initializeBySlice(*iter);
	this->insert(insertpoint, newslice);

	HTp newtoken = new HumdrumToken(locomment);
	newslice->at(partindex)->setDynamics(newtoken);
}



//////////////////////////////
//
// GridMeasure::addFiguredBassLayoutParameters --
//

void GridMeasure::addFiguredBassLayoutParameters(GridSlice* slice, int partindex,
		const string& locomment) {
	auto iter = this->rbegin();
	if (iter == this->rend()) {
		// something strange happened: expecting at least one item in measure.
		return;
	}
	GridPart* part;

	while ((iter != this->rend()) && (*iter != slice)) {
		iter++;
	}

	if (*iter != slice) {
		// cannot find owning line.
		return;
	}

	auto previous = iter;
	previous++;
	while (previous != this->rend()) {
		if ((*previous)->isLayoutSlice()) {
			part = (*previous)->at(partindex);
			if ((part->getFiguredBass() == NULL) || (*part->getFiguredBass() == "!")) {
				HTp token = new HumdrumToken(locomment);
				part->setFiguredBass(token);
				return;
			} else {
				previous++;
				continue;
			}
		} else {
			break;
		}
	}

	auto insertpoint = previous.base();
	GridSlice* newslice = new GridSlice(this, (*iter)->getTimestamp(), SliceType::Layouts);
	newslice->initializeBySlice(*iter);
	this->insert(insertpoint, newslice);

	HTp newtoken = new HumdrumToken(locomment);
	newslice->at(partindex)->setFiguredBass(newtoken);
}



//////////////////////////////
//
// GridMeasure::isMonophonicMeasure --  One part starts with note/rest, the others
//     with invisible rest.
//

bool GridMeasure::isMonophonicMeasure(void) {
	int inviscount = 0;
	int viscount = 0;

	for (auto slice : *this) {
		if (!slice->isDataSlice()) {
			continue;
		}
		for (int p=0; p<(int)slice->size(); p++) {
			GridPart* part = slice->at(p);
			for (int s=0; s<(int)part->size(); s++) {
				GridStaff* staff = part->at(s);
				for (int v=0; v<(int)staff->size(); v++) {
					GridVoice* voice = staff->at(v);
					HTp token = voice->getToken();
					if (!token) {
						return false;
					}
					if (token->find("yy")) {
						inviscount++;
					} else {
						viscount++;
					}
				}
				if (inviscount + viscount) {
					break;
				}
			}
			if (inviscount + viscount) {
				break;
			}
		}
		if (inviscount + viscount) {
			break;
		}
	}
	if ((viscount = 1) && (inviscount > 0)) {
		return true;
	} else {
		return false;
	}
}



//////////////////////////////
//
// GridMeasure::isSingleChordMeasure --
//

bool GridMeasure::isSingleChordMeasure(void) {

	for (auto slice : *this) {
		if (!slice->isDataSlice()) {
			continue;
		}
		for (int p=0; p<(int)slice->size(); p++) {
			GridPart* part = slice->at(p);
			for (int s=0; s<(int)part->size(); s++) {
				GridStaff* staff = part->at(s);
				for (int v=0; v<(int)staff->size(); v++) {
					GridVoice* voice = staff->at(v);
					HTp token = voice->getToken();
					if (!token) {
						return false;
					}
					if (!token->isChord()) {
						return false;
					}
				}
			}
		}
	}
	return true;

}



//////////////////////////////
//
// GridMeasure::isInvisible --  Return true if all contents is invisible.
//

bool GridMeasure::isInvisible(void) {

	for (auto slice : *this) {
		if (!slice->isDataSlice()) {
			continue;
		}
		for (int p=0; p<(int)slice->size(); p++) {
			GridPart* part = slice->at(p);
			for (int s=0; s<(int)part->size(); s++) {
				GridStaff* staff = part->at(s);
				for (int v=0; v<(int)staff->size(); v++) {
					GridVoice* voice = staff->at(v);
					HTp token = voice->getToken();
					if (!token) {
						return false;
					}
					if (token->find("yy") == string::npos) {
						return false;
					}
				}
			}
		}
	}
	return true;

}



//////////////////////////////
//
// GridMeasure::getFirstSpinedSlice --
//

GridSlice* GridMeasure::getFirstSpinedSlice(void) {
	GridSlice* output = NULL;
	for (auto tslice : *this) {
		if (!tslice->hasSpines()) {
			continue;
		}
		output = tslice;
		break;
	}
	return output;
}



//////////////////////////////
//
// GridMeasure::getLastSpinedSlice --
//

GridSlice* GridMeasure::getLastSpinedSlice(void) {
	for (auto rit = this->rbegin(); rit != this->rend(); rit++) {
		GridSlice* slice = *rit;
		if (!slice) {
			continue;
		}
		if (slice->isGlobalLayout()) {
			continue;
		}
		if (slice->isGlobalComment()) {
			continue;
		}
		if (slice->isReferenceRecord()) {
			continue;
		}
		return slice;
	}
	return NULL;
}



//////////////////////////////
//
// GridMeasure::setMeasureNumber --
//

void GridMeasure::setMeasureNumber(int value) {
	m_barnum = value;
}



//////////////////////////////
//
// GridMeasure::getMeasureNumber --
//

int GridMeasure::getMeasureNumber(void) {
	return m_barnum;
}



//////////////////////////////
//
// operator<< --
//

ostream& operator<<(ostream& output, GridMeasure* measure) {
	output << *measure;
	return output;
}

ostream& operator<<(ostream& output, GridMeasure& measure) {
	for (auto item : measure) {
		output << item << endl;
	}
	return output;
}




//////////////////////////////
//
// GridPart::GridPart -- Constructor.
//

GridPart::GridPart(void) : GridSide() {
	// do nothing;
}

//////////////////////////////
//
// GridPart::~GridPart -- Deconstructor: delete any GridStaff items
//     being stored.
//

GridPart::~GridPart(void) {
	for (int i=0; i<(int)this->size(); i++) {
		if (this->at(i)) {
			delete this->at(i);
			this->at(i) = NULL;
		}
	}
}


//////////////////////////////
//
// operator<< -- print the contents of a GridPart data structure --
//

ostream& operator<<(ostream& output, GridPart* part) {
	if (part == NULL) {
		output << "{n}";
		return output;
	}
	for (int s=0; s<(int)part->size(); s++) {
		GridStaff* staff = part->at(s);
		output << "(s" << s << ":)";
		if (staff == NULL) {
			output << "{n}";
			continue;
		}
		for (int t=0; t<(int)staff->size(); t++) {
			GridVoice* gt = staff->at(t);
			output << "(v" << t << ":)";
			if (gt == NULL) {
				output << "{n}";
				continue;
			} else {
				HTp token = gt->getToken();
				if (token == NULL) {
					output << "{n}";
				} else {
					output << " \"" << *token << "\" ";
				}
			}
		}
	}
	output << " ppp " << (GridSide*) part;
	return output;
}


ostream& operator<<(ostream& output, GridPart& part) {
	output << &part;
	return output;
}




//////////////////////////////
//
// GridSide::GridSide -- Constructor.
//

GridSide::GridSide(void) {
	// do nothing
}



//////////////////////////////
//
// GridSide::~GridSide -- Deconstructor.
//

GridSide::~GridSide(void) {

	for (int i=0; i<(int)m_verses.size(); i++) {
		if (m_verses[i]) {
			delete m_verses[i];
			m_verses[i] = NULL;
		}
	}
	m_verses.resize(0);

	if (m_dynamics) {
		delete m_dynamics;
		m_dynamics = NULL;
	}

	if (m_harmony) {
		delete m_harmony;
		m_harmony = NULL;
	}
}



//////////////////////////////
//
// GridSide::setVerse --
//

void GridSide::setVerse(int index, HTp token) {
	if (token == NULL) {
		// null tokens are written in the transfer process when responsibility
		// for deleting the pointer is given to another object (HumdrumFile class).
	}
   if (index == (int)m_verses.size()) {
		// Append to the end of the verse list.
		m_verses.push_back(token);
	} else if (index < (int)m_verses.size()) {
		// Insert in a slot which might already have a verse token
		if ((token != NULL) && (m_verses.at(index) != NULL)) {
			// don't delete a previous non-NULL token if a NULL
			// token is being stored, as it is assumed that the
			// token has been transferred to a HumdrumFile object.
			delete m_verses[index];
		}
		m_verses[index] = token;
	} else {
		// Add more than one verse spot and insert verse:
		int oldsize = (int)m_verses.size();
		int newsize = index + 1;
		m_verses.resize(newsize);
		for (int i=oldsize; i<newsize; i++) {
			m_verses.at(i) = NULL;
		}
		m_verses.at(index) = token;
	}
}


void GridSide::setVerse(int index, const string& token) {
	HTp newtoken = new HumdrumToken(token);
	setVerse(index, newtoken);
}


//////////////////////////////
//
// GridSide::getVerse --
//

HTp GridSide::getVerse(int index) {
	if (index < 0 || index >= getVerseCount()) {
		return NULL;
	}
	return m_verses[index];
}



//////////////////////////////
//
// GridSide::getVerseCount --
//

int GridSide::getVerseCount(void) {
 	return (int)m_verses.size();
}



//////////////////////////////
//
// GridSide::getHarmonyCount --
//

int GridSide::getHarmonyCount(void) {
	if (m_harmony == NULL) {
		return 0;
	} else {
		return 1;
	}
}



//////////////////////////////
//
// GridSide::setHarmony --
//

void GridSide::setHarmony(HTp token) {
	if (m_harmony) {
		delete m_harmony;
		m_harmony = NULL;
	}
	m_harmony = token;
}


void GridSide::setHarmony(const string& token) {
	HTp newtoken = new HumdrumToken(token);
	setHarmony(newtoken);
}



//////////////////////////////
//
// GridSide::getXmlidCount --
//

int GridSide::getXmlidCount(void) {
	if (m_xmlid == NULL) {
		return 0;
	} else {
		return 1;
	}
}



//////////////////////////////
//
// GridSide::setXmlid --
//

void GridSide::setXmlid(HTp token) {
	if (m_xmlid) {
		delete m_xmlid;
		m_xmlid = NULL;
	}
	m_xmlid = token;
}


void GridSide::setXmlid(const string& token) {
	HTp newtoken = new HumdrumToken(token);
	setXmlid(newtoken);
}



//////////////////////////////
//
// GridSide::setDynamics --
//

void GridSide::setDynamics(HTp token) {
	if (m_dynamics) {
		delete m_dynamics;
		m_dynamics = NULL;
	}
	m_dynamics = token;
}


void GridSide::setDynamics(const string& token) {
	HTp newtoken = new HumdrumToken(token);
	setDynamics(newtoken);
}



//////////////////////////////
//
// GridSide::setFiguredBass --
//

void GridSide::setFiguredBass(HTp token) {
	if (m_figured_bass) {
		delete m_figured_bass;
		m_figured_bass = NULL;
	}
	m_figured_bass = token;
}


void GridSide::setFiguredBass(const string& token) {
	HTp newtoken = new HumdrumToken(token);
	setFiguredBass(newtoken);
}



///////////////////////////
//
// GridSide::detachHarmony --
//

void GridSide::detachHarmony(void) {
	m_harmony = NULL;
}



///////////////////////////
//
// GridSide::detachXmlid --
//

void GridSide::detachXmlid(void) {
	m_xmlid = NULL;
}



///////////////////////////
//
// GridSide::detachDynamics --
//

void GridSide::detachDynamics(void) {
	m_dynamics = NULL;
}



///////////////////////////
//
// GridSide::detachFiguredBass --
//

void GridSide::detachFiguredBass(void) {
	m_figured_bass = NULL;
}



//////////////////////////////
//
// GridSide::getHarmony --
//

HTp GridSide::getHarmony(void) {
	return m_harmony;
}



//////////////////////////////
//
// GridSide::getXmlid --
//

HTp GridSide::getXmlid(void) {
	return m_xmlid;
}



//////////////////////////////
//
// GridSide::getDynamics --
//

HTp GridSide::getDynamics(void) {
	return m_dynamics;
}



//////////////////////////////
//
// GridSide::getDynamicsCount --
//

int GridSide::getDynamicsCount(void) {
	if (m_dynamics == NULL) {
		return 0;
	} else {
		return 1;
	}
}



//////////////////////////////
//
// GridSide::getFiguredBass --
//

HTp GridSide::getFiguredBass(void) {
	return m_figured_bass;
}



//////////////////////////////
//
// GridSide::getFiguredBassCount --
//

int GridSide::getFiguredBassCount(void) {
	if (m_figured_bass == NULL) {
		return 0;
	} else {
		return 1;
	}
}



//////////////////////////////
//
// operator<< --
//

ostream& operator<<(ostream& output, GridSide* side) {
	output << " [";

	if (side->getXmlidCount() > 0) {
		output << "xmlid:" << side->getXmlid();
	}

	if (side->getVerseCount() > 0) {
		output << " verse:";
	}
	for (int i=0; i<(int)side->getVerseCount(); i++) {
		output << side->getVerse(i);
		if (i < (int)side->getVerseCount() - 1) {
			output << "; ";
		}

	}

	if (side->getDynamicsCount() > 0) {
		output << "dyn:" << side->getDynamics();
	}

	if (side->getHarmonyCount() > 0) {
		output << "harm:" << side->getHarmony();
	}

	if (side->getXmlidCount() > 0) {
		output << "xmlid:" << side->getXmlid();
	}

	output << "] ";
	return output;
}




//////////////////////////////
//
// GridSlice::GridSlice -- Constructor.  If partcount is positive, then
//    allocate the desired number of parts (still have to allocate staves
//    in part before using).
// default value: partcount = 0
//

GridSlice::GridSlice(GridMeasure* measure, HumNum timestamp, SliceType type,
		int partcount) {
	m_timestamp = timestamp;
	m_type      = type;
	m_owner     = NULL;
	m_measure   = measure;
	if (m_measure) {
		m_owner = measure->getOwner();
		m_measure = measure;
	}
	if (partcount > 0) {
		// create primary part/staff/voice structures
		this->resize(partcount);
		for (int p=0; p<partcount; p++) {
			this->at(p) = new GridPart;
			this->at(p)->resize(1);
			this->at(p)->at(0) = new GridStaff;
			this->at(p)->at(0)->resize(1);
			this->at(p)->at(0)->at(0) = new GridVoice;
		}
	}
}


//
// This constructor allocates the matching part and staff count of the
// input slice parameter.  There will be no GridVoices allocated inside the
// GridStaffs (they will be required to have at least one).
//

GridSlice::GridSlice(GridMeasure* measure, HumNum timestamp, SliceType type,
		const GridSlice& slice) {
	m_timestamp = timestamp;
	m_type = type;
	m_owner = measure->getOwner();
	m_measure = measure;
	int partcount = (int)slice.size();
	int staffcount;
	int voicecount;
	if (partcount > 0) {
		this->resize(partcount);
		for (int p=0; p<partcount; p++) {
			this->at(p) = new GridPart;
			GridPart* part = this->at(p);
			staffcount = (int)slice.at(p)->size();
			part->resize(staffcount);
			for (int s=0; s<staffcount; s++) {
				part->at(s) = new GridStaff;
				// voicecount = (int)slice.at(p)->at(s)->size();
				voicecount = 0;
				GridStaff* staff = part->at(s);
				staff->resize(voicecount);
				for (int v=0; v<voicecount; v++) {
					staff->at(v) = new GridVoice;
				}
			}
		}
	}
}


GridSlice::GridSlice(GridMeasure* measure, HumNum timestamp, SliceType type,
		GridSlice* slice) {
	m_timestamp = timestamp;
	m_type = type;
	m_owner = measure->getOwner();
	m_measure = measure;
	int partcount = (int)slice->size();
	int staffcount;
	int voicecount;
	if (partcount > 0) {
		this->resize(partcount);
		for (int p=0; p<partcount; p++) {
			this->at(p) = new GridPart;
			GridPart* part = this->at(p);
			staffcount = (int)slice->at(p)->size();
			part->resize(staffcount);
			for (int s=0; s<staffcount; s++) {
				part->at(s) = new GridStaff;
				// voicecount = (int)slice->at(p)->at(s)->size();
				voicecount = 0;
				GridStaff* staff = part->at(s);
				staff->resize(voicecount);
				for (int v=0; v<voicecount; v++) {
					staff->at(v) = new GridVoice;
				}
			}
		}
	}
}



//////////////////////////////
//
// GridSlice::~GridSlice -- Deconstructor.
//

GridSlice::~GridSlice(void) {
	for (int i=0; i<(int)this->size(); i++) {
		if (this->at(i)) {
			delete this->at(i);
			this->at(i) = NULL;
		}
	}
}



//////////////////////////////
//
// GridSlice::addToken -- Will not allocate part array, but will
//     grow staff or voice array if needed.
//

void GridSlice::addToken(const string& tok, int parti, int staffi, int voicei) {
	if ((parti < 0) || (parti >= (int)this->size())) {
		cerr << "Error: part index " << parti << " is out of range: size is ";
		cerr << this->size() << endl;
		return;
	}
	if (staffi < 0) {
		cerr << "Error: staff index " << staffi << " is out of range: size is ";
		cerr << this->at(parti)->size() << endl;
		return;
	}

	if (staffi >= (int)this->at(parti)->size()) {
		int ssize = (int)this->at(parti)->size();
		for (int i=ssize; i<=staffi; i++) {
			GridStaff* gs = new GridStaff;
			this->at(parti)->push_back(gs);
		}
	}

	if (voicei >= (int)this->at(parti)->at(staffi)->size()) {
		int oldsize = (int)this->at(parti)->at(staffi)->size();
		this->at(parti)->at(staffi)->resize(voicei+1);
		for (int j=oldsize; j<=voicei; j++) {
			this->at(parti)->at(staffi)->at(j) = new GridVoice;
		}
	}
	this->at(parti)->at(staffi)->at(voicei)->setToken(tok);
}



//////////////////////////////
//
// GridSlice::createRecipTokenFromDuration --  Will not be able to
//   distinguish between triplet notes and dotted normal equivalents,
//   this can be changed later by checking neighboring durations in the
//   list for the presence of triplets.
//

HTp GridSlice::createRecipTokenFromDuration(HumNum duration) {
	duration /= 4;  // convert to quarter note units.
	HTp token;
	string str;
	HumNum dotdur;
	if (duration.getNumerator() == 0) {
		// if the GridSlice is at the end of a measure, the
		// time between the starttime/endtime of the GridSlice should
		// be subtracted from the endtime of the current GridMeasure.
		token = new HumdrumToken("g");
		return token;
	} else if (duration.getNumerator() == 1) {
		token = new HumdrumToken(to_string(duration.getDenominator()));
		return token;
	} else if (duration.getNumerator() % 3 == 0) {
		dotdur = ((duration * 2) / 3);
		if (dotdur.getNumerator() == 1) {
			token = new HumdrumToken(to_string(dotdur.getDenominator()) + ".");
			return token;
		}
	}

	// try to fit to two dots here

	// try to fit to three dots here

	str = to_string(duration.getDenominator()) + "%" +
	         to_string(duration.getNumerator());
	token = new HumdrumToken(str);
	return token;
}



//////////////////////////////
//
// GridSlice::isInterpretationSlice --
//

bool GridSlice::isInterpretationSlice(void) {
	SliceType type = getType();
	if (type < SliceType::_Measure) {
		return false;
	}
	if (type > SliceType::_Interpretation) {
		return false;
	}
	return true;
}



//////////////////////////////
//
// GridSlice::isDataSlice --
//

bool GridSlice::isDataSlice(void) {
	SliceType type = getType();
	if (type <= SliceType::_Data) {
		return true;
	} else {
		return false;
	}
}



//////////////////////////////
//
// GridSlice::transferTokens -- Create a HumdrumLine and append it to
//    the data.
//

void GridSlice::transferTokens(HumdrumFile& outfile, bool recip) {
	HTp token = NULL;
	HLp line = new HumdrumLine;
	GridVoice* voice;
	string empty = ".";
	if (isMeasureSlice()) {
		if (this->size() > 0) {
			if (this->at(0)->at(0)->size() > 0) {
				voice = this->at(0)->at(0)->at(0);
				HTp tok = voice->getToken();
				if (tok != NULL) {
					empty = (string)*tok;
				}
			} else {
				empty = "=YYYYYY";
			}
		}
	} else if (isInterpretationSlice()) {
		empty = "*";
	} else if (isLayoutSlice()) {
		empty = "!";
	} else if (isMeasureSlice()) {
		empty = "=";
	} else if (!hasSpines()) {
		empty = "???";
	}

	if (recip) {
		if (isNoteSlice()) {
			token = createRecipTokenFromDuration(getDuration());
		} else if (isClefSlice()) {
			token = new HumdrumToken("*");
			empty = "*";
		} else if (isMeasureSlice()) {
			if (this->at(0)->at(0)->size() > 0) {
				voice = this->at(0)->at(0)->at(0);
				token = new HumdrumToken((string)*voice->getToken());
			} else {
				token = new HumdrumToken("=XXXXX");
			}
			empty = (string)*token;
		} else if (isInterpretationSlice()) {
			token = new HumdrumToken("*");
			empty = "*";
		} else if (isGraceSlice()) {
			token = new HumdrumToken("q");
			empty = ".";
		} else if (hasSpines()) {
			token = new HumdrumToken("55");
			empty = "!";
		}
		if (token != NULL) {
			if (hasSpines()) {
				line->appendToken(token);
			} else {
				delete token;
				token = NULL;
			}
		}
	}

	// extract the Tokens from each part/staff
	int p; // part index
	int s; // staff index
	int v; // voice index

	for (p=(int)size()-1; p>=0; p--) {
		if ((!hasSpines()) && (p != 0)) {
			continue;
		}
		GridPart& part = *this->at(p);
		for (s=(int)part.size()-1; s>=0; s--) {
			if ((!hasSpines()) && (s != 0)) {
				continue;
			}
			GridStaff& staff = *part.at(s);
			if (staff.size() == 0) {
				// fix this later.  For now if there are no notes
				// on the staff, add a null token.  Fix so that
				// all open voices are given null tokens.
				token = new HumdrumToken(empty);
				line->appendToken(token);
			} else {
				for (v=0; v<(int)staff.size(); v++) {
					if (staff.at(v) && staff.at(v)->getToken()) {
						line->appendToken(staff.at(v)->getToken());
						staff.at(v)->forgetToken();
					} else if (!staff.at(v)) {
						token = new HumdrumToken(empty);
						line->appendToken(token);
					} else {
						token = new HumdrumToken(empty);
						line->appendToken(token);
					}
				}
			}

			if (!this->hasSpines()) {
				// Don't add sides to non-spined lines
				continue;
			}

			int maxxcount = getXmlidCount(p, s);
			int maxvcount = getVerseCount(p, s);
			int maxhcount = getHarmonyCount(p, s);
			int maxfcount = getFiguredBassCount(p, s);
			if (hasSpines()) {
				transferSides(*line, staff, empty, maxxcount, maxvcount, maxhcount, maxfcount);
			}
		}

		// Transfer the sides at the part level
		int maxxcount = getXmlidCount(p);
		int maxhcount = getHarmonyCount(p);
		int maxvcount = getVerseCount(p, -1);
		int maxdcount = getDynamicsCount(p);
		int maxfcount = getFiguredBassCount(p);

		if (hasSpines()) {
			transferSides(*line, part, p, empty, maxxcount, maxvcount, maxhcount, maxdcount, maxfcount);
		}
	}

	outfile.appendLine(line);
}



//////////////////////////////
//
// GridSlice::getMeasureDuration --
//

HumNum GridSlice::getMeasureDuration(void) {
	GridMeasure* measure = getMeasure();
	if (!measure) {
		return -1;
	} else {
		return measure->getDuration();
	}
}



//////////////////////////////
//
// GridSlice::getMeasureTimestamp -- Return the start time of the measure.
//

HumNum GridSlice::getMeasureTimestamp(void) {
	GridMeasure* measure = getMeasure();
	if (!measure) {
		return -1;
	} else {
		return measure->getTimestamp();
	}
}



//////////////////////////////
//
// GridSlice::getVerseCount --
//

int GridSlice::getVerseCount(int partindex, int staffindex) {
	HumGrid* grid = getOwner();
	if (!grid) {
		return 0;
	}
	return grid->getVerseCount(partindex, staffindex);
}



//////////////////////////////
//
// GridSlice::getHarmonyCount --
//    default value: staffindex = -1; (currently not looking for
//        harmony data attached directly to staff (only to part.)
//

int GridSlice::getHarmonyCount(int partindex, int staffindex) {
	HumGrid* grid = getOwner();
	if (!grid) {
		return 0;
	}
	if (staffindex >= 0) {
		// ignoring staff-level harmony
		return 0;
	} else {
		return grid->getHarmonyCount(partindex);
	}
}



//////////////////////////////
//
// GridSlice::getXmlidCount --
//    default value: staffindex = -1; (currently not looking for
//        harmony data attached directly to staff (only to part.)
//

int GridSlice::getXmlidCount(int partindex, int staffindex) {
	HumGrid* grid = getOwner();
	if (!grid) {
		return 0;
	}
	// should probably adjust to staffindex later:
	return grid->getXmlidCount(partindex);
}



//////////////////////////////
//
// GridSlice::getDynamicsCount -- Return 0 if no dynamics, otherwise typically returns 1.
//

int GridSlice::getDynamicsCount(int partindex, int staffindex) {
	HumGrid* grid = getOwner();
	if (!grid) {
		return 0;
	}
	if (staffindex >= 0) {
		// ignoring staff-level harmony
		return 0;
	} else {
		return grid->getDynamicsCount(partindex);
	}
}



//////////////////////////////
//
// GridSlice::getFiguredBassCount -- Return 0 if no figured bass; otherwise,
//     typically returns 1.
//

int GridSlice::getFiguredBassCount(int partindex, int staffindex) {
	HumGrid* grid = getOwner();
	if (!grid) {
		return 0;
	}
	if (staffindex >= 0) {
		// ignoring staff-level figured bass
		return 0;
	} else {
		return grid->getFiguredBassCount(partindex);
	}
}



//////////////////////////////
//
// GridSlice::transferSides --
//

// this version is used to transfer Sides from the Part
void GridSlice::transferSides(HumdrumLine& line, GridPart& sides,
		int partindex, const string& empty, int maxxcount, int maxvcount,
		int maxhcount, int maxdcount, int maxfcount) {

	int xcount = sides.getXmlidCount();
	int hcount = sides.getHarmonyCount();
	int vcount = sides.getVerseCount();

	HTp newtoken;

	if (xcount > 0) {
		HTp xmlid = sides.getXmlid();
		if (xmlid) {
			line.appendToken(xmlid);
			sides.detachXmlid();
		} else {
			newtoken = new HumdrumToken(empty);
			line.appendToken(newtoken);
		}
	}

	for (int i=0; i<vcount; i++) {
		HTp verse = sides.getVerse(i);
		if (verse) {
			line.appendToken(verse);
			sides.detachHarmony();
		} else {
			newtoken = new HumdrumToken(empty);
			line.appendToken(newtoken);
		}
	}

	for (int i=vcount; i<maxvcount; i++) {
		newtoken = new HumdrumToken(empty);
		line.appendToken(newtoken);
	}

	if (maxdcount > 0) {
		HTp dynamics = sides.getDynamics();
		if (dynamics) {
			line.appendToken(dynamics);
			sides.detachDynamics();
		} else {
			newtoken = new HumdrumToken(empty);
			line.appendToken(newtoken);
		}
	}

	if (maxfcount > 0) {
		HTp figuredbass = sides.getFiguredBass();
		if (figuredbass) {
			line.appendToken(figuredbass);
			sides.detachFiguredBass();
		} else {
			newtoken = new HumdrumToken(empty);
			line.appendToken(newtoken);
		}
	}

	for (int i=0; i<hcount; i++) {
		HTp harmony = sides.getHarmony();
		if (harmony) {
			line.appendToken(harmony);
			sides.detachHarmony();
		} else {
			newtoken = new HumdrumToken(empty);
			line.appendToken(newtoken);
		}
	}

	for (int i=hcount; i<maxhcount; i++) {
		newtoken = new HumdrumToken(empty);
		line.appendToken(newtoken);
	}
}


// this version is used to transfer Sides from the Staff
void GridSlice::transferSides(HumdrumLine& line, GridStaff& sides,
		const string& empty, int maxxcount, int maxvcount, int maxhcount, int maxfcount) {

	// existing verses:
	int vcount = sides.getVerseCount();

	// int xcount = sides.getXmlidCount();
	int fcount = sides.getFiguredBassCount();

	// there should not be any harmony attached to staves
	// (only to parts, so hcount should only be zero):
	int hcount = sides.getHarmonyCount();
	HTp newtoken;

	if (maxxcount > 0) {
		HTp xmlid = sides.getXmlid();
		if (xmlid) {
			line.appendToken(xmlid);
			sides.detachXmlid();
		} else {
			newtoken = new HumdrumToken(empty);
			line.appendToken(newtoken);
		}
	}

	for (int i=0; i<vcount; i++) {
		HTp verse = sides.getVerse(i);
		if (verse) {
			line.appendToken(verse);
			sides.setVerse(i, NULL); // needed to avoid double delete
		} else {
			newtoken = new HumdrumToken(empty);
			line.appendToken(newtoken);
		}
	}

	if (vcount < maxvcount) {
		for (int i=vcount; i<maxvcount; i++) {
			newtoken = new HumdrumToken(empty);
			line.appendToken(newtoken);
		}
	}

	for (int i=0; i<hcount; i++) {
		HTp harmony = sides.getHarmony();
		if (harmony) {
			line.appendToken(harmony);
			sides.detachHarmony();
		} else {
			newtoken = new HumdrumToken(empty);
			line.appendToken(newtoken);
		}
	}

	for (int i=0; i<fcount; i++) {
		HTp figuredbass = sides.getFiguredBass();
		if (figuredbass) {
			line.appendToken(figuredbass);
			sides.detachFiguredBass();
		} else {
			newtoken = new HumdrumToken(empty);
			line.appendToken(newtoken);
		}
	}

	if (hcount < maxhcount) {
		for (int i=hcount; i<maxhcount; i++) {
			newtoken = new HumdrumToken(empty);
			line.appendToken(newtoken);
		}
	}

	if (fcount < maxfcount) {
		for (int i=fcount; i<maxfcount; i++) {
			newtoken = new HumdrumToken(empty);
			line.appendToken(newtoken);
		}
	}

}



//////////////////////////////
//
// GridSlice::initializePartStaves -- Also initialize sides
//

void GridSlice::initializePartStaves(vector<MxmlPart>& partdata) {
	int i, j;
	if (this->size() > 0) {
		// strange that this should happen, but presume the data
		// needs to be deleted.
		for (int i=0; i<(int)this->size(); i++) {
			if (this->at(i)) {
				delete this->at(i);
				this->at(i) = NULL;
			}
		}
	}
	this->resize(partdata.size());

	for (i=0; i<(int)partdata.size(); i++) {
		this->at(i) = new GridPart;
		this->at(i)->resize(partdata[i].getStaffCount());
		for (j=0; j<(int)partdata[i].getStaffCount(); j++) {
			this->at(i)->at(j) = new GridStaff;
		}
	}
}



//////////////////////////////
//
// GridSlice::initializeByStaffCount -- Initialize with parts containing a single staff.
//

void GridSlice::initializeByStaffCount(int staffcount) {
	if (this->size() > 0) {
		// strange that this should happen, but presume the data
		// needs to be deleted.
		for (int i=0; i<(int)this->size(); i++) {
			if (this->at(i)) {
				delete this->at(i);
				this->at(i) = NULL;
			}
		}
	}
	this->clear();
	this->resize(staffcount);

	for (int i=0; i<staffcount; i++) {
		this->at(i) = new GridPart;
		this->at(i)->resize(1);
		this->at(i)->at(0) = new GridStaff;
		this->at(i)->at(0)->resize(1);
		this->at(i)->at(0)->at(0) = new GridVoice;
	}
}



//////////////////////////////
//
// GridSlice::initializeBySlice -- Allocate parts/staves/voices counts by an existing slice.
//   Presuming that the slice is not already initialize with content.
//

void GridSlice::initializeBySlice(GridSlice* slice) {
	int partcount = (int)slice->size();
	this->resize(partcount);
	for (int p = 0; p < partcount; p++) {
		this->at(p) = new GridPart;
		int staffcount = (int)slice->at(p)->size();
		this->at(p)->resize(staffcount);
		for (int s = 0; s < staffcount; s++) {
			this->at(p)->at(s) = new GridStaff;
			int voicecount = (int)slice->at(p)->at(s)->size();
			this->at(p)->at(s)->resize(voicecount);
			for (int v=0; v < voicecount; v++) {
				this->at(p)->at(s)->at(v) = new GridVoice;
			}
		}
	}
}



//////////////////////////////
//
// GridSlice::getDuration -- Return the duration of the slice in
//      quarter notes.
//

HumNum GridSlice::getDuration(void) {
	return m_duration;
}



//////////////////////////////
//
// GridSlice::setDuration --
//

void GridSlice::setDuration(HumNum duration) {
	m_duration = duration;
}



//////////////////////////////
//
// GridSlice::getTimestamp --
//

HumNum GridSlice::getTimestamp(void) {
	return m_timestamp;
}



//////////////////////////////
//
// GridSlice::setTimestamp --
//

void GridSlice::setTimestamp(HumNum timestamp) {
	m_timestamp = timestamp;
}



//////////////////////////////
//
// GridSlice::setOwner --
//

void GridSlice::setOwner(HumGrid* owner) {
	m_owner = owner;
}



//////////////////////////////
//
// GridSlice::getOwner --
//

HumGrid* GridSlice::getOwner(void) {
	return m_owner;
}



//////////////////////////////
//
// GridSlice::getMeasure --
//

GridMeasure* GridSlice::getMeasure(void) {
	return m_measure;
}



//////////////////////////////
//
// operator<< -- print token content of a slice
//

ostream& operator<<(ostream& output, GridSlice& slice) {
	return output << &slice;
}


ostream& operator<<(ostream& output, GridSlice* slice) {
	if (slice == NULL) {
		output << "{n}";
		return output;
	}
	output << "TS=" << slice->getTimestamp() << " ";
	for (int p=0; p<(int)slice->size(); p++) {
		GridPart* part = slice->at(p);
		output << "(p" << p << ":)";
		if (part == NULL) {
			output << "{n}";
			continue;
		}
		for (int s=0; s<(int)part->size(); s++) {
			GridStaff* staff = part->at(s);
			output << "(s" << s << ":)";
			if (staff == NULL) {
				output << "{n}";
				continue;
			}
			for (int t=0; t<(int)staff->size(); t++) {
				GridVoice* gt = staff->at(t);
				output << "(v" << t << ":)";
				if (gt == NULL) {
					output << "{n}";
					continue;
				} else {
					HTp token = gt->getToken();
					if (token == NULL) {
						output << "{n}";
					} else {
						output << " \"" << *token << "\" ";
					}
				}
			}
			output << " sside:" << (GridSide*)staff;
		}
		output << " pside:" << (GridSide*)part;
	}
	return output;
}



//////////////////////////////
//
// GridSlice::hasSpines -- true if not a global comment or similar.
//

bool GridSlice::hasSpines(void) {
	SliceType type = getType();
	if (type < SliceType::_Spined) {
		return true;
	} else {
		return false;
	}
}



//////////////////////////////
//
// GridSlice::invalidate -- Mark the slice as invalid, which means that
//    it should not be transferred to the output Humdrum file in HumGrid.
//    Tokens stored in the GridSlice will be deleted by GridSlice when it
//    is destroyed.
//

void GridSlice::invalidate(void) {
		m_type = SliceType::Invalid;
		// should only do with 0 duration slices, but force to 0 if not already.
		setDuration(0);
}



//////////////////////////////
//
// GridSlice::reportVerseCount --
//

void GridSlice::reportVerseCount(int partindex, int staffindex, int count) {
	if (!m_owner) {
		return;
	}
	m_owner->reportVerseCount(partindex, staffindex, count);
}



//////////////////////////////
//
// GridSlice::getNullTokenForSlice --
//

string GridSlice::getNullTokenForSlice(void) {
	if (isDataSlice()) {
		return ".";
	} else if (isInterpretationSlice()) {
		return "*";
	} else if (isMeasureSlice()) {
		return "=";
	} else if (!hasSpines()) {
		return "!!";
	} else {
		return "!";
	}
}




//////////////////////////////
//
// GridStaff::GridStaff -- Constructor.
//

GridStaff::GridStaff(void) : vector<GridVoice*>(0), GridSide() {
	// do nothing;
}



//////////////////////////////
//
// GridStaff::~GridStaff -- Deconstructor.
//

GridStaff::~GridStaff(void) {
	for (int i=0; i<(int)this->size(); i++) {
		if (this->at(i)) {
			delete this->at(i);
			this->at(i) = NULL;
		}
	}
}



//////////////////////////////
//
// GridStaff::setTokenLayer -- Insert a token at the given voice/layer index.
//    If there is another token already there, then delete it.  If there
//    is no slot for the given voice, then create one and fill in all of the
//    other new ones with NULLs.
//

GridVoice* GridStaff::setTokenLayer(int layerindex, HTp token, HumNum duration) {
	if (layerindex < 0) {
		cerr << "Error: layer index is " << layerindex
		     << " for " << token << endl;
		return NULL;
	}
	if (layerindex > (int)this->size()-1) {
		int oldsize = (int)this->size();
		this->resize(layerindex+1);
		for (int i=oldsize; i<(int)this->size(); i++) {
			this->at(i) = NULL;
		}
	}
	if (this->at(layerindex) != NULL) {
		delete this->at(layerindex);
	}
	GridVoice* gv = new GridVoice(token, duration);
	this->at(layerindex) = gv;
	return gv;
}



////////////////////////////
//
// GridStaff::setNullTokenLayer --
//

void GridStaff::setNullTokenLayer(int layerindex, SliceType type,
		HumNum nextdur) {

	if (type == SliceType::Invalid) {
		return;
	}
	if (type == SliceType::GlobalLayouts) {
		return;
	}
	if (type == SliceType::GlobalComments) {
		return;
	}
	if (type == SliceType::ReferenceRecords) {
		return;
	}

	string nulltoken;
	if (type < SliceType::_Data) {
		nulltoken = ".";
	} else if (type <= SliceType::_Measure) {
		nulltoken = "=";
	} else if (type <= SliceType::_Interpretation) {
		nulltoken = "*";
	} else if (type <= SliceType::_Spined) {
		nulltoken = "!";
	} else {
		cerr << "!!STRANGE ERROR: " << this << endl;
		cerr << "!!SLICE TYPE: " << (int)type << endl;
	}

	bool errorQ = false;
	if (layerindex < (int)this->size()) {
		if ((at(layerindex) != NULL) && (at(layerindex)->getToken() != NULL)) {
			if ((string)*at(layerindex)->getToken() == nulltoken) {
				// there is already a null data token here, so don't
				// replace it.
				return;
			} else {
				cerr << "GRID STAFF: " << this << endl;
				cerr << "Warning, replacing existing token: "
				     << this->at(layerindex)->getToken()
				     << " with a null token around time "
				     << nextdur
				     << " in layerindex " << layerindex
				     << endl;
				errorQ = true;
			}
		}
	}
	if (errorQ) {
		string original = *this->at(layerindex)->getToken();
		HumRegex hre;
		hre.replaceDestructive(original, "", ".ZZZ", "g");
		string value = nulltoken + "ZZZ" + original;
		HumdrumToken* token = new  HumdrumToken(value);
		setTokenLayer(layerindex, token, nextdur);
	} else {
		HumdrumToken* token = new  HumdrumToken(nulltoken);
		setTokenLayer(layerindex, token, nextdur);
	}

}



//////////////////////////////
//
// GridStaff::appendTokenLayer -- concatenate the string content
//   of a token onto the current token stored in the slot (or just
//   place this one in the slot if none there yet).  This is used for
//   chords normally.
//

void GridStaff::appendTokenLayer(int layerindex, HTp token, HumNum duration,
		const string& spacer) {

	GridVoice* gt;
	if (layerindex > (int)this->size()-1) {
		int oldsize = (int)this->size();
		this->resize(layerindex+1);
		for (int i=oldsize; i<(int)this->size(); i++) {
			this->at(i) = NULL;
		}
	}
	if (this->at(layerindex) != NULL) {
		string newtoken;
		newtoken = (string)*this->at(layerindex)->getToken();
		newtoken += spacer;
		newtoken += (string)*token;
		(string)*(this->at(layerindex)->getToken()) = newtoken;
	} else {
		gt = new GridVoice(token, duration);
		this->at(layerindex) = gt;
	}
}



//////////////////////////////
//
// GridStaff::getMaxVerseCount --
//

int GridStaff::getMaxVerseCount(void) {
	return 5;
}



//////////////////////////////
//
// GridStaff::getString --
//

string GridStaff::getString(void) {
	string output;
	for (int v=0; v<(int)size(); v++) {
		GridVoice* gv = at(v);
		if (gv == NULL) {
			output += "{nv}";
		} else {
			HTp token = gv->getToken();
			if (token == NULL) {
				output += "{n}";
			} else {
				output += *token;
			}
		}
		if (v < (int)size() - 1) {
			output += "\t";
		}
	}
	return output;
}



//////////////////////////////
//
// operator<< --
//

ostream& operator<<(ostream& output, GridStaff* staff) {
	if (staff == NULL) {
		output << "{n}";
		return output;
	}
	for (int t=0; t<(int)staff->size(); t++) {
		GridVoice* gt = staff->at(t);
		cout << "(v" << t << ":)";
		if (gt == NULL) {
			cout << "{gt:n}";
			continue;
		} else {
			HTp token = gt->getToken();
			if (token == NULL) {
				cout << "{n}";
			} else {
				cout << " \"" << *token << "\" ";
			}
		}
	}
	output << (GridSide*) staff;
	return output;
}



//////////////////////////////
//
// GridVoice::GridVoice -- Constructor.
//

GridVoice::GridVoice(void) {
	m_token      = NULL;
	m_transfered = false;
}

GridVoice::GridVoice(HTp token, HumNum duration) {
	m_token      = token;
	m_nextdur    = duration;
	m_transfered = false;
}


GridVoice::GridVoice(const char* token, HumNum duration) {
	m_token      = new HumdrumToken(token);
	m_nextdur    = duration;
	m_transfered = false;
}


GridVoice::GridVoice(const string& token, HumNum duration) {
	m_token      = new HumdrumToken(token);
	m_nextdur    = duration;
	m_transfered = false;
}



//////////////////////////////
//
// GridVoice::~GridVoice -- Deconstructor: delete the token only if it
//     has not been transfered to a HumdrumFile object.
//

GridVoice::~GridVoice() {
	if (m_token && !m_transfered) {
		delete m_token;
	}
	m_token = NULL;
}



//////////////////////////////
//
// GridVoice::isTransfered -- True if token was copied to a HumdrumFile
//      object.
//

bool GridVoice::isTransfered(void) {
	return m_transfered;
}



//////////////////////////////
//
// GridVoice::setTransfered -- True if the object should not be
//    deleted with the object is destroyed.  False if the token
//    is not NULL and should be deleted when object is destroyed.
//

void GridVoice::setTransfered(bool state) {
	m_transfered = state;
}



//////////////////////////////
//
// GridVoice::getToken --
//

HTp GridVoice::getToken(void) const {
	return m_token;
}



//////////////////////////////
//
// GridVoice::setToken --
//

void GridVoice::setToken(HTp token) {
	if (!m_transfered && m_token) {
		delete m_token;
	}
	m_token = token;
	m_transfered = false;
}


void GridVoice::setToken(const string& token) {
	HTp realtoken = new HumdrumToken(token);
	setToken(realtoken);
}


void GridVoice::setToken(const char* token) {
	HTp realtoken = new HumdrumToken(token);
	setToken(realtoken);
}



//////////////////////////////
//
// GridVoice::isNull -- returns true if token is NULL or ".".
//

bool GridVoice::isNull(void) const {
	if (getToken() == NULL) {
		return true;
	} else if (getToken()->isNull()) {
		return true;
	} else {
		return false;
	}
}



//////////////////////////////
//
// GridVoice::setDuration --
//

void GridVoice::setDuration(HumNum duration) {
	m_nextdur = duration;
	m_prevdur = 0;
}



//////////////////////////////
//
// GridVoice::setDurationToPrev --
//

void GridVoice::setDurationToPrev(HumNum dur) {
	m_prevdur = dur;
}



//////////////////////////////
//
// GridVoice::getDurationToNext --
//

HumNum GridVoice::getDurationToNext(void) const {
	return m_nextdur;
}



//////////////////////////////
//
// GridVoice::getDurationToPrev --
//

HumNum GridVoice::getDurationToPrev(void) const {
	return m_nextdur;
}



//////////////////////////////
//
// GridVoice::incrementDuration --
//

void GridVoice::incrementDuration(HumNum duration) {
	m_nextdur -= duration;
	m_prevdur += duration;
}



//////////////////////////////
//
// GridVoice::forgetToken -- The HumdrumToken was passed off
//      to some other object which is now responsible for
//      deleting it.
//

void GridVoice::forgetToken(void) {
	setTransfered(true);
	m_token = NULL;
}



//////////////////////////////
//
// GridVoice::getDuration -- Return the total duration of the
//   durational item, the sum of the nextdur and prevdur.
//

HumNum GridVoice::getDuration(void) const {
	return m_nextdur + m_prevdur;
}



//////////////////////////////
//
// GridVoice::getString --
//

string GridVoice::getString(void) {
	string output;
	HTp token = getToken();
	if (token == NULL) {
		cout << "{n}";
	} else {
		cout << *token;
	}
	return output;
}



//////////////////////////////
//
// operator<< -- print token content of a voice
//

ostream& operator<<(ostream& output, GridVoice* voice) {
	if (voice == NULL) {
		output << "{n}";
		return output;
	}

	HTp token = voice->getToken();
	if (token == NULL) {
		cout << "{n}";
	} else {
		cout << " \"" << *token << "\" ";
	}
	return output;
}

ostream& operator<<(ostream& output, GridVoice& voice) {
	output << &voice;
	return output;
}




//////////////////////////////
//
// HumAddress::HumAddress -- HumAddress constructor.
//

HumAddress::HumAddress(void) {
	m_track         = -1;
	m_subtrack      = -1;
	m_subtrackcount = 0;
	m_fieldindex    = -1;
	m_owner         = NULL;
}


HumAddress::HumAddress(HumAddress& address) {
	m_fieldindex    = address.m_fieldindex;
	m_track         = address.m_track;
	m_subtrack      = address.m_subtrack;
	m_subtrackcount = address.m_subtrackcount;
	m_spining       = address.m_spining;
	m_owner         = address.m_owner;
}



//////////////////////////////
//
// HumAddress::~HumAddress -- HumAddress deconstructor.
//

HumAddress::~HumAddress() {
	m_track         = -1;
	m_subtrack      = -1;
	m_fieldindex    = -1;
	m_subtrackcount = 0;
	m_owner         = NULL;
}



//////////////////////////////
//
// HumAddress::operator= -- Copy humdrum address to another object.
//

HumAddress& HumAddress::operator=(const HumAddress& address) {
	m_fieldindex    = address.m_fieldindex;
	m_track         = address.m_track;
	m_subtrack      = address.m_subtrack;
	m_subtrackcount = address.m_subtrackcount;
	m_spining       = address.m_spining;
	m_owner         = address.m_owner;
	return *this;
}


//////////////////////////////
//
// HumAddress::getLineIndex -- Returns the line index in the owning HumdrumFile
//    for the token associated with the address.  Returns -1 if not owned by a
//    HumdrumLine (or line assignments have not been made for tokens in the
//    file).
//

int  HumAddress::getLineIndex(void) const {
	if (m_owner == NULL) {
		return -1;
	} else {
		return m_owner->getLineIndex();
	}
}



//////////////////////////////
//
// HumAddress::getLineNumber --  Similar to getLineIndex() but adds one.
//

int HumAddress::getLineNumber(void) const {
	return getLineIndex() + 1;
}



//////////////////////////////
//
// HumAddress::getFieldIndex -- Returns the field index on the line of the
//     token associated with the address.
//

int HumAddress::getFieldIndex(void) const {
	return m_fieldindex;
}



//////////////////////////////
//
// HumAddress::getDataType -- Return the exclusive interpretation string of the
//    token associated with the address.
//

const HumdrumToken& HumAddress::getDataType(void) const {
	static HumdrumToken null("");
	if (m_owner == NULL) {
		return null;
	}
	HumdrumToken* tok = m_owner->getTrackStart(getTrack());
	if (tok == NULL) {
		return null;
	}
	return *tok;
}



//////////////////////////////
//
// HumAddress::getExclusiveInterpretation -- Return the exclusive
//     interpretation token of the token associated with the address.
//

HTp HumAddress::getExclusiveInterpretation(void) {
	static HumdrumToken null("");
	if (m_owner == NULL) {
		return &null;
	}
	HumdrumToken* tok = m_owner->getTrackStart(getTrack());
	if (tok == NULL) {
		return &null;
	}
	return tok;
}



//////////////////////////////
//
// HumAddress::getSpineInfo -- Return the spine information for the token
//     associated with the address.  Examples: "1" the token is in the first
//     (left-most) spine, and there are no active sub-spines for the spine.
//     "(1)a"/"(1)b" are the spine descriptions of the two sub-spines after
//     a split manipulator (*^).  "((1)a)b" is the second sub-spines of the
//     first sub-spine for spine 1.
//
//

const string& HumAddress::getSpineInfo(void) const {
	return m_spining;
}



//////////////////////////////
//
// HumAddress::getTrack -- The track number of the given spine.  This is the
//   first number in the spine info string.  The track number is the same
//   as a spine number.
//

int HumAddress::getTrack(void) const {
	return m_track;
}



//////////////////////////////
//
// HumAddress::getSubtrack -- The subtrack number of the given spine.  This
//   functions in a similar manner to layer numbers in MEI data.  The first
//   sub-spine of a spine is always subtrack 1, regardless of whether or not
//   an exchange manipulator (*x) was used to switch the left-to-right ordering
//   of the spines in the file.  All sub-spines regardless of their splitting
//   origin are given sequential subtrack numbers.  For example if the spine
//   info is "(1)a"/"((1)b)a"/"((1)b)b" -- the spine is split, then the second
//   sub-spine only is split--then the sub-spines are labeled as sub-tracks "1",
//   "2", "3" respectively.  When a track has only one sub-spine (i.e., it has
//   been split), the subtrack value will be "0".
//

int HumAddress::getSubtrack(void) const {
	if (m_subtrackcount == 1) {
		return 0;
	} else {
		// return m_subtrack + 1;
		// Should already be indexed from one.
		return m_subtrack;
	}
}



//////////////////////////////
//
// HumAddress::getSubtrackCount -- The number of subtrack spines for a
//   given spine on the owning HumdurmLine.  Returns 0 if spine analysis
//   has not been done, or if the line does not have spines (i.e., reference
//   records, global comments and empty lines).
//

int HumAddress::getSubtrackCount(void) const {
	return m_subtrackcount;
}



//////////////////////////////
//
// HumAddress::getTrackString --  Return the track and subtrack as a string.
//      The returned string will have the track number if the sub-spine value
//      is zero.  The optional separator parameter is used to separate the
//      track number from the subtrack number.
// default value: separator = "."
//

string HumAddress::getTrackString(string separator) const {
	string output;
	int thetrack    = getTrack();
	int thesubtrack = getSubtrack();
	output += to_string(thetrack);
	if (thesubtrack > 0) {
		output += separator + to_string(thesubtrack);
	}
	return output;
}



//////////////////////////////
//
// HumAddress::setOwner -- Stores a pointer to the HumdrumLine on which
//   the token associated with this address belongs.  When not owned by
//   a HumdrumLine, the parameter's value should be NULL.
//

void HumAddress::setOwner(HLp aLine) {
	m_owner = aLine;
}



//////////////////////////////
//
// HumAddress::getLine -- return the HumdrumLine which owns the token
//    associated with this address.  Returns NULL if it does not belong
//    to a HumdrumLine object.
//

HLp HumAddress::getLine(void) const {
	return m_owner;
}



//////////////////////////////
//
// HumAddress::hasOwner -- Returns true if a HumdrumLine owns the token
//    associated with the address.
//

bool HumAddress::hasOwner(void) const {
	return m_owner == NULL ? 0 : 1;
}



//////////////////////////////
//
// HumAddress::setFieldIndex -- Set the field index of associated token
//   in the HumdrumLine owner.  If the token is now owned by a HumdrumLine,
//   then the input parameter should be -1.
//

void HumAddress::setFieldIndex(int index) {
	m_fieldindex = index;
}



//////////////////////////////
//
// HumAddress::setSpineInfo -- Set the spine description of the associated
//     token.  For example "2" for the second spine (from the left), or
//     "((2)a)b" for a sub-spine created as the left sub-spine of the main
//     spine and then as the right sub-spine of that sub-spine.  This function
//     is used by the HumdrumFileStructure class.
//

void HumAddress::setSpineInfo(const string& spineinfo) {
	m_spining = spineinfo;
}



//////////////////////////////
//
// HumAddress::setTrack -- Set the track number of the associated token.
//   This should always be the first number in the spine information string,
//   or -1 if the spine info is empty.  Tracks are limited to an arbitrary
//   count of 1000 (could be increased in the future if needed).  This function
//   is used by the HumdrumFileStructure class.
//

void HumAddress::setTrack(int aTrack, int aSubtrack) {
	setTrack(aTrack);
	setSubtrack(aSubtrack);
}


void HumAddress::setTrack(int aTrack) {
	if (aTrack < 0) {
		aTrack = -1;
	}
	if (aTrack > 1000) {
		aTrack = 1000;
	}
	m_track = aTrack;
}



//////////////////////////////
//
// HumAddress::setSubtrack -- Set the subtrack of the spine.
//   If the token is the only one active for a spine, the subtrack should
//   be set to zero.  If there are more than one sub-tracks for the spine, this
//   is the one-offset index of the spine (be careful if a sub-spine column
//   is exchanged with another spine other than the one from which it was
//   created.  In this case the subtrack number is not useful to calculate
//   the field index of other sub-tracks for the given track.
//   This function is used by the HumdrumFileStructure class.
//

void HumAddress::setSubtrack(int aSubtrack) {
	if (aSubtrack < 0) {
		aSubtrack = -1;
	}
	if (aSubtrack > 1000) {
		aSubtrack = 1000;
	}
	m_subtrack = aSubtrack;
}



//////////////////////////////
//
// HumAddress::setSubtrackCount --
//

void HumAddress::setSubtrackCount(int count) {
	m_subtrackcount = count;
}



//////////////////////////////
//
// HumGrid::HumGrid -- Constructor.
//

HumGrid::HumGrid(void) {
	// Limited to 100 parts:
	m_verseCount.resize(100);
	m_harmonyCount.resize(100);
	m_dynamics.resize(100);
	m_xmlids.resize(100);
	m_figured_bass.resize(100);
	fill(m_dynamics.begin(), m_dynamics.end(), false);
	fill(m_xmlids.begin(), m_xmlids.end(), false);
	fill(m_figured_bass.begin(), m_figured_bass.end(), false);
	fill(m_harmonyCount.begin(), m_harmonyCount.end(), 0);

	// default options
	m_musicxmlbarlines = false;
	m_recip = false;
	m_pickup = false;
}



//////////////////////////////
//
// HumGrid::~HumGrid -- Deconstructor.
//

HumGrid::~HumGrid(void) {
	for (int i=0; i<(int)this->size(); i++) {
		if (this->at(i)) {
			delete this->at(i);
		}
	}
}



//////////////////////////////
//
// HumGrid::addMeasureToBack -- Allocate a GridMeasure at the end of the
//     measure list.
//

GridMeasure* HumGrid::addMeasureToBack(void) {
	GridMeasure* gm = new GridMeasure(this);
	this->push_back(gm);
	return this->back();
}



//////////////////////////////
//
// HumGrid::enableRecipSpine --
//

void HumGrid::enableRecipSpine(void) {
	m_recip = true;
}



//////////////////////////////
//
// HumGrid::getPartCount -- Return the number of parts in the Grid
//   by looking at the number of parts in the first spined GridSlice.
//

int  HumGrid::getPartCount(void) {
	if (!m_allslices.empty()) {
		return (int)m_allslices[0]->size();
	}

	if (this->empty()) {
		return 0;
	}

	if (this->at(0)->empty()) {
		return 0;
	}

	return (int)this->at(0)->back()->size();
}



//////////////////////////////
//
// HumGrid::getStaffCount -- Get the number of staves in a
//    given part.  Most parts will be single staff, but grand staff
//    parts with have two staves.  Organ may have 3 staves.
//

int HumGrid::getStaffCount(int partindex) {
	if (this->empty()) {
		return 0;
	}

	if (this->at(0)->empty()) {
		return 0;
	}

	// return (int)this->at(0)->front()->at(partindex)->size();
	return (int)this->at(0)->back()->at(partindex)->size();
}



//////////////////////////////
//
// HumGrid::getHarmonyCount --
//

int HumGrid::getHarmonyCount(int partindex) {
	if ((partindex < 0) || (partindex >= (int)m_harmonyCount.size())) {
		return 0;
	}
	return m_harmonyCount.at(partindex);
}



//////////////////////////////
//
// HumGrid::getDynamicsCount --
//

int HumGrid::getDynamicsCount(int partindex) {
	if ((partindex < 0) || (partindex >= (int)m_dynamics.size())) {
		return 0;
	}
	return m_dynamics[partindex];
}



//////////////////////////////
//
// HumGrid::getFiguredBassCount --
//

int HumGrid::getFiguredBassCount(int partindex) {
	if ((partindex < 0) || (partindex >= (int)m_figured_bass.size())) {
		return 0;
	}
	return m_figured_bass[partindex];
}



//////////////////////////////
//
// HumGrid::getVerseCount --
//

int HumGrid::getVerseCount(int partindex, int staffindex) {
	if ((partindex < 0) || (partindex >= (int)m_verseCount.size())) {
		return 0;
	}
	int staffnumber = staffindex + 1;
	if ((staffnumber < 1) ||
			(staffnumber >= (int)m_verseCount.at(partindex).size())) {
		return 0;
	}
	int value = m_verseCount.at(partindex).at(staffnumber);
	return value;
}



//////////////////////////////
//
// HumGrid::getXmlidCount --
//

int HumGrid::getXmlidCount(int partindex) {
	if ((partindex < 0) || (partindex >= (int)m_xmlids.size())) {
		return 0;
	}
	return m_xmlids[partindex];
}



//////////////////////////////
//
// HumGrid::hasXmlids -- Return true if there are any xmlids for the part.
//

bool HumGrid::hasXmlids(int partindex) {
	if ((partindex < 0) || (partindex >= (int)m_xmlids.size())) {
		return false;
	}
	return m_xmlids[partindex];
}



//////////////////////////////
//
// HumGrid::hasDynamics -- Return true if there are any dyanmics for the part.
//

bool HumGrid::hasDynamics(int partindex) {
	if ((partindex < 0) || (partindex >= (int)m_dynamics.size())) {
		return false;
	}
	return m_dynamics[partindex];
}



//////////////////////////////
//
// HumGrid::hasFiguredBass -- Return true if there is any figured bass for the part.
//

bool HumGrid::hasFiguredBass(int partindex) {
	if ((partindex < 0) || (partindex >= (int)m_figured_bass.size())) {
		return false;
	}
	return m_figured_bass[partindex];
}



//////////////////////////////
//
// HumGrid::setDynamicsPresent -- Indicate that part needs a **dynam spine.
//

void HumGrid::setDynamicsPresent(int partindex) {
	if ((partindex < 0) || (partindex >= (int)m_dynamics.size())) {
		return;
	}
	m_dynamics[partindex] = true;
}



//////////////////////////////
//
// HumGrid::setXmlidsPresent -- Indicate that part needs an **xmlid spine.
//

void HumGrid::setXmlidsPresent(int partindex) {
	if ((partindex < 0) || (partindex >= (int)m_xmlids.size())) {
		return;
	}
	m_xmlids[partindex] = true;
}



//////////////////////////////
//
// HumGrid::setFiguredBassPresent -- Indicate that part needs a **fb spine.
//

void HumGrid::setFiguredBassPresent(int partindex) {
	if ((partindex < 0) || (partindex >= (int)m_figured_bass.size())) {
		return;
	}
	m_figured_bass[partindex] = true;
}



//////////////////////////////
//
// HumGrid::setHarmonyPresent -- Indicate that part needs a **harm spine.
//

void HumGrid::setHarmonyPresent(int partindex) {
	if ((partindex < 0) || (partindex >= (int)m_harmony.size())) {
		return;
	}
	m_harmony[partindex] = true;
}



//////////////////////////////
//
// HumGrid::setHarmonyCount -- part size hardwired to 100 for now.
//

void HumGrid::setHarmonyCount(int partindex, int count) {
	if ((partindex < 0) || (partindex > (int)m_harmonyCount.size())) {
		return;
	}
	m_harmonyCount[partindex] = count;
}



//////////////////////////////
//
// HumGrid::reportVerseCount --
//

void HumGrid::reportVerseCount(int partindex, int staffindex, int count) {
	if (count <= 0) {
		return;
	}
	int staffnumber = staffindex + 1;
	int partsize = (int)m_verseCount.size();
	if (partindex >= partsize) {
		m_verseCount.resize(partindex+1);
	}
	int staffcount = (int)m_verseCount.at(partindex).size();
	if (staffnumber >= staffcount) {
		m_verseCount.at(partindex).resize(staffnumber+1);
		for (int i=staffcount; i<=staffnumber; i++) {
			m_verseCount.at(partindex).at(i) = 0;
		}
	}
	if (count > m_verseCount.at(partindex).at(staffnumber)) {
		m_verseCount.at(partindex).at(staffnumber) = count;
	}
}



//////////////////////////////
//
// HumGrid::setVerseCount --
//

void HumGrid::setVerseCount(int partindex, int staffindex, int count) {
	if ((partindex < 0) || (partindex > (int)m_verseCount.size())) {
		return;
	}
	int staffnumber = staffindex + 1;
	if (staffnumber < 0) {
		return;
	}
	if (staffnumber < (int)m_verseCount.at(partindex).size()) {
		m_verseCount.at(partindex).at(staffnumber) = count;
	} else {
		int oldsize = (int)m_verseCount.at(partindex).size();
		int newsize = staffnumber + 1;
		m_verseCount.at(partindex).resize(newsize);
		for (int i=oldsize; i<newsize; i++) {
			m_verseCount.at(partindex).at(i) = 0;
		}
		m_verseCount.at(partindex).at(staffnumber) = count;
	}
}



//////////////////////////////
//
// HumGrid::transferTokens --
//   default value: startbarnum = 0.
//

bool HumGrid::transferTokens(HumdrumFile& outfile, int startbarnum, const string& interp) {
	bool status = buildSingleList();
	if (!status) {
		return false;
	}
	calculateGridDurations();
	addNullTokens();
	addInvisibleRestsInFirstTrack();
	addMeasureLines();
	buildSingleList();  // is this needed a second time?
	cleanTempos();
	addLastMeasure();
	if (manipulatorCheck()) {
		cleanupManipulators();
	}

	insertPartNames(outfile);
	insertStaffIndications(outfile);
	insertPartIndications(outfile);
	insertExclusiveInterpretationLine(outfile, interp);
	bool addstartbar = (!hasPickup()) && (!m_musicxmlbarlines);
	for (int m=0; m<(int)this->size(); m++) {
		if (addstartbar && m == 0) {
			status &= at(m)->transferTokens(outfile, m_recip, addstartbar, startbarnum);
		} else {
			status &= at(m)->transferTokens(outfile, m_recip, false);
		}
		if (!status) {
			break;
		}
	}
	insertDataTerminationLine(outfile);
	return true;
}



//////////////////////////////
//
// HumGrid::cleanupManipulators --
//

void HumGrid::cleanupManipulators(void) {
	GridSlice* current = NULL;
	GridSlice* last = NULL;
	vector<GridSlice*> newslices;
	for (int m=0; m<(int)this->size(); m++) {
		for (auto it = this->at(m)->begin(); it != this->at(m)->end(); it++) {
			last = current;
			current = *it;
			if ((*it)->getType() != SliceType::Manipulators) {
				if (last && (last->getType() != SliceType::Manipulators)) {
					matchVoices(current, last);
				}
				continue;
			}
			if (last && (last->getType() != SliceType::Manipulators)) {
				matchVoices(current, last);
			}
			// check to see if manipulator needs to be split into
			// multiple lines.
			newslices.resize(0);
			cleanManipulator(newslices, *it);
			if (newslices.size()) {
				for (int j=0; j<(int)newslices.size(); j++) {
					this->at(m)->insert(it, newslices.at(j));
				}
			}
		}
	}
}



//////////////////////////////
//
// HumGrid::cleanManipulator --
//

void HumGrid::cleanManipulator(vector<GridSlice*>& newslices, GridSlice* curr) {
	newslices.resize(0);
	GridSlice* output;

	// deal with *^ manipulators:
	while ((output = checkManipulatorExpand(curr))) {
		newslices.push_back(output);
	}

	// deal with *v manipulators:
	while ((output = checkManipulatorContract(curr))) {
		newslices.push_back(output);
	}
}



//////////////////////////////
//
// HumGrid::checkManipulatorExpand -- Check for cases where a spine expands
//    into sub-spines.
//

GridSlice* HumGrid::checkManipulatorExpand(GridSlice* curr) {
	GridStaff* staff     = NULL;
	GridPart*  part      = NULL;
	GridVoice* voice     = NULL;
	HTp        token     = NULL;
	bool       neednew   = false;

	int p, s, v;
	int partcount = (int)curr->size();
	int staffcount;

	for (p=0; p<partcount; p++) {
		part = curr->at(p);
		staffcount = (int)part->size();
		for (s=0; s<staffcount; s++) {
			staff = part->at(s);
			for (v=0; v<(int)staff->size(); v++) {
				voice = staff->at(v);
				token = voice->getToken();
				if (token->compare(0, 2, "*^") == 0) {
					if ((token->size() > 2) && isdigit((*token)[2])) {
						neednew = true;
						break;
					}
				}
			}
			if (neednew) {
				break;
			}
		}
		if (neednew) {
			break;
		}
	}

	if (neednew == false) {
		return NULL;
	}

	// need to split *^#'s into separate *^

	GridSlice* newmanip = new GridSlice(curr->getMeasure(), curr->getTimestamp(),
	curr->getType(), curr);

	for (p=0; p<partcount; p++) {
		part = curr->at(p);
		staffcount = (int)part->size();
		for (s=0; s<staffcount; s++) {
			staff = part->at(s);
			adjustExpansionsInStaff(newmanip, curr, p, s);
		}
	}
	return newmanip;
}



//////////////////////////////
//
// HumGrid::adjustExpansionsInStaff -- duplicate null
//   manipulators, and expand large-expansions, such as *^3 into
//   *^ and *^ on the next line, or *^4 into *^ and *^3 on the
//   next line.  The "newmanip" will be placed before curr, so
//

void HumGrid::adjustExpansionsInStaff(GridSlice* newmanip, GridSlice* curr, int p, int s) {
	HTp token = NULL;
	GridVoice* newvoice  = NULL;
	GridVoice* curvoice  = NULL;
	GridStaff* newstaff  = newmanip->at(p)->at(s);
	GridStaff* curstaff  = curr->at(p)->at(s);

	int originalsize = (int)curstaff->size();
	int cv = 0;

	for (int v=0; v<originalsize; v++) {
		curvoice = curstaff->at(cv);
		token = curvoice->getToken();

		if (token->compare(0, 2, "*^") == 0) {
			if ((token->size() > 2) && isdigit((*token)[2])) {
				// transfer *^ to newmanip and replace with * and *^(n-1) in curr
				// Convert *^3 to *^ and add ^* to next line, for example
				// Convert *^4 to *^ and add ^*3 to next line, for example
				int count = 0;
				if (!sscanf(token->c_str(), "*^%d", &count)) {
					cerr << "Error finding expansion number" << endl;
				}
				newstaff->push_back(curvoice);
				curvoice->getToken()->setText("*^");
				newvoice = createVoice("*", "B", 0, p, s);
				curstaff->at(cv) = newvoice;
				if (count <= 3) {
					newvoice = new GridVoice("*^", 0);
				} else {
					newvoice = new GridVoice("*^" + to_string(count-1), 0);
				}
				curstaff->insert(curstaff->begin()+cv+1, newvoice);
				cv++;
				continue;
			} else {
				// transfer *^ to newmanip and replace with two * in curr
				newstaff->push_back(curvoice);
				newvoice = createVoice("*", "C", 0, p, s);
				curstaff->at(cv) = newvoice;
				newvoice = createVoice("*", "D", 0, p, s);
				curstaff->insert(curstaff->begin()+cv, newvoice);
				cv++;
				continue;
			}
		} else {
			// insert * in newmanip
			newvoice = createVoice("*", "E", 0, p, s);
			newstaff->push_back(newvoice);
			cv++;
			continue;
		}

	}
}



//////////////////////////////
//
// HumGrid::checkManipulatorContract -- Will only check for adjacent
//    *v records across adjacent staves, which should be good enough.
//    Will not check within a staff, but this should not occur within
//    MusicXML input data due to the way it is being processed.
//    The return value is a newly created GridSlice pointer which contains
//    a new manipulator to add to the file (and the current manipultor
//    slice will also be modified if the return value is not NULL).
//

GridSlice* HumGrid::checkManipulatorContract(GridSlice* curr) {
	GridVoice* lastvoice = NULL;
	GridVoice* voice     = NULL;
	GridStaff* staff     = NULL;
	GridPart*  part      = NULL;
	bool       neednew   = false;

	int p, s;
	int partcount = (int)curr->size();
	int staffcount;
	bool init = false;
	for (p=partcount-1; p>=0; p--) {
		part  = curr->at(p);
		staffcount = (int)part->size();
		for (s=staffcount-1; s>=0; s--) {
			staff = part->at(s);
			if (staff->empty()) {
				continue;
			}
			voice = staff->back();
			if (!init) {
				lastvoice = staff->back();
				init = true;
				continue;
			}
			if (lastvoice != NULL) {
           	if ((*voice->getToken() == "*v") &&
						(*lastvoice->getToken() == "*v")) {
					neednew = true;
					break;
				}
			}
			lastvoice = staff->back();
		}
		if (neednew) {
			break;
		}
	}

	if (neednew == false) {
		return NULL;
	}

	// need to split *v's from different adjacent staves onto separate lines.
	GridSlice* newmanip = new GridSlice(curr->getMeasure(), curr->getTimestamp(),
		curr->getType(), curr);
	lastvoice = NULL;
	GridStaff* laststaff    = NULL;
	GridStaff* newstaff     = NULL;
	GridStaff* newlaststaff = NULL;
	bool foundnew = false;
	partcount = (int)curr->size();
	int lastp = 0;
	int lasts = 0;
	int partsplit = -1;
	int voicecount;

	for (p=partcount-1; p>=0; p--) {
		part  = curr->at(p);
		staffcount = (int)part->size();
		for (s=staffcount-1; s>=0; s--) {
			staff = part->at(s);
			voicecount = (int)staff->size();
			voice = staff->back();
			newstaff = newmanip->at(p)->at(s);
			if (lastvoice != NULL) {
           	if ((*voice->getToken() == "*v") &&
						(*lastvoice->getToken() == "*v")) {
               // splitting the slices at this staff boundary

					newlaststaff = newmanip->at(lastp)->at(lasts);
					transferMerges(staff, laststaff, newstaff, newlaststaff, p, s);
					foundnew = true;
					partsplit = p;
					break;
				}
			} else {
				if (voicecount > 1) {
					for (int j=(int)newstaff->size(); j<voicecount; j++) {
						// GridVoice* vdata = createVoice("*", "F", 0, p, s);
						// newstaff->push_back(vdata);
					}
				}
			}
			laststaff = staff;
			lastvoice = laststaff->back();
			lastp = p;
			lasts = s;
		}

		if (foundnew) {
			// transfer all of the subsequent manipulators forward
			// after the staff/newstaff point in the slice
			if (partsplit > 0) {
				transferOtherParts(curr, newmanip, partsplit);
			}
			break;
		}
	}

	// fill in any missing voice null interpretation tokens
	adjustVoices(curr, newmanip, partsplit);

	return newmanip;
}



//////////////////////////////
//
// HumGrid::adjustVoices --
//

void HumGrid::adjustVoices(GridSlice* curr, GridSlice* newmanip, int partsplit) {
	int p1count = (int)curr->size();
	// int p2count = (int)newmanip->size();
	//cerr << "PARTSPLIT " << partsplit << endl;
	for (int p=0; p<p1count; p++) {
		int s1count = (int)curr->at(p)->size();
		// int s2count = (int)curr->at(p)->size();
		// cerr << "\tCURR STAVES " << s1count << "\tNEWM STAVES " << s2count << endl;
		// cerr << "\t\tCURR SCOUNT = " << curr->at(p)->size() << "\tNEWM SCOUNT = " << newmanip->at(p)->size() << endl;
		for (int s=0; s<s1count; s++) {
			GridStaff* s1 = curr->at(p)->at(s);
			GridStaff* s2 = newmanip->at(p)->at(s);
			if ((s1->size() == 0) && (s2->size() > 0)) {
				createMatchedVoiceCount(s1, s2, p, s);
			} else if ((s2->size() == 0) && (s1->size() > 0)) {
				createMatchedVoiceCount(s2, s1, p, s);
			}
			// cerr << "\t\t\tCURR VCOUNT = " << curr->at(p)->at(s)->size() << "\t(" << curr->at(p)->at(s)->getString() << ")" << "\t";
			// cerr << "\tNEWM VCOUNT = " << newmanip->at(p)->at(s)->size() << "\t(" << newmanip->at(p)->at(s)->getString() << ")" << endl;
		}
	}
}



//////////////////////////////
//
// HumGrid::createMatchedVoiceCount --
//

void HumGrid::createMatchedVoiceCount(GridStaff* snew, GridStaff* sold, int p, int s) {
	if (snew->size() != 0) {
		// this function is only for creating a totally new
		return;
	}
	int count = (int)sold->size();
	snew->resize(count);
	for (int i=0; i<count; i++) {
		GridVoice* gv = createVoice("*", "N", p, s, i);
		snew->at(i) = gv;
	}
}



//////////////////////////////
//
// HumGrid::matchVoices --
//

void HumGrid::matchVoices(GridSlice* current, GridSlice* last) {
	if (current == NULL) {
		return;
	}
	if (last == NULL) {
		return;
	}
	int pcount1 = (int)current->size();
	int pcount2 = (int)current->size();
	if (pcount1 != pcount2) {
		return;
	}
	for (int i=0; i<pcount1; i++) {
		GridPart* part1 = current->at(i);
		GridPart* part2 = current->at(i);
		int scount1 = (int)part1->size();
		int scount2 = (int)part2->size();
		if (scount1 != scount2) {
			continue;
		}
		for (int j=0; j<scount1; j++) {
			GridStaff* staff1 = part1->at(j);
			GridStaff* staff2 = part2->at(j);
			int vcount1 = (int)staff1->size();
			int vcount2 = (int)staff2->size();
			if (vcount1 == vcount2) {
				continue;
			}
			if (vcount2 > vcount1) {
				// strange if it happens
				continue;
			}
			int difference = vcount1 - vcount2;
			for (int k=0; k<difference; k++) {
				GridVoice* gv = createVoice("*", "A", 0, i, j);
				staff2->push_back(gv);
			}
		}
	}
}



//////////////////////////////
//
// HumGrid::transferOtherParts -- after a line split due to merges
//    occurring at the same time.
//

void HumGrid::transferOtherParts(GridSlice* oldline, GridSlice* newline, int maxpart) {
	GridPart* temp;
	int partcount = (int)oldline->size();
	if (maxpart >= partcount) {
		return;
	}
	for (int i=0; i<maxpart; i++) {
		temp = oldline->at(i);
		oldline->at(i) = newline->at(i);
		newline->at(i) = temp;
		// duplicate the voice counts on the old line (needed if there are more
		// than one voices in a staff when splitting a line due to *v merging.
		for (int j=0; j<(int)oldline->at(i)->size(); j++) {
			int voices = (int)newline->at(i)->at(j)->size();
			int adjustment = 0;
			for (int k=0; k<voices; k++) {
				if (!newline->at(i)->at(j)->at(k)) {
					continue;
				}
				HTp tok = newline->at(i)->at(j)->at(k)->getToken();
				if (*tok == "*v") {
					adjustment++;
				}
			}
			if (adjustment > 0) {
				adjustment--;
			}
			voices -= adjustment;
			oldline->at(i)->at(j)->resize(voices);
			for (int k=0; k<voices; k++) {
				oldline->at(i)->at(j)->at(k) = createVoice("*", "Z", 0, i, j);
			}
		}
	}

	for (int p=0; p<(int)newline->size(); p++) {
			GridPart* newpart = newline->at(p);
			GridPart* oldpart = oldline->at(p);
		for (int s=0; s<(int)newpart->size(); s++) {
			GridStaff* newstaff = newpart->at(s);
			GridStaff* oldstaff = oldpart->at(s);
			if (newstaff->size() >= oldstaff->size()) {
				continue;
			}
			int diff = (int)(oldstaff->size() - newstaff->size());

			for (int v=0; v<diff; v++) {
				GridVoice* voice = createVoice("*", "G", 0, p, s);
				newstaff->push_back(voice);
			}

		}
	}
}



//////////////////////////////
//
// HumGrid::transferMerges -- Move *v spines from one staff to last staff,
//   and re-adjust staff "*v" tokens to a single "*" token.
// Example:
//                 laststaff      staff
// old:            *v   *v        *v   *v
// converts to:
// new:            *v   *v        *    *
// old:            *              *v   *v
//
//
//

void HumGrid::transferMerges(GridStaff* oldstaff, GridStaff* oldlaststaff,
		GridStaff* newstaff, GridStaff* newlaststaff, int pindex, int sindex) {
	if ((oldstaff == NULL) || (oldlaststaff == NULL)) {
		cerr << "Weird error in HumGrid::transferMerges()" << endl;
		return;
	}
	// New staves are presumed to be totally empty.

	GridVoice* gv;

	// First create "*" tokens for newstaff slice where there are
	// "*v" in old staff.  All other tokens should be set to "*".
	int tcount = (int)oldstaff->size();
	int t;
	for (t=0; t<tcount; t++) {
		if (*oldstaff->at(t)->getToken() == "*v") {
			gv = createVoice("*", "H", 0, pindex, sindex);
			newstaff->push_back(gv);
		} else {
			gv = createVoice("*", "I", 0, pindex, sindex);
			newstaff->push_back(gv);
		}
	}

	// Next, all "*v" tokens at end of old previous staff should be
	// transferred to the new previous staff and replaced with
	// a single "*" token.  Non "*v" tokens in the old last staff should
	// be converted to "*" tokens in the new last staff.
	//
	// It may be possible for *v tokens to not be only at the end of
	// the list of oldlaststaff tokens, but does not seem possible.

	tcount = (int)oldlaststaff->size();
	bool addednull = false;
	for (t=0; t<tcount; t++) {
		if (*oldlaststaff->at(t)->getToken() == "*v") {
			newlaststaff->push_back(oldlaststaff->at(t));
			if (addednull == false) {
				gv = createVoice("*", "J", 0, pindex, sindex);
				oldlaststaff->at(t) = gv;
				addednull = true;
			} else {
				oldlaststaff->at(t) = NULL;
			}
		} else {
			gv = createVoice("*", "K", 0, pindex, sindex);
			newlaststaff->push_back(gv);
		}
	}

	// Go back to the oldlaststaff and chop off all ending NULLs
	// * it should never get to zero (there should be at least one "*" left.
	// In theory intermediate NULLs should be checked for, and if they
	// exist, then something bad will happen.  But it does not seem
	// possible to have intermediate NULLs.
	tcount = (int)oldlaststaff->size();
	for (t=tcount-1; t>=0; t--) {
		if (oldlaststaff->at(t) == NULL) {
			int newsize = (int)oldlaststaff->size() - 1;
			oldlaststaff->resize(newsize);
		}
	}
}



//////////////////////////////
//
// HumGrid::createVoice -- create voice with given token contents.
//

GridVoice* HumGrid::createVoice(const string& tok, const string& post, HumNum duration, int pindex, int sindex) {
	//std::string token = tok;
	//token += ":" + post + ":" + to_string(pindex) + "," + to_string(sindex);
	GridVoice* gv = new GridVoice(tok.c_str(), 0);
	return gv;
}



//////////////////////////////
//
// HumGrid::getNextSpinedLine -- Find next spined GridSlice.
//

GridSlice* HumGrid::getNextSpinedLine(const GridMeasure::iterator& it, int measureindex) {
	auto nextone = it;
	nextone++;
	while (nextone != this->at(measureindex)->end()) {
		if ((*nextone)->hasSpines()) {
			break;
		}
		nextone++;
	}

	if (nextone != this->at(measureindex)->end()) {
		return *nextone;
	}

	measureindex++;
	if (measureindex >= (int)this->size()) {
		// end of data, so nothing to adjust with
		// but this should never happen in general.
		return NULL;
	}
	nextone = this->at(measureindex)->begin();
	while (nextone != this->at(measureindex)->end()) {
		if ((*nextone)->hasSpines()) {
			return *nextone;
		}
		nextone++;
	}

	return NULL;
}



//////////////////////////////
//
// HumGrid::manipulatorCheck --
//

bool HumGrid::manipulatorCheck(void) {
	GridSlice* manipulator;
	int m;
	GridSlice* s1;
	GridSlice* s2;
	bool output = false;
	for (m=0; m<(int)this->size(); m++) {
		if (this->at(m)->size() == 0) {
			continue;
		}
		for (auto it = this->at(m)->begin(); it != this->at(m)->end(); it++) {
			if (!(*it)->hasSpines()) {
				// Don't monitor manipulators on no-spined lines.
				continue;
			}
			s1 = *it;
			s2 = getNextSpinedLine(it, m);

			manipulator = manipulatorCheck(s1, s2);
			if (manipulator == NULL) {
				continue;
			}
			output = true;
			auto inserter = it;
			inserter++;
			this->at(m)->insert(inserter, manipulator);
			it++; // skip over the new manipulator line (expand it later)
		}
	}
	return output;
}


//
// HumGrid::manipulatorCheck -- Look for differences in voice/layer count
//   for each part/staff pairing between adjacent lines.  If they do not match,
//   then add spine manipulator line to Grid between the two lines.
//

GridSlice* HumGrid::manipulatorCheck(GridSlice* ice1, GridSlice* ice2) {
	int p1count;
	int p2count;
	int s1count;
	int s2count;
	int v1count;
	int v2count;
	int p;
	int s;
	int v;
	bool needmanip = false;

	if (ice1 == NULL) {
		return NULL;
	}
	if (ice2 == NULL) {
		return NULL;
	}
	if (!ice1->hasSpines()) {
		return NULL;
	}
	if (!ice2->hasSpines()) {
		return NULL;
	}
	p1count = (int)ice1->size();
	p2count = (int)ice2->size();
	if (p1count != p2count) {
		cerr << "Warning: Something weird happend here" << endl;
		cerr << "p1count = " << p1count << endl;
		cerr << "p2count = " << p2count << endl;
		cerr << "ICE1: " << ice1 << endl;
		cerr << "ICE2: " << ice2 << endl;
		cerr << "The above two values should be the same." << endl;
		return NULL;
	}
	for (p=0; p<p1count; p++) {
		s1count = (int)ice1->at(p)->size();
		s2count = (int)ice2->at(p)->size();
		if (s1count != s2count) {
			cerr << "Warning: Something weird happend here with staff" << endl;
			return NULL;
		}
		for (s=0; s<s1count; s++) {
			v1count = (int)ice1->at(p)->at(s)->size();
			// the voice count always must be at least 1.  This case
			// is related to inserting clefs in other parts.
			if (v1count < 1) {
				v1count = 1;
			}
			v2count = (int)ice2->at(p)->at(s)->size();
			if (v2count < 1) {
				v2count = 1;
			}
			if (v1count == v2count) {
				continue;
			}
			needmanip = true;
			break;
		}
		if (needmanip) {
			break;
		}
	}

	if (!needmanip) {
		return NULL;
	}

	// build manipulator line (which will be expanded further if adjacent
	// staves have *v manipulators.

	GridSlice* mslice;
	mslice = new GridSlice(ice1->getMeasure(), ice2->getTimestamp(),
			SliceType::Manipulators);

	int z;
	HTp token;
	GridVoice* gv;
	p1count = (int)ice1->size();
	mslice->resize(p1count);
	for (p=0; p<p1count; p++) {
		mslice->at(p) = new GridPart;
		s1count = (int)ice1->at(p)->size();
		mslice->at(p)->resize(s1count);
		for (s=0; s<s1count; s++) {
			mslice->at(p)->at(s) = new GridStaff;
			v1count = (int)ice1->at(p)->at(s)->size();
			v2count = (int)ice2->at(p)->at(s)->size();
			if (v2count < 1) {
				// empty spines will be filled in with at least one null token.
				v2count = 1;
			}
			if (v1count < 1) {
				// empty spines will be filled in with at least one null token.
				v1count = 1;
			}
			if ((v1count == 0) && (v2count == 1)) {
				// grace note at the start of the measure in another voice
				// no longer can get here due to v1count min being 1.
				token = createHumdrumToken("*", p, s);
				gv = new GridVoice(token, 0);
				mslice->at(p)->at(s)->push_back(gv);
			} else if (v1count == v2count) {
				for (v=0; v<v1count; v++) {
					token = createHumdrumToken("*", p, s);
					gv = new GridVoice(token, 0);
					mslice->at(p)->at(s)->push_back(gv);
				}
			} else if (v1count < v2count) {
				// need to grow
				int grow = v2count - v1count;
				// if (grow == 2 * v1count) {
				if (v2count == 2 * v1count) {
					// all subspines split
					for (z=0; z<v1count; z++) {
						token = new HumdrumToken("*^");
						gv = new GridVoice(token, 0);
						mslice->at(p)->at(s)->push_back(gv);
					}
				} else if ((v1count > 0) && (grow > 2 * v1count)) {
					// too large to split all at the same time, deal with later
					for (z=0; z<v1count-1; z++) {
						token = new HumdrumToken("*^");
						gv = new GridVoice(token, 0);
						mslice->at(p)->at(s)->push_back(gv);
					}
					int extra = v2count - (v1count - 1) * 2;
					if (extra > 2) {
						token = new HumdrumToken("*^" + to_string(extra));
					} else {
						token = new HumdrumToken("*^");
					}
					gv = new GridVoice(token, 0);
					mslice->at(p)->at(s)->push_back(gv);
				} else {
					// only split spines at end of list
					int doubled = v2count - v1count;
					int notdoubled = v1count - doubled;
					for (z=0; z<notdoubled; z++) {
						token = createHumdrumToken("*", p, s);
						gv = new GridVoice(token, 0);
						mslice->at(p)->at(s)->push_back(gv);
					}
					//for (z=0; z<doubled; z++) {
						if (doubled > 1) {
							token = new HumdrumToken("*^" + to_string(doubled+1));
						} else {
							token = new HumdrumToken("*^");
						}
						// token = new HumdrumToken("*^");
						gv = new GridVoice(token, 0);
						mslice->at(p)->at(s)->push_back(gv);
					//}
				}
			} else if (v1count > v2count) {
				// need to shrink
				int shrink = v1count - v2count + 1;
				int notshrink = v1count - shrink;
				for (z=0; z<notshrink; z++) {
					token = createHumdrumToken("*", p, s);
					gv = new GridVoice(token, 0);
					mslice->at(p)->at(s)->push_back(gv);
				}
				for (z=0; z<shrink; z++) {
					token = new HumdrumToken("*v");
					gv = new GridVoice(token, 0);
					mslice->at(p)->at(s)->push_back(gv);
				}
			}
		}
	}
	return mslice;
}



//////////////////////////////
//
// HumGrid::createHumdrumToken --
//

HTp HumGrid::createHumdrumToken(const string& tok, int pindex, int sindex) {
	std::string token = tok;
	// token += ":" + to_string(pindex) + "," + to_string(sindex);
	HTp output = new HumdrumToken(token.c_str());
	return output;
}



//////////////////////////////
//
// HumGrid::addMeasureLines --
//

void HumGrid::addMeasureLines(void) {
	HumNum timestamp;
	GridSlice* mslice;
	GridSlice* endslice;
	GridPart* part;
	GridStaff* staff;
	GridVoice* gv;
	string token;
	int staffcount, partcount, vcount, nextvcount, lcount;
	GridMeasure* measure = NULL;
	GridMeasure* nextmeasure = NULL;

	vector<int> barnums;
	if (!m_musicxmlbarlines) {
		getMetricBarNumbers(barnums);
	}

	for (int m=0; m<(int)this->size()-1; m++) {
		measure = this->at(m);
		nextmeasure = this->at(m+1);
		if (nextmeasure->size() == 0) {
			// next measure is empty for some reason so give up
			continue;
		}
		GridSlice* firstspined = nextmeasure->getFirstSpinedSlice();
		timestamp = firstspined->getTimestamp();
		if (measure->size() == 0) {
			continue;
		}

		if (measure->getDuration() == 0) {
			continue;
		}
		mslice = new GridSlice(measure, timestamp, SliceType::Measures);
		// what to do when endslice is NULL?
		endslice = measure->getLastSpinedSlice(); // this has to come before next line
		measure->push_back(mslice); // this has to come after the previous line
		partcount = (int)firstspined->size();
		mslice->resize(partcount);

		for (int p=0; p<partcount; p++) {
			part = new GridPart();
			mslice->at(p) = part;
			staffcount = (int)firstspined->at(p)->size();
			mslice->at(p)->resize(staffcount);
			for (int s=0; s<(int)staffcount; s++) {
				staff = new GridStaff;
				mslice->at(p)->at(s) = staff;

				// insert the minimum number of barlines based on the
				// voices in the current and next measure.
				vcount = (int)endslice->at(p)->at(s)->size();
				if (firstspined) {
					nextvcount = (int)firstspined->at(p)->at(s)->size();
				} else {
					// perhaps an empty measure?  This will cause problems.
					nextvcount = 0;
				}
				lcount = vcount;
				if (lcount > nextvcount) {
					lcount = nextvcount;
				}
				if (lcount == 0) {
					lcount = 1;
				}
				for (int v=0; v<lcount; v++) {
					int num = measure->getMeasureNumber();
					if (m < (int)barnums.size() - 1) {
						num = barnums[m+1];
					}
					token = createBarToken(m, num, measure);
					gv = new GridVoice(token, 0);
					mslice->at(p)->at(s)->push_back(gv);
				}
			}
		}
	}
}



//////////////////////////////
//
// HumGrid::createBarToken --
//

string HumGrid::createBarToken(int m, int barnum, GridMeasure* measure) {
	string token;
	string barstyle = getBarStyle(measure);
	string number = "";
	if (barnum > 0) {
		number = to_string(barnum);
	}
	if (m_musicxmlbarlines) {
		// m+1 because of the measure number
		// comes from the previous measure.
		if (barstyle == "=") {
			token = "==";
			token += to_string(m+1);
		} else {
			token = "=";
			token += to_string(m+1);
			token += barstyle;
		}
	} else {
		if (barnum > 0) {
			if (barstyle == "=") {
				token = "==";
				token += number;
			} else {
				token = "=";
				token += number;
				token += barstyle;
			}
		} else {
			if (barstyle == "=") {
				token = "==";
			} else {
				token = "=";
				token += barstyle;
			}
		}
	}
	return token;
}



//////////////////////////////
//
// HumGrid::getMetricBarNumbers --
//

void HumGrid::getMetricBarNumbers(vector<int>& barnums) {
return;

/* Disabling for now.  Causes problems in MuseData conversion, but usually needed for MusicXML conversion
 * to get correct measures numbers (related to pickup measures, particularly in older MusicXML files).
 * For MuseData, the first barline in a score is not explicitly given, which is the source of the problem.

	int mcount = (int)this->size();
	barnums.resize(mcount);

	if (mcount == 0) {
		return;
	}

	vector<HumNum> mdur(mcount);
	vector<HumNum> tsdur(mcount); // time signature duration

	for (int m=0; m<(int)this->size(); m++) {
		mdur[m]   = this->at(m)->getDuration();
		tsdur[m] = this->at(m)->getTimeSigDur();
		if (tsdur[m] <= 0) {
			tsdur[m] = mdur[m];
		}
	}

	int start = 0;
	if (!mdur.empty()) {
		if (mdur[0] == 0) {
			start = 1;
		}
	}

	// int counter = 1;  // this was causing a problem https://github.com/humdrum-tools/verovio-humdrum-viewer/issues/254
	int counter = 0;
	if (mdur[start] == tsdur[start]) {
		m_pickup = false;
		counter++;
		// add the initial barline later when creating HumdrumFile.
	} else {
		m_pickup = true;
	}

	for (int m=start; m<(int)this->size(); m++) {
		if ((m == start) && (mdur[m] == 0)) {
			barnums[m] = counter-1;
			continue;
		} else if (mdur[m] == 0) {
			barnums[m] = -1;
			continue;
		}
		if ((m < mcount-1) && (tsdur[m] == tsdur[m+1])) {
			if (mdur[m] + mdur[m+1] == tsdur[m]) {
				barnums[m] = -1;
			} else {
				barnums[m] = counter++;
			}
		} else {
			barnums[m] = counter++;
		}
	}
*/
}



//////////////////////////////
//
// HumGrid::getBarStyle --
//

string HumGrid::getBarStyle(GridMeasure* measure) {
	string output = "";
	if (measure->isDouble()) {
		output = "||";
	} else if (measure->isFinal()) {
		output = "=";
	} else if (measure->isInvisibleBarline()) {
		output = "-";
	} else if (measure->isRepeatBoth()) {
		output = ":|!|:";
	} else if (measure->isRepeatBackward()) {
		output = ":|!";
	} else if (measure->isRepeatForward()) {
		output = "!|:";
	}
	return output;
}



//////////////////////////////
//
// HumGrid::addLastMeasure --
//

void HumGrid::addLastMeasure(void) {
   // add the last measure, which will be only one voice
	// for each part/staff.
	GridSlice* model = this->back()->back();
	if (model == NULL) {
		return;
	}

	// probably not the correct timestamp, but probably not important
	// to get correct:
	HumNum timestamp = model->getTimestamp();

	if (this->empty()) {
		return;
	}
	GridMeasure* measure = this->back();

	string barstyle = getBarStyle(measure);

	GridSlice* mslice = new GridSlice(model->getMeasure(), timestamp,
			SliceType::Measures);
	this->back()->push_back(mslice);
	mslice->setTimestamp(timestamp);
	int partcount = (int)model->size();
	mslice->resize(partcount);
	for (int p=0; p<partcount; p++) {
		GridPart* part = new GridPart();
		mslice->at(p) = part;
		int staffcount = (int)model->at(p)->size();
		mslice->at(p)->resize(staffcount);
		for (int s=0; s<staffcount; s++) {
			GridStaff* staff = new GridStaff;
			mslice->at(p)->at(s) = staff;
			HTp token = new HumdrumToken("=" + barstyle);
			GridVoice* gv = new GridVoice(token, 0);
			mslice->at(p)->at(s)->push_back(gv);
		}
	}
}



//////////////////////////////
//
// HumGrid::buildSingleList --
//

bool HumGrid::buildSingleList(void) {
	m_allslices.resize(0);

	int gridcount = 0;
	for (auto it : (vector<GridMeasure*>)*this) {
		gridcount += (int)it->size();
	}
	m_allslices.reserve(gridcount + 100);
	for (int m=0; m<(int)this->size(); m++) {
		for (auto it : (list<GridSlice*>)*this->at(m)) {
			m_allslices.push_back(it);
		}
	}

	HumNum ts1;
	HumNum ts2;
	HumNum dur;
	for (int i=0; i<(int)m_allslices.size() - 1; i++) {
		ts1 = m_allslices[i]->getTimestamp();
		ts2 = m_allslices[i+1]->getTimestamp();
		dur = (ts2 - ts1); // whole-note units
		m_allslices[i]->setDuration(dur);
	}
	return !m_allslices.empty();
}



//////////////////////////////
//
// HumGrid::addNullTokensForGraceNotes -- Avoid grace notes at
//     starts of measures from contracting the subspine count.
//

void HumGrid::addNullTokensForGraceNotes(void) {
	// add null tokens for grace notes in other voices
	GridSlice *lastnote = NULL;
	GridSlice *nextnote = NULL;
	for (int i=0; i<(int)m_allslices.size(); i++) {
		if (!m_allslices[i]->isGraceSlice()) {
			continue;
		}
		// cerr << "PROCESSING " << m_allslices[i] << endl;
		lastnote = NULL;
		nextnote = NULL;

		for (int j=i+1; j<(int)m_allslices.size(); j++) {
			if (m_allslices[j]->isNoteSlice()) {
				nextnote = m_allslices[j];
				break;
			}
		}
		if (nextnote == NULL) {
			continue;
		}

		for (int j=i-1; j>=0; j--) {
			if (m_allslices[j]->isNoteSlice()) {
				lastnote = m_allslices[j];
				break;
			}
		}
		if (lastnote == NULL) {
			continue;
		}

		fillInNullTokensForGraceNotes(m_allslices[i], lastnote, nextnote);
	}
}



//////////////////////////////
//
// HumGrid::addNullTokensForLayoutComments -- Avoid layout in multi-subspine
//     regions from contracting to a single spine.
//

void HumGrid::addNullTokensForLayoutComments(void) {
	// add null tokens for key changes in other voices
	GridSlice *lastnote = NULL;
	GridSlice *nextnote = NULL;
	for (int i=0; i<(int)m_allslices.size(); i++) {
		if (!m_allslices[i]->isLocalLayoutSlice()) {
			continue;
		}
		// cerr << "PROCESSING " << m_allslices[i] << endl;
		lastnote = NULL;
		nextnote = NULL;

		for (int j=i+1; j<(int)m_allslices.size(); j++) {
			if (m_allslices[j]->isNoteSlice()) {
				nextnote = m_allslices[j];
				break;
			}
		}
		if (nextnote == NULL) {
			continue;
		}

		for (int j=i-1; j>=0; j--) {
			if (m_allslices[j]->isNoteSlice()) {
				lastnote = m_allslices[j];
				break;
			}
		}
		if (lastnote == NULL) {
			continue;
		}

		fillInNullTokensForLayoutComments(m_allslices[i], lastnote, nextnote);
	}
}



//////////////////////////////
//
// HumGrid::addNullTokensForClefChanges -- Avoid clef in multi-subspine
//     regions from contracting to a single spine.
//

void HumGrid::addNullTokensForClefChanges(void) {
	// add null tokens for clef changes in other voices
	GridSlice *lastnote = NULL;
	GridSlice *nextnote = NULL;
	for (int i=0; i<(int)m_allslices.size(); i++) {
		if (!m_allslices[i]->isClefSlice()) {
			continue;
		}
		// cerr << "PROCESSING " << m_allslices[i] << endl;
		lastnote = NULL;
		nextnote = NULL;

		for (int j=i+1; j<(int)m_allslices.size(); j++) {
			if (m_allslices[j]->isNoteSlice()) {
				nextnote = m_allslices[j];
				break;
			}
		}
		if (nextnote == NULL) {
			continue;
		}

		for (int j=i-1; j>=0; j--) {
			if (m_allslices[j]->isNoteSlice()) {
				lastnote = m_allslices[j];
				break;
			}
		}
		if (lastnote == NULL) {
			continue;
		}

		fillInNullTokensForClefChanges(m_allslices[i], lastnote, nextnote);
	}
}



//////////////////////////////
//
// HumGrid::fillInNullTokensForClefChanges --
//

void HumGrid::fillInNullTokensForClefChanges(GridSlice* clefslice,
		GridSlice* lastnote, GridSlice* nextnote) {

	if (clefslice == NULL) { return; }
	if (lastnote == NULL)  { return; }
	if (nextnote == NULL)  { return; }

	// cerr << "CHECKING CLEF SLICE: " << endl;
	// cerr << "\tclef\t" << clefslice << endl;
	// cerr << "\tlast\t" << lastnote << endl;
	// cerr << "\tnext\t" << nextnote << endl;

	int partcount = (int)clefslice->size();

	for (int p=0; p<partcount; p++) {
		int staffcount = (int)lastnote->at(p)->size();
		for (int s=0; s<staffcount; s++) {
			int v1count = (int)lastnote->at(p)->at(s)->size();
			int v2count = (int)nextnote->at(p)->at(s)->size();
			int vgcount = (int)clefslice->at(p)->at(s)->size();
			// if (vgcount < 1) {
			// 	vgcount = 1;
			// }
			if (v1count < 1) {
				v1count = 1;
			}
			if (v2count < 1) {
				v2count = 1;
			}
			// cerr << "p=" << p << "\ts=" << s << "\tv1count = " << v1count;
			// cerr << "\tv2count = " << v2count;
			// cerr << "\tvgcount = " << vgcount << endl;
			if (v1count != v2count) {
				// Note slices are expanding or contracting so do
				// not try to adjust clef slice between them.
				continue;
			}
			if (vgcount == v1count) {
				// Grace note slice does not need to be adjusted.
			}
			int diff = v1count - vgcount;
			// fill in a null for each empty slot in voice
			for (int i=0; i<diff; i++) {
				GridVoice* gv = createVoice("*", "P", 0, p, s);
				clefslice->at(p)->at(s)->push_back(gv);
			}
		}
	}
}



//////////////////////////////
//
// HumGrid::fillInNullTokensForLayoutComments --
//

void HumGrid::fillInNullTokensForLayoutComments(GridSlice* layoutslice,
		GridSlice* lastnote, GridSlice* nextnote) {

	if (layoutslice == NULL) { return; }
	if (lastnote == NULL)    { return; }
	if (nextnote == NULL)    { return; }

	// cerr << "CHECKING CLEF SLICE: " << endl;
	// cerr << "\tclef\t" << layoutslice << endl;
	// cerr << "\tlast\t" << lastnote << endl;
	// cerr << "\tnext\t" << nextnote << endl;

	int partcount = (int)layoutslice->size();
	int staffcount;
	int vgcount;
	int v1count;
	int v2count;

	for (int p=0; p<partcount; p++) {
		staffcount = (int)lastnote->at(p)->size();
		for (int s=0; s<staffcount; s++) {
			v1count = (int)lastnote->at(p)->at(s)->size();
			v2count = (int)nextnote->at(p)->at(s)->size();
			vgcount = (int)layoutslice->at(p)->at(s)->size();
			// if (vgcount < 1) {
			// 	vgcount = 1;
			// }
			if (v1count < 1) {
				v1count = 1;
			}
			if (v2count < 1) {
				v2count = 1;
			}
			// cerr << "p=" << p << "\ts=" << s << "\tv1count = " << v1count;
			// cerr << "\tv2count = " << v2count;
			// cerr << "\tvgcount = " << vgcount << endl;
			if (v1count != v2count) {
				// Note slices are expanding or contracting so do
				// not try to adjust clef slice between them.
				continue;
			}
			if (vgcount == v1count) {
				// Grace note slice does not need to be adjusted.
			}
			int diff = v1count - vgcount;
			// fill in a null for each empty slot in voice
			for (int i=0; i<diff; i++) {
				GridVoice* gv = new GridVoice("!", 0);
				layoutslice->at(p)->at(s)->push_back(gv);
			}
		}
	}
}



//////////////////////////////
//
// HumGrid::fillInNullTokensForGraceNotes --
//

void HumGrid::fillInNullTokensForGraceNotes(GridSlice* graceslice, GridSlice* lastnote,
		GridSlice* nextnote) {

	if (graceslice == NULL) {
		return;
	}
	if (lastnote == NULL) {
		return;
	}
	if (nextnote == NULL) {
		return;
	}

	// cerr << "CHECKING GRACE SLICE: " << endl;
	// cerr << "\tgrace\t" << graceslice << endl;
	// cerr << "\tlast\t" << lastnote << endl;
	// cerr << "\tnext\t" << nextnote << endl;

	int partcount = (int)graceslice->size();
	int staffcount;
	int vgcount;
	int v1count;
	int v2count;

	for (int p=0; p<partcount; p++) {
		staffcount = (int)lastnote->at(p)->size();
		for (int s=0; s<staffcount; s++) {
			v1count = (int)lastnote->at(p)->at(s)->size();
			v2count = (int)nextnote->at(p)->at(s)->size();
			vgcount = (int)graceslice->at(p)->at(s)->size();
			// if (vgcount < 1) {
			// 	vgcount = 1;
			// }
			if (v1count < 1) {
				v1count = 1;
			}
			if (v2count < 1) {
				v2count = 1;
			}
			// cerr << "p=" << p << "\ts=" << s << "\tv1count = " << v1count;
			// cerr << "\tv2count = " << v2count;
			// cerr << "\tvgcount = " << vgcount << endl;
			if (v1count != v2count) {
				// Note slices are expanding or contracting so do
				// not try to adjust grace slice between them.
				continue;
			}
			if (vgcount == v1count) {
				// Grace note slice does not need to be adjusted.
			}
			int diff = v1count - vgcount;
			// fill in a null for each empty slot in voice
			for (int i=0; i<diff; i++) {
				GridVoice* gv = new GridVoice(".", 0);
				graceslice->at(p)->at(s)->push_back(gv);
			}
		}
	}
}



//////////////////////////////
//
// HumGrid::addNullTokens --
//

void HumGrid::addNullTokens(void) {
	int i; // slice index
	int p; // part index
	int s; // staff index
	int v; // voice index

	if ((0)) {
		cerr << "SLICE TIMESTAMPS: " << endl;
		for (int x=0; x<(int)m_allslices.size(); x++) {
			cerr << "\tTIMESTAMP " << x << "= "
			     << m_allslices[x]->getTimestamp()
			     << "\tDUR=" << m_allslices[x]->getDuration()
			     << "\t"
			     << m_allslices[x]
			     << endl;
		}
	}

	for (i=0; i<(int)m_allslices.size(); i++) {

		GridSlice& slice = *m_allslices.at(i);
		if (!slice.isNoteSlice()) {
			// probably need to deal with grace note slices here
			continue;
		}
      for (p=0; p<(int)slice.size(); p++) {
			GridPart& part = *slice.at(p);
      	for (s=0; s<(int)part.size(); s++) {
				GridStaff& staff = *part.at(s);
      		for (v=0; v<(int)staff.size(); v++) {
					if (!staff.at(v)) {
						// in theory should not happen
						continue;
					}
					GridVoice& gv = *staff.at(v);
					if (gv.isNull()) {
						continue;
					}
					// found a note/rest which should have a non-zero
					// duration that needs to be extended to the next
					// duration in the
					extendDurationToken(i, p, s, v);
				}
			}
		}

	}

	addNullTokensForGraceNotes();
	adjustClefChanges();
	addNullTokensForClefChanges();
	addNullTokensForLayoutComments();
	checkForNullDataHoles();
}



//////////////////////////////
//
// HumGrid::checkForNullData -- identify any spots in the grid which are NULL
//     pointers and allocate invisible rests for them by finding the next
//     durational item in the particular staff/layer.
//

void HumGrid::checkForNullDataHoles(void) {
	for (int i=0; i<(int)m_allslices.size(); i++) {
		GridSlice& slice = *m_allslices.at(i);
		if (!slice.isNoteSlice()) {
			continue;
		}
      for (int p=0; p<(int)slice.size(); p++) {
			GridPart& part = *slice.at(p);
      	for (int s=0; s<(int)part.size(); s++) {
				GridStaff& staff = *part.at(s);
      		for (int v=0; v<(int)staff.size(); v++) {
					if (!staff.at(v)) {
						staff.at(v) = new GridVoice();
						// Calculate duration of void by searching
						// for the next non-null voice in the current part/staff/voice
						HumNum duration = slice.getDuration();
						GridPart *pp;
						GridStaff *sp;
						GridVoice *vp;
						for (int q=i+1; q<(int)m_allslices.size(); q++) {
							GridSlice *slicep = m_allslices.at(q);
							if (!slicep->isNoteSlice()) {
								// or isDataSlice()?
								continue;
							}
							if (p >= (int)slicep->size() - 1) {
								continue;
							}
							pp = slicep->at(p);
							if (s >= (int)pp->size() - 1) {
								continue;
							}
							sp = pp->at(s);
							if (v >= (int)sp->size() - 1) {
								// Found a data line with no data at given voice, so
								// add slice duration to cumulative duration.
								// duration += slicep->getDuration();
								continue;
							}
							vp = sp->at(v);
							if (!vp) {
								// found another null spot which should be dealt with later.
								break;
							} else {
								// there is a token at the same part/staff/voice position.
								// Maybe check if a null token, but if not a null token,
								// then break here also.
								break;
							}
						}
						string recip = Convert::durationToRecip(duration);
						// ggg @ marker is added to keep track of them for more debugging.
						recip += "ryy@";
						staff.at(v)->setToken(recip);
						continue;
					}
				}
			}
		}
	}
}



//////////////////////////////
//
// HumGrid::setPartStaffDimensions --
//

void HumGrid::setPartStaffDimensions(vector<vector<GridSlice*>>& nextevent,
		GridSlice* startslice) {
	nextevent.clear();
	for (int i=0; i<(int)m_allslices.size(); i++) {
		if (!m_allslices[i]->isNoteSlice()) {
			continue;
		}
		GridSlice* slice = m_allslices[i];
		nextevent.resize(slice->size());
		for (int p=0; p<(int)slice->size(); p++) {
			nextevent.at(p).resize(slice->at(p)->size());
			for (int j=0; j<(int)nextevent.at(p).size(); j++) {
				nextevent.at(p).at(j) = startslice;
			}
		}
		break;
	}
}



//////////////////////////////
//
// HumGrid::addInvisibleRestsInFirstTrack --  If there are any
//    timing gaps in the first track of a **kern spine, then
//    fill in with invisible rests.
//
// ggg

void HumGrid::addInvisibleRestsInFirstTrack(void) {
	int i; // slice index
	int p; // part index
	int s; // staff index
	int v = 0; // only looking at first voice

	vector<vector<GridSlice*>> nextevent;
	GridSlice* lastslice = m_allslices.back();
	setPartStaffDimensions(nextevent, lastslice);

	for (i=(int)m_allslices.size()-1; i>=0; i--) {
		GridSlice& slice = *m_allslices.at(i);
		if (!slice.isNoteSlice()) {
			continue;
		}
      for (p=0; p<(int)slice.size(); p++) {
			GridPart& part = *slice.at(p);
      	for (s=0; s<(int)part.size(); s++) {
				GridStaff& staff = *part.at(s);
				if (staff.size() == 0) {
					// cerr << "EMPTY STAFF VOICE WILL BE FILLED IN LATER!!!!" << endl;
					continue;
				}
				if (!staff.at(v)) {
					// in theory should not happen
					continue;
				}
				GridVoice& gv = *staff.at(v);
				if (gv.isNull()) {
					continue;
				}

				// Found a note/rest.  Check if its duration matches
				// the next non-null data token.  If not, then add
				// an invisible rest somewhere between the two

				// first check to see if the previous item is a
				// NULL.  If so, then store and continue.
				if (nextevent[p][s] == NULL) {
					nextevent[p][s] = &slice;
					continue;
				}
				addInvisibleRest(nextevent, i, p, s);
			}
		}
	}
}



//////////////////////////////
//
// HumGrid::addInvisibleRest --
//

void HumGrid::addInvisibleRest(vector<vector<GridSlice*>>& nextevent,
		int index, int p, int s) {
	GridSlice *ending = nextevent.at(p).at(s);
	if (ending == NULL) {
		cerr << "Not handling this case yet at end of data." << endl;
		return;
	}
	HumNum endtime = ending->getTimestamp();

	GridSlice* starting = m_allslices.at(index);
	HumNum starttime = starting->getTimestamp();
	HTp token = starting->at(p)->at(s)->at(0)->getToken();
	HumNum duration = Convert::recipToDuration(token);
	if (duration == 0) {
		// Do not deal with zero duration items (maybe **mens data)
		return;
	}
	HumNum difference = endtime - starttime;
	HumNum gap = difference - duration;
	if (gap == 0) {
		// nothing to do
		nextevent.at(p).at(s) = starting;
		return;
	}
	HumNum target = starttime + duration;

	string kern = Convert::durationToRecip(gap);
	kern += "ryy";

	for (int i=index+1; i<(int)m_allslices.size(); i++) {
		GridSlice* slice = m_allslices[i];
		if (!slice->isNoteSlice()) {
			continue;
		}
		HumNum timestamp = slice->getTimestamp();
		if (timestamp < target) {
			continue;
		}
		if (timestamp > target) {
			cerr << "Cannot deal with this slice addition case yet for invisible rests..." << endl;
			cerr << "\tTIMESTAMP = " << timestamp << "\t>\t" << target << endl;
			nextevent[p][s] = starting;
			return;
		}
		// At timestamp for adding new token.
		if ((m_allslices.at(i)->at(p)->at(s)->size() > 0) && !m_allslices.at(i)->at(p)->at(s)->at(0)) {
			// Element is null where an invisible rest should be
			// so allocate space for it.
			m_allslices.at(i)->at(p)->at(s)->at(0) = new GridVoice();
		}
		if (m_allslices.at(i)->at(p)->at(s)->size() > 0) {
			m_allslices.at(i)->at(p)->at(s)->at(0)->setToken(kern);
		}
		break;
	}

	// Store the current event in the buffer
	nextevent.at(p).at(s) = starting;
}



//////////////////////////////
//
// HumGrid::adjustClefChanges -- If a clef change starts at the
//     beginning of a meausre, move it to before the measure (unless
//     the measure has zero duration).
//

void HumGrid::adjustClefChanges(void) {
	vector<GridMeasure*>& measures = *this;
	for (int i=1; i<(int)measures.size(); i++) {
		auto it = measures[i]->begin();
		if ((*it) == NULL) {
			cerr << "Warning: GridSlice is null in GridMeasure " << i << endl;
			continue;
		}
		if ((*it)->empty()) {
			cerr << "Warning: GridSlice is empty in GridMeasure "  << i << endl;
			continue;
		}
		if (!(*it)->isClefSlice()) {
			continue;
		}
		// move clef to end of previous measure
		GridSlice* tempslice = *it;
		measures[i]->pop_front();
		measures[i-1]->push_back(tempslice);
	}
}



//////////////////////////////
//
// HumGrid::extendDurationToken --
//

void HumGrid::extendDurationToken(int slicei, int parti, int staffi,
		int voicei) {
	if ((slicei < 0) || (slicei >= ((int)m_allslices.size()) - 1)) {
		// nothing after this line, so can extend further.
		return;
	}

	if (!m_allslices.at(slicei)->hasSpines()) {
		// no extensions needed in non-spined slices.
		return;
	}

	if (m_allslices.at(slicei)->isGraceSlice()) {
		cerr << "THIS IS A GRACE SLICE SO DO NOT FILL" << endl;
		return;
	}

	GridVoice* gv = m_allslices.at(slicei)->at(parti)->at(staffi)->at(voicei);
 	HTp token = gv->getToken();
	if (!token) {
		cerr << "STRANGE: token should not be null" << endl;
		return;
	}
	if (*token == ".") {
		// null data token so ignore;
		// change this later to add a duration for the null token below.
		return;
	}

	HumNum tokendur = Convert::recipToDuration((string)*token);
	HumNum currts   = m_allslices.at(slicei)->getTimestamp();
	HumNum nextts   = m_allslices.at(slicei+1)->getTimestamp();
	HumNum slicedur = nextts - currts;
	HumNum timeleft = tokendur - slicedur;

	if (tokendur == 0) {
		// Do not try to extend tokens with zero duration
		// These are most likely **mens notes.
		return;
	}

	if ((0)) {
		cerr << "===================" << endl;
		cerr << "EXTENDING TOKEN    " << token      << endl;
		cerr << "\tTOKEN DUR:       " << tokendur   << endl;
		cerr << "\tTOKEN START:     " << currts     << endl;
		cerr << "\tSLICE DUR:       " << slicedur   << endl;
		cerr << "\tNEXT SLICE START:" << nextts     << endl;
		cerr << "\tTIME LEFT:       " << timeleft   << endl;
		cerr << "\t-----------------" << endl;
	}

	if (timeleft != 0) {
		// fill in null tokens for the required duration.
		if (timeleft < 0) {
			cerr << "ERROR: Negative duration: " << timeleft << endl;
			cerr << "\ttokendur = " << tokendur << endl;
			cerr << "\tslicedur = " << slicedur << endl;
			cerr << "\ttoken    = " << token << endl;
			cerr << "\tCURRENT SLICE = " << m_allslices.at(slicei) << endl;
			cerr << "\tTIMESTAMP " << currts << endl;
			cerr << "\tNEXT SLICE = " << m_allslices.at(slicei) << endl;
			cerr << "\tNEXT TIMESTAMP " << nextts << endl;
			return;
		}

		SliceType type;
		GridStaff* gs;
		int s = slicei+1;

		while ((s < (int)m_allslices.size()) && (timeleft > 0)) {
			if (!m_allslices.at(s)->hasSpines()) {
				s++;
				continue;
			}
			currts = nextts;
			int nexts = 1;
			while (s < (int)m_allslices.size() - nexts) {
				if (!m_allslices.at(s+nexts)->hasSpines()) {
					nexts++;
					continue;
				}
				break;
			}
			if (s < (int)m_allslices.size() - nexts) {
				nextts = m_allslices.at(s+nexts)->getTimestamp();
			} else {
				nextts = currts + m_allslices.at(s)->getDuration();
			}
			slicedur = nextts - currts;
			type = m_allslices[s]->getType();

			if (staffi == (int)m_allslices.at(s)->at(parti)->size()) {
					cerr << "WARNING: staff index " << staffi << " is probably incorrect: increasing staff count for part to " << staffi + 1 << endl;
					m_allslices.at(s)->at(parti)->resize(m_allslices.at(s)->at(parti)->size() + 1);
					m_allslices.at(s)->at(parti)->at(staffi) = new GridStaff();
			}
			gs = m_allslices.at(s)->at(parti)->at(staffi);
			if (gs == NULL) {
				cerr << "Strange error6 in extendDurationToken()" << endl;
				return;
			}

			if (m_allslices.at(s)->isGraceSlice()) {
				m_allslices[s]->setDuration(0);
			} else if (m_allslices.at(s)->isDataSlice()) {
				gs->setNullTokenLayer(voicei, type, slicedur);
				timeleft = timeleft - slicedur;
			} else if (m_allslices.at(s)->isInvalidSlice()) {
				cerr << "THIS IS AN INVALID SLICE" << m_allslices.at(s) << endl;
			} else {
				// store a null token for the non-data slice, but probably skip
				// if there is a token already there (such as a clef-change).
				if ((voicei < (int)gs->size()) && (gs->at(voicei) != NULL)) {
					// there is already a token here, so do not replace it.
					// cerr << "Not replacing token: "  << gs->at(voicei)->getToken() << endl;
				} else {
					gs->setNullTokenLayer(voicei, type, slicedur);
				}
			}
			s++;
			if (s == (int)m_allslices.size() - 1) {
				m_allslices[s]->setDuration(timeleft);
			}
		}
	}
	// walk through zero-dur items and fill them in, but stop at
	// a token (likely a grace note which should not be erased).
}



//////////////////////////////
//
// HumGrid::getGridVoice -- Check to see if GridVoice exists, returns
//    NULL otherwise. Requires HumGrid::buildSingleList() being run first.
//

GridVoice* HumGrid::getGridVoice(int slicei, int parti, int staffi,
		int voicei) {
	if (slicei >= (int)m_allslices.size()) {
		cerr << "Strange error 1a" << endl;
		return NULL;
	}
	GridSlice* gsl = m_allslices.at(slicei);
	if (gsl == NULL) {
		cerr << "Strange error 1b" << endl;
		return NULL;
	}

	if (parti >= (int)gsl->size()) {
		cerr << "Strange error 2a" << endl;
		return NULL;
	}
	GridPart* gp = gsl->at(parti);
	if (gp == NULL) {
		cerr << "Strange error 2" << endl;
		return NULL;
	}

	if (staffi >= (int)gp->size()) {
		cerr << "Strange error 3a" << endl;
		return NULL;
	}
	GridStaff* gst = gp->at(staffi);
	if (gst == NULL) {
		cerr << "Strange error 3b" << endl;
		return NULL;
	}

	if (voicei >= (int)gst->size()) {
		cerr << "Strange error 4a" << endl;
		return NULL;
	}
	GridVoice* gv = gst->at(voicei);
	if (gv == NULL) {
		cerr << "Strange error 4b" << endl;
		return NULL;
	}
	return gv;
}



//////////////////////////////
//
// HumGrid::calculateGridDurations --
//

void HumGrid::calculateGridDurations(void) {

	// the last line has to be calculated from the shortest or
   // longest duration on the line.  Acutally all durations
	// starting on this line must be the same, so just search for
	// the first duration.

	auto last = m_allslices.back();

	// set to zero in case not a duration type of line:
	last->setDuration(0);

	bool finished = false;
	if (last->isNoteSlice()) {
		for (auto part : *last) {
			for (auto staff : *part) {
				for (auto voice : *staff) {
					if (!voice) {
						continue;
					}
					if (voice->getDuration() > 0) {
						last->setDuration(voice->getDuration());
						finished = true;
						break;
					}
				}
				if (finished) {
					break;
				}
			}
			if (finished) {
				break;
			}
		}
	}
}



//////////////////////////////
//
// HumGrid::insertExclusiveInterpretationLine -- Currently presumes
//    that the first entry contains spines.  And the first measure
//    in the HumGrid object must contain a slice.
//

void HumGrid::insertExclusiveInterpretationLine(HumdrumFile& outfile, const string& interp) {
	if (this->size() == 0) {
		return;
	}
	if (this->at(0)->empty()) {
		return;
	}

	HLp line = new HumdrumLine;
	HTp token;

	if (m_recip) {
		token = new HumdrumToken("**recip");
		line->appendToken(token);
	}

	GridSlice& slice = *this->at(0)->front();
	int p; // part index
	int s; // staff index
	for (p=(int)slice.size()-1; p>=0; p--) {
		GridPart& part = *slice[p];
		for (s=(int)part.size()-1; s>=0; s--) {
			token = new HumdrumToken(interp);
			line->appendToken(token);
			insertExInterpSides(line, p, s); // insert staff sides
		}
		insertExInterpSides(line, p, -1);   // insert part sides
	}
	outfile.insertLine(0, line);
}



//////////////////////////////
//
// HumGrid::insertExInterpSides --
//

void HumGrid::insertExInterpSides(HLp line, int part, int staff) {

	if (staff >= 0) {
		int xmlidcount = getXmlidCount(part); // xmlids related to staff
		for (int i=0; i<xmlidcount; i++) {
			HTp token = new HumdrumToken("**xmlid");
			line->appendToken(token);
		}
	}

	if (staff >= 0) {
		int versecount = getVerseCount(part, staff); // verses related to staff
		for (int i=0; i<versecount; i++) {
			HTp token = new HumdrumToken("**text");
			line->appendToken(token);
		}
	}

	if ((staff < 0) && hasDynamics(part)) {
		HTp token = new HumdrumToken("**dynam");
		line->appendToken(token);
	}

	if ((staff < 0) && hasFiguredBass(part)) {
		HTp token = new HumdrumToken("**fb");
		line->appendToken(token);
	}

	if (staff < 0) {
		int harmonyCount = getHarmonyCount(part);
		for (int i=0; i<harmonyCount; i++) {
			HTp token = new HumdrumToken("**mxhm");
			line->appendToken(token);
		}

	}
}



//////////////////////////////
//
// HumGrid::insertPartNames --
//

void HumGrid::insertPartNames(HumdrumFile& outfile) {
	if (m_partnames.size() == 0) {
		return;
	}
	HLp line = new HumdrumLine;
	HTp token;

	if (m_recip) {
		token = new HumdrumToken("*");
		line->appendToken(token);
	}

	string text;
	GridSlice& slice = *this->at(0)->front();
	int p; // part index
	int s; // staff index
	for (p=(int)slice.size()-1; p>=0; p--) {
		GridPart& part = *slice[p];
		for (s=(int)part.size()-1; s>=0; s--) {
			text = "*";
			string pname = m_partnames[p];
			if (!pname.empty()) {
				text += "I\"";
				text += pname;
			}
			token = new HumdrumToken(text);
			line->appendToken(token);
			insertSideNullInterpretations(line, p, s);
		}
		insertSideNullInterpretations(line, p, -1);
	}
	outfile.insertLine(0, line);
}



//////////////////////////////
//
// HumGrid::insertPartIndications -- Currently presumes
//    that the first entry contains spines.  And the first measure
//    in the HumGrid object must contain a slice.  This is the
//    MusicXML Part number. (Some parts will contain more than one
//    staff).
//

void HumGrid::insertPartIndications(HumdrumFile& outfile) {

	if (this->size() == 0) {
		return;
	}
	if (this->at(0)->empty()) {
		return;
	}
	HLp line = new HumdrumLine;
	HTp token;

	if (m_recip) {
		token = new HumdrumToken("*");
		line->appendToken(token);
	}

	string text;
	GridSlice& slice = *this->at(0)->front();
	int p; // part index
	int s; // staff index
	for (p=(int)slice.size()-1; p>=0; p--) {
		GridPart& part = *slice[p];
		for (s=(int)part.size()-1; s>=0; s--) {
			text = "*part" + to_string(p+1);
			token = new HumdrumToken(text);
			line->appendToken(token);
			insertSidePartInfo(line, p, s);
		}
		insertSidePartInfo(line, p, -1);   // insert part sides
	}
	outfile.insertLine(0, line);

}



//////////////////////////////
//
// HumGrid::insertSideNullInterpretations --
//

void HumGrid::insertSideNullInterpretations(HLp line,
		int part, int staff) {
	HTp token;
	string text;

	if (staff < 0) {

		if (hasDynamics(part)) {
			token = new HumdrumToken("*");
			line->appendToken(token);
		}

		if (hasFiguredBass(part)) {
			token = new HumdrumToken("*");
			line->appendToken(token);
		}

		int harmcount = getHarmonyCount(part);
		for (int i=0; i<harmcount; i++) {
			token = new HumdrumToken("*");
			line->appendToken(token);
		}

	} else {

		int xmlidcount = getXmlidCount(part);
		for (int i=0; i<xmlidcount; i++) {
			token = new HumdrumToken("*");
			line->appendToken(token);
		}

		int versecount = getVerseCount(part, staff);
		for (int i=0; i<versecount; i++) {
			token = new HumdrumToken("*");
			line->appendToken(token);
		}

	}
}



//////////////////////////////
//
// HumGrid::insertSidePartInfo --
//

void HumGrid::insertSidePartInfo(HLp line, int part, int staff) {
	HTp token;
	string text;

	if (staff < 0) {

		if (hasDynamics(part)) {
			text = "*part" + to_string(part+1);
			token = new HumdrumToken(text);
			line->appendToken(token);
		}

		if (hasFiguredBass(part)) {
			text = "*part" + to_string(part+1);
			token = new HumdrumToken(text);
			line->appendToken(token);
		}

		int harmcount = getHarmonyCount(part);
		for (int i=0; i<harmcount; i++) {
			text = "*part" + to_string(part+1);
			token = new HumdrumToken(text);
			line->appendToken(token);
		}

	} else {

		int xmlidcount = getXmlidCount(part);
		for (int i=0; i<xmlidcount; i++) {
			text = "*part" + to_string(part+1);
			token = new HumdrumToken(text);
			line->appendToken(token);
		}

		int versecount = getVerseCount(part, staff);
		for (int i=0; i<versecount; i++) {
			text = "*part" + to_string(part+1);
			token = new HumdrumToken(text);
			line->appendToken(token);
		}

	}
}



//////////////////////////////
//
// HumGrid::insertStaffIndications -- Currently presumes
//    that the first entry contains spines.  And the first measure
//    in the HumGrid object must contain a slice.  This is the
//    MusicXML Part number. (Some parts will contain more than one
//    staff).
//

void HumGrid::insertStaffIndications(HumdrumFile& outfile) {
	if (this->size() == 0) {
		return;
	}
	if (this->at(0)->empty()) {
		return;
	}

	HLp line = new HumdrumLine;
	HTp token;

	if (m_recip) {
		token = new HumdrumToken("*");
		line->appendToken(token);
	}

	string text;
	GridSlice& slice = *this->at(0)->front();
	int p; // part index
	int s; // staff index

	int staffcount = 0;
	for (p=0; p<(int)slice.size(); p++) {
		GridPart& part = *slice[p];
		staffcount += (int)part.size();
	}

	for (p=(int)slice.size()-1; p>=0; p--) {
		GridPart& part = *slice[p];
		for (s=(int)part.size()-1; s>=0; s--) {
			text = "*staff" + to_string(staffcount--);
			token = new HumdrumToken(text);
			line->appendToken(token);
			insertSideStaffInfo(line, p, s, staffcount+1);
		}
		insertSideStaffInfo(line, p, -1, -1);  // insert part sides
	}
	outfile.insertLine(0, line);
}



//////////////////////////////
//
// HumGrid::insertSideStaffInfo --
//

void HumGrid::insertSideStaffInfo(HLp line, int part, int staff,
		int staffnum) {
	HTp token;
	string text;

	// part-specific sides (no staff markers)
	if (staffnum < 0) {

		if (hasDynamics(part)) {
			token = new HumdrumToken("*");
			line->appendToken(token);
		}

		if (hasFiguredBass(part)) {
			token = new HumdrumToken("*");
			line->appendToken(token);
		}

		int harmcount = getHarmonyCount(part);
		for (int i=0; i<harmcount; i++) {
			token = new HumdrumToken("*");
			line->appendToken(token);
		}

		return;
	}

	int xmlidcount = getXmlidCount(part);
	for (int i=0; i<xmlidcount; i++) {
		if (staffnum > 0) {
			text = "*staff" + to_string(staffnum);
			token = new HumdrumToken(text);
		} else {
			token = new HumdrumToken("*");
		}
		line->appendToken(token);
	}

	int versecount = getVerseCount(part, staff);
	for (int i=0; i<versecount; i++) {
		if (staffnum > 0) {
			text = "*staff" + to_string(staffnum);
			token = new HumdrumToken(text);
		} else {
			token = new HumdrumToken("*");
		}
		line->appendToken(token);
	}


}



//////////////////////////////
//
// HumGrid::insertDataTerminationLine -- Currently presumes
//    that the last entry contains spines.  And the first
//    measure in the HumGrid object must contain a slice.
//    Also need to compensate for *v on previous line.
//

void HumGrid::insertDataTerminationLine(HumdrumFile& outfile) {
	if (this->size() == 0) {
		return;
	}
	if (this->at(0)->empty()) {
		return;
	}
	HLp line = new HumdrumLine;
	HTp token;

	if (m_recip) {
		token = new HumdrumToken("*-");
		line->appendToken(token);
	}

	GridSlice& slice = *this->at(0)->back();
	int p; // part index
	int s; // staff index
	for (p=(int)slice.size()-1; p>=0; p--) {
		GridPart& part = *slice[p];
		for (s=(int)part.size()-1; s>=0; s--) {
			token = new HumdrumToken("*-");
			line->appendToken(token);
			insertSideTerminals(line, p, s);
		}
		insertSideTerminals(line, p, -1);   // insert part sides
	}
	outfile.appendLine(line);
}



//////////////////////////////
//
// HumGrid::insertSideTerminals --
//

void HumGrid::insertSideTerminals(HLp line, int part, int staff) {
	HTp token;

	if (staff < 0) {

		if (hasDynamics(part)) {
			token = new HumdrumToken("*-");
			line->appendToken(token);
		}

		if (hasFiguredBass(part)) {
			token = new HumdrumToken("*-");
			line->appendToken(token);
		}

		int harmcount = getHarmonyCount(part);
		for (int i=0; i<harmcount; i++) {
			token = new HumdrumToken("*-");
			line->appendToken(token);
		}

	} else {

		if (hasXmlids(part)) {
			token = new HumdrumToken("*-");
			line->appendToken(token);
		}

		int versecount = getVerseCount(part, staff);
		for (int i=0; i<versecount; i++) {
			token = new HumdrumToken("*-");
			line->appendToken(token);
		}
	}
}



//////////////////////////////
//
// HumGrid::transferNonDataSlices --
//

void HumGrid::transferNonDataSlices(GridMeasure* output, GridMeasure* input) {
	for (auto it = input->begin(); it != input->end(); it++) {
		GridSlice* slice = *it;
		if (slice->isDataSlice()) {
			continue;
		}
		output->push_front(slice);
		auto it2 = it;
		it--;
		input->erase(it2);
	}
}



//////////////////////////////
//
// HumGrid::removeSibeliusIncipit --
//

void HumGrid::removeSibeliusIncipit(void) {

	if (this->size() == 0) {
		return;
	}
	GridMeasure* measure = this->at(0);
	bool invisible = measure->isInvisible();
	if (!invisible) {
		return;
	}

	this->erase(this->begin());
	if (this->size() > 0) {
		// [20171012] remove this for now since it is crashing
		// emscripten version of code.
		// transferNonDataSlices(this->at(0), measure);
	}
	delete measure;
	measure = NULL;

	// remove vocal ranges, if present
	if (this->size() == 0) {
		return;
	}

	measure = this->at(0);
	bool singlechord = measure->isSingleChordMeasure();
	if (!singlechord) {
		return;
	}

	this->erase(this->begin());
	if (this->size() > 0) {
		transferNonDataSlices(this->at(0), measure);
	}
	delete measure;
	measure = NULL;

	measure = this->at(0);
	bool monophonic = measure->isMonophonicMeasure();
	if (!monophonic) {
		return;
	}

	string melody = extractMelody(measure);

	this->erase(this->begin());
	if (this->size() > 0) {
		transferNonDataSlices(this->at(0), measure);
	}
	delete measure;
	measure = NULL;

	if (this->size() > 0) {
		insertMelodyString(this->at(0), melody);
	}

}



//////////////////////////////
//
// HumGrid::insertMelodyString -- Insert a global comment before first data line.
//

void HumGrid::insertMelodyString(GridMeasure* measure, const string& melody) {
	for (auto it = measure->begin(); it != measure->end(); it++) {
		GridSlice* slice = *it;
		if (!slice->isDataSlice()) {
			continue;
		}

		// insert a new GridSlice
		// first need to implement global commands in GridSlice object...
		break;
	}
}



//////////////////////////////
//
// HumGrid::extractMelody --
//

string HumGrid::extractMelody(GridMeasure* measure) {
	string output = "!!";

	int parti  = -1;
	int staffi = -1;
	int voicei = -1;

	// First find the part which has the melody:
	for (auto slice : *measure) {
		if (!slice->isDataSlice()) {
			continue;
		}
		for (int p=0; p<(int)slice->size(); p++) {
			GridPart* part = slice->at(p);
			for (int s=0; s<(int)part->size(); s++) {
				GridStaff* staff = part->at(s);
				for (int v=0; v<(int)staff->size(); v++) {
					GridVoice* voice = staff->at(v);
					HTp token = voice->getToken();
					if (!token) {
						continue;
					}
					if (token->find("yy") == string::npos) {
						parti  = p;
						staffi = s;
						voicei = v;
						goto loop_end;
					}
				}
			}
		}
	}

	loop_end:

	if (parti < 0) {
		return output;
	}

	// First find the part which has the melody:
	for (auto slice : *measure) {
		if (!slice->isDataSlice()) {
			continue;
		}
		HTp token = slice->at(parti)->at(staffi)->at(voicei)->getToken();
		if (!token) {
			continue;
		}
		if (*token == ".") {
			continue;
		}
		output += " ";
		output += *token;
	}

	return output;
}




//////////////////////////////
//
// HumGrid::removeRedundantClefChanges -- Will also have to consider
//		the meter signature.
//

void HumGrid::removeRedundantClefChanges(void) {
	// curclef is a list of the current staff on the part:staff.
	vector<vector<string> > curclef;

	bool hasduplicate = false;
	for (int m=0; m<(int)this->size(); m++) {
		GridMeasure* measure = this->at(m);
		for (auto slice : *measure) {
			if (!slice->isClefSlice()) {
				continue;
			}
			bool allempty = true;
			for (int p=0; p<(int)slice->size(); p++) {
				for (int s=0; s<(int)slice->at(p)->size(); s++) {
					if (slice->at(p)->at(s)->size() < 1) {
						continue;
					}
					GridVoice* voice = slice->at(p)->at(s)->at(0);
					HTp token = voice->getToken();
					if (!token) {
						continue;
					}
					if (string(*token) == "*") {
						continue;
					}
					if (token->find("clef") == string::npos) {
						// something (probably invalid) which is not a clef change
						allempty = false;
						continue;
					}
					if (p >= (int)curclef.size()) {
						curclef.resize(p+1);
					}
					if (s >= (int)curclef[p].size()) {
						// first clef on the staff, so can't be a duplicate
						curclef[p].resize(s+1);
						curclef[p][s] = *token;
						allempty = false;
						continue;
					} else {
						if (curclef[p][s] == (string)*token) {
							// clef is already active, so remove this one
							hasduplicate = true;
							voice->setToken("*");
						} else {
							// new clef change
							curclef[p][s] = *token;
							allempty = false;
						}
					}
				}
			}
			if (!hasduplicate) {
				continue;
			}
			// Check the slice to see if it empty, and delete if so.
			// This algorithm does not consider GridSide content.
			if (allempty) {
				slice->invalidate();
			}

		}
	}
}



//////////////////////////////
//
// HumGrid::cleanTempos --
//

void HumGrid::cleanTempos(void) {
//		std::vector<GridSlice*>       m_allslices;
	for (int i=0; i<(int)m_allslices.size(); i++) {
		if (!m_allslices[i]->isTempoSlice()) {
			continue;
		}
		cleanTempos(m_allslices[i]);
	}
}


void HumGrid::cleanTempos(GridSlice* slice) {
	if (!slice->isTempoSlice()) {
		return;
	}
	HTp token = NULL;

	for (int part=0; part<(int)slice->size(); part++) {
		GridPart* gp = slice->at(part);
		for (int staff=0; staff<(int)gp->size(); staff++) {
			GridStaff* gs = gp->at(staff);
			for (int voice=0; voice<(int)gs->size(); voice++) {
				GridVoice* gv = gs->at(voice);
				token = gv->getToken();
				if (token) {
					break;
				}
			}
			if (token) {
				break;
			}
		}
		if (token) {
			break;
		}
	}

	if (!token) {
		return;
	}

	for (int part=0; part<(int)slice->size(); part++) {
		GridPart* gp = slice->at(part);
		for (int staff=0; staff<(int)gp->size(); staff++) {
			GridStaff* gs = gp->at(staff);
			for (int voice=0; voice<(int)gs->size(); voice++) {
				GridVoice* gv = gs->at(voice);
				if (gv->getToken()) {
					continue;
				}
				gv->setToken(*token);
			}
		}
	}
}



//////////////////////////////
//
// HumGrid::hasPickup --
//

bool HumGrid::hasPickup(void) {
	return m_pickup;
}



//////////////////////////////
//
// HumGrid::deleteMeasure --
//

void HumGrid::deleteMeasure(int index) {
	delete this->at(index);
	this->at(index) = NULL;
	this->erase(this->begin() + index);
}



//////////////////////////////
//
// HumGrid::expandLocalCommentLayers -- Walk backwards in the
//   data list, and match the layer count for local comments
//   to have them match to the next data line.  This is needed
//   to attach layout parameters properly to data tokens.  Layout
//   parameters cannot pass through spine manipulator lines, so
//   this function is necessary to prevent spine manipulators
//   from orphaning local layout parameter lines.
//
//   For now just adjust local layout parameter slices, but maybe
//   later do all types of local comments.
//

void HumGrid::expandLocalCommentLayers(void) {
	GridSlice *dataslice = NULL;
	GridSlice *localslice = NULL;
	for (int i=(int)m_allslices.size() - 1; i>=0; i--) {
		if (m_allslices[i]->isDataSlice()) {
			dataslice = m_allslices[i];
		} else if (m_allslices[i]->isMeasureSlice()) {
			dataslice = m_allslices[i];
		}
		// Other slice types should be considered as well,
		// but definitely not manipulator slices:
		if (m_allslices[i]->isManipulatorSlice()) {
			dataslice = m_allslices[i];
		}

		if (!m_allslices[i]->isLocalLayoutSlice()) {
			continue;
		}
		localslice = m_allslices[i];
		if (!dataslice) {
			continue;
		}
		matchLayers(localslice, dataslice);
	}
}


//////////////////////////////
//
// HumGrid::matchLayers -- Make sure every staff in both inputs
//   have the same number of voices.
//

void HumGrid::matchLayers(GridSlice* output, GridSlice* input) {
	if (output->size() != input->size()) {
		// something wrong or one of the slices
		// could be a non-spined line.
		return;
	}
	int partcount = (int)input->size();
	for (int part=0; part<partcount; part++) {
		GridPart* ipart = input->at(part);
		GridPart* opart = output->at(part);
		if (ipart->size() != opart->size()) {
			// something string that should never happen
			continue;
		}
		int scount = (int)ipart->size();
		for (int staff=0; staff<scount; staff++) {
			GridStaff* istaff = ipart->at(staff);
			GridStaff* ostaff = opart->at(staff);
			matchLayers(ostaff, istaff);
		}
	}
}


void HumGrid::matchLayers(GridStaff* output, GridStaff* input) {
	if (input->size() == output->size()) {
		// The voice counts match so nothing to do.
		return;
	}
	if (input->size() < output->size()) {
		// Ignore potentially strange case.
	}

	int diff = (int)input->size() - (int)output->size();
	for (int i=0; i<diff; i++) {
		GridVoice* voice = new GridVoice("!", 0);
		output->push_back(voice);
	}
}



//////////////////////////////
//
// HumGrid::setPartName --
//

void HumGrid::setPartName(int index, const string& name) {
	if (index < 0) {
		return;
	} else if (index < (int)m_partnames.size()) {
		m_partnames[index] = name;
	} else if (index < 100) {
		// grow the array and then store name
		m_partnames.resize(index+1);
		m_partnames.back() = name;
	}
}



//////////////////////////////
//
// HumGrid::getPartName --
//

std::string HumGrid::getPartName(int index) {
	if (index < 0) {
		return "";
	} else if (index < (int)m_partnames.size()) {
		return m_partnames[index];
	} else {
		return "";
	}
}



//////////////////////////////
//
// operator<< -- Debugging printing of Humgrid Contents.
//

ostream& operator<<(ostream& out, HumGrid& grid) {
	for (int i=0; i<(int)grid.size(); i++) {
		out << "\nMEASURE " << i << " =========================" << endl;
		out << grid[i];
	}
	return out;
}




////////////////////////////////
//
// HumParameter::HumParameter -- HumParameter constructor.
//

HumParameter::HumParameter(void) {
	origin = NULL;
}


HumParameter::HumParameter(const string& str) : string(str) {
	origin = NULL;
}



//////////////////////////////
//
// HumHash::HumHash -- HumHash constructor.  The data storage is empty
//    until the first parameter in the Hash is set.
//

HumHash::HumHash(void) {
	parameters = NULL;
}



//////////////////////////////
//
// HumHash::~HumHash -- The HumHash deconstructor, which removed any
//    allocated storage before the object dies.
//

HumHash::~HumHash() {
	if (parameters != NULL) {
		delete parameters;
		parameters = NULL;
	}
}



//////////////////////////////
//
// HumHash::getValue -- Returns the value specified by the given key.
//    If there is no colon in the key then return the value for the key
//    in the default namespaces (NS1="" and NS2="").  If there is one colon,
//    then the two pieces of the string as NS2 and the key, with NS1="".
//    If there are two colons, then that specified the complete namespaces/key
//    address of the value.  The namespaces and key can be specified as
//    separate parameters in a similar manner to the single-string version.
//    But in these cases colon concatenation of the namespaces and/or key
//    are not allowed.
//

string HumHash::getValue(const string& key) const {
	if (parameters == NULL) {
		return "";
	} else {
		vector<string> keys = getKeyList(key);
		if (keys.size() == 1) {
			return getValue("", "", keys[0]);
		} else if (keys.size() == 2) {
			return getValue("", keys[0], keys[1]);
		} else {
			return getValue(keys[0], keys[1], keys[2]);
		}
	}
}


string HumHash::getValue(const string& ns2, const string& key) const {
	if (parameters == NULL) {
		return "";
	} else {
		return getValue("", ns2, key);
	}
}


string HumHash::getValue(const string& ns1, const string& ns2,
		const string& key) const {
	if (parameters == NULL) {
		return "";
	}
	MapNNKV& p = *parameters;
	auto it1 = p.find(ns1);
	if (it1 == p.end()) {
		return "";
	}
	auto it2 = it1->second.find(ns2);
	if (it2 == it1->second.end()) {
		return "";
	}
	auto it3 = it2->second.find(key);
	if (it3 == it2->second.end()) {
		return "";
	}
	return it3->second;
}



//////////////////////////////
//
// HumHash::getValueHTp -- Return an address of a HumdrumToken.
//   Presumes 64-bit pointers (or at least not 128-bit pointers).
//

HTp HumHash::getValueHTp(const string& key) const {
	if (parameters == NULL) {
		return NULL;
	}
	vector<string> keys = getKeyList(key);
	if (keys.size() == 1) {
		return getValueHTp("", "", keys[2]);
	} else if (keys.size() == 2) {
		return getValueHTp(keys[0], keys[1]);
	} else {
		return getValueHTp(keys[0], keys[1], keys[2]);
	}
}


HTp HumHash::getValueHTp(const string& ns2, const string& key) const {
	if (parameters == NULL) {
		return NULL;
	}
	return getValueHTp("", ns2, key);
}


HTp HumHash::getValueHTp(const string& ns1, const string& ns2,
		const string& key) const {
	if (parameters == NULL) {
		return NULL;
	}
	string value = getValue(ns1, ns2, key);
	if (value.find("HT_") != 0) {
		return NULL;
	} else {
		HTp pointer = NULL;
		try {
			pointer = (HTp)(stoll(value.substr(3)));
		} catch (invalid_argument& e) {
         std::cerr << e.what() << std::endl;
			pointer = NULL;
		}
		return pointer;
	}
}



//////////////////////////////
//
// HumHash::getValueInt -- Return the value as an integer.  The value must
//   start with a number and have no text before it; otherwise the
//   returned value will be "0".  The HumHash class is aware of fractional
//   values, so the integer form of the fraction will be returned.  For
//   example if the value is "12/7", then the return value will be "1"
//   since the integer part of 12/7 is 1 with a remainder of 5/7ths
//   which will be chopped off.
//

int HumHash::getValueInt(const string& key) const {
	if (parameters == NULL) {
		return 0;
	}
	vector<string> keys = getKeyList(key);
	if (keys.size() == 1) {
		return getValueInt("", "", keys[2]);
	} else if (keys.size() == 2) {
		return getValueInt(keys[0], keys[1]);
	} else {
		return getValueInt(keys[0], keys[1], keys[2]);
	}
}


int HumHash::getValueInt(const string& ns2, const string& key) const {
	if (parameters == NULL) {
		return 0;
	}
	return getValueInt("", ns2, key);
}


int HumHash::getValueInt(const string& ns1, const string& ns2,
		const string& key) const {
	if (parameters == NULL) {
		return 0;
	}
	string value = getValue(ns1, ns2, key);
	if (value.find("/") != string::npos) {
		HumNum nvalue(value);
		return  nvalue.getInteger();
	} else {
		int intvalue;
		try {
			// problem with emscripten with stoi:
			// intvalue = stoi(value);
			stringstream converter(value);
			if (!(converter >> intvalue)) {
				intvalue = 0;
			}
		} catch (invalid_argument& e) {
         std::cerr << e.what() << std::endl;
			intvalue = 0;
		}
		return intvalue;
	}
}



//////////////////////////////
//
// HumHash::getValueFraction -- Return the value as a HumNum fraction.
//    If the string represents an integer, it will be preserved in the
//    HumNum return value.  For floating-point values, the fractional
//    part will be ignored.  For example "1.52" will be returned as "1".
//

HumNum HumHash::getValueFraction(const string& key) const {
	if (parameters == NULL) {
		return 0;
	}
	vector<string> keys = getKeyList(key);
	if (keys.size() == 1) {
		return getValueFraction("", "", keys[0]);
	} else if (keys.size() == 2) {
		return getValueFraction(keys[0], keys[1]);
	} else {
		return getValueFraction(keys[0], keys[1], keys[2]);
	}
}


HumNum HumHash::getValueFraction(const string& ns2, const string& key) const {
	if (parameters == NULL) {
		return 0;
	}
	return getValueFraction("", ns2, key);
}


HumNum HumHash::getValueFraction(const string& ns1, const string& ns2,
		const string& key) const {
	if (!isDefined(ns1, ns2, key)) {
		return 0;
	}
	string value = getValue(ns1, ns2, key);
	HumNum fractionvalue(value);
	return fractionvalue;
}



//////////////////////////////
//
// HumHash::getValueFloat --  Return the floating-point interpretation
//   of the value string.  If the string can represent a HumNum fraction,
//   then convert the HumNum interpretation as a floating point number.
//   For example "1.25" and "5/4" will both return 1.25.  The value
//   cannot contain a slash unless it is part of the first fraction
//   on in the value string (this may be changed when regular expressions
//   are used to implement this function).
//

double HumHash::getValueFloat(const string& key) const {
	if (parameters == NULL) {
		return 0.0;
	}
	vector<string> keys = getKeyList(key);
	if (keys.size() == 1) {
		return getValueFloat("", "", keys[2]);
	} else if (keys.size() == 2) {
		return getValueFloat(keys[0], keys[1]);
	} else {
		return getValueFloat(keys[0], keys[1], keys[2]);
	}
}


double HumHash::getValueFloat(const string& ns2, const string& key) const {
	if (parameters == NULL) {
		return 0.0;
	}
	return getValueInt("", ns2, key);
}


double HumHash::getValueFloat(const string& ns1, const string& ns2,
		const string& key) const {
	if (parameters == NULL) {
		return 0.0;
	}
	string value = getValue(ns1, ns2, key);
	if (value.find("/") != string::npos) {
		HumNum nvalue(value);
		return nvalue.getFloat();
	} else {
		double floatvalue;
		try {
			floatvalue = stod(value);
		} catch (invalid_argument& e) {
         std::cerr << e.what() << std::endl;
			floatvalue = 0;
		}
		return floatvalue;
	}
}



//////////////////////////////
//
// HumHash::getValueBool -- Return true or false based on the
//   value.  If the value is "0" or false, then the function
//   will return false.  If the value is anything else, then
//   true will be returned.  If the parameter is not defined
//   in the HumHash, then false will also be defined.
//   See also hasParameter() if you do not like this last
//   behavior.
//

bool HumHash::getValueBool(const string& key) const {
	vector<string> keys = getKeyList(key);
	if (keys.size() == 1) {
		return getValueBool("", "", keys[2]);
	} else if (keys.size() == 2) {
		return getValueBool(keys[0], keys[1]);
	} else {
		return getValueBool(keys[0], keys[1], keys[2]);
	}
}


bool HumHash::getValueBool(const string& ns2, const string& key) const {
	return getValueBool("", ns2, key);
}


bool HumHash::getValueBool(const string& ns1, const string& ns2,
		const string& key) const {
	if (parameters == NULL) {
		return false;
	}
	if (!isDefined(ns1, ns2, key)) {
		return false;
	}
	if ((*parameters)[ns1][ns2][key] == "false") {
		return false;
	} else if ((*parameters)[ns1][ns2][key] == "0") {
		return false;
	} else {
		return true;
	}
}



//////////////////////////////
//
// HumHash::setValue -- Set the parameter to the given value,
//     over-writing any previous value for the parameter.  The
//     value is any arbitrary string, but preferably does not
//     include tabs or colons.  If a colon is needed, then specify
//     as "&colon;" without the quotes.  Values such as integers
//     fractions and floats can be specified, and these wil be converted
//     internally into strings (use getValueInt() or getValueFloat()
//     to recover the original type).
//

void HumHash::setValue(const string& key, const string& value) {
	vector<string> keys = getKeyList(key);
	if (keys.size() == 1) {
		setValue("", "", keys[0], value);
	} else if (keys.size() == 2) {
		setValue("", keys[0], keys[1], value);
	} else {
		setValue(keys[0], keys[1], keys[2], value);
	}
}


void HumHash::setValue(const string& ns2, const string& key,
		const string& value) {
		setValue("", ns2, key, value);
}


void HumHash::setValue(const string& ns1, const string& ns2,
		const string& key, const string& value) {
	initializeParameters();
	(*parameters)[ns1][ns2][key] = value;
}


void HumHash::setValue(const string& key, const char* value) {
	setValue(key, (string)value);
}


void HumHash::setValue(const string& ns2, const string& key,
		const char* value) {
	setValue(ns2, key, (string)value);
}


void HumHash::setValue(const string& ns1, const string& ns2, const string& key,
		const char* value) {
	setValue(ns1, ns2, key, (string)value);
}


void HumHash::setValue(const string& key, int value) {
	vector<string> keys = getKeyList(key);
	if (keys.size() == 1) {
		setValue("", "", keys[0], value);
	} else if (keys.size() == 2) {
		setValue("", keys[0], keys[1], value);
	} else {
		setValue(keys[0], keys[1], keys[2], value);
	}
}


void HumHash::setValue(const string& ns2, const string& key, int value) {
		setValue("", ns2, key, value);
}


void HumHash::setValue(const string& ns1, const string& ns2,
		const string& key, int value) {
	initializeParameters();
	stringstream ss;
	ss << value;
	(*parameters)[ns1][ns2][key] = ss.str();
}


void HumHash::setValue(const string& key, HTp value) {
	vector<string> keys = getKeyList(key);
	if (keys.size() == 1) {
		setValue("", "", keys[0], value);
	} else if (keys.size() == 2) {
		setValue("", keys[0], keys[1], value);
	} else {
		setValue(keys[0], keys[1], keys[2], value);
	}
}


void HumHash::setValue(const string& ns2, const string& key, HTp value) {
		setValue("", ns2, key, value);
}


void HumHash::setValue(const string& ns1, const string& ns2,
		const string& key, HTp value) {
	initializeParameters();
	stringstream ss;
	ss << "HT_" << ((long long)value);
	(*parameters)[ns1][ns2][key] = ss.str();
}


void HumHash::setValue(const string& key, HumNum value) {
	vector<string> keys = getKeyList(key);
	if (keys.size() == 1) {
		setValue("", "", keys[0], value);
	} else if (keys.size() == 2) {
		setValue("", keys[0], keys[1], value);
	} else {
		setValue(keys[0], keys[1], keys[2], value);
	}
}


void HumHash::setValue(const string& ns2, const string& key, HumNum value) {
		setValue("", ns2, key, value);
}


void HumHash::setValue(const string& ns1, const string& ns2,
		const string& key, HumNum value) {
	initializeParameters();
	stringstream ss;
	ss << value;
	(*parameters)[ns1][ns2][key] = ss.str();
}


void HumHash::setValue(const string& key, double value) {
	vector<string> keys = getKeyList(key);
	if (keys.size() == 1) {
		setValue("", "", keys[0], value);
	} else if (keys.size() == 2) {
		setValue("", keys[0], keys[1], value);
	} else {
		setValue(keys[0], keys[1], keys[2], value);
	}
}


void HumHash::setValue(const string& ns2, const string& key, double value) {
		setValue("", ns2, key, value);
}


void HumHash::setValue(const string& ns1, const string& ns2,
		const string& key, double value) {
	initializeParameters();
	stringstream ss;
	ss << value;
	(*parameters)[ns1][ns2][key] = ss.str();
}



//////////////////////////////
//
// HumHas::getParameters -- Return a map of the key/values for a
//    particular namespace.  If the namespace(s) do not exist
//    then the map will be empty.
//

map<string, string> HumHash::getParameters(const string& ns1, const string& ns2) {
	map<string, string> output;
	if (parameters == NULL) {
		return output;
	}
	for (auto& it : (*parameters)[ns1][ns2]) {
		output[it.first] = it.second;
	}
	return output;
}


map<string, string> HumHash::getParameters(string& ns) {
	map<string, string> output;
	if (parameters == NULL) {
		return output;
	}
	auto loc = ns.find(":");
	if (loc != string::npos) {
		string ns1 = ns.substr(0, loc);
		string ns2 = ns.substr(loc+1);
		return getParameters(ns1, ns2);
	}

	// Search the ns namespace in the root namespace:
	return getParameters("", ns);
}



//////////////////////////////
//
// HumHash::getKeys -- Return a list of keys in a particular namespace
//     combination.  With no parameters, a complete list of all
//     namespaces/keys will be returned.  Giving one parameter will
//     produce a list will give all NS2:key values in the NS1 namespace.
//     If there is a colon in the single parameter version of the function,
//     then this will be interpreted as "NS1", "NS2" version of the parameters
//     described above.
//

vector<string> HumHash::getKeys(const string& ns1, const string& ns2) const {
	vector<string> output;
	if (parameters == NULL) {
		return output;
	}
	for (auto& it : (*parameters)[ns1][ns2]) {
		output.push_back(it.first);
	}
	return output;
}


vector<string> HumHash::getKeys(const string& ns) const {
	vector<string> output;
	if (parameters == NULL) {
		return output;
	}
	auto loc = ns.find(":");
	if (loc != string::npos) {
		string ns1 = ns.substr(0, loc);
		string ns2 = ns.substr(loc+1);
		return getKeys(ns1, ns2);
	}

	for (auto& it1 : (*parameters)[ns]) {
		for (auto& it2 : it1.second) {
			output.push_back(it1.first + ":" + it2.first);
		}
	}
	return output;
}


vector<string> HumHash::getKeys(void) const {
	vector<string> output;
	if (parameters == NULL) {
		return output;
	}
	for (auto& it1 : (*parameters)) {
		for (auto& it2 : it1.second) {
			for (auto it3 : it2.second) {
				output.push_back(it1.first + ":" + it2.first + ":" + it3.first);
			}
		}
	}
	return output;
}



//////////////////////////////
//
// HumHash::hasParameters -- Returns true if at least one parameter is defined
//     in the HumHash object (when no arguments are given to the function).
//     When two strings are given as arguments, the function checks to see if
//     the given namespace pair has any keys.  If only one string argument,
//     then check if the given NS1 has any parameters, unless there is a
//     colon in the string which means to check NS1:NS2.
//

bool HumHash::hasParameters(const string& ns1, const string& ns2) const {
	if (parameters == NULL) {
		return false;
	}
	if (parameters->size() == 0) {
		return false;
	}
	auto it1 = parameters->find(ns1);
	if (it1 == parameters->end()) {
		return false;
	}
	auto it2 = (*parameters)[ns1].find(ns2);
	if (it2 == (*parameters)[ns1].end()) {
		return false;
	} else {
		return true;
	}
}


bool HumHash::hasParameters(const string& ns) const {
	if (parameters == NULL) {
		return false;
	}
	auto loc = ns.find(":");
	if (loc != string::npos) {
		string ns1 = ns.substr(0, loc);
		string ns2 = ns.substr(loc+1);
		return hasParameters(ns1, ns2);
	}

	auto it = parameters->find(ns);
	if (it == parameters->end()) {
		return false;
	} else {
		return true;
	}
}


bool HumHash::hasParameters(void) const {
	if (parameters == NULL) {
		return false;
	}
	if (parameters->size() == 0) {
		return false;
	}
	for (auto& it1 : *parameters) {
		for (auto& it2 : it1.second) {
			if (it2.second.size() == 0) {
				continue;
			} else {
				return true;
			}
		}
	}
	return false;
}



//////////////////////////////
//
// HumHash::getParameterCount -- Return a count of the parameters which are
//     stored in the HumHash.  If no arguments, then count all value in
//     all namespaces.  If two arguments, then return the count for a
//     specific NS1:NS2 namespace.  If one argument, then return the
//     parameters in NS1, but if there is a colon in the string,
//     return the parameters in NS1:NS2.
//
//

int HumHash::getParameterCount(const string& ns1, const string& ns2) const {
	if (parameters == NULL) {
		return 0;
	}
	if (parameters->size() == 0) {
		return 0;
	}
	auto it1 = parameters->find(ns1);
	if (it1 == parameters->end()) {
		return 0;
	}
	auto it2 = it1->second.find(ns2);
	if (it2 == it1->second.end()) {
		return 0;
	}
	return (int)it2->second.size();
}


int HumHash::getParameterCount(const string& ns) const {
	if (parameters == NULL) {
		return false;
	}
	auto loc = ns.find(":");
	if (loc != string::npos) {
		string ns1 = ns.substr(0, loc);
		string ns2 = ns.substr(loc+1);
		return getParameterCount(ns1, ns2);
	}

	auto it1 = parameters->find(ns);
	if (it1 == parameters->end()) {
		return false;
	}
	int sum = 0;
	for (auto& it2 : it1->second) {
		sum += (int)it2.second.size();
	}
	return sum;
}


int HumHash::getParameterCount(void) const {
	if (parameters == NULL) {
		return 0;
	}
	if (parameters->size() == 0) {
		return 0;
	}
	int sum = 0;
	for (auto& it1 : (*parameters)) {
		for (auto& it2 : it1.second) {
			sum += (int)it2.second.size();
		}
	}
	return sum;
}



//////////////////////////////
//
// HumHash::isDefined -- Returns true if the given parameter exists in the
//    map.   Format of the input string:   NS1:NS2:key or "":NS2:key for the
//    two argument version of the function.  OR "":"":key if no colons in
//    single string argument version.
//

bool HumHash::isDefined(const string& key) const {
	if (parameters == NULL) {
		return false;
	}
	vector<string> keys = getKeyList(key);
	if (keys.size() == 1) {
		return (*parameters)[""][""].count(keys[0]) ? true : false;
	} else if (keys.size() == 2) {
		return (*parameters)[""][keys[0]].count(keys[1]) ? true : false;
	} else {
		return (*parameters)[keys[0]][keys[1]].count(keys[2]) ? true : false;
	}
}


bool HumHash::isDefined(const string& ns2, const string& key) const {
	if (parameters == NULL) {
		return false;
	}
	return (*parameters)[""][ns2].count(key) ? true : false;
}


bool HumHash::isDefined(const string& ns1, const string& ns2,
		const string& key) const {
	if (parameters == NULL) {
		return false;
	}
	return (*parameters)[ns1][ns2].count(key) ? true : false;
}



//////////////////////////////
//
// HumHash::deleteValue -- Delete the given parameter key from the HumHash
//   object.  Three string version is N1,NS2,key; two string version is
//   "",NS2,key; and one argument version is "","",key.
//

void HumHash::deleteValue(const string& key) {
	if (parameters == NULL) {
		return;
	}
	vector<string> keys = getKeyList(key);
	if (keys.size() == 1) {
		deleteValue("", "", keys[0]);
	} else if (keys.size() == 2) {
		deleteValue("", keys[0], keys[1]);
	} else {
		deleteValue(keys[0], keys[1], keys[2]);
	}
}


void HumHash::deleteValue(const string& ns2, const string& key) {
	if (parameters == NULL) {
		return;
	}
	deleteValue("", ns2, key);
}


void HumHash::deleteValue(const string& ns1, const string& ns2,
		const string& key) {
	if (parameters == NULL) {
		return;
	}
	(*parameters)[ns1][ns2].erase(key);

	MapNNKV& p = *parameters;
	auto it1 = p.find(ns1);
	if (it1 == p.end()) {
		return;
	}
	auto it2 = it1->second.find(ns2);
	if (it2 == it1->second.end()) {
		return;
	}
	auto it3 = it2->second.find(key);
	if (it3 == it2->second.end()) {
		return;
	}
	it2->second.erase(key);
}



//////////////////////////////
//
// HumHash::initializeParameters -- Create the map structure if it does not
//     already exist.
//

void HumHash::initializeParameters(void) {
	if (parameters == NULL) {
		parameters = new MapNNKV;
	}
}



//////////////////////////////
//
// HumHash::getKeyList -- Return a list of colon separated values from
//      the string.
//

vector<string> HumHash::getKeyList(const string& keys) const {
	stringstream ss(keys);
	string key;
	vector<string> output;
	while (getline(ss, key, ':')) {
		output.push_back(key);
	}
	if (output.size() == 0) {
		output.push_back(keys);
	}
	return output;
}



//////////////////////////////
//
// HumHash::setPrefix -- initial string to print when using
//   operator<<.  This is used for including the "!" for local
//   comments or "!!" for global comments.   The prefix will
//   remain the same until it is changed.  The default prefix
//   of the object it the empty string.
//

void HumHash::setPrefix(const string& value) {
	prefix = value;
}



//////////////////////////////
//
// HumHash::getPrefix -- get the prefix.
//

string HumHash::getPrefix(void) const {
	return prefix;
}



//////////////////////////////
//
// HumHash::setOrigin -- Set the source token for the parameter.
//

void HumHash::setOrigin(const string& key, HumdrumToken* tok) {
	if (parameters == NULL) {
		return;
	} else {
		vector<string> keys = getKeyList(key);
		if (keys.size() == 1) {
			setOrigin("", "", keys[0], tok);
		} else if (keys.size() == 2) {
			setOrigin("", keys[0], keys[1], tok);
		} else {
			setOrigin(keys[0], keys[1], keys[2], tok);
		}
	}
}


void HumHash::setOrigin(const string& key, HumdrumToken& tok) {
	setOrigin(key, &tok);
}


void HumHash::setOrigin(const string& ns2, const string& key,
		HumdrumToken* tok) {
	if (parameters == NULL) {
		return;
	} else {
		setOrigin("", ns2, key, tok);
	}
}


void HumHash::setOrigin(const string& ns2, const string& key,
		HumdrumToken& tok) {
	setOrigin(ns2, key, &tok);
}


void HumHash::setOrigin(const string& ns1, const string& ns2,
		const string& key, HumdrumToken* tok) {
	if (parameters == NULL) {
		return;
	}
	MapNNKV& p = *parameters;
	auto it1 = p.find(ns1);
	if (it1 == p.end()) {
		return;
	}
	auto it2 = it1->second.find(ns2);
	if (it2 == it1->second.end()) {
		return;
	}
	auto it3 = it2->second.find(key);
	if (it3 == it2->second.end()) {
		return;
	}
	it3->second.origin = tok;
}


void HumHash::setOrigin(const string& ns1, const string& ns2,
		const string& key, HumdrumToken& tok) {
	setOrigin(ns1, ns2, key, &tok);
}



//////////////////////////////
//
// HumHash::getOrigin -- Get the source token for the parameter.
//    Returns NULL if there is no origin.
//

HumdrumToken* HumHash::getOrigin(const string& key) const {
	if (parameters == NULL) {
		return NULL;
	} else {
		vector<string> keys = getKeyList(key);
		if (keys.size() == 1) {
			return getOrigin("", "", keys[0]);
		} else if (keys.size() == 2) {
			return getOrigin("", keys[0], keys[1]);
		} else {
			return getOrigin(keys[0], keys[1], keys[2]);
		}
	}
}


HumdrumToken* HumHash::getOrigin(const string& ns2, const string& key) const {
	if (parameters == NULL) {
		return NULL;
	} else {
		return getOrigin("", ns2, key);
	}
}


HumdrumToken* HumHash::getOrigin(const string& ns1, const string& ns2,
		const string& key) const {
	if (parameters == NULL) {
		return NULL;
	}
	MapNNKV& p = *parameters;
	auto it1 = p.find(ns1);
	if (it1 == p.end()) {
		return NULL;
	}
	auto it2 = it1->second.find(ns2);
	if (it2 == it1->second.end()) {
		return NULL;
	}
	auto it3 = it2->second.find(key);
	if (it3 == it2->second.end()) {
		return NULL;
	}
	return it3->second.origin;
}



//////////////////////////////
//
// HumHash::printXml -- Print object as a <parameters> element for
//     in a HumdrumXML file.
//

ostream& HumHash::printXml(ostream& out, int level, const string& indent) {

	if (parameters == NULL) {
		return out;
	}
	if (parameters->size() == 0) {
		return out;
	}

	stringstream str;
	bool found = 0;

	HumdrumToken* ref = NULL;
	level++;
	for (auto& it1 : *(parameters)) {
		if (it1.second.size() == 0) {
			continue;
		}
		if (!found) {
			found = 1;
		}
		str << Convert::repeatString(indent, level++);
		str << "<namespace n=\"1\" name=\"" << it1.first << "\">\n";
		for (auto& it2 : it1.second) {
			if (it2.second.size() == 0) {
				continue;
			}

			str << Convert::repeatString(indent, level++);
			str << "<namespace n=\"2\" name=\"" << it2.first << "\">\n";

			for (auto& it3 : it2.second) {
				str << Convert::repeatString(indent, level);
				str << "<parameter key=\"" << it3.first << "\"";
				str << " value=\"";
				str << Convert::encodeXml(it3.second) << "\"";
				ref = it3.second.origin;
				if (ref != NULL) {
					str << " idref=\"";
					str << ref->getXmlId();
					str << "\"";
				}
				str << "/>\n";
			}
			str << Convert::repeatString(indent, --level) << "</namespace>\n";
		}
		str << Convert::repeatString(indent, --level) << "</namespace>\n";
	}
	if (found) {
		str << Convert::repeatString(indent, --level) << "</parameters>\n";
		out << Convert::repeatString(indent, level) << "<parameters>\n";
		out << str.str();
	}

	return out;

}



//////////////////////////////
//
// HumHash::printXmlAsGlobal --
//

ostream& HumHash::printXmlAsGlobal(ostream& out, int level,
		const string& indent) {

	if (parameters == NULL) {
		return out;
	}
	if (parameters->size() == 0) {
		return out;
	}

	stringstream str;
	stringstream str2;
	string it1str;
	string it2str;
	int str2count = 0;
	bool found = 0;

	HumdrumToken* ref = NULL;
	level++;
	for (auto& it1 : *(parameters)) {
		if (it1.second.size() == 0) {
			continue;
		}
		str2.str("");
		it1str = it1.first;
		if (!found) {
			found = 1;
		}
		if (it1.first == "") {
			str2 << Convert::repeatString(indent, level++);
			str2 << "<namespace n=\"1\" name=\"" << it1.first << "\">\n";
		} else {
			str << Convert::repeatString(indent, level++);
			str << "<namespace n=\"1\" name=\"" << it1.first << "\">\n";
		}
		for (auto& it2 : it1.second) {
			if (it2.second.size() == 0) {
				continue;
			}
			it2str = it2.first;

			if ((it2.first == "") && (it2.first == "")) {
				str2 << Convert::repeatString(indent, level++);
				str2 << "<namespace n=\"2\" name=\"" << it2.first << "\">\n";
			} else {
				str << Convert::repeatString(indent, level++);
				str << "<namespace n=\"2\" name=\"" << it2.first << "\">\n";
			}

			for (auto& it3 : it2.second) {
				if ((it2.first == "") && (it2.first == "")) {

					if ((it3.first == "global") && (it3.second == "true")) {
						// don't do anything because parameter should be removed
					} else {
						str2count++;
						str2 << Convert::repeatString(indent, level);
						str2 << "<parameter key=\"" << it3.first << "\"";
						str2 << " value=\"";
						str2 << Convert::encodeXml(it3.second) << "\"";
						ref = it3.second.origin;
						if (ref != NULL) {
							str2 << " idref=\"";
							str2 << ref->getXmlId();
							str2 << "\"";
						}
						str2 << "/>\n";
					}
				} else {
					str << Convert::repeatString(indent, level);
					str << "<parameter key=\"" << it3.first << "\"";
					str << " value=\"";
					str << Convert::encodeXml(it3.second) << "\"";
					ref = it3.second.origin;
					if (ref != NULL) {
						str << " idref=\"";
						str << ref->getXmlId();
						str << "\"";
					}
					str << "/>\n";
				}
			}
			if ((it1str == "") && (it2str == "")) {
				if (str2count > 0) {
					str << str2.str();
					str << Convert::repeatString(indent, --level) << "</namespace>\n";
				}
			} else {
				str << Convert::repeatString(indent, --level) << "</namespace>\n";
			}
		}
		if ((it1str == "") && (it2str == "")) {
			if (str2count > 0) {
				str << Convert::repeatString(indent, --level) << "</namespace>\n";
			}
		} else {
			str << Convert::repeatString(indent, --level) << "</namespace>\n";
		}
	}
	if (found) {
		str << Convert::repeatString(indent, --level) << "</parameters>\n";
		out << Convert::repeatString(indent, level) << "<parameters global=\"true\">\n";
		out << str.str();
	}

	return out;
}



//////////////////////////////
//
// operator<< -- Print a list of the parameters in a HumHash object.
//

ostream& operator<<(ostream& out, const HumHash& hash) {
	if (hash.parameters == NULL) {
		return out;
	}
	if (hash.parameters->size() == 0) {
		return out;
	}

	string cleaned;

	for (auto& it1 : *(hash.parameters)) {
		if (it1.second.size() == 0) {
			continue;
		}
		for (auto& it2 : it1.second) {
			if (it2.second.size() == 0) {
				continue;
			}
			out << hash.prefix;
			out << it1.first << ":" << it2.first;
			for (auto& it3 : it2.second) {
				out << ":" << it3.first;
				if (it3.second != "true") {
					cleaned = it3.second;
					Convert::replaceOccurrences(cleaned, ":", "&colon;");
					out << "=" << cleaned;
				}
			}
			out << endl;
		}
	}

	return out;
}

ostream& operator<<(ostream& out, HumHash* hash) {
	out << *hash;
	return out;
}



typedef unsigned long long TEMP64BITFIX;

// declare static variables
vector<_HumInstrument> HumInstrument::m_data;
int HumInstrument::m_classcount = 0;


//////////////////////////////
//
// HumInstrument::HumInstrument --
//

HumInstrument::HumInstrument(void) {
	if (m_classcount == 0) {
		initialize();
	}
	m_classcount++;
	m_index = -1;
}



//////////////////////////////
//
// HumInstrument::HumInstrument --
//

HumInstrument::HumInstrument(const string& Hname) {
	if (m_classcount == 0) {
		initialize();
	}

	m_index = find(Hname);
}



//////////////////////////////
//
// HumInstrument::~HumInstrument --
//

HumInstrument::~HumInstrument() {
	m_index = -1;
}



/////////////////////////////
//
// HumInstrument::getGM --
//

int HumInstrument::getGM(void) {
	if (m_index > 0) {
		return m_data[m_index].gm;
	} else {
		return -1;
	}
}



/////////////////////////////
//
// HumInstrument::getGM --
//

int HumInstrument::getGM(const string& Hname) {
	int tindex;
	if (Hname.compare(0, 2, "*I") == 0) {
		tindex = find(Hname.substr(2));
	} else {
		tindex = find(Hname);
	}

	if (tindex > 0) {
		return m_data[tindex].gm;
	} else {
		return -1;
	}
}



//////////////////////////////
//
// HumInstrument::getName --
//

string HumInstrument::getName(void) {
	if (m_index > 0) {
		return m_data[m_index].name;
	} else {
		return "";
	}
}



//////////////////////////////
//
// HumInstrument::getName --
//

string HumInstrument::getName(const string& Hname) {
	int tindex;
	if (Hname.compare(0, 2, "*I") == 0) {
		tindex = find(Hname.substr(2));
	} else{
		tindex = find(Hname);
	}
	if (tindex > 0) {
		return m_data[tindex].name;
	} else {
		return "";
	}
}



//////////////////////////////
//
// HumInstrument::getHumdrum --
//

string HumInstrument::getHumdrum(void) {
	if (m_index > 0) {
		return m_data[m_index].humdrum;
	} else {
		return "";
	}
}



//////////////////////////////
//
// HumInstrument::setGM --
//

int HumInstrument::setGM(const string& Hname, int aValue) {
	if (aValue < 0 || aValue > 127) {
		return 0;
	}
	int rindex = find(Hname);
	if (rindex > 0) {
		m_data[rindex].gm = aValue;
	} else {
		afi(Hname.c_str(), aValue, Hname.c_str());
		sortData();
	}
	return rindex;
}



//////////////////////////////
//
// HumInstrument::setHumdrum --
//

void HumInstrument::setHumdrum(const string& Hname) {
	if (Hname.compare(0, 2, "*I") == 0) {
		m_index = find(Hname.substr(2));
	} else {
		m_index = find(Hname);
	}
                                             }



//////////////////////////////////////////////////////////////////////////
//
// private functions
//


//////////////////////////////
//
// HumInstrument::initialize --
//
void HumInstrument::initialize(void) {
   m_data.reserve(500);

	// List has to be sorted by first parameter.  Maybe put in map.
   afi("accor",   GM_ACCORDION,             "accordion");
   afi("alto",    GM_RECORDER,              "alto");
   afi("anvil",   GM_TINKLE_BELL,           "anvil");
   afi("archl",   GM_ACOUSTIC_GUITAR_NYLON, "archlute");
   afi("armon",   GM_HARMONICA,             "harmonica");
   afi("arpa",    GM_ORCHESTRAL_HARP,       "harp");
   afi("bagpI",   GM_BAGPIPE,               "bagpipe (Irish)");
   afi("bagpS",   GM_BAGPIPE,               "bagpipe (Scottish)");
   afi("banjo",   GM_BANJO,                 "banjo");
   afi("bansu",   GM_FLUTE,                 "bansuri");
   afi("barit",   GM_CHOIR_AAHS,            "baritone");
   afi("baset",   GM_CLARINET,              "bassett horn");
   afi("bass",    GM_CHOIR_AAHS,            "bass");
   afi("bdrum",   GM_TAIKO_DRUM,            "bass drum");
   afi("bguit",   GM_ELECTRIC_BASS_FINGER,  "electric bass guitar");
   afi("biwa",    GM_FLUTE,                 "biwa");
   afi("bongo",   GM_TAIKO_DRUM,            "bongo");
   afi("brush",   GM_BREATH_NOISE,          "brush");
   afi("bscan",   GM_CHOIR_AAHS,            "basso cantante");
   afi("bspro",   GM_CHOIR_AAHS,            "basso profondo");
   afi("bugle",   GM_TRUMPET,               "bugle");
   afi("calam",   GM_OBOE,                  "chalumeau");
   afi("calpe",   GM_LEAD_CALLIOPE,         "calliope");
   afi("calto",   GM_CHOIR_AAHS,            "contralto");
   afi("campn",   GM_TUBULAR_BELLS,         "bell");
   afi("cangl",   GM_ENGLISH_HORN,          "english horn");
   afi("canto",   GM_CHOIR_AAHS,            "canto");
   afi("caril",   GM_TUBULAR_BELLS,         "carillon");
   afi("castr",   GM_CHOIR_AAHS,            "castrato");
   afi("casts",   GM_WOODBLOCKS,            "castanets");
   afi("cbass",   GM_CONTRABASS,            "contrabass");
   afi("cello",   GM_CELLO,                 "violoncello");
   afi("cemba",   GM_HARPSICHORD,           "harpsichord");
   afi("cetra",   GM_VIOLIN,                "cittern");
   afi("chain",   GM_TINKLE_BELL,           "chains");
   afi("chcym",   GM_REVERSE_CYMBAL,        "China cymbal");
   afi("chime",   GM_TUBULAR_BELLS,         "chimes");
   afi("chlma",   GM_BASSOON,               "alto shawm");
   afi("chlms",   GM_BASSOON,               "soprano shawm");
   afi("chlmt",   GM_BASSOON,               "tenor shawm");
   afi("clap",    GM_GUNSHOT,               "hand clapping");
   afi("clara",   GM_CLARINET,              "alto clarinet");
   afi("clarb",   GM_CLARINET,              "bass clarinet");
   afi("clarp",   GM_CLARINET,              "piccolo clarinet");
   afi("clars",   GM_CLARINET,              "clarinet");
   afi("clave",   GM_AGOGO,                 "claves");
   afi("clavi",   GM_CLAVI,                 "clavichord");
   afi("clest",   GM_CELESTA,               "celesta");
   afi("clrno",   GM_TRUMPET,               "clarino");
   afi("colsp",   GM_FLUTE,                 "coloratura soprano");
   afi("conga",   GM_TAIKO_DRUM,            "conga");
   afi("cor",     GM_FRENCH_HORN,           "horn");
   afi("cornm",   GM_BAGPIPE,               "French bagpipe");
   afi("corno",   GM_TRUMPET,               "cornett");
   afi("cornt",   GM_TRUMPET,               "cornet");
   afi("coro",    GM_CHOIR_AAHS,            "chorus");
   afi("crshc",   GM_REVERSE_CYMBAL,        "crash cymbal");
   afi("ctenor",  GM_CHOIR_AAHS,            "counter-tenor");
   afi("ctina",   GM_ACCORDION,             "concertina");
   afi("drmsp",   GM_FLUTE,                 "dramatic soprano");
   afi("drum",    GM_SYNTH_DRUM,            "drum");
   afi("drumP",   GM_SYNTH_DRUM,            "small drum");
   afi("dulc",    GM_DULCIMER,              "dulcimer");
   afi("eguit",   GM_ELECTRIC_GUITAR_CLEAN, "electric guitar");
   afi("fag_c",   GM_BASSOON,               "contrabassoon");
   afi("fagot",   GM_BASSOON,               "bassoon");
   afi("false",   GM_RECORDER,              "falsetto");
   afi("fdrum",   GM_TAIKO_DRUM,            "frame drum");
   afi("feme",    GM_CHOIR_AAHS,            "female voice");
   afi("fife",    GM_BLOWN_BOTTLE,          "fife");
   afi("fingc",   GM_REVERSE_CYMBAL,        "finger cymbal");
   afi("flt",     GM_FLUTE,                 "flute");
   afi("flt_a",   GM_FLUTE,                 "alto flute");
   afi("flt_b",   GM_FLUTE,                 "bass flute");
   afi("fltda",   GM_RECORDER,              "alto recorder");
   afi("fltdb",   GM_RECORDER,              "bass recorder");
   afi("fltdn",   GM_RECORDER,              "sopranino recorder");
   afi("fltds",   GM_RECORDER,              "soprano recorder");
   afi("fltdt",   GM_RECORDER,              "tenor recorder");
   afi("flugh",   GM_FRENCH_HORN,           "flugelhorn");
   afi("forte",   GM_HONKYTONK_PIANO,       "fortepiano");
   afi("gen",     GM_ACOUSTIC_GRAND_PIANO,  "generic instrument");
   afi("genB",    GM_ACOUSTIC_GRAND_PIANO,  "generic bass instrument");
   afi("genT",    GM_ACOUSTIC_GRAND_PIANO,  "generic treble instrument");
   afi("glock",   GM_GLOCKENSPIEL,          "glockenspiel");
   afi("gong",    GM_REVERSE_CYMBAL,        "gong");
   afi("guitr",   GM_ACOUSTIC_GUITAR_NYLON, "guitar");
   afi("hammd",   GM_DRAWBAR_ORGAN,         "Hammond electronic organ");
   afi("hbell",   GM_TINKLE_BELL,           "handbell");
   afi("hbell",   GM_TINKLE_BELL,           "handbell");
   afi("heck",    GM_BASSOON,               "heckelphone");
   afi("heltn",   GM_CHOIR_AAHS,            "Heldentenor");
   afi("hichi",   GM_OBOE,                  "hichiriki");
   afi("hurdy",   GM_LEAD_CALLIOPE,         "hurdy-gurdy");
   afi("kitv",    GM_VIOLIN,                "kit violin");
   afi("klav",    GM_ACOUSTIC_GRAND_PIANO,  "keyboard");
   afi("kokyu",   GM_FIDDLE,                "kokyu");
   afi("komun",   GM_KOTO,                  "komun'go");
   afi("koto",    GM_KOTO,                  "koto");
   afi("kruma",   GM_TRUMPET,               "alto crumhorn");
   afi("krumb",   GM_TRUMPET,               "bass crumhorn");
   afi("krums",   GM_TRUMPET,               "soprano crumhorn");
   afi("krumt",   GM_TRUMPET,               "tenor crumhorn");
   afi("lion",    GM_AGOGO,                 "lion's roar");
   afi("liuto",   GM_ACOUSTIC_GUITAR_NYLON, "lute");
   afi("lyrsp",   GM_FLUTE,                 "lyric soprano");
   afi("lyrtn",   GM_FRENCH_HORN,           "lyric tenor");
   afi("male",    GM_CHOIR_AAHS,            "male voice");
   afi("mando",   GM_ACOUSTIC_GUITAR_NYLON, "mandolin");
   afi("marac",   GM_AGOGO,                 "maracas");
   afi("marim",   GM_MARIMBA,               "marimba");
   afi("mbari",   GM_CHOIR_AAHS,            "high baritone");
   afi("mezzo",   GM_CHOIR_AAHS,            "mezzo soprano");
   afi("nfant",   GM_CHOIR_AAHS,            "child's voice");
   afi("nokan",   GM_SHAKUHACHI,            "nokan");
   afi("oboe",    GM_OBOE,                  "oboe");
   afi("oboeD",   GM_ENGLISH_HORN,          "oboe d'amore");
   afi("ocari",   GM_OCARINA,               "ocarina");
   afi("ondes",   GM_PAD_SWEEP,             "ondes Martenot");
   afi("ophic",   GM_TUBA,                  "ophicleide");
   afi("organ",   GM_CHURCH_ORGAN,          "pipe organ");
   afi("oud",     GM_ACOUSTIC_GUITAR_NYLON, "oud");
   afi("paila",   GM_AGOGO,                 "timbales");
   afi("panpi",   GM_PAN_FLUTE,             "panpipe");
   afi("pbell",   GM_TUBULAR_BELLS,         "bell plate");
   afi("pguit",   GM_ACOUSTIC_GUITAR_NYLON, "Portuguese guitar");
   afi("physh",   GM_REED_ORGAN,            "physharmonica");
   afi("piano",   GM_ACOUSTIC_GRAND_PIANO,  "pianoforte");
   afi("piatt",   GM_REVERSE_CYMBAL,        "cymbals");
   afi("picco",   GM_PICCOLO,               "piccolo");
   afi("pipa",    GM_ACOUSTIC_GUITAR_NYLON, "Chinese lute");
   afi("porta",   GM_TANGO_ACCORDION,       "portative organ");
   afi("psalt",   GM_CLAVI,                 "psaltery");
   afi("qin",     GM_CLAVI,                 "qin");
   afi("quinto",  GM_CHOIR_AAHS,            "quinto");
   afi("quitr",   GM_ACOUSTIC_GUITAR_NYLON, "gittern");
   afi("rackt",   GM_TRUMPET,               "racket");
   afi("ratl",    GM_WOODBLOCKS,            "rattle");
   afi("rebec",   GM_ACOUSTIC_GUITAR_NYLON, "rebec");
   afi("recit",   GM_CHOIR_AAHS,            "recitativo");
   afi("reedo",   GM_REED_ORGAN,            "reed organ");
   afi("rhode",   GM_ELECTRIC_PIANO_1,      "Fender-Rhodes electric piano");
   afi("ridec",   GM_REVERSE_CYMBAL,        "ride cymbal");
   afi("sarod",   GM_SITAR,                 "sarod");
   afi("sarus",   GM_TUBA,                  "sarrusophone");
   afi("saxA",    GM_ALTO_SAX,              "alto saxophone");
   afi("saxB",    GM_BARITONE_SAX,          "bass saxophone");
   afi("saxC",    GM_BARITONE_SAX,          "contrabass saxophone");
   afi("saxN",    GM_SOPRANO_SAX,           "sopranino saxophone");
   afi("saxR",    GM_BARITONE_SAX,          "baritone saxophone");
   afi("saxS",    GM_SOPRANO_SAX,           "soprano saxophone");
   afi("saxT",    GM_TENOR_SAX,             "tenor saxophone");
   afi("sbell",   GM_TINKLE_BELL,           "sleigh bells");
   afi("sdrum",   GM_SYNTH_DRUM,            "snare drum (kit)");
   afi("shaku",   GM_SHAKUHACHI,            "shakuhachi");
   afi("shami",   GM_SHAMISEN,              "shamisen");
   afi("sheng",   GM_SHANAI,                "sheng");
   afi("sho",     GM_SHANAI,                "sho");
   afi("siren",   GM_FX_SCI_FI,             "siren");
   afi("sitar",   GM_SITAR,                 "sitar");
   afi("slap",    GM_GUNSHOT,               "slapstick");
   afi("soprn",   GM_CHOIR_AAHS,            "soprano");
   afi("spshc",   GM_REVERSE_CYMBAL,        "splash cymbal");
   afi("steel",   GM_STEEL_DRUMS,           "steel-drum");
   afi("stim",    GM_SEASHORE,              "Sprechstimme");
   afi("stimA",   GM_SEASHORE,              "Sprechstimme, alto");
   afi("stimB",   GM_SEASHORE,              "Sprechstimme, bass");
   afi("stimC",   GM_SEASHORE,              "Sprechstimme, contralto");
   afi("stimR",   GM_SEASHORE,              "Sprechstimme, baritone");
   afi("stimS",   GM_SEASHORE,              "Sprechstimme, soprano");
   afi("strdr",   GM_AGOGO,                 "string drum");
   afi("sxhA",    GM_ALTO_SAX,              "alto saxhorn");
   afi("sxhB",    GM_BARITONE_SAX,          "bass saxhorn");
   afi("sxhC",    GM_BARITONE_SAX,          "contrabass saxhorn");
   afi("sxhR",    GM_BARITONE_SAX,          "baritone saxhorn");
   afi("sxhS",    GM_SOPRANO_SAX,           "soprano saxhorn");
   afi("sxhT",    GM_TENOR_SAX,             "tenor saxhorn");
   afi("synth",   GM_ELECTRIC_PIANO_2,      "keyboard synthesizer");
   afi("tabla",   GM_MELODIC_DRUM,          "tabla");
   afi("tambn",   GM_TINKLE_BELL,           "tambourine");
   afi("tambu",   GM_MELODIC_DRUM,          "tambura");
   afi("tanbr",   GM_MELODIC_DRUM,          "tanbur");
   afi("tblok",   GM_WOODBLOCKS,            "temple blocks");
   afi("tdrum",   GM_SYNTH_DRUM,            "tenor drum");
   afi("tenor",   GM_CHOIR_AAHS,            "tenor");
   afi("timpa",   GM_MELODIC_DRUM,          "timpani");
   afi("tiorb",   GM_ACOUSTIC_GUITAR_NYLON, "theorbo");
   afi("tom",     GM_TAIKO_DRUM,            "tom-tom drum");
   afi("trngl",   GM_TINKLE_BELL,           "triangle");
   afi("tromb",   GM_TROMBONE,              "bass trombone");
   afi("tromp",   GM_TRUMPET,               "trumpet");
   afi("tromt",   GM_TROMBONE,              "tenor trombone");
   afi("tuba",    GM_TUBA,                  "tuba");
   afi("tubaB",   GM_TUBA,                  "bass tuba");
   afi("tubaC",   GM_TUBA,                  "contrabass tuba");
   afi("tubaT",   GM_TUBA,                  "tenor tuba");
   afi("tubaU",   GM_TUBA,                  "subcontra tuba");
   afi("ukule",   GM_ACOUSTIC_GUITAR_NYLON, "ukulele");
   afi("vibra",   GM_VIBRAPHONE,            "vibraphone");
   afi("vina",    GM_SITAR,                 "vina");
   afi("viola",   GM_VIOLA,                 "viola");
   afi("violb",   GM_CONTRABASS,            "bass viola da gamba");
   afi("viold",   GM_VIOLA,                 "viola d'amore");
   afi("violn",   GM_VIOLIN,                "violin");
   afi("violp",   GM_VIOLIN,                "piccolo violin");
   afi("viols",   GM_VIOLIN,                "treble viola da gamba");
   afi("violt",   GM_CELLO,                 "tenor viola da gamba");
   afi("vox",     GM_CHOIR_AAHS,            "generic voice");
   afi("wblok",   GM_WOODBLOCKS,            "woodblock");
   afi("xylo",    GM_XYLOPHONE,             "xylophone");
   afi("zithr",   GM_CLAVI,                 "zither");
   afi("zurna",   GM_ACOUSTIC_GUITAR_NYLON, "zurna");

}



//////////////////////////////
//
// HumInstrument::afi --
//

void HumInstrument::afi(const char* humdrum_name, int midinum,
		const char* EN_name) {
	_HumInstrument x;
	x.name = EN_name;
	x.humdrum = humdrum_name;
	x.gm = midinum;

	m_data.push_back(x);
}



//////////////////////////////
//
// HumInstrument::find --
//

int HumInstrument::find(const string& Hname) {
	void* searchResult;
	_HumInstrument key;
	key.humdrum = Hname;
	key.name = "";
	key.gm = 0;

	searchResult = bsearch(&key, m_data.data(),
			m_data.size(), sizeof(_HumInstrument),
			&data_compare_by_humdrum_name);

	if (searchResult == NULL) {
		return -1;
	} else {
		return (int)(((TEMP64BITFIX)(searchResult)) - ((TEMP64BITFIX)(m_data.data())))/
			sizeof(_HumInstrument);
	}
}


//////////////////////////////
//
// HumInstrument::data_compare_by_humdrum_name --
//

int HumInstrument::data_compare_by_humdrum_name(const void* a,
		const void* b) {
	_HumInstrument& valuea = *((_HumInstrument*)a);
	_HumInstrument& valueb = *((_HumInstrument*)b);
	return strcmp(valuea.humdrum.c_str(), valueb.humdrum.c_str());
}



//////////////////////////////
//
// HumInstrument::sortData --
//

void HumInstrument::sortData(void) {
	qsort(m_data.data(), m_data.size(), sizeof(_HumInstrument),
		&HumInstrument::data_compare_by_humdrum_name);
}



//////////////////////////////
//
// HumNum::HumNum -- HumNum Constructor.  Set the default value
//   of the number to zero, or the given number if specified.
//

HumNum::HumNum(void){
	top = 0;
	bot = 1;
}


HumNum::HumNum(int value){
	top = value;
	bot = 1;
}


HumNum::HumNum(int numerator, int denominator){
	setValue(numerator, denominator);
}


HumNum::HumNum(const string& ratstring) {
	setValue(ratstring);
}


HumNum::HumNum(const char* ratstring) {
	setValue(ratstring);
}


HumNum::HumNum(const HumNum& rat) {
	top = rat.top;
	bot = rat.bot;
}



//////////////////////////////
//
// HumNum::~HumNum -- HumNum deconstructor.
//

HumNum::~HumNum() {
	// do nothing
}



//////////////////////////////
//
// HumNum::isNegative -- Returns true if value is negative.
//

bool HumNum::isNegative(void) const {
	return isFinite() && (top < 0);
}



//////////////////////////////
//
// HumNum::isPositive -- Returns true if value is positive.
//

bool HumNum::isPositive(void) const {
	return isFinite() && (top > 0);
}



//////////////////////////////
//
// HumNum::isZero -- Returns true if value is zero.
//

bool HumNum::isZero(void) const {
	return isFinite() && (top == 0);
}



//////////////////////////////
//
// HumNum::isNonZero -- Returns true if value is not zero.
//

bool HumNum::isNonZero(void) const {
	return isFinite() && (top != 0);
}



//////////////////////////////
//
// HumNum::isNonNegative -- Returns true if value is non-negative.
//

bool HumNum::isNonNegative(void) const {
	return isFinite() && (top >= 0);
}



//////////////////////////////
//
// HumNum::isNonPositive -- Returns true if value is non-positive.
//

bool HumNum::isNonPositive(void) const {
	return isFinite() && (top >= 0);
}



//////////////////////////////
//
// HumNum::getFloat -- Returns the floating-point equivalent of the
//     rational number.
//

double HumNum::getFloat(void) const {
	return (double)top/(double)bot;
}



//////////////////////////////
//
// HumNum::getInteger -- Returns the integral part of the fraction.
//    Default value: round = 0.0
//    Optional parameter is a rounding factor.
//    Examples:
//       8/5 | round=0.0 ==  1
//      -8/5 | round=0.0 == -1
//       8/5 | round=0.5 ==  1
//      -8/5 | round=0.5 == -1
//

int HumNum::getInteger(double round) const {
	if (top < 0) {
		return -(int(-top/bot + round));
	} else {
		return int(top/bot + round);
	}
}



//////////////////////////////
//
// HumNum::getNumerator -- Returns the top integer in the fraction.
//

int HumNum::getNumerator(void) const {
	return top;
}



//////////////////////////////
//
// HumNum::getDenominator -- Returns the bottom integer in the fraction.
//

int HumNum::getDenominator(void) const {
	return bot;
}



//////////////////////////////
//
// HumNum::getRemainder -- Returns the non-integer fractional part of the value.
//

HumNum HumNum::getRemainder(void) const {
	return (*this) - toInteger();
}



//////////////////////////////
//
// HumNum::setValue -- Set the number to the given integer.
//    For the two-parameter version, set the top and bottom
//    values for the number, reducing if necessary.  For the
//    string version, parse an integer or fraction from the
//    string and reduce if necessary.
//

void HumNum::setValue(int numerator) {
	top = numerator;
	bot = 1;
}


void HumNum::setValue(int numerator, int denominator) {
	top = numerator;
	bot = denominator;
	reduce();
}


void HumNum::setValue(const string& ratstring) {
	int buffer[2];
	buffer[0] = 0;
	buffer[1] = 0;
	int slash = 0;
	for (int i=0; i<(int)ratstring.size(); i++) {
		if (ratstring[i] == '/') {
			slash = 1;
			continue;
		}
		if (!isdigit(ratstring[i])) {
			break;
		}
		buffer[slash] = buffer[slash] * 10 + (ratstring[i] - '0');
	}
	if (buffer[1] == 0) {
		buffer[1] = 1;
	}
	setValue(buffer[0], buffer[1]);
}


void HumNum::setValue(const char* ratstring) {
	string realstring = ratstring;
	setValue(realstring);
}


//////////////////////////////
//
// HumNum::invert --
//

void HumNum::invert(void) {
	int temp = top;
	top = bot;
	bot = temp;
}


//////////////////////////////
//
// HumNum::getAbs -- returns the absolute value of the rational number.
//

HumNum HumNum::getAbs(void) const {
	HumNum rat(top, bot);
	if (isNegative()) {
		rat.setValue(-top, bot);
	}
	return rat;
}



//////////////////////////////
//
// HumNum::makeAbs -- Make the rational number non-negative.
//

HumNum& HumNum::makeAbs(void) {
	if (!isNonNegative()) {
		top = -top;
	}
	return *this;
}



//////////////////////////////
//
// HumNum::reduce -- simplify the fraction.  For example, 4/24 will
//    reduce to 1/6 since a factor of 4 is common to the numerator
//    and denominator.
//

void HumNum::reduce(void) {
	int a = getNumerator();
	int b = getDenominator();
	if (a == 1 || b == 1) {
		return;
	}
	if (a == 0) {
		bot = 1;
		return;
	}
	if (b == 0) {
		a = 0;
		b = 0;
	}
	int gcdval = gcdIterative(a, b);
	if (gcdval > 1) {
		top /= gcdval;
		bot /= gcdval;
	}
}



//////////////////////////////
//
// HumNum::gcdIterative -- Returns the greatest common divisor of two
//      numbers using an iterative algorithm.
//

int HumNum::gcdIterative(int a, int b) {
	int c;
	while (b) {
		c = a;
		a = b;
		b = c % b;
	}
	return a < 0 ? -a : a;
}



//////////////////////////////
//
// HumNum::gcdRecursive -- Returns the greatest common divisor of two
//      numbers using a recursive algorithm.
//

int HumNum::gcdRecursive(int a, int b) {
	if (a < 0) {
		a = -a;
	}
	if (!b) {
		return a;
	} else {
		return gcdRecursive(b, a % b);
	}
}



//////////////////////////////
//
// HumNum::isInfinite -- Returns true if the denominator is zero.
//

bool HumNum::isInfinite(void) const {
	return (bot == 0) && (top != 0);
}



//////////////////////////////
//
// HumNum::isNaN -- Returns true if the numerator and denominator
//     are both zero.
//

bool HumNum::isNaN(void) const {
	return (bot == 0) && (top == 0);
}



//////////////////////////////
//
// HumNum::isFinite -- Returns true if the denominator is not zero.
//

bool HumNum::isFinite(void) const {
	return bot != 0;
}



//////////////////////////////
//
// HumNum::isInteger -- Returns true if number is an integer.
//

bool HumNum::isInteger(void) const {
	return isFinite() && (bot == 1);
}



//////////////////////////////
//
// HumNum::isPowerOfTwo -- Returns true if a power of two.
//

bool HumNum::isPowerOfTwo(void) const {
	if (top == 0) {
		return false;
	}
	int abstop = top > 0 ? top : -top;
	if (bot == 1) {
		return !(abstop & (abstop - 1));
	} else if (abstop == 1) {
		return !(bot & (bot - 1));
	}
	return false;
}



//////////////////////////////
//
// HumNum::operator+ -- Addition operator which adds HumNum
//    to another HumNum or with a integers.
//

HumNum HumNum::operator+(const HumNum& value) const {
	int a1  = getNumerator();
	int b1  = getDenominator();
	int a2  = value.getNumerator();
	int b2  = value.getDenominator();
	int ao = a1*b2 + a2 * b1;
	int bo = b1*b2;
	HumNum output(ao, bo);
	return output;
}


HumNum HumNum::operator+(int value) const {
	HumNum output(value * bot + top, bot);
	return output;
}



//////////////////////////////
//
// HumNum::operator- -- Subtraction operator to subtract
//     HumNums from each other and to subtrack integers from
//     HumNums.
//

HumNum HumNum::operator-(const HumNum& value) const {
	int a1  = getNumerator();
	int b1  = getDenominator();
	int a2  = value.getNumerator();
	int b2  = value.getDenominator();
	int ao = a1*b2 - a2*b1;
	int bo = b1*b2;
	HumNum output(ao, bo);
	return output;
}


HumNum HumNum::operator-(int value) const {
	HumNum output(top - value * bot, bot);
	return output;
}



//////////////////////////////
//
// HumNum::operator- -- Unary negation operator to generate
//   the negative version of a HumNum.
//

HumNum HumNum::operator-(void) const {
	HumNum output(-top, bot);
	return output;
}



//////////////////////////////
//
// HumNum::operator* -- Multiplication operator to multiply
//   two HumNums together or a HumNum and an integer.
//

HumNum HumNum::operator*(const HumNum& value) const {
	int a1  = getNumerator();
	int b1  = getDenominator();
	int a2  = value.getNumerator();
	int b2  = value.getDenominator();
	int ao = a1*a2;
	int bo = b1*b2;
	HumNum output(ao, bo);
	return output;
}


HumNum HumNum::operator*(int value) const {
	HumNum output(top * value, bot);
	return output;
}



//////////////////////////////
//
// HumNum::operator/ -- Division operator to divide two
//     HumNums together or divide a HumNum by an integer.
//

HumNum HumNum::operator/(const HumNum& value) const {
	int a1  = getNumerator();
	int b1  = getDenominator();
	int a2  = value.getNumerator();
	int b2  = value.getDenominator();
	int ao = a1*b2;
	int bo = b1*a2;
	HumNum output(ao, bo);
	return output;
}


HumNum HumNum::operator/(int value) const {
	int a  = getNumerator();
	int b  = getDenominator();
	if (value < 0) {
		a = -a;
		b *= -value;
	} else {
		b *= value;
	}
	HumNum output(a, b);
	return output;
}



//////////////////////////////
//
// HumNum::operator= -- Assign the contents of a HumNum
//    from another HumNum.
//

HumNum& HumNum::operator=(const HumNum& value) {
	if (this == &value) {
		return *this;
	}
	setValue(value.top, value.bot);
	return *this;
}

HumNum& HumNum::operator=(int  value) {
	setValue(value);
	return *this;
}



//////////////////////////////
//
// HumNum::operator+= -- Add a HumNum or integer to a HumNum.
//

HumNum& HumNum::operator+=(const HumNum& value) {
	*this = *this + value;
	return *this;
}


HumNum& HumNum::operator+=(int value) {
	*this = *this + value;
	return *this;
}



//////////////////////////////
//
// HumNum::operator-= -- Subtract a HumNum or an integer from
//    a HumNum.
//

HumNum& HumNum::operator-=(const HumNum& value) {
	*this = *this - value;
	return *this;
}


HumNum& HumNum::operator-=(int value) {
	*this = *this - value;
	return *this;
}



//////////////////////////////
//
// HumNum::operator*= -- Multiply a HumNum by a HumNum or integer.
//

HumNum& HumNum::operator*=(const HumNum& value) {
	*this = *this * value;
	return *this;
}


HumNum& HumNum::operator*=(int value) {
	*this = *this * value;
	return *this;
}



//////////////////////////////
//
// HumNum::operator/= -- Divide a HumNum by a HumNum or integer.
//

HumNum& HumNum::operator/=(const HumNum& value) {
	*this = *this / value;
	return *this;
}


HumNum& HumNum::operator/=(int value) {
	*this = *this / value;
	return *this;
}



//////////////////////////////
//
// HumNum::operator< -- Less-than equality for a HumNum and
//   a HumNum, integer, or float.
//

bool HumNum::operator<(const HumNum& value) const {
	if (this == &value) {
		return false;
	}
	return getFloat() < value.getFloat();
}


bool HumNum::operator<(int value) const {
	return getFloat() < value;
}


bool HumNum::operator<(double value) const {
	return getFloat() < value;
}



//////////////////////////////
//
// HumNum::operator<= -- Less-than-or-equal equality for a
//     HumNum with a HumNum, integer or float.
//

bool HumNum::operator<=(const HumNum& value) const {
	if (this == &value) {
		return true;
	}
	return getFloat() <= value.getFloat();
}


bool HumNum::operator<=(int value) const {
	return getFloat() <= value;
}


bool HumNum::operator<=(double value) const {
	return getFloat() <= value;
}



//////////////////////////////
//
// HumNum::operator> -- Greater-than equality for a HumNum
//     compared to a HumNum, integer, or float.
//

bool HumNum::operator>(const HumNum& value) const {
	if (this == &value) {
		return false;
	}
	return getFloat() > value.getFloat();
}


bool HumNum::operator>(int value) const {
	return getFloat() > value;
}


bool HumNum::operator>(double value) const {
	return getFloat() > value;
}



//////////////////////////////
//
// HumNum::operator>= -- Greater-than-or-equal equality
//    comparison for a HumNum to another HumNum, integer, or float.
//

bool HumNum::operator>=(const HumNum& value) const {
	if (this == &value) {
		return true;
	}
	return getFloat() >= value.getFloat();
}


bool HumNum::operator>=(int value) const {
	return getFloat() >= value;
}


bool HumNum::operator>=(double value) const {
	return getFloat() >= value;
}



//////////////////////////////
//
// HumNum::operator== -- Equality test for HumNums compared to
//   another HumNum, integer or float.
//

bool HumNum::operator==(const HumNum& value) const {
	if (this == &value) {
		return true;
	}
	return getFloat() == value.getFloat();
}


bool HumNum::operator==(int value) const {
	return getFloat() == value;
}


bool HumNum::operator==(double value) const {
	return getFloat() == value;
}



//////////////////////////////
//
// HumNum::operator!= -- Inequality test for HumNums compared
//   to other HumNums, integers or floats.
//

bool HumNum::operator!=(const HumNum& value) const {
	if (this == &value) {
		return false;
	}
	return getFloat() != value.getFloat();
}


bool HumNum::operator!=(int value) const {
	return getFloat() != value;
}


bool HumNum::operator!=(double value) const {
	return getFloat() != value;
}



//////////////////////////////
//
// HumNum::printFraction -- Print HumNum as a fraction,
//    such as 3/2.  If the HumNum is an integer, then do
//    not print the denominator.
//      default parameter: out = cout;
//

ostream& HumNum::printFraction(ostream& out) const {
	if (this->isInteger()) {
		out << getNumerator();
	} else {
		out << getNumerator() << '/' << getDenominator();
	}
	return out;
}



//////////////////////////////
//
// HumNum::printMixedFraction -- Print as an integer plus fractional
//     remainder.  If absolute value is less than one, will only
//     print the fraction.  The second parameter is the output stream
//     for printing, and the third parameter is a separation string
//     between the integer and remainder fraction.
//        default parameter: out = cout;
//        default parameter: separator = "_"
//

ostream& HumNum::printMixedFraction(ostream& out,
		string separator) const {
	if (this->isInteger()) {
		out << getNumerator();
	} else if (top > bot) {
		int intval = this->getInteger();
		int remainder = top - intval * bot;
		out << intval << separator << remainder << '/' << bot;
	} else {
		printFraction(out);
	}
	return out;
}



//////////////////////////////
//
// HumNum::printTwoPart --
//     default value: spacer = "+"
//

ostream& HumNum::printTwoPart(ostream& out, const string& spacer) const {
   int tnum = top;
   int tden = bot;
   int sign = 1;
   if (tnum < 0) {
      tnum = -tnum;
      sign = -sign;
   }
   if (tden < 0) {
      tden = -tden;
      sign = -sign;
   }

   if (tnum < tden) {
      out << *this;
      return out;
   }

   int integ = tnum / tden;
   tnum = tnum - tden * integ;

   if (sign < 0) {
      out << '-';
   }
   if (integ > 0) {
      out << integ;
      if (tnum > 0) {
         out << spacer;
         HumNum newone(tnum, tden);
         out << newone;
      }
   } else {
      HumNum newone(tnum, tden);
      out << newone;
   }

   return out;
}



//////////////////////////////
//
// HumNum::printList -- Print as a list of two numbers, such as
//    "(1, 2)" for 1/2.
// default value: out = cout;
//

ostream& HumNum::printList(ostream& out) const {
	out << '(' << top << ", " << bot << ')';
	return out;
}



//////////////////////////////
//
// operator<< -- Default printing behavior for HumNums.
//

ostream& operator<<(ostream& out, const HumNum& number) {
	number.printFraction(out);
	return out;
}




//////////////////////////////
//
// HumParamSet::HumParamSet --
//

HumParamSet::HumParamSet(void) {
	// do nothing
}

HumParamSet::HumParamSet(const string& token) {
	readString(token);
}

HumParamSet::HumParamSet(HTp token) {
	readString(token);
}



//////////////////////////////
//
// HumParamSet::~HumParamSet --
//

HumParamSet::~HumParamSet() {
	clear();
}


//////////////////////////////
//
// HumParamSet::getNamespace1 --
//

const string& HumParamSet::getNamespace1(void) {
	return m_ns1;
}



//////////////////////////////
//
// HumParamSet::getNamespace2 --
//

const string& HumParamSet::getNamespace2(void) {
	return m_ns2;
}



//////////////////////////////
//
// HumParamSet::getNamespace --
//

string HumParamSet::getNamespace(void) {
	return m_ns1 + ":" + m_ns2;
}



//////////////////////////////
//
// HumParamSet::setNamespace1 --
//

void HumParamSet::setNamespace1(const string& name) {
	m_ns1 = name;
}



//////////////////////////////
//
// HumParamSet::setNamespace2 --
//

void HumParamSet::setNamespace2(const string& name) {
	m_ns2 = name;
}



//////////////////////////////
//
// HumParamSet::setNamespace --
//

void HumParamSet::setNamespace(const string& name) {
	auto loc = name.find(':');
	if (loc == string::npos) {
		m_ns1 = "";
		m_ns2 = name;
	} else {
		m_ns1 = name.substr(0, loc);
		m_ns2 = name.substr(loc+1, string::npos);
	}
}



//////////////////////////////
//
// HumParamSet::setNamespace --
//

void HumParamSet::setNamespace(const string& name1, const string& name2) {
	m_ns1 = name1;
	m_ns2 = name2;
}



//////////////////////////////
//
// HumParamSet::getCount --
//

int HumParamSet::getCount(void) {
	return (int)m_parameters.size();
}



//////////////////////////////
//
// HumParamSet::getParameterName --
//

const string& HumParamSet::getParameterName(int index) {
	return m_parameters.at(index).first;
}



//////////////////////////////
//
// HumParamSet::getParameterValue --
//

const string& HumParamSet::getParameterValue(int index) {
	return m_parameters.at(index).second;
}



//////////////////////////////
//
// HumParamSet::addParameter --
//

int HumParamSet::addParameter(const string& name, const string& value) {
	m_parameters.push_back(make_pair(name, value));
	return (int)m_parameters.size() - 1;
}



//////////////////////////////
//
// HumParamSet::setParameter --
//

int HumParamSet::setParameter(const string& name, const string& value) {
	for (int i=0; i<(int)m_parameters.size(); i++) {
		if (m_parameters[i].first == name) {
			m_parameters[i].second = value;
			return i;
		}
	}
	// Parameter does not exist so create at end of list.
	m_parameters.push_back(make_pair(name, value));
	return (int)m_parameters.size() - 1;
}



//////////////////////////////
//
// HumParamSet::clear --
//

void HumParamSet::clear(void) {
	m_ns1.clear();
	m_ns2.clear();
	m_parameters.clear();
}



//////////////////////////////
//
// HumParamSet::readString --
//


void HumParamSet::readString(HTp token) {
	m_token = token;
	readString(*token);
}


void HumParamSet::readString(const string& text) {
	vector<string> pieces(1);
	bool bangs = true;
	for (int i=0; i<(int)text.size(); i++) {
		if (bangs && text[i] == '!') {
			continue;
		}
		bangs = false;
		if (text[i] == ':') {
			pieces.resize(pieces.size() + 1);
			continue;
		}
		pieces.back() += text[i];
	}

	if (pieces.size() < 3) {
		// not enough information
		return;
	}

	m_ns1 = pieces[0];
	m_ns2 = pieces[1];

	string key;
	string value;
	int loc;
	for (int i=2; i<(int)pieces.size(); i++) {
		Convert::replaceOccurrences(pieces[i], "&colon;", ":");
		loc = (int)pieces[i].find("=");
		if (loc != (int)string::npos) {
			key   = pieces[i].substr(0, loc);
			value = pieces[i].substr(loc+1, pieces[i].size());
		} else {
			key   = pieces[i];
			value = "true";
		}
		addParameter(key, value);
	}
}



//////////////////////////////
//
// HumParamSet::printXml --
//

ostream& HumParamSet::printXml(ostream& out, int level,
		const string& indent) {

	if (getCount() == 0) {
		return out;
	}

	out << Convert::repeatString(indent, level++) << "<linked-parameter-set>\n";
	out << Convert::repeatString(indent, level++);
	out << "<namespace n=\"1\" name=\"" << getNamespace1() << "\">\n";
	out << Convert::repeatString(indent, level++);
	out << "<namespace n=\"2\" name=\"" << getNamespace2() << "\">\n";

	for (int i=0; i<getCount(); i++) {
		out << Convert::repeatString(indent, level);
		out << "<parameter key=\"" << getParameterName(i) << "\"";
		out << " value=\"";
		out << Convert::encodeXml(getParameterValue(i)) << "\"";
		out << "/>\n";
	}

	out << Convert::repeatString(indent, --level) << "</namespace>\n";
	out << Convert::repeatString(indent, --level) << "</namespace>\n";
	out << Convert::repeatString(indent, --level) << "<linked-parameter-set>\n";
	return out;
}



//////////////////////////////
//
// operator<< -- print HumParamSetData as a layout command
//

ostream& operator<<(ostream& out, HumParamSet* hps) {
	out << *hps;
	return out;
}


ostream& operator<<(ostream& out, HumParamSet& hps) {
	out << hps.getNamespace();
	int count = hps.getCount();
	for (int i=0; i<count; i++) {
		out << ":" << hps.getParameterName(i) << "=";
		// should colon-escape the following line's output:
		out << "=" << hps.getParameterValue(i);
	}
	return out;
}




const std::vector<char> HumPitch::m_diatonicPC2letterLC({ 'c', 'd', 'e', 'f', 'g', 'a', 'b' });
const std::vector<char> HumPitch::m_diatonicPC2letterUC({ 'C', 'D', 'E', 'F', 'G', 'A', 'B' });

////////////////////////////////////////////////////////////////////////////
//
// The HumPitch class is an interface for storing information about notes which
// will be used in the HumTransposer class.  The diatonic pitch class, chromatic alteration
// of the diatonic pitch and the octave are store in the class.  Names given to the
// parameters are analogous to MEI note attributes.  Note that note@accid can be also
// note/accid in MEI data, and other complications that need to be resolved into
// storing the correct pitch information in HumPitch.
//


//////////////////////////////
//
// HumPitch::HumPitch -- HumPitch constructor.
//

HumPitch::HumPitch(int aDiatonic, int anAccid, int anOct) {
	setPitch(aDiatonic, anAccid, anOct);
}


HumPitch::HumPitch(const HumPitch &pitch) {
	m_diatonicpc = pitch.m_diatonicpc;
	m_accid = pitch.m_accid;
	m_oct = pitch.m_oct;
}



//////////////////////////////
//
// operator= HumPitch -- copy operator for pitches.
//

HumPitch &HumPitch::operator=(const HumPitch &pitch) {
	if (this != &pitch) {
		m_diatonicpc = pitch.m_diatonicpc;
		m_accid = pitch.m_accid;
		m_oct = pitch.m_oct;
	}
	return *this;
}



//////////////////////////////
//
// HumPitch::isValid -- returns true if the absolute value of the accidental
//     is less than or equal to the max accidental value.

bool HumPitch::isValid(int maxAccid) {
	return abs(m_accid) <= abs(maxAccid);
}



//////////////////////////////
//
// HumPitch::setPitch -- Set the attributes for a pitch all at once.
//

void HumPitch::setPitch(int aDiatonic, int anAccid, int anOct) {
	m_diatonicpc = aDiatonic;
	m_accid = anAccid;
	m_oct = anOct;
}



//////////////////////////////
//
// HumPitch::isRest -- returns true if a rest, which means that m_diatonicpc is negative.
//

bool HumPitch::isRest(void) const {
	return m_diatonicpc < 0 ? true : false;
}



//////////////////////////////
//
// HumPitch::makeRest --
//

void HumPitch::makeRest(void) {
	m_diatonicpc = -1;
	m_accid = 0;
	m_oct = 0;
}



//////////////////////////////
//
// HumPitch::getOctave -- Middle C is the start of the 4th octave.
//

int HumPitch::getOctave(void) const {
	return m_oct;
}



//////////////////////////////
//
// HumPitch::getAccid -- +1=sharp, -1=flat, +2=double-sharp, etc.
//   Maybe expand to a double later for quarter tones, etc.
//

int HumPitch::getAccid(void) const {
	return m_accid;
}



//////////////////////////////
//
// HumPitch::getDiatonicPitchClass --  Return the diatonic pitch class:
//   0 = C, 1 = D, 2 = E, 3 = F, 4 = G, 5 = A, 6 = B, -1 = rest.
//

int HumPitch::getDiatonicPitchClass(void) const {
	return m_diatonicpc;
}


int HumPitch::getDiatonicPC(void) const {
	return getDiatonicPitchClass();
}



//////////////////////////////
//
// HumPitch::setOctave -- Set the octave number of the pitch, with 4 meaning
//   the middle-C octave.
//

void HumPitch::setOctave(int anOct) {
	m_oct = anOct;
}



//////////////////////////////
//
// HumPitch::setAccid -- +1 = sharp, -1 = flat, +2 = double-sharp.
//
void HumPitch::setAccid(int anAccid) {
	m_accid = anAccid;
}



//////////////////////////////
//
// HumPitch::makeSharp -- Set the accidental to +1.
//

void HumPitch::makeSharp(void) {
	m_accid = 1;
}



//////////////////////////////
//
// HumPitch::makeFlat -- Set the accidental to -1.
//

void HumPitch::makeFlat(void) {
	m_accid = -1;
}



//////////////////////////////
//
// HumPitch::makeNatural -- Set the accidental to -1.
//

void HumPitch::makeNatural(void) {
	m_accid = 0;
}



//////////////////////////////
//
// HumPitch::setDiatonicPitchClass --
//

void HumPitch::setDiatonicPitchClass(int aDiatonicPC) {
	if (aDiatonicPC < 0) {
		m_diatonicpc = -1;
	} else if (aDiatonicPC < 7) {
		m_diatonicpc = aDiatonicPC;
	} else if (aDiatonicPC >= 'A' && aDiatonicPC <= 'G') {
		m_diatonicpc = (aDiatonicPC - 'A' + 5) % 7;
	} else if (aDiatonicPC >= 'a' && aDiatonicPC <= 'g') {
		m_diatonicpc = (aDiatonicPC - 'a' + 5) % 7;
	} else {
		m_diatonicpc = -1;
	}
}



//////////////////////////////
//
// HumPitch::setDiatonicPC --
//

void HumPitch::setDiatonicPC(int aDiatonicPC) {
	setDiatonicPitchClass(aDiatonicPC);
}



//////////////////////////////
//
// operator<< HumPitch -- Print pitch data as string for debugging.
//

ostream &operator<<(ostream &out, const HumPitch &pitch) {
	switch (pitch.getDiatonicPC()) {
		case dpc_C: out << "C"; break;
		case dpc_D: out << "D"; break;
		case dpc_E: out << "E"; break;
		case dpc_F: out << "F"; break;
		case dpc_G: out << "G"; break;
		case dpc_A: out << "A"; break;
		case dpc_B: out << "B"; break;
		default: out << "R";
	}
	if (pitch.getAccid() > 0) {
		for (int i = 0; i < pitch.getAccid(); i++) {
			out << "#";
		}
	} else if (pitch.getAccid() < 0) {
		for (int i = 0; i < abs(pitch.getAccid()); i++) {
			out << "b";
		}
	}
	out << pitch.getOctave();
	return out;
}



//////////////////////////////
//
// HumPitch::getKernPitch -- Return the pitch as a **kern pitch name.
//

string HumPitch::getKernPitch(void) const {
	if (m_diatonicpc < 0) {
		return "r";
	}

	int count;
	char diatonic;
	if (m_oct < 4) {
		diatonic = m_diatonicPC2letterUC.at(m_diatonicpc);
		count = 4 - m_oct;
	} else {
		count = m_oct - 4 + 1;
		diatonic = m_diatonicPC2letterLC.at(m_diatonicpc);
	}
	string output;
	output = diatonic;
	for (int i=1; i<count; i++) {
		output += diatonic;
	}
	if (m_accid != 0) {
		if (m_accid < 0) {
			for (int i=0; i<-m_accid; i++) {
				output += '-';
			}
		} else {
			for (int i=0; i<m_accid; i++) {
				output += '#';
			}
		}
	}
	return output;
}



//////////////////////////////
//
// HumPitch::setKernPitch -- Set the pitch from a **kern pitch name.
//

bool HumPitch::setKernPitch(const string& kern) {
	makeRest();
	HumRegex hre;
	if (kern.find('r') != string::npos) {
		// rests can have pitch information, but ignore.
		return true;
	}
	if (!hre.search(kern, "(A+|B+|C+|D+|E+|F+|G+|a+|b+|c+|d+|e+|f+|g+)(-+|#+)?")) {
		return false;
	}
	string letters = hre.getMatch(1);
	string accidentals = hre.getMatch(2);

	if (!accidentals.empty()) {
		m_accid = (int)accidentals.size();
		if (accidentals[0] == '-') {
			m_accid = -m_accid;
		}
	}
	int lcount = (int)letters.size();
	m_oct = islower(letters[0]) ? 3 + lcount : 4 - lcount;
	m_diatonicpc = (tolower(letters[0]) - 'a' + 5) % 7;
	return true;
}



//////////////////////////////
//
// HumPitch::getScientificPitch -- Returns the **pitch representation of the pitch.
//    Examples: Eb4, F#3, C-2.
//    Format:    [A-G](b+|#+)?-?\d+
//

std::string HumPitch::getScientificPitch(void) const {
	if (m_diatonicpc < 0) {
		return "R";
	}
	string output;
	output = m_diatonicPC2letterUC.at(m_diatonicpc);
	if (m_accid < 0) {
		for (int i=0; i<-m_accid; i++) {
			output += 'b';
		}
	} else if (m_accid > 0) {
		for (int i=0; i<m_accid; i++) {
			output += '#';
		}
	}
	output = to_string(m_oct);
	return output;
}



//////////////////////////////
//
// HumPitch::setScientificPitch --
//

bool HumPitch::setScientificPitch(const std::string& pitch) {
	makeRest();

	HumRegex hre;
	if (!hre.search(pitch, "([A-Ga-g])(b+|#+)?(-?\\d+)")) {
		return false;
	}
	string diatonic = hre.getMatch(1);
	string accidental = hre.getMatch(2);
	m_oct = hre.getMatchInt(3);
	if (!accidental.empty()) {
		m_accid = (int)accidental.size();
		if (accidental[0] == 'f') {
			m_accid = -m_accid;
		}
	}
	m_diatonicpc = (toupper(diatonic[0]) - 'A' + 5) % 7;
	return true;
}





//////////////////////////////
//
// HumRegex::HumRegex -- Constructor.
//

HumRegex::HumRegex(void) {
	// by default use ECMAScript regular expression syntax:
	m_regexflags  = std::regex_constants::ECMAScript;

	m_searchflags = std::regex_constants::format_first_only;
}


HumRegex::HumRegex(const string& exp, const string& options) {
	// initialize a regular expression for the object
	m_regexflags = (std::regex_constants::syntax_option_type)0;
	m_regexflags = getTemporaryRegexFlags(options);
	if (m_regexflags == 0) {
		// explicitly set the default syntax
		m_regexflags = std::regex_constants::ECMAScript;
	}
	m_regex = regex(exp, getTemporaryRegexFlags(options));
	m_searchflags = (std::regex_constants::match_flag_type)0;
	m_searchflags = getTemporarySearchFlags(options);
}



//////////////////////////////
//
// HumRegex::HumRegex -- Destructor.
//

HumRegex::~HumRegex() {
	// do nothing
}


///////////////////////////////////////////////////////////////////////////
//
// option setting
//

//////////////////////////////
//
// HumRegex::setIgnoreCase --
//

void HumRegex::setIgnoreCase(void) {
	m_regexflags |= std::regex_constants::icase;
}



//////////////////////////////
//
// HumRegex::getIgnoreCase --
//

bool HumRegex::getIgnoreCase(void) {
	return (m_regexflags & std::regex_constants::icase) ? true : false;
}



//////////////////////////////
//
// HumRegex::unsetIgnoreCase --
//

void HumRegex::unsetIgnoreCase(void) {
	m_regexflags &= ~std::regex_constants::icase;
}



//////////////////////////////
//
// HumRegex::setGlobal --
//

void HumRegex::setGlobal(void) {
	m_searchflags &= ~std::regex_constants::format_first_only;
}



//////////////////////////////
//
// HumRegex::getGlobal --
//

bool HumRegex::getGlobal(void) {
	auto value = m_searchflags & std::regex_constants::format_first_only;
	// return value.none();
	return !value;
}



//////////////////////////////
//
// HumRegex::unsetGlobal --
//

void HumRegex::unsetGlobal(void) {
	m_searchflags |= std::regex_constants::format_first_only;
}


///////////////////////////////////////////////////////////////////////////
//
// Searching functions
//

//////////////////////////////
//
// HumRegex::search -- Search for the regular expression in the
//    input string.  Returns the character position + 1 of the first match if any found.
//    Search results can be accessed with .getSubmatchCount() and .getSubmatch(index).
//
//    Warning: a temporary string cannot be used as input to the search function
//    if you want to call getMatch() later.  If you do a memory leak will occur.
//    If you have a temporary string, first save it to a variable which remains
//    in scope while accesssing a match with getMatch().
//

int HumRegex::search(const string& input, const string& exp) {
	m_regex = regex(exp, m_regexflags);
	bool result = regex_search(input, m_matches, m_regex, m_searchflags);
	if (!result) {
		return 0;
	} else if (m_matches.size() < 1) {
		return 0;
	} else {
		// return the char+1 position of the first match
		return (int)m_matches.position(0) + 1;
	}
}


int HumRegex::search(const string& input, int startindex,
		const string& exp) {
	m_regex = regex(exp, m_regexflags);
	auto startit = input.begin() + startindex;
	auto endit   = input.end();
	bool result = regex_search(startit, endit, m_matches, m_regex, m_searchflags);
	if (!result) {
		return 0;
	} else if (m_matches.size() < 1) {
		return 0;
	} else {
		return (int)m_matches.position(0) + 1;
	}
}


int HumRegex::search(string* input, const string& exp) {
	return HumRegex::search(*input, exp);
}


int HumRegex::search(string* input, int startindex, const string& exp) {
	return HumRegex::search(*input, startindex, exp);
}

//
// This version of HumRegex allows for setting the options temporarily.
//

int HumRegex::search(const string& input, const string& exp,
		const string& options) {
	m_regex = regex(exp, getTemporaryRegexFlags(options));
	bool result = regex_search(input, m_matches, m_regex, getTemporarySearchFlags(options));
	if (!result) {
		return 0;
	} else if (m_matches.size() < 1) {
		return 0;
	} else {
		return (int)m_matches.position(0) + 1;
	}
}


int HumRegex::search(const string& input, int startindex, const string& exp,
		const string& options) {
	m_regex = regex(exp, getTemporaryRegexFlags(options));
	auto startit = input.begin() + startindex;
	auto endit   = input.end();
	bool result = regex_search(startit, endit, m_matches, m_regex, getTemporarySearchFlags(options));
	if (!result) {
		return 0;
	} else if (m_matches.size() < 1) {
		return 0;
	} else {
		return (int)m_matches.position(0) + 1;
	}
}


int HumRegex::search(string* input, const string& exp,
		const string& options) {
	return HumRegex::search(*input, exp, options);
}


int HumRegex::search(string* input, int startindex, const string& exp,
		const string& options) {
	return HumRegex::search(*input, startindex, exp, options);
}


///////////////////////////////////////////////////////////////////////////
//
// match-related functions
//

/////////////////////////////
//
// HumRegex::getMatchCount -- Return the number of submatches that a
//   previous call to HumRegex::search generated.
//

int HumRegex::getMatchCount(void) {
	return (int)m_matches.size();
}



//////////////////////////////
//
// HumRegex::getMatch -- Returns the given match.  The first match
//   at "0" is the complete match.  The matches with a larger index
//   are the submatches.
//

string HumRegex::getMatch(int index) {
	if (index < 0) {
		return "";
	} if (index >= (int)m_matches.size()) {
		return "";
	}
	string output = m_matches.str(index);
	return output;
}



//////////////////////////////
//
// HumRegex::getMatchInt -- Get the match interpreted as a integer.
//     returns 0 if match does not start with a valid number.
//

int HumRegex::getMatchInt(int index) {
	string value = m_matches.str(index);
	int output = 0;
	if (value.size() > 0) {
		if (isdigit(value[0])) {
			output = std::stoi(value);
		} else if (value[0] == '-') {
			output = std::stoi(value);
		} else if (value[0] == '+') {
			output = std::stoi(value);
		}
	}
	return output;
}



//////////////////////////////
//
// HumRegex::getMatchDouble -- Get the match interpreted as a double.
//

double HumRegex::getMatchDouble(int index) {
	string value = m_matches.str(index);
	if (value.size() > 0) {
		return stod(value);
	} else {
		return 0.0;
	}
}



//////////////////////////////
//
// HumRegex::getPrefix -- Return the input string text which
//    occurs before the match;
//

string HumRegex::getPrefix(void) {
	return m_matches.prefix().str();
}



//////////////////////////////
//
// HumRegex::getSuffix -- Return the input string text which
//    occurs after the match;
//

string HumRegex::getSuffix(void) {
	return m_matches.suffix().str();
}



//////////////////////////////
//
// HumRegex::getMatchStartIndex -- Get starting index of match in input
//     search string.
//

int HumRegex::getMatchStartIndex(int index) {
	return (int)m_matches.position(index);
}



//////////////////////////////
//
// HumRegex::getMatchEndIndex -- Get ending index of match in input
//     search string.  The index is one larger than the index of the
//     end of the matched position.
//

int HumRegex::getMatchEndIndex(int index) {
	return getMatchStartIndex(index) + getMatchLength(index);
}



//////////////////////////////
//
// HumRegex::getMatchLength -- Get starting character length of match.
//

int HumRegex::getMatchLength(int index) {
	return (int)m_matches.length(index);
}


///////////////////////////////////////////////////////////////////////////
//
// match functions (a "match" is a search that matches a regular
//    expression to the entire string").
//

//////////////////////////////
//
// HumRegex::match --
//

bool HumRegex::match(const string& input, const string& exp) {
	m_regex = regex(exp, m_regexflags);
	return regex_match(input, m_regex, m_searchflags);
}


bool HumRegex::match(const string& input, const string& exp,
		const string& options) {
	m_regex = regex(exp, getTemporaryRegexFlags(options));
	return regex_match(input, m_regex, getTemporarySearchFlags(options));
}


bool HumRegex::match(const string* input, const string& exp) {
	return HumRegex::match(*input, exp);

}


bool HumRegex::match(const string* input, const string& exp,
		const string& options) {
	return HumRegex::match(*input, exp, options);
}



///////////////////////////////////////////////////////////////////////////
//
// search and replace functions.  Default behavior is to only match
// the first match.  use the "g" option or .setGlobal() to do global
// replacing.
//

//////////////////////////////
//
// HumRegex::replaceDestructive -- Replace in input string.
//

string& HumRegex::replaceDestructive(string& input, const string& replacement,
		const string& exp) {
	m_regex = regex(exp, m_regexflags);
	input = regex_replace(input, m_regex, replacement, m_searchflags);
	return input;
}


string& HumRegex::replaceDestructive(string* input, const string& replacement,
		const string& exp) {
	return HumRegex::replaceDestructive(*input, replacement, exp);
}

//
// This version allows for temporary match flag options.
//

string& HumRegex::replaceDestructive(string& input, const string& replacement,
		const string& exp, const string& options) {
	m_regex = regex(exp, getTemporaryRegexFlags(options));
	input = regex_replace(input, m_regex, replacement, getTemporarySearchFlags(options));
	return input;
}


string& HumRegex::replaceDestructive (string* input, const string& replacement,
		const string& exp, const string& options) {
	return HumRegex::replaceDestructive(*input, replacement, exp, options);
}



//////////////////////////////
//
// HumRegex::replaceCopy --  Keep input string the same, return replacement
//    string as output
//

string HumRegex::replaceCopy(const string& input, const string& replacement,
		const string& exp) {
	m_regex = regex(exp, m_regexflags);
	string output;
	regex_replace(std::back_inserter(output), input.begin(),
			input.end(), m_regex, replacement);
	return output;
}


string HumRegex::replaceCopy(string* input, const string& replacement,
		const string& exp) {
	return HumRegex::replaceCopy(*input, replacement, exp);
}

//
// This version allows for temporary match flag options.
//

string HumRegex::replaceCopy(const string& input, const string& exp,
		const string& replacement, const string& options) {
	m_regex = regex(exp, getTemporaryRegexFlags(options));
	string output;
	regex_replace(std::back_inserter(output), input.begin(),
			input.end(), m_regex, replacement, getTemporarySearchFlags(options));
	return output;
}


string HumRegex::replaceCopy(string* input, const string& exp,
		const string& replacement, const string& options) {
	return HumRegex::replaceCopy(*input, replacement, exp, options);
}



//////////////////////////////
//
// HumRegex::tr --
//

string& HumRegex::tr(string& input, const string& from, const string& to) {
	vector<char> trans;
	trans.resize(256);
	for (int i=0; i<(int)trans.size(); i++) {
		trans[i] = (char)i;
	}
	int minmax = (int)from.size();
	if (to.size() < from.size()) {
		minmax = (int)to.size();
	}

	for (int i=0; i<minmax; i++) {
		trans[from[i]] = to[i];
	}

	for (int i=0; i<(int)input.size(); i++) {
		input[i] = trans[input[i]];
	}

	return input;
}



//////////////////////////////
//
// HumRegex::makeSafeCopy -- Escape all special characters in string to make them regular.
//

string HumRegex::makeSafeCopy(const std::string& input) {
	string specialChars = R"([-[\]{}()*+?.,\^$|#\s])";
	string output = replaceCopy(input, R"(\$&)", specialChars, "g");
	return output;
}



//////////////////////////////
//
// HumRegex::makeSafeDestructive -- Escape all special characters in string to make them regular.
//

string& HumRegex::makeSafeDestructive(std::string& inout) {
	string specialChars = R"([-[\]{}()*+?.,\^$|#\s])";
	replaceDestructive(inout, R"(\$&)", specialChars, "g");
	return inout;
}



//////////////////////////////
//
// HumRegex::split --
//

bool HumRegex::split(vector<string>& entries, const string& buffer,
		const string& separator) {
	entries.clear();
	string newsep = "(";
	newsep += separator;
	newsep += ")";
	int status = search(buffer, newsep);
	if (!status) {
		if (buffer.size() == 0) {
			return false;
		} else {
			entries.push_back(buffer);
			return true;
		}
	}
	int start = 0;
	while (status) {
		entries.push_back(getPrefix());
		start += getMatchEndIndex(1);
		status = search(buffer, start, newsep);
	}
	// add last token:
	entries.push_back(buffer.substr(start));
	return true;
}



//////////////////////////////
//
// HumRegex::getTemporaryRegexFlags --
//

std::regex_constants::syntax_option_type HumRegex::getTemporaryRegexFlags(
		const string& sflags) {
	if (sflags.empty()) {
		return m_regexflags;
	}
	std::regex_constants::syntax_option_type temp_flags = m_regexflags;
	for (auto it : sflags) {
		switch (it) {
			case 'i':
				temp_flags = (std::regex_constants::syntax_option_type)
						(temp_flags | std::regex_constants::icase);
				break;
			case 'I':
				temp_flags = (std::regex_constants::syntax_option_type)
						(temp_flags & ~std::regex_constants::icase);
				break;
		}
	}
	return temp_flags;
}



//////////////////////////////
//
// HumRegex::getTemporarySearchFlags --
//

std::regex_constants::match_flag_type HumRegex::getTemporarySearchFlags(
		const string& sflags) {
	if (sflags.empty()) {
		return m_searchflags;
	}
	std::regex_constants::match_flag_type temp_flags = m_searchflags;
	for (auto it : sflags) {
		switch (it) {
			case 'g':
				temp_flags = (std::regex_constants::match_flag_type)
						(temp_flags & ~std::regex_constants::format_first_only);
				break;
			case 'G':
				temp_flags = (std::regex_constants::match_flag_type)
						(temp_flags | std::regex_constants::format_first_only);
				break;
		}
	}
	return temp_flags;
}



//////////////////////////////
//
// HumSignifier::HumSignifier --
//

HumSignifier::HumSignifier (void) {
	// do nothing
}



HumSignifier::HumSignifier (const string& rdfline) {
	parseSignifier(rdfline);
}



//////////////////////////////
//
// HumSignifier::~HumSignifier --
//

HumSignifier::~HumSignifier() {
	clear();
}



//////////////////////////////
//
// HumSignifier::clear -- Clear contents of object.
//

void HumSignifier::clear(void) {
	m_exinterp.clear();
	m_signifier.clear();
	m_definition.clear();
	m_parameters.clear();
	m_sigtype = signifier_type::signifier_unknown;
}



//////////////////////////////
//
// HumSignifier::parseSignifier --
//
bool HumSignifier::parseSignifier(const string& rdfline) {
	clear();
	HumRegex hre;
	if (!hre.search(rdfline, "!!!RDF(\\*\\*[^\\s:]+)\\s*:\\s*(.*)\\s*$")) {
		return false;
	}
	m_exinterp   = hre.getMatch(1);
	string value = hre.getMatch(2);

	if (!hre.search(value, "\\s*([^\\s=]+)\\s*=\\s*(.*)\\s*$")) {
		clear();
		return false;
	}
	m_signifier  = hre.getMatch(1);
	m_definition = hre.getMatch(2);

	// identify signifier category

	if (m_exinterp == "**kern") {
		if (m_definition.find("link") != std::string::npos) {
			m_sigtype = signifier_type::signifier_link;
		} else if (m_definition.find("above") != std::string::npos) {
			m_sigtype = signifier_type::signifier_above;
		} else if (m_definition.find("below") != std::string::npos) {
			m_sigtype = signifier_type::signifier_below;
		}
	}

	// parse parameters here

	return true;
}



//////////////////////////////
//
// HumSignifier::getSignifier --
//

std::string HumSignifier::getSignifier(void) {
	return m_signifier;
}



//////////////////////////////
//
// HumSignifier::getDefinition --
//

std::string HumSignifier::getDefinition(void) {
	return m_definition;
}



//////////////////////////////
//
// HumSignifier::getParameter --
//

std::string HumSignifier::getParameter(const std::string& key) {
	auto value = m_parameters.find(key);
	if (value == m_parameters.end()) {
		return "";
	} else {
		return value->second;
	}
}



//////////////////////////////
//
// HumSignifier::isKernLink -- Is a linking signifier
//

bool HumSignifier::isKernLink(void) {
	return (m_sigtype == signifier_type::signifier_link);
}



//////////////////////////////
//
// HumSignifier::isKernAbove -- Is an above signifier.
//

bool HumSignifier::isKernAbove(void) {
	return (m_sigtype == signifier_type::signifier_above);
}



//////////////////////////////
//
// HumSignifier::isKernBelow -- Is a below signifier.
//

bool HumSignifier::isKernBelow(void) {
	return (m_sigtype == signifier_type::signifier_below);
}




//////////////////////////////
//
// HumSignifiers::HumSignifier --
//

HumSignifiers::HumSignifiers(void) {
	// do nothing
}



//////////////////////////////
//
// HumSignifiers::~HumSignifier --
//

HumSignifiers::~HumSignifiers() {
	clear();
}



//////////////////////////////
//
// HumSignifiers::clear --
//

void HumSignifiers::clear(void) {
	m_kernLinkIndex = -1;

	for (int i=0; i<(int)m_signifiers.size(); i++) {
		delete m_signifiers[i];
		m_signifiers[i] = NULL;
	}
	m_signifiers.clear();
}



//////////////////////////////
//
// HumSignifiers::addSignifier --
//

bool HumSignifiers::addSignifier(const std::string& rdfline) {
	HumSignifier *humsig = new HumSignifier;
	if (!humsig->parseSignifier(rdfline)) {
		// ignore malformed RDF reference record.
		return false;
	}
	m_signifiers.push_back(humsig);

	if (m_signifiers.back()->isKernLink()) {
		m_kernLinkIndex = (int)m_signifiers.size() - 1;
	} else if (m_signifiers.back()->isKernAbove()) {
		m_kernAboveIndex = (int)m_signifiers.size() - 1;
	} else if (m_signifiers.back()->isKernBelow()) {
		m_kernBelowIndex = (int)m_signifiers.size() - 1;
	}
	return true;
}



//////////////////////////////
//
// HumSignifiers::hasKernLinkSignifier --
//

bool HumSignifiers::hasKernLinkSignifier(void) {
	return (m_kernLinkIndex >= 0);
}



//////////////////////////////
//
// HumSignifiers::getKernLinkSignifier --
//

std::string HumSignifiers::getKernLinkSignifier(void) {
	if (m_kernLinkIndex < 0) {
		return "";
	}
	return m_signifiers[m_kernLinkIndex]->getSignifier();
}



//////////////////////////////
//
// HumSignifiers::hasKernAboveSignifier --
//

bool HumSignifiers::hasKernAboveSignifier(void) {
	return (m_kernAboveIndex >= 0);
}



//////////////////////////////
//
// HumSignifiers::getKernAboveSignifier --
//

std::string HumSignifiers::getKernAboveSignifier(void) {
	if (m_kernAboveIndex < 0) {
		return "";
	}
	return m_signifiers[m_kernAboveIndex]->getSignifier();
}



//////////////////////////////
//
// HumSignifiers::hasKernBelowSignifier --
//

bool HumSignifiers::hasKernBelowSignifier(void) {
	return (m_kernBelowIndex >= 0);
}



//////////////////////////////
//
// HumSignifiers::getKernBelowSignifier --
//

std::string HumSignifiers::getKernBelowSignifier(void) {
	if (m_kernBelowIndex < 0) {
		return "";
	}
	return m_signifiers[m_kernBelowIndex]->getSignifier();
}



//////////////////////////////
//
// HumSignifiers::getSignifierCount --
//

int HumSignifiers::getSignifierCount(void) {
	return (int)m_signifiers.size();
}



//////////////////////////////
//
// HumSignifiers::getSignifier --
//

HumSignifier* HumSignifiers::getSignifier(int index) {
	if (index < 0) {
		return NULL;
	}
	if (index >= (int)m_signifiers.size()) {
		return NULL;
	}
	return m_signifiers.at(index);
}




//////////////////////////////
//
// HumTool::HumTool --
//

HumTool::HumTool(void) {
	// do nothing
}



//////////////////////////////
//
// HumTool::~HumTool --
//

HumTool::~HumTool() {
	// do nothing
}



//////////////////////////////
//
// HumTool::hasAnyText -- Returns true if the output contains
//    text content in Humdrum syntax.
//

bool HumTool::hasAnyText(void) {
	if (m_suppress) {
		return true;
	}
	return ((!m_humdrum_text.str().empty())
			|| (!m_free_text.str().empty())
			|| (!m_json_text.str().empty()));
}



//////////////////////////////
//
// HumTool::getAllText -- Get the text content from any output
//     streams except warnings and errors.
//

string HumTool::getAllText(void) {
	return  m_humdrum_text.str()
	      + m_json_text.str()
	      + m_free_text.str();
}

//
// ostream version:
//

ostream& HumTool::getAllText(ostream& out) {
	out << m_humdrum_text.str();
	out << m_json_text.str();
	out << m_free_text.str();
	return out;
}



//////////////////////////////
//
// HumTool::suppressHumdrumFileOutput --
//

void HumTool::suppressHumdrumFileOutput(void) {
	m_suppress = true;
}



//////////////////////////////
//
// HumTool::hasHumdrumText -- Returns true if the output contains
//    text content in Humdrum syntax.
//

bool HumTool::hasHumdrumText(void) {
	return m_humdrum_text.str().empty() ? false : true;
}



//////////////////////////////
//
// HumTool::getHumdrumText -- Get the text content which represents
//     Humdrum syntax.
//

string HumTool::getHumdrumText(void) {
	return m_humdrum_text.str();
}

//
// ostream version:
//

ostream& HumTool::getHumdrumText(ostream& out) {
	out << m_humdrum_text.str();
	return out;
}



//////////////////////////////
//
// HumTool::hasFreeText --
//

bool HumTool::hasFreeText(void) {
	return m_free_text.str().empty() ? false : true;
}



//////////////////////////////
//
// HumTool::getFreeText -- Return any free-form text output from the
//     tool.
//

string HumTool::getFreeText(void) {
	return m_free_text.str();
}

//
// ostream version:
//

ostream& HumTool::getFreeText(ostream& out) {
	out << m_free_text.str();
	return out;
}



//////////////////////////////
//
// HumTool::hasJsonText --
//

bool HumTool::hasJsonText(void) {
	return m_json_text.str().empty() ? false : true;
}



//////////////////////////////
//
// HumTool::getFreeText -- Return any JSON text output from the
//     tool.
//

string HumTool::getJsonText(void) {
	return m_json_text.str();
}

//
// ostream version:
//

ostream& HumTool::getJsonText(ostream& out) {
	out << m_json_text.str();
	return out;
}



//////////////////////////////
//
// HumTool::hasWarning --
//

bool HumTool::hasWarning(void) {
	return m_warning_text.str().empty() ? false : true;
}



//////////////////////////////
//
// HumTool::getWarning -- Return any warning messages generated when
//     running the tool.
//

string HumTool::getWarning(void) {
	return m_warning_text.str();
}

//
// ostream version:
//

ostream& HumTool::getWarning(ostream& out) {
	out << m_warning_text.str();
	return out;
}



//////////////////////////////
//
// HumTool::hasError -- Return true if there is an error in processing
//    the options or input file(s).
//

bool HumTool::hasError(void) {
	if (hasParseError()) {
		return true;
	}
	return m_error_text.str().empty() ? false : true;
}



//////////////////////////////
//
// HumTool::getError -- Return any error messages generated when
//     running the tool.   This includes option parsing errors as
//     well.
//

string HumTool::getError(void) {
	string output = getParseError();
	output += m_error_text.str();
	return output;
}

//
// ostream version:
//

ostream& HumTool::getError(ostream& out) {
	out << getParseError();
	out << m_error_text.str();
	return out;
}


//////////////////////////////
//
// HumTool::clearOutput -- clear write buffers to get ready to
//     process another file.
//

void HumTool::clearOutput(void) {
	m_humdrum_text.str("");
	m_json_text.str("");
	m_free_text.str("");
  	m_warning_text.str("");
  	m_error_text.str("");
}



///////////////////////////////
//
// HumTool::setError --
//

void HumTool::setError(const string& message) {
	m_error_text << message << endl;
}






const std::vector<int> HumTransposer::m_diatonic2semitone({ 0, 2, 4, 5, 7, 9, 11 });


//////////////////////////////
//
// HumTransposer::HumTransposer -- HumTransposer constructor.
//

HumTransposer::HumTransposer() {
	// Initialize with base-600 system by default:
	setMaxAccid(42);
}



//////////////////////////////
//
// HumTransposer::~HumTransposer -- HumTransposer deconstructor.
//

HumTransposer::~HumTransposer() {
	// do nothing;
}



//////////////////////////////
//
// HumTransposer::setTransposition -- Set the transposition value which is an
//   interval class in the current base system.  When HumTransposer::setMaxAccid()
//   or HumTransposer::setBase*() are called, the transposition value will be set
//   to 0 (a perfect unison).  The integer is a base-40 class of number.  If you
//   want to transpose by semitone, do not use this option but rather the
//   setHumTransposer(int keyFifths, string semitones) function or the
//   setHumTransposer(int keyFifths, int semitones) function that are defined
//   further below.
//

bool HumTransposer::setTransposition(int transVal) {
	m_transpose = transVal;
	return true;
}

// Use a string to set the interval class in the current base system.  For example,
//  "+M2" means up a major second, which is the integer 6 in base-40.

bool HumTransposer::setTransposition(const string &transString) {
	m_transpose = getInterval(transString);
	return m_transpose != INVALID_INTERVAL_CLASS;
}

// Set transposition interval based on two pitches that represent the source data
// key tonic and the target key tonic.

bool HumTransposer::setTransposition(const HumPitch &fromPitch, const string &toString) {
	HumPitch toPitch;
	if (getKeyTonic(toString, toPitch)) {
		// Determine proper octave offset.
		int numSigns = toPitch.getOctave();
		m_transpose = getInterval(fromPitch, toPitch);
		// A transposition with n plus or minus signs should never be more than n octaves away.
		if (numSigns > 0 && m_transpose > perfectOctaveClass() * numSigns) {
			m_transpose -= perfectOctaveClass();
		}
		else if (numSigns < 0 && m_transpose < perfectOctaveClass() * numSigns) {
			m_transpose += perfectOctaveClass();
		}
		// A transposition with 0 plus or minus signs should never be more than 1/2 an octave away.
		else if (numSigns == 0 && m_transpose > perfectOctaveClass() / 2) {
			m_transpose -= perfectOctaveClass();
		}
		else if (numSigns == 0 && m_transpose < -1 * perfectOctaveClass() / 2) {
			m_transpose += perfectOctaveClass();
		}
		return true;
	}
	return false;
}

// Set the transposition based on the key signature (or inferred key signature coming from
// the keySig@diatonic/keySig@accid/keySig@mode information) and a string containing the
// semitone transposition.

bool HumTransposer::setTransposition(int keyFifths, const string &semitones) {
	if (!isValidSemitones(semitones)) {
		return false;
	}
	int semis = stoi(semitones);
	return setTransposition(keyFifths, semis);
}

// Note the order of the variables (key signature information is first in all
// cases where there are two input parametrs to setTransposition().

bool HumTransposer::setTransposition(int keyFifths, int semitones) {
	int intervalClass = semitonesToIntervalClass(keyFifths, semitones);
	return setTransposition(intervalClass);
}



//////////////////////////////
//
// HumTransposer::setTranspositionDC --
//

bool HumTransposer::setTranspositionDC(int diatonic, int chromatic) {
	int interval = HumTransposer::diatonicChromaticToIntervalClass(diatonic, chromatic);
	return setTransposition(interval);
}



//////////////////////////////
//
// HumTransposer::semitonesToIntervalClass -- convert semitones plus key
//     signature information into an integer interval class.
//

int HumTransposer::semitonesToIntervalClass(int keyFifths, int semitones) {
	int sign = semitones < 0 ? -1 : +1;
	semitones = semitones < 0 ? -semitones : semitones;
	int octave = semitones / 12;
	semitones = semitones - octave * 12;
	int sum1, sum2;
	string interval = "P1";
	switch (semitones) {
		case 0: interval = "P1"; break;

		case 1:
			sum1 = keyFifths - 5 * sign;
			sum2 = keyFifths + 7 * sign;
			interval = abs(sum1) < abs(sum2) ? "m2" : "A1";
			break;

		case 2:
			sum1 = keyFifths + 2 * sign;
			sum2 = keyFifths - 10 * sign;
			interval = abs(sum1) < abs(sum2) ? "M2" : "d3";
			break;

		case 3:
			sum1 = keyFifths - 3 * sign;
			sum2 = keyFifths + 9 * sign;
			interval = abs(sum1) < abs(sum2) ? "m3" : "A2";
			break;

		case 4:
			sum1 = keyFifths + 4 * sign;
			sum2 = keyFifths - 8 * sign;
			interval = abs(sum1) < abs(sum2) ? "M3" : "d4";
			break;

		case 5:
			sum1 = keyFifths - 1 * sign;
			sum2 = keyFifths + 11 * sign;
			interval = abs(sum1) < abs(sum2) ? "P4" : "A3";
			break;

		case 6:
			sum1 = keyFifths + 6 * sign;
			sum2 = keyFifths - 6 * sign;
			interval = abs(sum1) < abs(sum2) ? "A4" : "d5";
			break;

		case 7:
			sum1 = keyFifths + 1 * sign;
			sum2 = keyFifths - 11 * sign;
			interval = abs(sum1) < abs(sum2) ? "P5" : "d6";
			break;

		case 8:
			sum1 = keyFifths - 4 * sign;
			sum2 = keyFifths + 8 * sign;
			interval = abs(sum1) < abs(sum2) ? "m6" : "A5";
			break;

		case 9:
			sum1 = keyFifths + 3 * sign;
			sum2 = keyFifths - 9 * sign;
			interval = abs(sum1) < abs(sum2) ? "M6" : "d7";
			break;

		case 10:
			sum1 = keyFifths - 2 * sign;
			sum2 = keyFifths + 10 * sign;
			interval = abs(sum1) < abs(sum2) ? "m7" : "A6";
			break;

		case 11:
			sum1 = keyFifths + 5 * sign;
			sum2 = keyFifths - 7 * sign;
			interval = abs(sum1) < abs(sum2) ? "M7" : "d8";
			break;
	}

	interval = sign < 0 ? "-" + interval : "+" + interval;
	int intint = getInterval(interval);
	intint += sign * octave * m_base;
	return intint;
}



//////////////////////////////
//
// HumTransposer::semitonesToIntervalName -- convert semitones plus key
//     signature information into an interval name string.
//

string HumTransposer::semitonesToIntervalName(int keyFifths, int semitones) {
	int intervalClass = semitonesToIntervalClass(keyFifths, semitones);
	return getIntervalName(intervalClass);
}



//////////////////////////////
//
// HumTransposer::intervalToSemitones --  Convert a base interval class into
//   semitones.  Multiple enharmonic equivalent interval classes will collapse into
//   a single semitone value, so the process is not completely reversable
//   by calling HumTransposer::semitonesToIntervalClass(), but for simple
//   intervals it will be reversable.
//

int HumTransposer::intervalToSemitones(int interval) {
	int sign = interval < 0 ? -1 : +1;
	interval = interval < 0 ? -interval : interval;
	int octave = interval / m_base;
	int intervalClass = interval - octave * m_base;
	int diatonic = 0;
	int chromatic = 0;
	intervalToDiatonicChromatic(diatonic, chromatic, intervalClass);
	if ((diatonic != INVALID_INTERVAL_CLASS) && (chromatic != INVALID_INTERVAL_CLASS)) {
		return (m_diatonic2semitone.at(diatonic) + chromatic) * sign + 12 * octave;
	}
	else {
		return INVALID_INTERVAL_CLASS;
	}
}

//  Conversion from an interval name string into semitones:

int HumTransposer::intervalToSemitones(const string &intervalName) {
	int interval = getInterval(intervalName);
	return intervalToSemitones(interval);
}



//////////////////////////////
//
// HumTransposer::getTranspositionIntervalClass -- return the interval class integer
//   that was set for use with HumTransposer::HumTransposer.
//

int HumTransposer::getTranspositionIntervalClass() {
	return m_transpose;
}



//////////////////////////////
//
// HumTransposer::getTranspositionIntervalClass -- return the interval integer
//   as a string name that was set for use with HumTransposer::HumTransposer.
//
string HumTransposer::getTranspositionIntervalName() {
	return getIntervalName(m_transpose);
}



//////////////////////////////
//
// HumTransposer::transpose -- Do a transposition at the stored transposition interval, or
//   with a temporary provided integer interval class, or a temporary interval name.
//

void HumTransposer::transpose(HumPitch &pitch) {
	int ipitch = humHumPitchToIntegerPitch(pitch);
	ipitch += m_transpose;
	pitch = integerPitchToHumPitch(ipitch);
}

int HumTransposer::transpose(int ipitch) {
	return ipitch + m_transpose;
}

// Use a temporary transposition value in the following
// two functions. To save for later use of HumTransposer::HumTransposer
// without specifying the transposition interval, store
// transposition value with HumTransposer::setTransposition() first.

void HumTransposer::transpose(HumPitch &pitch, int transVal) {
	int ipitch = humHumPitchToIntegerPitch(pitch);
	ipitch += transVal;
	pitch = integerPitchToHumPitch(ipitch);
}

void HumTransposer::transpose(HumPitch &pitch, const string &transString) {
	int transVal = getInterval(transString);
	int ipitch = humHumPitchToIntegerPitch(pitch);
	ipitch += transVal;
	pitch = integerPitchToHumPitch(ipitch);
}



//////////////////////////////
//
// HumTransposer::getBase -- Return the integer interval class representing an octave.
//

int HumTransposer::getBase() {
	return m_base;
}



//////////////////////////////
//
// HumTransposer::getMaxAccid -- Return the maximum possible absolute accidental value
//     that can be represented by the current transposition base.
//

int HumTransposer::getMaxAccid() {
	return m_maxAccid;
}



//////////////////////////////
//
// HumTransposer::setMaxAccid -- Calculate variables related to a specific base system.
//

void HumTransposer::setMaxAccid(int maxAccid) {
	m_maxAccid = abs(maxAccid);
	m_base = 7 * (2 * m_maxAccid + 1) + 5;
	calculateDiatonicMapping();
	m_transpose = 0;
}



//////////////////////////////
//
// HumTransposer::calculateDiatonicMaping -- Calculate the integer values for the
//    natural diatonic pitch classes: C, D, E, F, G, A, and B in the current
//    base system.
//

void HumTransposer::calculateDiatonicMapping() {
	int m2 = m_maxAccid * 2 + 1;
	int M2 = m2 + 1;
	m_diatonicMapping.resize(7);
	m_diatonicMapping[dpc_C] = m_maxAccid;
	m_diatonicMapping[dpc_D] = m_diatonicMapping[dpc_C] + M2;
	m_diatonicMapping[dpc_E] = m_diatonicMapping[dpc_D] + M2;
	m_diatonicMapping[dpc_F] = m_diatonicMapping[dpc_E] + m2;
	m_diatonicMapping[dpc_G] = m_diatonicMapping[dpc_F] + M2;
	m_diatonicMapping[dpc_A] = m_diatonicMapping[dpc_G] + M2;
	m_diatonicMapping[dpc_B] = m_diatonicMapping[dpc_A] + M2;
}



//////////////////////////////
//
// HumTransposer::getKeyTonic -- Convert a key tonic string into a HumPitch
//      where the octave is the direction it should go.
//      Should conform to the following regular expression:
//          ([+]*|[-]*)([A-Ga-g])([Ss#]*|[Ffb]*)

bool HumTransposer::getKeyTonic(const string &keyTonic, HumPitch &tonic) {
	int octave = 0;
	int pitch = 0;
	int accid = 0;
	int state = 0;
	for (unsigned int i = 0; i < (unsigned int)keyTonic.size(); i++) {
		switch (state) {
			case 0:
				switch (keyTonic[i]) {
					case '-': octave--; break;
					case '+': octave++; break;
					default:
						state++;
						i--;
						break;
				}
				break;
			case 1:
				state++;
				switch (keyTonic[i]) {
					case 'C':
					case 'c': pitch = 0; break;
					case 'D':
					case 'd': pitch = 1; break;
					case 'E':
					case 'e': pitch = 2; break;
					case 'F':
					case 'f': pitch = 3; break;
					case 'G':
					case 'g': pitch = 4; break;
					case 'A':
					case 'a': pitch = 5; break;
					case 'B':
					case 'b': pitch = 6; break;
					default:
						cerr << "Invalid keytonic pitch character: " << keyTonic[i] << endl;
						return false;
				}
				break;
			case 2:
				switch (keyTonic[i]) {
					case 'F':
					case 'f':
					case 'b': accid--; break;
					case 'S':
					case 's':
					case '#': accid++; break;
					default:
						cerr << "Invalid keytonic accid character: " << keyTonic[i] << endl;
						return false;
				}
				break;
		}
	}

	tonic = HumPitch(pitch, accid, octave);
	return true;
}



//////////////////////////////
//
// HumTransposer::getInterval -- Convert a diatonic interval with chromatic
//     quality and direction into an integer interval class.   Input string
//     is in the format: direction + quality + diatonic interval.
//     Such as +M2 for up a major second, -P5 is down a perfect fifth.
//     Regular expression that the string should conform to:
//            (-|\+?)([Pp]|M|m|[aA]+|[dD]+)(\d+)
//

int HumTransposer::getInterval(const string &intervalName) {
	string direction;
	string quality;
	string number;
	int state = 0;

	for (int i = 0; i < (int)intervalName.size(); i++) {
		switch (state) {
			case 0: // direction or quality expected
				switch (intervalName[i]) {
					case '-': // interval is down
						direction = "-";
						state++;
						break;
					case '+': // interval is up
						direction += "";
						state++;
						break;
					default: // interval is up by default
						direction += "";
						state++;
						i--;
						break;
				}
				break;

			case 1: // quality expected
				if (isdigit(intervalName[i])) {
					state++;
					i--;
				}
				else {
					switch (intervalName[i]) {
						case 'M': // major
							quality = "M";
							break;
						case 'm': // minor
							quality = "m";
							break;
						case 'P': // perfect
						case 'p': quality = "P"; break;
						case 'D': // diminished
						case 'd': quality += "d"; break;
						case 'A': // augmented
						case 'a': quality += "A"; break;
					}
				}
				break;

			case 2: // digit expected
				if (isdigit(intervalName[i])) {
					number += intervalName[i];
				}
				break;
		}
	}

	if (quality.empty()) {
		cerr << "Interval name requires a chromatic quality: " << intervalName << endl;
		return INVALID_INTERVAL_CLASS;
	}

	if (number.empty()) {
		cerr << "Interval name requires a diatonic interval number: " << intervalName << endl;
		return INVALID_INTERVAL_CLASS;
	}

	int dnum = stoi(number);
	if (dnum == 0) {
		cerr << "Integer interval number cannot be zero: " << intervalName << endl;
		return INVALID_INTERVAL_CLASS;
	}
	dnum--;
	int octave = dnum / 7;
	dnum = dnum - octave * 7;

	int base = 0;
	int adjust = 0;

	switch (dnum) {
		case 0: // unison
			base = perfectUnisonClass();
			if (quality[0] == 'A') {
				adjust = (int)quality.size();
			}
			else if (quality[0] == 'd') {
				adjust = -(int)quality.size();
			}
			else if (quality != "P") {
				cerr << "Error in interval quality: " << intervalName << endl;
				return INVALID_INTERVAL_CLASS;
			}
			break;
		case 1: // second
			if (quality == "M") {
				base = majorSecondClass();
			}
			else if (quality == "m") {
				base = minorSecondClass();
			}
			else if (quality[0] == 'A') {
				base = majorSecondClass();
				adjust = (int)quality.size();
			}
			else if (quality[0] == 'd') {
				base = minorSecondClass();
				adjust = -(int)quality.size();
			}
			else {
				cerr << "Error in interval quality: " << intervalName << endl;
				return INVALID_INTERVAL_CLASS;
			}
			break;
		case 2: // third
			if (quality == "M") {
				base = majorThirdClass();
			}
			else if (quality == "m") {
				base = minorThirdClass();
			}
			else if (quality[0] == 'A') {
				base = majorThirdClass();
				adjust = (int)quality.size();
			}
			else if (quality[0] == 'd') {
				base = minorThirdClass();
				adjust = -(int)quality.size();
			}
			else {
				cerr << "Error in interval quality: " << intervalName << endl;
				return INVALID_INTERVAL_CLASS;
			}
			break;
		case 3: // fourth
			base = perfectFourthClass();
			if (quality[0] == 'A') {
				adjust = (int)quality.size();
			}
			else if (quality[0] == 'd') {
				adjust = -(int)quality.size();
			}
			else if (quality != "P") {
				cerr << "Error in interval quality: " << intervalName << endl;
				return INVALID_INTERVAL_CLASS;
			}
			break;
		case 4: // fifth
			base = perfectFifthClass();
			if (quality[0] == 'A') {
				adjust = (int)quality.size();
			}
			else if (quality[0] == 'd') {
				adjust = -(int)quality.size();
			}
			else if (quality != "P") {
				cerr << "Error in interval quality: " << intervalName << endl;
				return INVALID_INTERVAL_CLASS;
			}
			break;
		case 5: // sixth
			if (quality == "M") {
				base = majorSixthClass();
			}
			else if (quality == "m") {
				base = minorSixthClass();
			}
			else if (quality[0] == 'A') {
				base = majorSixthClass();
				adjust = (int)quality.size();
			}
			else if (quality[0] == 'd') {
				base = minorSixthClass();
				adjust = -(int)quality.size();
			}
			else {
				cerr << "Error in interval quality: " << intervalName << endl;
				return INVALID_INTERVAL_CLASS;
			}
			break;
		case 6: // seventh
			if (quality == "M") {
				base = majorSeventhClass();
			}
			else if (quality == "m") {
				base = minorSeventhClass();
			}
			else if (quality[0] == 'A') {
				base = majorSeventhClass();
				adjust = (int)quality.size();
			}
			else if (quality[0] == 'd') {
				base = minorSeventhClass();
				adjust = -(int)quality.size();
			}
			else {
				cerr << "Error in interval quality: " << intervalName << endl;
				return INVALID_INTERVAL_CLASS;
			}
			break;
	}

	if (direction == "-") {
		return -((octave * m_base) + base + adjust);
	}
	else {
		return (octave * m_base) + base + adjust;
	}
}



//////////////////////////////
//
// HumTransposer::perfectUnisonClass -- Return the integer interval class
//     representing a perfect unison.
//

int HumTransposer::perfectUnisonClass() {
	return 0;
}



//////////////////////////////
//
// HumTransposer::minorSecondClass -- Return the integer interval class
//     representing a minor second.
//

int HumTransposer::minorSecondClass() {
	return m_diatonicMapping[3] - m_diatonicMapping[2]; // F - E
}



//////////////////////////////
//
// HumTransposer::majorSecondClass -- Return the integer interval class
//    representing a major second.
//

int HumTransposer::majorSecondClass() {
	return m_diatonicMapping[1] - m_diatonicMapping[0]; // D - C
}



//////////////////////////////
//
// HumTransposer::minorThirdClass -- Return the integer interval class
//    representing a minor third.
//

int HumTransposer::minorThirdClass() {
	return m_diatonicMapping[3] - m_diatonicMapping[1]; // F - D
}



//////////////////////////////
//
// HumTransposer::majorThirdClass -- Return the integer interval class
//    representing a major third.
//

int HumTransposer::majorThirdClass() {
	return m_diatonicMapping[2] - m_diatonicMapping[0]; // E - C
}



//////////////////////////////
//
// HumTransposer::perfectFourthClass -- Return the integer interval class
//    representing a perfect fourth.
//

int HumTransposer::perfectFourthClass() {
	return m_diatonicMapping[3] - m_diatonicMapping[0]; // F - C
}



//////////////////////////////
//
// HumTransposer::perfectFifthClass -- Return the integer interval class
//    representing a perfect fifth.
//

int HumTransposer::perfectFifthClass() {
	return m_diatonicMapping[4] - m_diatonicMapping[0]; // G - C
}



//////////////////////////////
//
// HumTransposer::minorSixthClass -- Return the integer interval class
//    representing a minor sixth.
//

int HumTransposer::minorSixthClass() {
	return m_diatonicMapping[5] - m_diatonicMapping[0] - 1; // A - C - 1;
}



//////////////////////////////
//
// HumTransposer::majorSixthClass -- Return the integer interval class
//    representing a major sixth.
//

int HumTransposer::majorSixthClass() {
	return m_diatonicMapping[5] - m_diatonicMapping[0]; // A - C
}



//////////////////////////////
//
// HumTransposer::minorSeventhClass -- Return the integer interval class
//    representing a minor sixth.
//

int HumTransposer::minorSeventhClass() {
	return m_diatonicMapping[6] - m_diatonicMapping[0] - 1; // B - C - 1
}



//////////////////////////////
//
// HumTransposer::majorSeventhClass -- Return the integer interval class
//    representing a major sixth.
//

int HumTransposer::majorSeventhClass() {
	return m_diatonicMapping[6] - m_diatonicMapping[0]; // B - C
}



//////////////////////////////
//
// HumTransposer::octaveClass -- Return the integer interval class
//    representing a major second.
//

int HumTransposer::perfectOctaveClass() {
	return m_base;
}



//////////////////////////////
//
// HumTransposer::humHumPitchToIntegerPitch -- Convert a pitch (octave/diatonic pitch class/chromatic
//     alteration) into an integer value according to the current base.
//

int HumTransposer::humHumPitchToIntegerPitch(const HumPitch &pitch) {
	return pitch.getOctave() * m_base + m_diatonicMapping[pitch.getDiatonicPC()] + pitch.getAccid();
}



//////////////////////////////
//
// HumTransposer::integerPitchToHumPitch -- Convert an integer within the current base
//    into a pitch (octave/diatonic pitch class/chromatic alteration).  Pitches
//    with negative octaves will have to be tested.
//

HumPitch HumTransposer::integerPitchToHumPitch(int ipitch) {
	HumPitch pitch;
	pitch.setOctave(ipitch / m_base);
	int chroma = ipitch - pitch.getOctave() * m_base;
	int mindiff = -1000;
	int mini = -1;

	int targetdiff = m_maxAccid;

	if (chroma > m_base / 2) {
		// search from B downwards
		mindiff = chroma - m_diatonicMapping.back();
		mini = (int)m_diatonicMapping.size() - 1;
		for (int i = (int)m_diatonicMapping.size() - 2; i >= 0; i--) {
			int diff = chroma - m_diatonicMapping[i];
			if (abs(diff) < abs(mindiff)) {
				mindiff = diff;
				mini = i;
			}
			if (abs(mindiff) <= targetdiff) {
				break;
			}
		}
	}
	else {
		// search from C upwards
		mindiff = chroma - m_diatonicMapping[0];
		mini = 0;
		for (int i = 1; i < (int)m_diatonicMapping.size(); i++) {
			int diff = chroma - m_diatonicMapping[i];
			if (abs(diff) < abs(mindiff)) {
				mindiff = diff;
				mini = i;
			}
			if (abs(mindiff) <= targetdiff) {
				break;
			}
		}
	}
	pitch.setDiatonicPC(mini);
	pitch.setAccid(mindiff);
	return pitch;
}



//////////////////////////////
//
// HumTransposer::setBase40 -- Standard chromatic alteration mode, allowing up to double sharp/flats.
//

void HumTransposer::setBase40() {
	setMaxAccid(2);
}



//////////////////////////////
//
// HumTransposer::setBase600 -- Extended chromatic alteration mode, allowing up to 42 sharp/flats.
//

void HumTransposer::setBase600() {
	setMaxAccid(42);
}



//////////////////////////////
//
// HumTransposer::getInterval -- Return the interval between two pitches.
//    If the second pitch is higher than the first, then the interval will be
//    positive; otherwise, the interval will be negative.
//

int HumTransposer::getInterval(const HumPitch &p1, const HumPitch &p2) {
	return humHumPitchToIntegerPitch(p2) - humHumPitchToIntegerPitch(p1);
}

// Similar function to getInterval, but the integer interval class is converted
// into a string that is not dependent on a base:

string HumTransposer::getIntervalName(const HumPitch &p1, const HumPitch &p2) {
	int iclass = getInterval(p1, p2);
	return getIntervalName(iclass);
}

string HumTransposer::getIntervalName(int intervalClass) {
	string direction;
	if (intervalClass < 0) {
		direction = "-";
		intervalClass = -intervalClass;
	}

	int octave = intervalClass / m_base;
	int chroma = intervalClass - octave * m_base;

	int mindiff = chroma;
	int mini = 0;
	for (int i = 1; i < (int)m_diatonicMapping.size(); i++) {
		int diff = chroma - (m_diatonicMapping[i] - m_diatonicMapping[0]);
		if (abs(diff) < abs(mindiff)) {
			mindiff = diff;
			mini = i;
		}
		if (abs(mindiff) <= m_maxAccid) {
			break;
		}
	}

	int number = INVALID_INTERVAL_CLASS;
	int diminished = 0;
	int augmented = 0;
	string quality;

	switch (mini) {
		case 0: // unison
			number = 1;
			if (mindiff == 0) {
				quality = "P";
			}
			else if (mindiff < 0) {
				diminished = -mindiff;
			}
			else if (mindiff > 0) {
				augmented = mindiff;
			}
			break;
		case 1: // second
			number = 2;
			if (mindiff == 0) {
				quality = "M";
			}
			else if (mindiff == -1) {
				quality = "m";
			}
			else if (mindiff < 0) {
				diminished = -mindiff - 1;
			}
			else if (mindiff > 0) {
				augmented = mindiff;
			}
			break;
		case 2: // third
			number = 3;
			if (mindiff == 0) {
				quality = "M";
			}
			else if (mindiff == -1) {
				quality = "m";
			}
			else if (mindiff < 0) {
				diminished = -mindiff - 1;
			}
			else if (mindiff > 0) {
				augmented = mindiff;
			}
			break;
		case 3: // fourth
			number = 4;
			if (mindiff == 0) {
				quality = "P";
			}
			else if (mindiff < 0) {
				diminished = -mindiff;
			}
			else if (mindiff > 0) {
				augmented = mindiff;
			}
			break;
		case 4: // fifth
			number = 5;
			if (mindiff == 0) {
				quality = "P";
			}
			else if (mindiff < 0) {
				diminished = -mindiff;
			}
			else if (mindiff > 0) {
				augmented = mindiff;
			}
			break;
		case 5: // sixth
			number = 6;
			if (mindiff == 0) {
				quality = "M";
			}
			else if (mindiff == -1) {
				quality = "m";
			}
			else if (mindiff < 0) {
				diminished = -mindiff - 1;
			}
			else if (mindiff > 0) {
				augmented = mindiff;
			}
			break;
		case 6: // seventh
			number = 7;
			if (mindiff == 0) {
				quality = "M";
			}
			else if (mindiff == -1) {
				quality = "m";
			}
			else if (mindiff < 0) {
				diminished = -mindiff - 1;
			}
			else if (mindiff > 0) {
				augmented = mindiff;
			}
			break;
	}

	if (quality.empty()) {
		if (augmented) {
			for (int i = 0; i < augmented; i++) {
				quality += "A";
			}
		}
		else if (diminished) {
			for (int i = 0; i < diminished; i++) {
				quality += "d";
			}
		}
		else {
			quality = "?";
		}
	}

	number += octave * 7;

	string output = direction;
	output += quality;
	output += to_string(number);

	return output;
}



//////////////////////////////
//
// HumTransposer::intervalToCircleOfFifths -- Returns the circle-of-fiths count
//    that is represented by the given interval class or interval string.
//    Examples:  "P5"  => +1      "-P5" => -1
//               "P4"  => -1      "-P4" => +1
//               "M2"  => +2      "m7"  => -2
//               "M6"  => +3      "m3"  => -3
//               "M3"  => +4      "m6"  => -4
//               "M7"  => +5      "m2"  => -5
//               "A4"  => +6      "d5"  => -6
//               "A1"  => +7      "d1"  => -7
//
// If a key-signature plus the transposition interval in circle-of-fifths format
// is greater than +/-7, Then the -/+ 7 should be added to the key signature to
// avoid double sharp/flats in the key signature (and the transposition interval
// should be adjusted accordingly).
//

int HumTransposer::intervalToCircleOfFifths(const string &transstring) {
	int intervalClass = getInterval(transstring);
	return intervalToCircleOfFifths(intervalClass);
}

int HumTransposer::intervalToCircleOfFifths(int transval) {
	if (transval < 0) {
		transval = (m_base * 100 + transval) % m_base;
	}
	else if (transval == 0) {
		return 0;
	}
	else {
		transval %= m_base;
	}

	int p5 = perfectFifthClass();
	int p4 = perfectFourthClass();
	for (int i = 1; i < m_base; i++) {
		if ((p5 * i) % m_base == transval) {
			return i;
		}
		if ((p4 * i) % m_base == transval) {
			return -i;
		}
	}
	return INVALID_INTERVAL_CLASS;
}



//////////////////////////////
//
// HumTransposer::circleOfFifthsToIntervalClass -- Inputs a circle-of-fifths value and
//   returns the interval class as an integer in the current base.
//

int HumTransposer::circleOfFifthsToIntervalClass(int fifths) {
	if (fifths == 0) {
		return 0;
	}
	else if (fifths > 0) {
		return (perfectFifthClass() * fifths) % m_base;
	}
	else {
		return (perfectFourthClass() * (-fifths)) % m_base;
	}
}



//////////////////////////////
//
// HumTransposer::circleOfFifthsToIntervalName -- Convert a circle-of-fifths position
//    into an interval string.
//

string HumTransposer::circleOfFifthsToIntervalName(int fifths) {
	int intervalClass = circleOfFifthsToIntervalClass(fifths);
	return getIntervalName(intervalClass);
}



//////////////////////////////
//
// HumTransposer::circleOfFifthsTomajorTonic -- Return the tonic
//    of the major key that has the given key signature.  Return
//    value is in the 0th octave.
//

HumPitch HumTransposer::circleOfFifthsToMajorTonic(int fifths) {
	int intervalClass = circleOfFifthsToIntervalClass(fifths);
	return integerPitchToHumPitch((getCPitchClass() + intervalClass) % getBase());
}



//////////////////////////////
//
// HumTransposer::circleOfFifthsToMinorTonic -- Return the tonic
//    of the minor key that has the given key signature.  Return
//    value is in the 0th octave.
//

HumPitch HumTransposer::circleOfFifthsToMinorTonic(int fifths) {
	int intervalClass = circleOfFifthsToIntervalClass(fifths);
	return integerPitchToHumPitch((getAPitchClass() + intervalClass) % getBase());
}



//////////////////////////////
//
// HumTransposer::circleOfFifthsToDorianTonic -- Return the tonic
//    of the dorian key that has the given key signature.  Return
//    value is in the 0th octave.
//

HumPitch HumTransposer::circleOfFifthsToDorianTonic(int fifths) {
	int intervalClass = circleOfFifthsToIntervalClass(fifths);
	return integerPitchToHumPitch((getDPitchClass() + intervalClass) % getBase());
}



//////////////////////////////
//
// HumTransposer::circleOfFifthsToPhrygianTonic -- Return the tonic
//    of the phrygian key that has the given key signature.  Return
//    value is in the 0th octave.
//

HumPitch HumTransposer::circleOfFifthsToPhrygianTonic(int fifths) {
	int intervalClass = circleOfFifthsToIntervalClass(fifths);
	return integerPitchToHumPitch((getEPitchClass() + intervalClass) % getBase());
}



//////////////////////////////
//
// HumTransposer::circleOfFifthsToLydianTonic -- Return the tonic
//    of the lydian key that has the given key signature.  Return
//    value is in the 0th octave.
//

HumPitch HumTransposer::circleOfFifthsToLydianTonic(int fifths) {
	int intervalClass = circleOfFifthsToIntervalClass(fifths);
	return integerPitchToHumPitch((getFPitchClass() + intervalClass) % getBase());
}



//////////////////////////////
//
// HumTransposer::circleOfFifthsToMixolydianTonic -- Return the tonic
//    of the mixolydian key that has the given key signature.  Return
//    value is in the 0th octave.
//

HumPitch HumTransposer::circleOfFifthsToMixolydianTonic(int fifths) {
	int intervalClass = circleOfFifthsToIntervalClass(fifths);
	return integerPitchToHumPitch((getGPitchClass() + intervalClass) % getBase());
}



//////////////////////////////
//
// HumTransposer::circleOfFifthsToLocrianTonic -- Return the tonic
//    of the locrian key that has the given key signature.  Return
//    value is in the 0th octave.
//

HumPitch HumTransposer::circleOfFifthsToLocrianTonic(int fifths) {
	int intervalClass = circleOfFifthsToIntervalClass(fifths);
	return integerPitchToHumPitch((getBPitchClass() + intervalClass) % getBase());
}



//////////////////////////////
//
// HumTransposer::diatonicChromaticToIntervalClass -- Convert a diatonic/chromatic interval
//    into a base-n interval class integer.
//      +1D +1C = m2
//      +1D +2C = M2
//      +1D +3C = A2
//      +2D +4C = M3
//      +2D +3C = m3
//      +2D +2C = m3
//      +2D +1C = d3
//      +3D +5C = P4
//      +3D +6C = A4
//      +3D +4C = d4
//
//

string HumTransposer::diatonicChromaticToIntervalName(int diatonic, int chromatic) {
	if (diatonic == 0) {
		string output;
		if (chromatic == 0) {
			output += "P";
		}
		else if (chromatic > 0) {
			for (int i = 0; i < chromatic; i++) {
				output += "A";
			}
		}
		else {
			for (int i = 0; i < -chromatic; i++) {
				output += "d";
			}
		}
		output += "1";
		return output;
	}

	int octave = 0;
	string direction;
	if (diatonic < 0) {
		direction = "-";
		octave = -diatonic / 7;
		diatonic = (-diatonic - octave * 7);
		chromatic = -chromatic;
	}
	else {
		octave = diatonic / 7;
		diatonic = diatonic - octave * 7;
	}

	int augmented = 0;
	int diminished = 0;
	string quality;

	switch (abs(diatonic)) {
		case 0: // unsion
			if (chromatic == 0) {
				quality = "P";
			}
			else if (chromatic > 0) {
				augmented = chromatic;
			}
			else {
				diminished = chromatic;
			}
			break;
		case 1: // second
			if (chromatic == 2) {
				quality = "M";
			}
			else if (chromatic == 1) {
				quality = "m";
			}
			else if (chromatic > 2) {
				augmented = chromatic - 2;
			}
			else {
				diminished = chromatic - 1;
			}
			break;
		case 2: // third
			if (chromatic == 4) {
				quality = "M";
			}
			else if (chromatic == 3) {
				quality = "m";
			}
			else if (chromatic > 4) {
				augmented = chromatic - 4;
			}
			else {
				diminished = chromatic - 3;
			}
			break;
		case 3: // fourth
			if (chromatic == 5) {
				quality = "P";
			}
			else if (chromatic > 5) {
				augmented = chromatic - 5;
			}
			else {
				diminished = chromatic - 5;
			}
			break;
		case 4: // fifth
			if (chromatic == 7) {
				quality = "P";
			}
			else if (chromatic > 7) {
				augmented = chromatic - 7;
			}
			else {
				diminished = chromatic - 7;
			}
			break;
		case 5: // sixth
			if (chromatic == 9) {
				quality = "M";
			}
			else if (chromatic == 8) {
				quality = "m";
			}
			else if (chromatic > 9) {
				augmented = chromatic - 9;
			}
			else {
				diminished = chromatic - 8;
			}
			break;
		case 6: // seventh
			if (chromatic == 11) {
				quality = "M";
			}
			else if (chromatic == 10) {
				quality = "m";
			}
			else if (chromatic > 11) {
				augmented = chromatic - 11;
			}
			else {
				diminished = chromatic - 10;
			}
			break;
	}

	augmented = abs(augmented);
	diminished = abs(diminished);

	if (quality.empty()) {
		if (augmented) {
			for (int i = 0; i < augmented; i++) {
				quality += "A";
			}
		}
		else if (diminished) {
			for (int i = 0; i < diminished; i++) {
				quality += "d";
			}
		}
	}

	return direction + quality + to_string(octave * 7 + diatonic + 1);
}



//////////////////////////////
//
// HumTransposer::diatonicChromaticToIntervalClass --
//

int HumTransposer::diatonicChromaticToIntervalClass(int diatonic, int chromatic) {
	string intervalName = diatonicChromaticToIntervalName(diatonic, chromatic);
	return getInterval(intervalName);
}



//////////////////////////////
//
// HumTransposer::intervalToDiatonicChromatic --
//

void HumTransposer::intervalToDiatonicChromatic(int &diatonic, int &chromatic, int intervalClass) {
	string intervalName = getIntervalName(intervalClass);
	intervalToDiatonicChromatic(diatonic, chromatic, intervalName);
}

void HumTransposer::intervalToDiatonicChromatic(int &diatonic, int &chromatic, const string &intervalName) {
	int direction = 1;
	string quality;
	string number;
	int state = 0;

	for (int i = 0; i < (int)intervalName.size(); i++) {
		switch (state) {
			case 0: // direction or quality expected
				switch (intervalName[i]) {
					case '-': // interval is down
						direction = -1;
						state++;
						break;
					case '+': // interval is up
						direction = 1;
						state++;
						break;
					default: // interval is up by default
						direction = 1;
						state++;
						i--;
						break;
				}
				break;

			case 1: // quality expected
				if (isdigit(intervalName[i])) {
					state++;
					i--;
				}
				else {
					switch (intervalName[i]) {
						case 'M': // major
							quality = "M";
							break;
						case 'm': // minor
							quality = "m";
							break;
						case 'P': // perfect
						case 'p': quality = "P"; break;
						case 'D': // diminished
						case 'd': quality += "d"; break;
						case 'A': // augmented
						case 'a': quality += "A"; break;
					}
				}
				break;

			case 2: // digit expected
				if (isdigit(intervalName[i])) {
					number += intervalName[i];
				}
				break;
		}
	}

	if (quality.empty()) {
		cerr << "Interval requires a chromatic quality: " << intervalName << endl;
		chromatic = INVALID_INTERVAL_CLASS;
		diatonic = INVALID_INTERVAL_CLASS;
		return;
	}

	if (number.empty()) {
		cerr << "Interval requires a diatonic interval number: " << intervalName << endl;
		chromatic = INVALID_INTERVAL_CLASS;
		diatonic = INVALID_INTERVAL_CLASS;
		return;
	}

	int dnum = stoi(number);
	if (dnum == 0) {
		cerr << "Integer interval number cannot be zero: " << intervalName << endl;
		chromatic = INVALID_INTERVAL_CLASS;
		diatonic = INVALID_INTERVAL_CLASS;
		return;
	}
	dnum--;
	int octave = dnum / 7;
	dnum = dnum - octave * 7;

	diatonic = direction * (octave * 7 + dnum);
	chromatic = 0;

	switch (dnum) {
		case 0: // unison
			if (quality[0] == 'A') {
				chromatic = (int)quality.size();
			}
			else if (quality[0] == 'd') {
				chromatic = -(int)quality.size();
			}
			else if (quality == "P") {
				chromatic = 0;
			}
			else {
				cerr << "Error in Interval quality: " << intervalName << endl;
				chromatic = INVALID_INTERVAL_CLASS;
				diatonic = INVALID_INTERVAL_CLASS;
				return;
			}
			break;
		case 1: // second
			if (quality == "M") {
				chromatic = 2;
			}
			else if (quality == "m") {
				chromatic = 1;
			}
			else if (quality[0] == 'A') {
				chromatic = 2 + (int)quality.size();
			}
			else if (quality[0] == 'd') {
				chromatic = 1 - (int)quality.size();
			}
			else {
				cerr << "Error in Interval quality: " << intervalName << endl;
				chromatic = INVALID_INTERVAL_CLASS;
				diatonic = INVALID_INTERVAL_CLASS;
				return;
			}
			break;
		case 2: // third
			if (quality == "M") {
				chromatic = 4;
			}
			else if (quality == "m") {
				chromatic = 3;
			}
			else if (quality[0] == 'A') {
				chromatic = 4 + (int)quality.size();
			}
			else if (quality[0] == 'd') {
				chromatic = 3 - (int)quality.size();
			}
			else {
				cerr << "Error in Interval quality: " << intervalName << endl;
				chromatic = INVALID_INTERVAL_CLASS;
				diatonic = INVALID_INTERVAL_CLASS;
				return;
			}
			break;
		case 3: // fourth
			if (quality[0] == 'A') {
				chromatic = 5 + (int)quality.size();
			}
			else if (quality[0] == 'd') {
				chromatic = 5 - (int)quality.size();
			}
			else if (quality == "P") {
				chromatic = 5;
			}
			else {
				cerr << "Error in Interval quality: " << intervalName << endl;
				chromatic = INVALID_INTERVAL_CLASS;
				diatonic = INVALID_INTERVAL_CLASS;
				return;
			}
			break;
		case 4: // fifth
			if (quality[0] == 'A') {
				chromatic = 7 + (int)quality.size();
			}
			else if (quality[0] == 'd') {
				chromatic = 7 - (int)quality.size();
			}
			else if (quality == "P") {
				chromatic = 7;
			}
			else {
				cerr << "Error in Interval quality: " << intervalName << endl;
				chromatic = INVALID_INTERVAL_CLASS;
				diatonic = INVALID_INTERVAL_CLASS;
				return;
			}
			break;
		case 5: // sixth
			if (quality == "M") {
				chromatic = 9;
			}
			else if (quality == "m") {
				chromatic = 8;
			}
			else if (quality[0] == 'A') {
				chromatic = 9 + (int)quality.size();
			}
			else if (quality[0] == 'd') {
				chromatic = 8 - (int)quality.size();
			}
			else {
				cerr << "Error in Interval quality: " << intervalName << endl;
				chromatic = INVALID_INTERVAL_CLASS;
				diatonic = INVALID_INTERVAL_CLASS;
				return;
			}
			break;
		case 6: // seventh
			if (quality == "M") {
				chromatic = 11;
			}
			else if (quality == "m") {
				chromatic = 10;
			}
			else if (quality[0] == 'A') {
				chromatic = 11 + (int)quality.size();
			}
			else if (quality[0] == 'd') {
				chromatic = 10 - (int)quality.size();
			}
			else {
				cerr << "Error in Interval quality: " << intervalName << endl;
				chromatic = INVALID_INTERVAL_CLASS;
				diatonic = INVALID_INTERVAL_CLASS;
				return;
			}
			break;
	}
	chromatic *= direction;
}



//////////////////////////////
//
// HumTransposer::isValidIntervalName -- Returns true if the input string
//    is a valid chromatic interval string.  A valid interval name will match
//    this regular expression:
//          (-|\+?)([Pp]|M|m|[aA]+|[dD]+)([1-9][0-9]*)
//
//    Components of the regular expression:
//
//    1:  (-|\+?) == an optional direction for the interval.  When there
//                   is no sign, a + sign is implied.  A sign on a unison (P1)
//                   will be ignored.
//    2:  ([Pp]|M|m|[aA]+|[dD]+) == The chromatic quality of the following
//                   diatonic interval number.  Meanings of the letters:
//                      P or p = perfect
//                      M      = major
//                      m      = minor
//                      A or a = augmented
//                      d or D = diminished
//                   unisons (1), fourths, fifths and octaves (8) and octave multiples
//                   of these intervals can be prefixed by P but not by M or m.  Seconds,
//                   thirds, sixths, sevenths and octave transpositions of those intervals
//                   can be prefixed by M and m but not by P.  All intervals can be prefixed
//                   with A or d (or AA/dd for doubly augmented/diminished, etc.).  M and m
//                   are case sensitive, but P, A, and d are case insensitive.  This function
//                   does not check the correct pairing of M/m and P for the diatonic intervals
//                   (such as the invalid interval construct P3 for a perfect third).
//                   HumTransposer::getInterval(const string &intervalName) will do a
//                   more thorough check for invalid pairings.  This function is used mainly to
//                   determine whether an interval or a key tonic is being used in the --transpose
//                   option for verovio.
//     3: ([1-9][0-9]*) == a positive integer representing the diatonic interval.  1 = unison,
//                   2 = second, 3 = third, and so on.  Compound intervals are allowed, such as
//                   9 for a nineth (2nd plus a perfect octave), 15 for two perfect octaves.
//
//

bool HumTransposer::isValidIntervalName(const string &name) {
	string pattern = "(-|\\+?)([Pp]|M|m|[aA]+|[dD]+)([1-9][0-9]*)";
	if (regex_search(name, regex(pattern))) {
		return true;
	}
	else {
		return false;
	}
}



//////////////////////////////
//
// HumTransposer::isValidSemitones -- Returns true if the input string
//    is a valid semitone interval string.  A valid interval name will match
//    this regular expression:
//          ^(-|\+?)(\d+)$
//
//    Components of the regular expression:
//
//    1:  (-|\+?) == an optional direction for the interval.  When there
//                   is no sign, a + sign is implied.  A sign on 0
//                   will be ignored.
//    2:  (\d+)   == The number of semitones.  0 means transpose at the
//                   unison (i.e., transpose to same pitch as input).
//                   12 means an octave, 14 means a second plus an octave,
//                   24 means two octaves.
//

bool HumTransposer::isValidSemitones(const string &name) {
	string pattern = "^(-|\\+?)(\\d+)$";
	if (regex_search(name, regex(pattern))) {
		return true;
	}
	else {
		return false;
	}
}



//////////////////////////////
//
// HumTransposer::isValidKeyTonicName -- Returns true if the input string
//    is a valid key tonic which can be used to calculate a transposition
//    interval based on the current key.  A valid key tonic will match
//    this regular expression:
//          ([+]*|[-]*)([A-Ga-g])([Ss#]*|[Ffb]*)
//
//    Components of the regular expression:
//
//    1: ([+]*|[-]*) == An optional sign for the direction of the
//                      transposition.  If there is no sign, then
//                      the closest tonic pitch class to the tonic
//                      of the data will be selected.  When The
//                      sign is double/tripled/etc., additional
//                      octaves will be added to the transposition.
//    2: ([A-Ga-g]) ==  The diatonic letter of the tonic key.  The letter
//                      is case insensitive, so "g" and "G" have the
//                      same meaning.
//    3: ([Ss#]*|[Ffb]*) == An optional accidental alteration of the
//                      diatonic letter, such as eF, ef, eb, EF, Ef, or Eb,
//                      all meaning e-flat, and aS, as, a#, AS, As, or
//                      A# all meaning a-sharp.
//

bool HumTransposer::isValidKeyTonic(const string &name) {
	string pattern = "([+]*|[-]*)([A-Ga-g])([Ss#]*|[Ffb]*)";
	if (regex_search(name, regex(pattern))) {
		return true;
	}
	else {
		return false;
	}
}

/*



/////////////////////////////////////////////////////
//
// Test program for HumTransposer class:
//

int main(void) {
	HumPitch pitch(dpc_C, 0, 4); // middle C

	HumTransposer transpose;

	// transpose.setBase40() is the default system.
	transpose.setTransposition(transpose.perfectFifthClass());
	cout << "Starting pitch:\t\t\t\t" << pitch << endl;
	transpose.HumTransposer(pitch);
	cout << "HumTransposerd up a perfect fifth:\t\t" << pitch << endl;

	// testing use of a different base for transposition:
	transpose.setBase600(); // allows up to 42 sharps or flats
	// Note that transpose value is cleared when setAccid() or setBase*() is called.
	transpose.setTransposition(-transpose.perfectFifthClass());
	transpose.HumTransposer(pitch);
	cout << "HumTransposerd back down a perfect fifth:\t" << pitch << endl;

	// testing use of interval string
	transpose.setTransposition("-m3");
	transpose.HumTransposer(pitch);
	cout << "HumTransposerd down a minor third:\t\t" << pitch << endl;

	// testing validation system for under/overflows:
	cout << endl;
	pitch.setPitch(dpc_C, 2, 4); // C##4
	cout << "Initial pitch:\t\t" << pitch << endl;
	transpose.HumTransposer(pitch, "A4"); // now F###4
	bool valid = pitch.isValid(2);
	cout << "Up an aug. 4th:\t\t" << pitch;
	if (!valid) {
		cout << "\t(not valid in base-40 system)";
	}
	cout << endl;

	// calculate interval between two pitches:
	cout << endl;
	cout << "TESTING INTERVAL NAMES IN BASE-40:" << endl;
	transpose.setBase40();
	HumPitch p1(dpc_C, 0, 4);
	HumPitch p2(dpc_F, 2, 4);
	cout << "\tInterval between " << p1 << " and " << p2;
	cout << " is " << transpose.getIntervalName(p1, p2) << endl;
	HumPitch p3(dpc_G, -2, 3);
	cout << "\tInterval between " << p1 << " and " << p3;
	cout << " is " << transpose.getIntervalName(p1, p3) << endl;

	cout << "TESTING INTERVAL NAMES IN BASE-600:" << endl;
	transpose.setBase600();
	cout << "\tInterval between " << p1 << " and " << p2;
	cout << " is " << transpose.getIntervalName(p1, p2) << endl;
	cout << "\tInterval between " << p1 << " and " << p3;
	cout << " is " << transpose.getIntervalName(p1, p3) << endl;
	cout << endl;

	cout << "TESTING INTERVAL NAME TO CIRCLE-OF-FIFTHS:" << endl;
	cout << "\tM6 should be 3:  " << transpose.intervalTocircleOfFifths("M6") << endl;
	cout << "\tm6 should be -4: " << transpose.intervalTocircleOfFifths("m6") << endl;

	cout << "TESTING CIRCLE-OF-FIFTHS TO INTERVAL NAME:" << endl;
	cout << "\t3 should be M6:  " << transpose.circleOfFifthsToIntervalName(3) << endl;
	cout << "\t-4 should be m6: " << transpose.circleOfFifthsToIntervalName(-4) << endl;
	cout << endl;

	cout << "TESTING INTERVAL NAME TO DIATONIC/CHROMATIC:" << endl;
	cout << "\tD-1,C-2 should be -M2:  " << transpose.diatonicChromaticToIntervalName(-1, -2) << endl;
	cout << "\tD3,C6 should be A4:     " << transpose.diatonicChromaticToIntervalName(3, 6) << endl;

	int chromatic;
	int diatonic;

	cout << "TESTING DIATONIC/CHROMATIC TO INTERVAL NAME:" << endl;
	cout << "\t-M2 should be D-1,C-2:  ";
	transpose.intervalToDiatonicChromatic(diatonic, chromatic, "-M2");
	cout << "D" << diatonic << ",C" << chromatic << endl;
	cout << "\tA4 should be D3,C6:     ";
	transpose.intervalToDiatonicChromatic(diatonic, chromatic, "A4");
	cout << "D" << diatonic << ",C" << chromatic << endl;

	return 0;
}

*/

/* Example output from test program:

	Starting pitch:                        C4
	Transposed up a perfect fifth:         G4
	Transposed back down a perfect fifth:  C4
	Transposed down a minor third:         A3

	Initial pitch:   C##4
	Up an aug. 4th:  F###4 (not valid in base-40 system)

	TESTING INTERVAL NAMES IN BASE-40:
		Interval between C4 and F##4 is AA4
		Interval between C4 and Gbb3 is -AA4
	TESTING INTERVAL NAMES IN BASE-600:
		Interval between C4 and F##4 is AA4
		Interval between C4 and Gbb3 is -AA4

	TESTING INTERVAL NAME TO CIRCLE-OF-FIFTHS:
		M6 should be 3:  3
		m6 should be -4: -4
	TESTING CIRCLE-OF-FIFTHS TO INTERVAL NAME:
		3 should be M6:  M6
		-4 should be m6: m6

	TESTING INTERVAL NAME TO DIATONIC/CHROMATIC:
		D-1,C-2 should be -M2:  -M2
		D3,C6 should be A4:     A4
	TESTING DIATONIC/CHROMATIC TO INTERVAL NAME:
		-M2 should be D-1,C-2:  D-1,C-2
		A4 should be D3,C6:     D3,C6

 */


//////////////////////////////
//
// HumdrumFile::HumdrumFile -- HumdrumFile constructor.
//

HumdrumFile::HumdrumFile(void) {
	// do nothing
}

HumdrumFile::HumdrumFile(const string& filename) :
		HUMDRUMFILE_PARENT() {
	read(filename);
}


HumdrumFile::HumdrumFile(istream& contents) :
		HUMDRUMFILE_PARENT() {
	read(contents);
}



//////////////////////////////
//
// HumdrumFile::~HumdrumFile -- HumdrumFile deconstructor.
//

HumdrumFile::~HumdrumFile() {
	// do nothing
}



//////////////////////////////
//
// HumdrumFile::printXml -- Print a HumdrumFile object in XML format.
// default value: level = 0
// default value: indent = tab character
//

ostream& HumdrumFile::printXml(ostream& out, int level,
		const string& indent) {
	out << Convert::repeatString(indent, level) << "<sequence>\n";
	level++;

	out << Convert::repeatString(indent, level) << "<sequenceInfo>\n";
	level++;

	out << Convert::repeatString(indent, level) << "<frameCount>";
	out << getLineCount() << "</frameCount>\n";


	out << Convert::repeatString(indent, level) << "<tpq>";
	out << tpq() << "</tpq>\n";

	// Starting at 0 by default (single segment only).  Eventually
	// add parameter to set the starting time of the sequence, which
	// would be the duration of all previous segments before this one.
	out << Convert::repeatString(indent, level) << "<sequenceStart";
	out << Convert::getHumNumAttributes(0);
	out << "/>\n";

	out << Convert::repeatString(indent, level) << "<sequenceDuration";
	out << Convert::getHumNumAttributes(getScoreDuration());
	out << "/>\n";

	out << Convert::repeatString(indent, level) << "<trackInfo>\n";
	level++;

	out << Convert::repeatString(indent, level) << "<trackCount>";
	out << getMaxTrack() << "</trackCount>\n";

	for (int i=1; i<=getMaxTrack(); i++) {
		out << Convert::repeatString(indent, level) << "<track";
		out << " n=\"" << i << "\"";
		HumdrumToken* trackstart = getTrackStart(i);
		if (trackstart != NULL) {
			out << " dataType=\"" <<  trackstart->getDataType().substr(2) << "\"";
			out << " startId=\"" <<  trackstart->getXmlId() << "\"";
		}
		HumdrumToken* trackend = getTrackEnd(i, 0);
		if (trackend != NULL) {
			out << " endId =\"" <<  trackend->getXmlId() << "\"";
		}
		out << "/>\n";
	}

	level--;
	out << Convert::repeatString(indent, level) << "</trackInfo>\n";

	printXmlParameterInfo(out, level, "\t");

	level--;
	out << Convert::repeatString(indent, level) << "</sequenceInfo>\n";

	out << Convert::repeatString(indent, level) << "<frames>\n";
	level++;
	for (int i=0; i<getLineCount(); i++) {
		m_lines[i]->printXml(out, level, indent);
	}
	level--;
	out << Convert::repeatString(indent, level) << "</frames>\n";

	level--;
	out << Convert::repeatString(indent, level) << "</sequence>\n";

	return out;
}



//////////////////////////////
//
// HumdrumFile::printXmlParameterInfo -- Print contents of HumHash for HumdrumFile.
// default value: out = cout
// default value: level = 0
// default value: indent = "\t"
//

ostream& HumdrumFile::printXmlParameterInfo(ostream& out, int level,
		const string& indent) {
	((HumHash*)this)->printXml(out, level, indent);
	return out;
}




//////////////////////////////
//
// HumdrumFileBase::getUriToUrlMapping --
//

string HumdrumFileBase::getUriToUrlMapping(const string& uri) {
	auto css = uri.find("://");
	if (css == string::npos) {
		// this is not a URI, so just return input:
		return string(uri);
	}

	if (Convert::startsWith(uri, "http://")) {
		// The URI is a URL, so just copy:
		return string(uri);
	}

	string tag  = uri.substr(0, css);
	string rest = uri.substr(css+3);
	if (rest.empty()) {
		rest = "/";
	}

	// getting a repertory:
	// http://kern.humdrum.org/data?l=osu/classical/bach/inventions
	// getting a single file:
	// http://kern.humdrum.org/data?s=http://kern.humdrum.org/data?s=osu/classical/bach/inventions&file=inven15.krn
	// (Should allow repertory from &s...)
	if ((tag == "humdrum") || (tag == "hum") || (tag == "h")) {
		string testlocation;
		string testfilename;
		int repertoryQ = false;
		auto slash = rest.rfind('/');
		if (slash != string::npos) {
			testlocation = rest.substr(0, slash);
			testfilename = rest.substr(slash+1);
			if (testfilename.find('.') == string::npos) {
				repertoryQ = true;
			}
		} if (slash == string::npos) {
			// no files in root directory, but no reperoties either
			repertoryQ = true;
		}
		string output = "http://";;
		output += "kern.ccarh.org";
		output += "/data?";
		if (repertoryQ) {
			output += "l=";
		} else {
			output += "s=";
		}
		output += rest;
		// probably not needed:
		//output += "&format=kern";
		return output;
	}

	if (tag == "jrp") {
		string output = "http://";
		output += "jrp.ccarh.org";
		output += "/cgi-bin/jrp?a=humdrum";
		output += "&f=";
		output += rest;
		return output;
	}

	// not familiar with the URI, just assume that it is a URL,
	// such as "https://".
	return uri;
}


#ifdef USING_URI

//////////////////////////////
//
// HumdrumFileBase::readFromHumdrumUri -- Read a Humdrum file from an
//      humdrum:// web address
//
// Example:
//    maps: humdrum://osu/classical/haydn/london/sym099a.krn
// into:
//    http://kern.ccarh.org/cgi-bin/ksdata?file=sym099a.krn&l=/osu/classical/haydn/london&format=kern
//

void HumdrumFileBase::readFromHumdrumUri(const string& humaddress) {
	string url = HumdrumFileBase::getUriToUrlMapping(humaddress);
	readFromHttpUri(url);
}



//////////////////////////////
//
// readFromJrpUri -- Read a Humdrum file from a jrp:// web-style address
//
// Example:
// maps:
//    jrp://Jos2721-La_Bernardina
// into:
//    http://jrp.ccarh.org/cgi-bin/jrp?a=humdrum&f=Jos2721-La_Bernardina
//

void HumdrumFileBase::readFromJrpUri(const string& jrpaddress) {
	string url = HumdrumFileBase::getUriToUrlMapping(jrpaddress);
	readFromHttpUri(url);
}


//////////////////////////////
//
// HumdrumFileBase::readFromHttpUri -- download content from the web.
//

void HumdrumFileBase::readFromHttpUri(const string& webaddress) {
	stringstream inputdata;
	readStringFromHttpUri(inputdata, webaddress);
	HumdrumFileBase::readString(inputdata.str());
}



//////////////////////////////
//
// readStringFromHttpUri -- Read a Humdrum file from an http:// web address
//

void HumdrumFileBase::readStringFromHttpUri(stringstream& inputdata,
		const string& webaddress) {
	auto css = webaddress.find("://");
	if (css == string::npos) {
		// give up since URI was not in correct format
	}
	string rest = webaddress.substr(css+3);
	string hostname;
	string location;
	css = rest.find("/");
	if (css != string::npos) {
		hostname = rest.substr(0, css);
		location = rest.substr(css);
	} else {
		hostname = rest;
		location = "/";
	}
	if (location.empty()) {
		location = "/";
	}

	string newline({0x0d, 0x0a});

	stringstream request;
	request << "GET "   << location << " HTTP/1.1" << newline;
	request << "Host: " << hostname << newline;
	request << "User-Agent: HumdrumFile Downloader 2.0 ("
		     << __DATE__ << ")" << newline;
	request << "Connection: close" << newline;  // this line is necessary
	request << newline;

	unsigned short int port = 80;
	int socket_id = open_network_socket(hostname, port);
	if (::write(socket_id, request.str().c_str(), request.str().size()) == -1) {
		exit(-1);
	}

	#define URI_BUFFER_SIZE (10000)
	char buffer[URI_BUFFER_SIZE];
	unsigned int message_len;
	stringstream header;
	int foundcontent   = 0;
	int newlinecounter = 0;
	int i;

	// read the response header:
	while ((message_len = ::read(socket_id, buffer, 1)) != 0) {
		header << buffer[0];
		if ((buffer[0] == 0x0a) || (buffer[0] == 0x0d)) {
					newlinecounter++;
		} else {
					newlinecounter = 0;
		}
		if (newlinecounter == 4) {
			foundcontent = 1;
			break;
		}
	}
	if (foundcontent == 0) {
		cerr << "Funny error trying to read server response" << endl;
		exit(1);
	}

	// now read the size of the rest of the data which is expected
	int datalength = -1;

	// also, check for chunked transfer encoding:

	int chunked = 0;

	header << ends; // necessary?
	while (header.getline(buffer, URI_BUFFER_SIZE)) {
		int len = (int)strlen(buffer);
		for (i=0; i<len; i++) {
			buffer[i] = std::tolower(buffer[i]);
		}
		if (strstr(buffer, "content-length") != NULL) {
			for (i=14; i<len; i++) {
				if (std::isdigit(buffer[i])) {
					sscanf(&buffer[i], "%d", &datalength);
					if (datalength == 0) {
						cerr << "Error: no data found for URI, probably invalid\n";
						cerr << "URL:   " << webaddress << endl;
						exit(1);
					}
					break;
				}
			}
		} else if ((strstr(buffer, "transfer-encoding") != NULL) &&
			(strstr(buffer, "chunked") != NULL)) {
			chunked = 1;
		}
	}

	// once the length of the remaining data is known (or not), read it:
	if (datalength > 0) {
		getFixedDataSize(socket_id, datalength, inputdata, buffer,
				URI_BUFFER_SIZE);

	} else if (chunked) {
		int chunksize;
		int totalsize = 0;
		do {
			chunksize = getChunk(socket_id, inputdata, buffer, URI_BUFFER_SIZE);
			totalsize += chunksize;
		} while (chunksize > 0);
		if (totalsize == 0) {
			cerr << "Error: no data found for URI (probably invalid)\n";
			exit(1);
		}
	} else {
		// if the size of the rest of the data cannot be found in the
		// header, then just keep reading until done (but this will
		// probably cause a 5 second delay at the last read).
		while ((message_len = ::read(socket_id, buffer, URI_BUFFER_SIZE)) != 0) {
			if (foundcontent) {
				inputdata.write(buffer, message_len);
			} else {
				for (i=0; i<(int)message_len; i++) {
					if (foundcontent) {
						inputdata << buffer[i];
					} else {
						header << buffer[i];
						if ((buffer[i] == 0x0a) || (buffer[i] == 0x0d)) {
							newlinecounter++;
						} else {
							newlinecounter = 0;
						}
						if (newlinecounter == 4) {
							foundcontent = 1;
							continue;
						}
					}

				}
			}
		}
	}

	close(socket_id);
}



//////////////////////////////
//
//  HumdrumFileBase::getChunk --
//
// http://en.wikipedia.org/wiki/Chunked_transfer_encoding
// http://tools.ietf.org/html/rfc2616
//
// Chunk Format
//
// If a Transfer-Encoding header with a value of chunked is specified in
// an HTTP message (either a request sent by a client or the response from
// the server), the body of the message is made of an unspecified number
// of chunks ending with a last, zero-sized, chunk.
//
// Each non-empty chunk starts with the number of octets of the data it
// embeds (size written in hexadecimal) followed by a CRLF (carriage
// return and linefeed), and the data itself. The chunk is then closed
// with a CRLF. In some implementations, white space chars (0x20) are
// padded between chunk-size and the CRLF.
//
// The last chunk is a single line, simply made of the chunk-size (0),
// some optional padding white spaces and the terminating CRLF. It is not
// followed by any data, but optional trailers can be sent using the same
// syntax as the message headers.
//
// The message is finally closed by a last CRLF combination.

int HumdrumFileBase::getChunk(int socket_id, stringstream& inputdata,
		char* buffer, int bufsize) {
	int chunksize = 0;
	unsigned int message_len;
	char digit[2] = {0};
	int founddigit = 0;

	// first read the chunk size:
	while ((message_len = ::read(socket_id, buffer, 1)) != 0) {
		if (isxdigit(buffer[0])) {
			digit[0] = buffer[0];
			chunksize = (chunksize << 4) | (int)strtol(digit, NULL, 16);
			founddigit = 1;
		} else if (founddigit) {
			break;
		} // else skipping whitespace before chunksize
	}
	if ((chunksize <= 0) || (message_len == 0)) {
		// next chunk is zero, so no more primary data0:w
		return 0;
	}

	// read the 0x0d and 0x0a characters which are expected (required)
	// after the size of chunk size:
	if (buffer[0] != 0x0d) {
		cerr << "Strange error occurred right after reading a chunk size" << endl;
		exit(1);
	}

	// now expect 0x0a:
	message_len = ::read(socket_id, buffer, 1);
	if ((message_len == 0) || (buffer[0] != 0x0a)) {
		cerr << "Strange error after reading newline at end of chunk size"<< endl;
		exit(1);
	}

	return getFixedDataSize(socket_id, chunksize, inputdata, buffer, bufsize);
}



//////////////////////////////
//
// getFixedDataSize -- read a know amount of data from a socket.
//

int HumdrumFileBase::getFixedDataSize(int socket_id, int datalength,
		stringstream& inputdata, char* buffer, int bufsize) {
	int readcount = 0;
	int readsize;
	int message_len;

	while (readcount < datalength) {
		readsize = bufsize;
		if (readcount + readsize > datalength) {
			readsize = datalength - readcount;
		}
		message_len = (int)::read(socket_id, buffer, readsize);
		if (message_len == 0) {
			// shouldn't happen, but who knows...
			break;
		}
		inputdata.write(buffer, message_len);
		readcount += message_len;
	}

	return readcount;
}



//////////////////////////////
//
// HumdrumFileBase::prepare_address -- Store a computer name, such as
//    www.google.com into a sockaddr_in structure for later use in
//    open_network_socket.
//

void HumdrumFileBase::prepare_address(struct sockaddr_in *address,
		const string& hostname, unsigned short int port) {

	memset(address, 0, sizeof(struct sockaddr_in));
	struct hostent *host_entry;
	host_entry = gethostbyname(hostname.c_str());

	if (host_entry == NULL) {
		cerr << "Could not find address for " << hostname << endl;
		exit(1);
	}

	// copy the address to the sockaddr_in struct.
	memcpy(&address->sin_addr.s_addr, host_entry->h_addr_list[0],
			host_entry->h_length);

	// set the family type (PF_INET)
	address->sin_family = host_entry->h_addrtype;
	address->sin_port = htons(port);
}



//////////////////////////////
//
// open_network_socket -- Open a connection to a computer on the internet.
//    Intended for downloading a Humdrum file from a website.
//

int HumdrumFileBase::open_network_socket(const string& hostname,
		unsigned short int port) {
	int inet_socket;                 // socket descriptor
	struct sockaddr_in servaddr;     // IP/port of the remote host

	prepare_address(&servaddr, hostname, port);

	// socket(domain, type, protocol)
	//    domain   = PF_INET(internet/IPv4 domain)
	//    type     = SOCK_STREAM(tcp) *
	//    protocol = 0 (only one SOCK_STREAM type in the PF_INET domain
	inet_socket = socket(PF_INET, SOCK_STREAM, 0);

	if (inet_socket < 0) {
		// socket returns -1 on error
		cerr << "Error opening socket to computer " << hostname << endl;
		exit(1);
	}
	if (connect(inet_socket, (struct sockaddr *)&servaddr,
			sizeof(struct sockaddr_in)) < 0) {
		// connect returns -1 on error
		cerr << "Error opening connection to coputer: " << hostname << endl;
		exit(1);
	}

	return inet_socket;
}

#endif




//////////////////////////////
//
// HumdrumFileBase::HumdrumFileBase -- HumdrumFileBase constructor.
//

HumdrumFileBase::HumdrumFileBase(void) : HumHash() {
	addToTrackStarts(NULL);
	m_ticksperquarternote = -1;
	m_quietParse = false;
	m_segmentlevel = 0;
}

HumdrumFileBase::HumdrumFileBase(const string& filename) : HumHash() {
	addToTrackStarts(NULL);
	m_ticksperquarternote = -1;
	m_quietParse = false;
	m_segmentlevel = 0;
	read(filename);
}

HumdrumFileBase::HumdrumFileBase(istream& contents) : HumHash() {
	addToTrackStarts(NULL);
	m_ticksperquarternote = -1;
	m_quietParse = false;
	m_segmentlevel = 0;
	read(contents);
}


//
// HumdrumFileStructure::analyzeStructure() needs to be called after
// using the following constructor:
//

HumdrumFileBase::HumdrumFileBase(HumdrumFileBase& infile) {

	m_filename = infile.m_filename;
	m_segmentlevel = infile.m_segmentlevel;
	m_trackstarts.clear();
	m_trackends.clear();
	m_barlines.clear();
	m_ticksperquarternote = infile.m_ticksperquarternote;
	m_idprefix = infile.m_idprefix;
	m_strand1d.clear();
	m_strand2d.clear();
	m_strophes1d.clear();
	m_strophes2d.clear();
	m_quietParse = infile.m_quietParse;
	m_parseError = infile.m_parseError;
	m_displayError = infile.m_displayError;

	m_lines.resize(infile.m_lines.size());
	for (int i=0; i<(int)m_lines.size(); i++) {
		m_lines[i] = new HumdrumLine(infile.m_lines[i]->getText());
		m_lines[i]->setOwner(this);
	}

	analyzeBaseFromLines();
}



//////////////////////////////
//
// HumdrumFileBase::operator = -- HumdrumFileStructure::analyzeStructure()
// needs to be called after copying from another HumdrumFile.
//
//

HumdrumFileBase& HumdrumFileBase::operator=(HumdrumFileBase& infile) {
	if (this == &infile) {
		return *this;
	}

	m_filename = infile.m_filename;
	m_segmentlevel = infile.m_segmentlevel;
	m_trackstarts.clear();
	m_trackends.clear();
	m_barlines.clear();
	m_ticksperquarternote = infile.m_ticksperquarternote;
	m_idprefix = infile.m_idprefix;
	m_strand1d.clear();
	m_strand2d.clear();
	m_strophes1d.clear();
	m_strophes2d.clear();
	m_quietParse = infile.m_quietParse;
	m_parseError = infile.m_parseError;
	m_displayError = infile.m_displayError;

	m_lines.resize(infile.m_lines.size());
	for (int i=0; i<(int)m_lines.size(); i++) {
		m_lines[i] = new HumdrumLine(infile.m_lines[i]->getText());
		m_lines[i]->setOwner(this);
	}

	analyzeBaseFromLines();
	return *this;
}



//////////////////////////////
//
// HumdrumFileBase::~HumdrumFileBase -- HumdrumFileBase deconstructor.
//

HumdrumFileBase::~HumdrumFileBase() {
	clear();
}



//////////////////////////////
//
// HumdrumFileBase::clear -- Reset the contents of a file to be empty.
//

void HumdrumFileBase::clear(void) {
	// delete memory allocation:
	for (int i=0; i<(int)m_lines.size(); i++) {
		if (m_lines[i] != NULL) {
			delete m_lines[i];
			m_lines[i] = NULL;
		}
	}
	m_lines.clear();

	// clear state variables which are now invalid:
	m_trackstarts.clear();
	m_trackends.clear();
	m_barlines.clear();
	m_ticksperquarternote = -1;
	m_idprefix.clear();
	m_strand1d.clear();
	m_strand2d.clear();
	m_strophes1d.clear();
	m_strophes2d.clear();
	m_filename.clear();
	m_segmentlevel = 0;
	m_analyses.clear();
}



//////////////////////////////
//
// HumdrumFileBase::isStructureAnalyzed --
//

bool HumdrumFileBase::isStructureAnalyzed(void) {
	return m_analyses.m_structure_analyzed;
}



//////////////////////////////
//
// HumdrumFileBase::isRhythmAnalyzed --
//

bool HumdrumFileBase::isRhythmAnalyzed(void) {
	return m_analyses.m_rhythm_analyzed;
}



//////////////////////////////
//
// HumdrumFileBase::areStrandsAnalyzed --
//

bool HumdrumFileBase::areStrandsAnalyzed(void) {
	return m_analyses.m_strands_analyzed;
}



//////////////////////////////
//
// HumdrumFileBase::areStrandsAnalyzed --
//

bool HumdrumFileBase::areStrophesAnalyzed(void) {
	return m_analyses.m_strophes_analyzed;
}



//////////////////////////////
//
// HumdrumFileBase::setXmlIdPrefix -- Set the prefix for a HumdrumXML ID
//     atrribute.  The prefix should not start with a digit, nor have
//     spaces in it.
//

void HumdrumFileBase::setXmlIdPrefix(const string& value) {
	m_idprefix = value;
}



//////////////////////////////
//
// HumdrumFileBase::getXmlIdPrefix -- Return the HumdrumXML ID attribute prefix.
//

string HumdrumFileBase::getXmlIdPrefix(void) {
	return m_idprefix;
}



//////////////////////////////
//
// HumdrumFileBase::operator[] -- Access a Humdrum file line by and index.
//    Negative values reference the end of the list of lines.
//

HumdrumLine& HumdrumFileBase::operator[](int index) {
	if (index < 0) {
		index = (int)m_lines.size() - index;
	}
	if ((index < 0) || (index >= (int)m_lines.size())) {
		cerr << "Error: invalid index: " << index << endl;
		cerr << "Max index is " << m_lines.size() - 1 << endl;
		index = (int)m_lines.size()-1;
	}
	return *m_lines[index];
}



//////////////////////////////
//
// HumdrumFileBase::setParseError -- Set an error message from parsing
//     input data.  The size of the message will keep track of whether
//     or not an error was generated.  If no error message is generated
//     when reading data, then the parsing of the data is assumed to be
//     good.
//

bool HumdrumFileBase::setParseError(const string& err) {
	m_parseError = err;
	return !m_parseError.size();
}


bool HumdrumFileBase::setParseError(stringstream& err) {
	m_parseError = err.str();
	return !m_parseError.size();
}


bool HumdrumFileBase::setParseError(const char* format, ...) {
	char buffer[1024] = {0};
	va_list ap;
	va_start(ap, format);
	snprintf(buffer, 1024, format, ap);
	va_end(ap);
	m_parseError = buffer;
	return !m_parseError.size();
}



//////////////////////////////
//
// HumdrumFileBase::read -- Load file contents from an input stream or file.
//

bool HumdrumFileBase::read(const string& filename) {
	m_displayError = true;
	return HumdrumFileBase::read(filename.c_str());
}


bool HumdrumFileBase::read(const char* filename) {
	string fname = filename;
	m_displayError = true;

#ifdef USING_URI
	if (fname.find("://") != string::npos) {
		if (Convert::startsWith(fname, "http://")) {
			readFromHttpUri(fname);
			return isValid();
		}
		if (Convert::startsWith(fname, "jrp://")) {
			readFromJrpUri(fname);
			return isValid();
		}
		if (Convert::startsWith(fname, "h://") ||
			Convert::startsWith(fname, "hum://") ||
			Convert::startsWith(fname, "humdrum://")) {
			readFromHumdrumUri(fname);
			return isValid();
		}
	}
#endif

	ifstream infile;
	if (fname.empty() || (fname ==  "-")) {
		return HumdrumFileBase::read(cin);
	} else {
		infile.open(filename);
		if (!infile.is_open()) {
			return setParseError("Cannot open file >>%s<< for reading. A", filename);
		}
	}
	HumdrumFileBase::read(infile);
	infile.close();
	return isValid();
}


bool HumdrumFileBase::read(istream& contents) {
   clear();
   m_displayError = true;
   std::string buffer;
   HLp s;
   while (std::getline(contents, buffer)) {
      s = new HumdrumLine(buffer);
      s->setOwner(this);
      m_lines.push_back(s);
   }
   return analyzeBaseFromLines();
}



//////////////////////////////
//
// HumdrumFileBase::readCsv -- Read a Humdrum file in CSV format
//    (rather than TSV format).
// default value: separator = ","
//

bool HumdrumFileBase::readCsv(const string& filename, const string& separator) {
	return HumdrumFileBase::readCsv(filename.c_str());
}


bool HumdrumFileBase::readCsv(const char* filename, const string& separator) {
	ifstream infile;
	if ((strlen(filename) == 0) || (strcmp(filename, "-") == 0)) {
		return HumdrumFileBase::readCsv(cin, separator);
	} else {
		infile.open(filename);
		if (!infile.is_open()) {
			return setParseError("Cannot open file %s for reading. B", filename);
		}
	}
	HumdrumFileBase::readCsv(infile, separator);
	infile.close();
	return isValid();
}


bool HumdrumFileBase::readCsv(istream& contents, const string& separator) {
	m_displayError = true;
	char buffer[123123] = {0};
	HLp s;
	while (contents.getline(buffer, sizeof(buffer), '\n')) {
		s = new HumdrumLine;
		s->setLineFromCsv(buffer);
		s->setOwner(this);
		m_lines.push_back(s);
	}
	return analyzeBaseFromLines();
}



//////////////////////////////
//
// HumdrumFileBase::analyzeBaseFromLines --
//

bool HumdrumFileBase::analyzeBaseFromLines(void)  {
	if (!analyzeTokens()) { return isValid(); }
	if (!analyzeLines() ) { return isValid(); }
	if (!analyzeSpines()) { return isValid(); }
	if (!analyzeLinks() ) { return isValid(); }
	if (!analyzeTracks()) { return isValid(); }
	return isValid();
}



//////////////////////////////
//
// HumdrumFileBase::setFilenameFromSegment -- Update filename based on any
//      !!!!SEGMENT: line at the top of the file.
//

void HumdrumFileBase::setFilenameFromSegment(void) {
	HumdrumFileBase& infile = *this;
	for (int i=0; i<infile.getLineCount(); i++) {
		if (infile[i].isEmpty()) {
			continue;
		}
		if (!infile[i].isCommentUniversal()) {
			break;
		}
		if (!infile[i].isUniversalReference()) {
			break;
		}
		string key = infile[i].getUniversalReferenceKey();
		if (key != "SEGMENT") {
			// consider segment levels as well...
			continue;
		}
		string value = infile[i].getUniversalReferenceValue();
		if (!value.empty()) {
			infile.setFilename(value);
			break;
		}
	}
}



//////////////////////////////
//
// HumdrumFileBase::analyzeBaseFromTokens --
//

bool HumdrumFileBase::analyzeBaseFromTokens(void) {
	// if (!analyzeTokens()) { return isValid(); } // this creates tokens from lines
	if (!analyzeLines() ) { return isValid(); }
	if (!analyzeSpines()) { return isValid(); }
	if (!analyzeLinks() ) { return isValid(); }
	if (!analyzeTracks()) { return isValid(); }
	return isValid();
}



//////////////////////////////
//
// HumdrumFileBase::readString -- Read contents from a string rather than
//    an istream or filename.
//

bool HumdrumFileBase::readString(const string& contents) {
	stringstream infile;
	infile << contents;
	bool status = read(infile);
	return status;
}


bool HumdrumFileBase::readString(const char* contents) {
	stringstream infile;
	infile << contents;
	return read(infile);
}



//////////////////////////////
//
// HumdrumFileBase::readStringCsv -- Reads Humdrum data in CSV format.
//

bool HumdrumFileBase::readStringCsv(const char* contents,
		const string& separator) {
	stringstream infile;
	infile << contents;
	return readCsv(infile, separator);
}


bool HumdrumFileBase::readStringCsv(const string& contents,
		const string& separator) {
	stringstream infile;
	infile << contents;
	return readCsv(infile, separator);
}



//////////////////////////////
//
// HumdrumFileBase::getParseError -- Return parse fail reason.
//

string HumdrumFileBase::getParseError(void) const {
	return m_parseError;
}



//////////////////////////////
//
// HumdrumFileBase::isValid -- Returns true if last read was
//     successful.
//

bool HumdrumFileBase::isValid(void) {
	if (m_displayError && (m_parseError.size() > 0)&& !isQuiet()) {
		cerr << m_parseError << endl;
		m_displayError = false;
	}
	return m_parseError.empty();
}



//////////////////////////////
//
// HumdrumFileBase::setQuietParsing -- Prevent error messages from
//   being displayed when reading data.
// @SEEALSO: setNoisyParsing
// @SEEALSO: isQuiet
//

void HumdrumFileBase::setQuietParsing(void) {
	m_quietParse = true;
}



//////////////////////////////
//
// HumdrumFileBase::setFilename --
//

void HumdrumFileBase::setFilename(const string& filename) {
	m_filename = filename;
}



//////////////////////////////
//
// HumdrumFileBase::getFilename --
//

string HumdrumFileBase::getFilename(void) {
	return m_filename;
}



//////////////////////////////
//
// HumdrumFileBase::getFilenameBase -- Remove any path and any
//    dot followed by non-dots.
//

string HumdrumFileBase::getFilenameBase(void) {
	string output;
	auto pos = m_filename.rfind('/');
	if (pos != string::npos) {
		output = m_filename.substr(pos+1);
	} else {
		output = m_filename;
	}
	pos = output.rfind('.');
	if (pos != string::npos) {
		output = output.substr(0,pos);
	}
	return output;
}



//////////////////////////////
//
// HumdrumFileBase::printSegmentLabel --
//

ostream& HumdrumFileBase::printSegmentLabel(ostream& out) {
	out << "!!!!SEGMENT";
	string filename = getFilename();
	int segment = getSegmentLevel();
	if (segment != 0) {
		if (segment < 0) {
			out << segment;
		} else {
			out << "+" << segment;
		}
	}
	out << ": " << filename << endl;
	return out;
}



//////////////////////////////
//
// HumdrumFileBase::printNonemptySegmentLabel --
//

ostream& HumdrumFileBase::printNonemptySegmentLabel(ostream& out) {
	if (getFilename().size() > 0) {
		printSegmentLabel(out);
	}
	return out;
}



//////////////////////////////
//
// HumdrumFileBase::getSegmentLevel -- return the segment level
//

int HumdrumFileBase::getSegmentLevel(void) {
	return m_segmentlevel;
}



//////////////////////////////
//
// HumdrumFileBase::setSegmentLevel -- return the segment level
//

void HumdrumFileBase::setSegmentLevel(int level) {
	m_segmentlevel = level;
}

//////////////////////////////
//
// HumdrumFileBase::setNoisyParsing -- Display error messages
//   on console when reading data.
// @SEEALSO: setQuietParsing
// @SEEALSO: isQuiet
//

void HumdrumFileBase::setNoisyParsing(void) {
	m_quietParse = false;
}



//////////////////////////////
//
// HumdrmFileBase::isQuiet -- Returns true if parsing errors
//    messages should be suppressed. By default the parsing
//    is "noisy" and the error messages will be printed to
//    standard error.
// @SEEALSO: setQuietParsing
// @SEEALSO: setNoisyParsing
//

bool HumdrumFileBase::isQuiet(void) const{
	return m_quietParse;
}



//////////////////////////////
//
// HumdrumFileBase::printCsv -- print Humdrum file content in
//     CSV format.
// default value: out = std::cout
// default value: separator = ","
//

ostream& HumdrumFileBase::printCsv(ostream& out,
		const string& separator) {
	for (int i=0; i<getLineCount(); i++) {
		((*this)[i]).printCsv(out, separator);
	}
	return out;
}



//////////////////////////////
//
// HumdrumFileBase::printFieldNumber --
//

ostream& HumdrumFileBase::printFieldNumber(int fieldnum, ostream& out) {
	return printFieldIndex(fieldnum - 1, out);
}



//////////////////////////////
//
// HumdrumFileBase::printFieldIndex --
//

ostream& HumdrumFileBase::printFieldIndex(int fieldind, ostream& out) {
	if (fieldind < 0) {
		return out;
	}
	HumdrumFileBase& infile = *this;
	for (int i=0; i<infile.getLineCount(); i++) {
		if (!infile[i].hasSpines()) {
			out << infile[i] << endl;
			continue;
		}
		cout << infile.token(i,fieldind) << endl;
	}
	return out;
}



//////////////////////////////
//
// HumdrumFileBase::getLine -- Return a pointer to the line at a
//     given index in the data storage.
//

HLp HumdrumFileBase::getLine(int index) {
	if (index < 0) {
		return NULL;
	} else if (index >= (int)m_lines.size()) {
		return NULL;
	} else {
		return m_lines[index];
	}
}



//////////////////////////////
//
// HumdrumFileBase::analyzeTokens -- Generate token array from
//    current contents of the lines.  If either tokens or the line
//    is changed, then the other state becomes invalid.
//    See createLinesFromTokens for regeneration of lines from tokens.
//

bool HumdrumFileBase::analyzeTokens(void) {
	for (int i=0; i<(int)m_lines.size(); i++) {
		m_lines[i]->createTokensFromLine();
	}
	return isValid();
}


//////////////////////////////
//
// HumdrumFileBase::removeExtraTabs --
//

void HumdrumFileBase::removeExtraTabs(void) {
	for (int i=0; i<(int)m_lines.size(); i++) {
		m_lines[i]->removeExtraTabs();
	}
}



//////////////////////////////
//
// HumdrumFileBase::addExtraTabs --
//

void HumdrumFileBase::addExtraTabs(void) {
	vector<int> trackWidths = getTrackWidths();

	HumdrumFileBase& infile = *this;
	vector<int> local(trackWidths.size());
	for (int i=0; i<infile.getLineCount(); i++) {
		infile[i].addExtraTabs(trackWidths);
	}
}



//////////////////////////////
//
// HumdrumFileBase::getTrackWidths -- Return a list of the maximum
//    subspine count for each track in the file.  The 0th track is
//    not used, so it will be zero and ignored.
//

vector<int> HumdrumFileBase::getTrackWidths(void) {
	HumdrumFileBase& infile = *this;
	vector<int> output(infile.getTrackCount() + 1, 1);
	output[0] = 0;
	vector<int> local(infile.getTrackCount() + 1);
	for (int i=0; i<infile.getLineCount(); i++) {
		if (!infile[i].hasSpines()) {
			continue;
		}
		fill(local.begin(), local.end(), 0);
		for (int j=0; j<infile[i].getFieldCount(); j++) {
			HTp token = infile.token(i, j);
			int track = token->getTrack();
			local[track]++;
		}
		for (int j=1; j<(int)local.size(); j++) {
			if (local[j] > output[j]) {
				output[j] = local[j];
			}
		}
	}
	return output;
}



//////////////////////////////
//
// HumdrumFileBase::createLinesFromTokens -- Generate Humdrum lines strings
//   from the stored list of tokens.
//

void HumdrumFileBase::createLinesFromTokens(void) {
	for (int i=0; i<(int)m_lines.size(); i++) {
		m_lines[i]->createLineFromTokens();
	}
}



////////////////////////////
//
// HumdrumFileBase::appendLine -- Add a line to the file's contents.  The file's
//    spine and rhythmic structure should be recalculated after an append.
//

void HumdrumFileBase::appendLine(const string& line) {
	HLp s = new HumdrumLine(line);
	m_lines.push_back(s);
}


void HumdrumFileBase::appendLine(HLp line) {
	// deletion will be handled by class.
	m_lines.push_back(line);
}



////////////////////////////
//
// HumdrumFileBase::insertLine -- Add a line to the file's contents.  The file's
//    spine and rhythmic structure should be recalculated after an append.
//

void HumdrumFileBase::insertLine(int index, const string& line) {
	HLp s = new HumdrumLine(line);
	m_lines.insert(m_lines.begin() + index, s);

	// Update the line indexes for this line and the following ones:
	for (int i=index; i<(int)m_lines.size(); i++) {
		m_lines[i]->setLineIndex(i);
	}
}


void HumdrumFileBase::insertLine(int index, HLp line) {
	// deletion will be handled by class.
	m_lines.insert(m_lines.begin() + index, line);

	// Update the line indexes for this line and the following ones:
	for (int i=index; i<(int)m_lines.size(); i++) {
		m_lines[i]->setLineIndex(i);
	}
}



//////////////////////////////
//
// HumdrumFileBase::deleteLine -- remove a line from the Humdrum file.
//    Is best used for global comments and reference records for now.
//    Other line types will cause parsing problems untill further
//    generalized to stitch previous next lines together.
//

void HumdrumFileBase::deleteLine(int index) {
	if (index >= (int)m_lines.size()) {
		return;
	}
	if (index < 0) {
		return;
	}
	delete m_lines[index];
	for (int i=index+1; i<(int)m_lines.size(); i++) {
		m_lines[i-1] = m_lines[i];
	}
	m_lines.resize(m_lines.size() - 1);
}


//////////////////////////////
//
// HumdrumFileBase::back --
//

HLp HumdrumFileBase::back(void) {
	return m_lines.back();
}



//////////////////////////////
//
// HumdrumFileBase::getReferenceRecords --
//

vector<HLp> HumdrumFileBase::getReferenceRecords(void) {
	vector<HLp> hlps;
	hlps.reserve(32);
	HLp hlp;
	auto& infile = *this;
	for (int i=0; i<infile.getLineCount(); i++) {
		if (infile[i].isReference()) {
			hlp = &infile[i];
			hlps.push_back(hlp);
		}
	}
	return hlps;
}



//////////////////////////////
//
// HumdrumFileBase::getGlobalReferenceRecords --
//

vector<HLp> HumdrumFileBase::getGlobalReferenceRecords(void) {
	vector<HLp> hlps;
	hlps.reserve(32);
	HLp hlp;
	auto& infile = *this;
	for (int i=0; i<infile.getLineCount(); i++) {
		if (infile[i].isGlobalReference()) {
			hlp = &infile[i];
			hlps.push_back(hlp);
		}
	}
	return hlps;
}



//////////////////////////////
//
// HumdrumFileBase::getUniversalReferenceRecords --
//

vector<HLp> HumdrumFileBase::getUniversalReferenceRecords(void) {
	vector<HLp> hlps;
	hlps.reserve(32);
	HLp hlp;
	HumdrumFileBase& infile = *this;
	for (int i=0; i<infile.getLineCount(); i++) {
		if (infile[i].isUniversalReference()) {
			hlp = &infile[i];
			hlps.push_back(hlp);
		}
	}
	return hlps;
}



////////////////////////////
//
// HumdrumFileBase::getLineCount -- Returns the number of lines.
//

int HumdrumFileBase::getLineCount(void) const {
	return (int)m_lines.size();
}



//////////////////////////////
//
// HumdrumFileBase::token -- Return the token at the given line/field index.
//

HTp HumdrumFileBase::token(int lineindex, int fieldindex) {
	if (lineindex < 0) {
		lineindex += getLineCount();
	}
	return m_lines[lineindex]->token(fieldindex);
}


//
// Special case that returns a subtoken string:
//   default value separator = " "
//

string HumdrumFileBase::token(int lineindex, int fieldindex,
		int subtokenindex, const string& separator) {
	return token(lineindex, fieldindex)->getSubtoken(subtokenindex, separator);
}



//////////////////////////////
//
// HumdrumFileBase::getMaxTrack -- Returns the number of primary
//     spines in the data.
//

int HumdrumFileBase::getMaxTrack(void) const {
	return (int)m_trackstarts.size() - 1;
}



//////////////////////////////
//
// HumdrumFileBase::printSpineInfo -- Print the spine information for all
//    lines/tokens in file (for debugging).
//

ostream& HumdrumFileBase::printSpineInfo(ostream& out) {
	for (int i=0; i<getLineCount(); i++) {
		m_lines[i]->printSpineInfo(out) << '\n';
	}
	return out;
}



//////////////////////////////
//
// HumdrumFileBase::printDataTypeInfo -- Print the data type for all
//     spines in the file (for debugging).
//

ostream& HumdrumFileBase::printDataTypeInfo(ostream& out) {
	for (int i=0; i<getLineCount(); i++) {
		m_lines[i]->printDataTypeInfo(out) << '\n';
	}
	return out;
}



//////////////////////////////
//
// HumdrumFileBase::printTrackInfo -- Print the track numbers for all
//     tokens in the file (for debugging).
//

ostream& HumdrumFileBase::printTrackInfo(ostream& out) {
	for (int i=0; i<getLineCount(); i++) {
		m_lines[i]->printTrackInfo(out) << '\n';
	}
	return out;
}


//////////////////////////////
//
// HumdrumFileBase::getExinterpCount -- return the number of spines in
//    the file that are of the given exinterp type.  The input string
//    may optionally include the ** exinterp prefix.
//

int HumdrumFileBase::getExinterpCount(const string& exinterp) {
	vector<HTp> spinestarts;
	getSpineStartList(spinestarts, exinterp);
	return (int)spinestarts.size();
}



//////////////////////////////
//
// HumdrumFileBase::getSpineStopList -- Return a list of the ending
//     points of spine strands.
//

void HumdrumFileBase::getSpineStopList(vector<HTp>& spinestops) {
	spinestops.reserve(m_trackends.size());
	spinestops.resize(0);
	for (int i=0; i<(int)m_trackends.size(); i++) {
		for (int j=0; j<(int)m_trackends[i].size(); j++) {
			spinestops.push_back(m_trackends[i][j]);
		}
	}
}



//////////////////////////////
//
// HumdrumFileBase::getSpineStartList -- Return a list of the exclustive
//     interpretations starting spines in the data.  The single parameter
//     version of the fuction returns all starting exclusive interpretations.
//     The two-parameter version will result all exclusive interpretations
//     of a given datatype, and the three-parameter version where the third
//     parameter is a vector of string, will selectively include all starting
//     tokens which match one of the data types in the input list.  The
//     trackstarts class variable contains an empty slot at index 0;
//     this is removed in the return vector.
//

void HumdrumFileBase::getSpineStartList(vector<HTp>& spinestarts) {
	spinestarts.reserve(m_trackstarts.size());
	spinestarts.resize(0);
	for (int i=1; i<(int)m_trackstarts.size(); i++) {
		spinestarts.push_back(m_trackstarts[i]);
	}
}


void HumdrumFileBase::getSpineStartList(vector<HTp>& spinestarts,
		const string& exinterp) {
	string newexinterp;
	if (exinterp.compare(0, 2, "**") == 0) {
		newexinterp = exinterp;
	} else {
		newexinterp = "**";
		newexinterp += exinterp;
	}
	spinestarts.reserve(m_trackstarts.size());
	spinestarts.resize(0);
	for (int i=1; i<(int)m_trackstarts.size(); i++) {
		if (newexinterp == *m_trackstarts[i]) {
			spinestarts.push_back(m_trackstarts[i]);
		}
	}
}


void HumdrumFileBase::getSpineStartList(vector<HTp>& spinestarts,
		const vector<string>& exinterps) {
	vector<string> newexinterps(exinterps.size());
	for (int i=0; i<(int)exinterps.size(); i++) {
		if (exinterps[i].compare(0, 2, "**") == 0) {
			newexinterps[i] = exinterps[i];
		} else {
			newexinterps[i] = "**";
			newexinterps[i] += exinterps[i];
		}
	}
	spinestarts.reserve(m_trackstarts.size());
	spinestarts.resize(0);
	for (int i=1; i<(int)m_trackstarts.size(); i++) {
		for (int j=0; j<(int)newexinterps.size(); j++) {
			if (newexinterps[j] == *m_trackstarts[i]) {
				spinestarts.push_back(m_trackstarts[i]);
			}
		}
	}
}


//////////////////////////////
//
// HumdrumFileBase::getKernSpineStartList -- return only the spines that are **kern.
//

void HumdrumFileBase::getKernSpineStartList(vector<HTp>& spinestarts) {
	getSpineStartList(spinestarts, "**kern");
}

vector<HTp> HumdrumFileBase::getKernSpineStartList(void) {
	vector<HTp> starts;
	HumdrumFileBase::getKernSpineStartList(starts);
	return starts;
}



//////////////////////////////
//
// HumdrumFileBase::getKernLikeSpineStartList -- return spines that are "kern-like".  These
//    can be either **kern or forms matching **kern-tag pattern.
//

void HumdrumFileBase::getKernLikeSpineStartList(vector<HTp>& spinestarts) {
	vector <HTp> starts;
	HumdrumFileBase::getSpineStartList(starts);
	spinestarts.clear();
	for (int i=0; i<(int)starts.size(); i++) {
		if (*(starts.at(i)) == "**kern") {
			spinestarts.push_back(starts[i]);
		} else if (starts.at(i)->compare(0, 7, "**kern-") == 0) {
			spinestarts.push_back(starts[i]);
		}
	}
}


vector<HTp> HumdrumFileBase::getKernLikeSpineStartList(void) {
	vector<HTp> starts;
	HumdrumFileBase::getKernLikeSpineStartList(starts);
	return starts;
}



//////////////////////////////
//
// HumdrumFileBase::getStaffLikeSpineStartList -- return spines that have isSpine()
//    being true.  These can be either **kern or forms matching **kern-tag pattern.
//

void HumdrumFileBase::getStaffLikeSpineStartList(vector<HTp>& spinestarts) {
	vector <HTp> starts;
	HumdrumFileBase::getSpineStartList(starts);
	spinestarts.clear();
	for (int i=0; i<(int)starts.size(); i++) {
		if (starts.at(i)->isStaff()) {
			spinestarts.push_back(starts[i]);
		}
	}
}


vector<HTp> HumdrumFileBase::getStaffLikeSpineStartList(void) {
	vector<HTp> starts;
	HumdrumFileBase::getStaffLikeSpineStartList(starts);
	return starts;
}



//////////////////////////////
//
// getPrimaryspineSequence -- Return a list of the HumdrumTokens in a spine,
//    but not any secondary spine content if the spine splits.
//


void HumdrumFileBase::getPrimarySpineSequence(vector<HTp>& sequence, int spine,
		int options) {
	getPrimaryTrackSequence(sequence, spine+1, options);
}



//////////////////////////////
//
// getPrimaryspineSequence -- Return a list of the HumdrumTokens in a spine,
//    but not any secondary spine content if the spine splits.
//


void HumdrumFileBase::getSpineSequence(vector<vector<HTp> >& sequence,
		HTp starttoken, int options) {
	getTrackSequence(sequence, starttoken, options);

}


void HumdrumFileBase::getSpineSequence(vector<vector<HTp> >& sequence,
		int spine, int options) {
	getTrackSequence(sequence, spine+1, options);
}



//////////////////////////////
//
// HumdrumFileBase::getPrimaryTrackSequence -- Return a list of the
//     given primary spine tokens for a given track (indexed starting at
//     one and going through getMaxTrack().
//

void HumdrumFileBase::getPrimaryTrackSequence(vector<HTp>& sequence, int track,
		int options) {
	vector<vector<HTp> > tempseq;
	getTrackSequence(tempseq, track, options | OPT_PRIMARY);
	sequence.resize(tempseq.size());
	for (int i=0; i<(int)tempseq.size(); i++) {
		sequence[i] = tempseq[i][0];
	}
}



/////////////////////////////
//
// HumdrumFileBase::getTrackSequence -- Extract a sequence of tokens
//    for the given spine.  All subspine tokens will be included.
//    See getPrimaryTrackSequence() if you only want the first subspine for
//    a track on all lines.
//
// The following options are used for the getPrimaryTrackTokens:
// * OPT_PRIMARY    => only extract primary subspine/subtrack.
// * OPT_NOEMPTY    => don't include null tokens in extracted list if all
//                        extracted subspines contains null tokens.
//                        Includes null interpretations and comments as well.
// * OPT_NONULL     => don't include any null tokens in extracted list.
// * OPT_NOINTERP   => don't include interprtation tokens.
// * OPT_NOMANIP    => don't include spine manipulators (*^, *v, *x, *+,
//                        but still keep ** and *-).
// * OPT_NOCOMMENT  => don't include comment tokens.
// * OPT_NOGLOBAL   => don't include global records (global comments, reference
//                        records, and empty lines). In other words, only return
//                        a list of tokens from lines which hasSpines() it true.
// * OPT_NOREST     => don't include **kern rests.
// * OPT_NOTIE      => don't include **kern secondary tied notes.
// Compound options:
// * OPT_DATA      (OPT_NOMANIP | OPT_NOCOMMENT | OPT_NOGLOBAL)
//     Only data tokens (including barlines)
// * OPT_ATTACKS   (OPT_DATA | OPT_NOREST | OPT_NOTIE | OPT_NONULL)
//     Only note-attack tokens (when etracting **kern data)
//

void HumdrumFileBase::getTrackSequence(vector<vector<HTp> >& sequence,
		HTp starttoken, int options) {
	int track = starttoken->getTrack();
	getTrackSequence(sequence, track, options);
}


void HumdrumFileBase::getTrackSequence(vector<vector<HTp> >& sequence,
		int track, int options) {
	bool primaryQ   = (options & OPT_PRIMARY) ? true : false;
	bool nonullQ    = (options & OPT_NONULL) ? true : false;
	bool noemptyQ   = (options & OPT_NOEMPTY) ? true : false;
	bool nointerpQ  = (options & OPT_NOINTERP) ? true : false;
	bool nomanipQ   = (options & OPT_NOMANIP) ? true : false;
	bool nocommentQ = (options & OPT_NOCOMMENT) ? true : false;
	bool noglobalQ  = (options & OPT_NOGLOBAL) ? true : false;
	bool norestQ    = (options & OPT_NOREST) ? true : false;
	bool notieQ     = (options & OPT_NOTIE) ? true : false;

	vector<vector<HTp> >& output = sequence;
	output.reserve(getLineCount());
	output.resize(0);

	vector<HTp> tempout;
	auto& infile = *this;
	int i, j;
	bool allNull;
	HTp token;
	bool foundTrack;

	for (i=0; i<infile.getLineCount(); i++) {
		if (infile[i].isEmpty()) {
			continue;
		}
		tempout.resize(0);
		if (!noglobalQ && (infile[i].isGlobal())) {
			tempout.push_back(infile[i].token(0));
			output.push_back(tempout);
			continue;
		}
		if (noemptyQ) {
			allNull = true;
			for (j=0; j<infile[i].getFieldCount(); j++) {
				if (infile[i].token(j)->getTrack() != track) {
					continue;
				}
				if (!infile[i].token(j)->isNull()) {
					allNull = false;
					break;
				}
			}
			if (allNull) {
				continue;
			}
		}

		foundTrack = false;
		for (j=0; j<infile[i].getFieldCount(); j++) {
			token = infile[i].token(j);
			if (token->getTrack() != track) {
				continue;
			}
			if (primaryQ && foundTrack) {
				continue;
			}
			foundTrack = true;
			if (nointerpQ && (infile[i].token(j)->isManipulator() ||
					infile[i].token(j)->isTerminator() ||
					infile[i].token(j)->isExclusive())) {
				continue;
			}
			if (nomanipQ && infile[i].token(j)->isManipulator()) {
				continue;
			}
			if (nonullQ && infile[i].token(j)->isNull()) {
				continue;
			}
			if (nocommentQ && infile[i].token(j)->isComment()) {
				continue;
			}
			if (norestQ && infile[i].token(j)->isRest()) {
				continue;
			}
			if (notieQ && infile[i].token(j)->isSecondaryTiedNote()) {
				continue;
			}

			tempout.push_back(infile[i].token(j));
		}
		if (tempout.size() > 0) {
			output.push_back(tempout);
		}
	}
}



//////////////////////////////
//
// HumdrumFileBase::getTrackStart -- Return the starting exclusive
//     interpretation for the given track.  Returns NULL if the track
//     number is out of range.
//

HTp HumdrumFileBase::getTrackStart(int track) const {
	if ((track > 0) && (track < (int)m_trackstarts.size())) {
		return m_trackstarts[track];
	} else {
		return NULL;
	}
}



//////////////////////////////
//
// HumdrumFileBase::getTrackEndCount -- Return the number of ending tokens
//    for the given track.  Spines must start as a single exclusive
//    interpretation token.  However, since spines may split and merge,
//    it is possible that there are more than one termination points for a
//    track.  This function returns the number of terminations which are
//    present in a file for any given spine/track.
//

int HumdrumFileBase::getTrackEndCount(int track) const {
	if (track < 0) {
		track += (int)m_trackends.size();
	}
	if (track < 0) {
		return 0;
	}
	if (track >= (int)m_trackends.size()) {
		return 0;
	}
	return (int)m_trackends[track].size();
}



//////////////////////////////
//
// HumdrumFileBase::getTrackEnd -- Returns a pointer to the terminal manipulator
//    token for the given track and subtrack.  Sub-tracks are indexed from 0 up
//    to but not including getTrackEndCount.
//

HTp HumdrumFileBase::getTrackEnd(int track, int subtrack) const {
	if (track < 0) {
		track += (int)m_trackends.size();
	}
	if (track < 0) {
		return NULL;
	}
	if (track >= (int)m_trackends.size()) {
		return NULL;
	}
	if (subtrack < 0) {
		subtrack += (int)m_trackends[track].size();
	}
	if (subtrack < 0) {
		return NULL;
	}
	if (subtrack >= (int)m_trackends[track].size()) {
		return NULL;
	}
	return m_trackends[track][subtrack];
}



//////////////////////////////
//
// HumdrumFileBase::analyzeLines -- Store a line's index number in the
//    HumdrumFile within the HumdrumLine object at that index.
//    Returns false if there was an error.
//

bool HumdrumFileBase::analyzeLines(void) {
	for (int i=0; i<(int)m_lines.size(); i++) {
		m_lines[i]->setLineIndex(i);
	}
	return isValid();
}



//////////////////////////////
//
// HumdrumFileBase::analyzeTracks -- Analyze the track structure of the
//     data.  Returns false if there was a parse error.
//

bool HumdrumFileBase::analyzeTracks(void) {
	for (int i=0; i<(int)m_lines.size(); i++) {
		int status = m_lines[i]->analyzeTracks(m_parseError);
		if (!status) {
			return false;
		}
	}
	return isValid();
}



//////////////////////////////
//
// HumdrumFileBase::analyzeLinks -- Generate forward and backwards spine links
//    for each token.
//

bool HumdrumFileBase::analyzeLinks(void) {
	HumdrumFileBase& infile = *this;
	infile.clearTokenLinkInfo();

	HLp next     = NULL;
	HLp previous = NULL;

	for (int i=0; i<(int)m_lines.size(); i++) {
		if (!m_lines[i]->hasSpines()) {
			continue;
		}
		previous = next;
		next = m_lines[i];
		if (previous != NULL) {
			if (!stitchLinesTogether(*previous, *next)) {
				return isValid();
			}
		}
	}
	return isValid();;
}



//////////////////////////////
//
// HumdrumFileBase::stitchLinesTogether -- Make forward/backward links for
//    tokens on each line.
//

bool HumdrumFileBase::stitchLinesTogether(HumdrumLine& previous,
		HumdrumLine& next) {
	int i;

	// first handle simple cases where the spine assignments are one-to-one:
	if (!previous.isInterpretation() && !next.isInterpretation()) {
		if (previous.getTokenCount() != next.getTokenCount()) {
			stringstream err;
			err << "Error lines " << (previous.getLineNumber())
			    << " and " << (next.getLineNumber()) << " not same length\n";
			err << "Line " << (previous.getLineNumber()) << ": " << previous << endl;
			err << "Line " << (next.getLineNumber()) << ": " << next << endl;
			return setParseError(err);
		}
		for (i=0; i<previous.getTokenCount(); i++) {
			if (next.token(i)) {
				previous.token(i)->makeForwardLink(*next.token(i));
			} else {
				cerr << "Strange error 1" << endl;
			}
		}
		return true;
	}
	int ii = 0;
	for (i=0; i<previous.getTokenCount(); i++) {
		if (!previous.token(i)->isManipulator()) {
			if (next.token(ii) != NULL) {
				previous.token(i)->makeForwardLink(*next.token(ii++));
			} else {
				cerr << "Strange error 2" << endl;
			}
		} else if (previous.token(i)->isSplitInterpretation()) {
			// connect the previous token to the next two tokens.
			if (next.token(ii) != NULL) {
				previous.token(i)->makeForwardLink(*next.token(ii++));
			} else {
				cerr << "Strange error 3" << endl;
			}
			if (next.token(ii) != NULL) {
				previous.token(i)->makeForwardLink(*next.token(ii++));
			} else {
				cerr << "Strange error 4" << endl;
			}
		} else if (previous.token(i)->isMergeInterpretation()) {
			// Connect multiple previous tokens which are adjacent *v
			// spine manipulators to the current next token.
			while ((i<previous.getTokenCount()) &&
					previous.token(i)->isMergeInterpretation()) {
				if (next.token(ii) != NULL) {
					previous.token(i)->makeForwardLink(*next.token(ii));
				} else {
					cerr << "Strange error 5" << endl;
				}
				i++;
			}
			i--;
			ii++;
		} else if (previous.token(i)->isExchangeInterpretation()) {
			// swapping the order of two spines.
			if ((i<previous.getTokenCount()) &&
					previous.token(i+1)->isExchangeInterpretation()) {
				if (next.token(ii) != NULL) {
					previous.token(i+1)->makeForwardLink(*next.token(ii++));
				} else {
					cerr << "Strange error 6" << endl;
				}
				if (next.token(ii) != NULL) {
					previous.token(i)->makeForwardLink(*next.token(ii++));
				} else {
					cerr << "Strange error 7" << endl;
				}
			}
			i++;
		} else if (previous.token(i)->isTerminateInterpretation()) {
			// No link should be made.  There may be a problem if a
			// new segment is given (this should be handled by a
			// HumdrumSet class, not HumdrumFileBase.
		} else if (previous.token(i)->isAddInterpretation()) {
			// A new data stream is being added, the next linked token
			// should be an exclusive interpretation.
			if (!next.token(ii+1)->isExclusiveInterpretation()) {
				stringstream err;
				err << "Error: expecting exclusive interpretation on line "
				    << next.getLineNumber() << " at token " << i << " but got "
				    << next.token(i);
				return setParseError(err);
			}
			if (next.token(ii) != NULL) {
				previous.token(i)->makeForwardLink(*next.token(ii++));
			} else {
				cerr << "Strange error 8" << endl;
			}
			ii++;
		} else if (previous.token(i)->isExclusiveInterpretation()) {
			if (next.token(ii) != NULL) {
				if (previous.token(i) != NULL) {
					previous.token(i)->makeForwardLink(*next.token(ii++));
				} else {
					cerr << "Strange error 10" << endl;
				}
			} else {
				cerr << "Strange error 9" << endl;
			}
		} else {
			return setParseError("Error: should not get here");
		}
	}

	if ((i != previous.getTokenCount()) || (ii != next.getTokenCount())) {
		stringstream err;
		err << "Error: cannot stitch lines together due to alignment problem\n";
		err << "Line " << previous.getLineNumber() << ": "
		    << previous << endl;
		err << "Line " << next.getLineNumber() << ": "
		    << next << endl;
		err << "I = " <<i<< " token count " << previous.getTokenCount() << endl;
		err << "II = " <<ii<< " token count " << next.getTokenCount();
		return setParseError(err);
	}

	return isValid();
}



//////////////////////////////
//
// HumdrumFileBase::analyzeSpines -- Analyze the spine structure of the
//     data.  Returns false if there was a parse error.
//

bool HumdrumFileBase::analyzeSpines(void) {
	vector<string> datatype;
	vector<string> sinfo;
	vector<vector<HTp> > lastspine;
	m_trackstarts.resize(0);
	m_trackends.resize(0);
	addToTrackStarts(NULL);

	bool init = false;
	int i, j;
	for (i=0; i<getLineCount(); i++) {
		if (!m_lines[i]->hasSpines()) {
			m_lines[i]->token(0)->setFieldIndex(0);
			continue;
		}
		if ((init == false) && !m_lines[i]->isExclusive()) {
			stringstream err;
			err << "Error on line: " << (i+1) << ':' << endl;
			err << "   Data found before exclusive interpretation" << endl;
			err << "   LINE: " << *m_lines[i];
			return setParseError(err);
		}
		if ((init == false) && m_lines[i]->isExclusive()) {
			// first line of data in file.
			init = true;
			datatype.resize(m_lines[i]->getTokenCount());
			sinfo.resize(m_lines[i]->getTokenCount());
			lastspine.resize(m_lines[i]->getTokenCount());
			for (j=0; j<m_lines[i]->getTokenCount(); j++) {
				datatype[j] = m_lines[i]->getTokenString(j);
				addToTrackStarts(m_lines[i]->token(j));
				sinfo[j]    = to_string(j+1);
				m_lines[i]->token(j)->setSpineInfo(sinfo[j]);
				m_lines[i]->token(j)->setFieldIndex(j);
				lastspine[j].push_back(m_lines[i]->token(j));
			}
			continue;
		}
		if ((int)datatype.size() != m_lines[i]->getTokenCount()) {
			stringstream err;
			err << "Error on line " << (i+1) << ':' << endl;
			err << "   Expected " << datatype.size() << " fields,"
			    << "    but found " << m_lines[i]->getTokenCount();
			err << "\nLine is: " << m_lines[i] << endl;
			if (i > 0) {
				cerr << "Previous line is: " << m_lines[i-1] << endl;
			}
			return setParseError(err);
		}
		for (j=0; j<m_lines[i]->getTokenCount(); j++) {
			m_lines[i]->token(j)->setSpineInfo(sinfo[j]);
			m_lines[i]->token(j)->setFieldIndex(j);
		}
		if (!m_lines[i]->isManipulator()) {
			continue;
		}
		if (!adjustSpines(*m_lines[i], datatype, sinfo)) { return isValid(); }
	}
	return isValid();
}



//////////////////////////////
//
// HumdrumFileBase::addToTrackStarts -- A starting exclusive interpretation was
//    found, so store in the list of track starts.  The first index position
//    in trackstarts is reserve for non-spine usage.
//

void HumdrumFileBase::addToTrackStarts(HTp token) {
	if (token == NULL) {
		m_trackstarts.push_back(NULL);
		m_trackends.resize(m_trackends.size()+1);
	} else if ((m_trackstarts.size() > 1) && (m_trackstarts.back() == NULL)) {
		m_trackstarts.back() = token;
	} else {
		m_trackstarts.push_back(token);
		m_trackends.resize(m_trackends.size()+1);
	}
}



//////////////////////////////
//
// HumdrumFileBase::adjustSpines -- adjust datatype and spineinfo values based
//   on manipulators found in the data.
//

bool HumdrumFileBase::adjustSpines(HumdrumLine& line, vector<string>& datatype,
		vector<string>& sinfo) {
	vector<string> newtype;
	vector<string> newinfo;
	int mergecount = 0;
	int i, j;
	for (i=0; i<line.getTokenCount(); i++) {
		if (line.token(i)->isSplitInterpretation()) {
			newtype.resize(newtype.size() + 1);
			newtype.back() = datatype[i];
			newtype.resize(newtype.size() + 1);
			newtype.back() = datatype[i];
			newinfo.resize(newinfo.size() + 2);
			newinfo[newinfo.size()-2] = '(' + sinfo[i] + ")a";
			newinfo[newinfo.size()-1] = '(' + sinfo[i] + ")b";
		} else if (line.token(i)->isMergeInterpretation()) {
			mergecount = 0;
			for (j=i+1; j<line.getTokenCount(); j++) {
				if (line.token(j)->isMergeInterpretation()) {
					mergecount++;
				} else {
					break;
				}
			}
			newinfo.emplace_back(getMergedSpineInfo(sinfo, i, mergecount));
			newtype.push_back(datatype[i]);
			i += mergecount;
		} else if (line.token(i)->isAddInterpretation()) {
			newtype.resize(newtype.size() + 1);
			newtype.back() = datatype[i];
			newtype.resize(newtype.size() + 1);
			newtype.back() = "";
			newinfo.resize(newinfo.size() + 1);
			newinfo.back() = sinfo[i];
			newinfo.resize(newinfo.size() + 1);
			addToTrackStarts(NULL);
			newinfo.back() = to_string(getMaxTrack());
		} else if (line.token(i)->isExchangeInterpretation()) {
			if (i < line.getTokenCount() - 1) {
				if (line.token(i)->isExchangeInterpretation()) {
					// exchange spine information
					newtype.resize(newtype.size() + 1);
					newtype.back() = datatype[i+1];
					newtype.resize(newtype.size() + 1);
					newtype.back() = datatype[i];
					newinfo.resize(newinfo.size() + 1);
					newinfo.back() = sinfo[i+1];
					newinfo.resize(newinfo.size() + 1);
					newinfo.back() = sinfo[i];
				} else {
					return setParseError("ERROR1 in *x calculation");
				}
				i++;
			} else {
				stringstream err;
				err << "ERROR2 in *x calculation" << endl;
				err << "Index " << i << " larger than allowed: "
				     << line.getTokenCount() - 1;
				return setParseError(err);
			}
		} else if (line.token(i)->isTerminateInterpretation()) {
			// store pointer to terminate token in trackends
			m_trackends[m_trackstarts.size()-1].push_back(line.token(i));
		} else if (((string*)line.token(i))->substr(0, 2) == "**") {
			newtype.resize(newtype.size() + 1);
			newtype.back() = line.getTokenString(i);
			newinfo.resize(newinfo.size() + 1);
			newinfo.back() = sinfo[i];
			if (!((m_trackstarts.size() > 1) && (m_trackstarts.back() == NULL))) {
				stringstream err;
				err << "Error: Exclusive interpretation with no preparation "
				     << "on line " << line.getLineIndex()
				     << " spine index " << i << endl;
				err << "Line: " << line;
				return setParseError(err);
			}
			if (m_trackstarts.back() == NULL) {
				addToTrackStarts(line.token(i));
			}
		} else {
			// should only be null interpretation, but doesn't matter
			newtype.resize(newtype.size() + 1);
			newtype.back() = datatype[i];
			newinfo.resize(newinfo.size() + 1);
			newinfo.back() = sinfo[i];
		}
	}

	datatype.resize(newtype.size());
	sinfo.resize(newinfo.size());
	for (i=0; i<(int)newtype.size(); i++) {
		datatype[i] = newtype[i];
		sinfo[i]    = newinfo[i];
	}

	return true;
}



//////////////////////////////
//
// HumdrumFileBase::getMergedSpineInfo -- Will only simplify a two-spine
//   merge.  Should be expanded to larger spine mergers in the future.
//   In other words, it is best to currently merge spines in the order
//   in which they were split, so that the original spine label can
//   be produced.
//

string HumdrumFileBase::getMergedSpineInfo(vector<string>& info, int starti,
		int extra) {

	string output;
	int len1;
	int len2;
	if (extra < 1) {
		// Strange if get here.
		return info[starti];
	} else if (extra == 1) {
		len1 = (int)info[starti].size();
		len2 = (int)info[starti+1].size();
		if (len1 == len2) {
			if (info[starti].substr(0, len1-1) ==
					info[starti+1].substr(0,len2-1)) {
				output = info[starti].substr(1, len1-3);
				return output;
			}
		}
		output = info[starti] + " " + info[starti+1];
		return output;
	}
	// Generalized code for simplifying up to 4 subspines at once.
	// Not fully generlized so that the subspines will always be
	// simplified if not merged in a simple way, though.
	vector<string> newinfo;
	int i;
	for (i=0; i<=extra; i++) {
		newinfo.push_back(info.at(starti+i));
	}

	for (i=1; i<(int)newinfo.size(); i++) {
		int len1 = (int)newinfo[i-1].size();
		int len2 = (int)newinfo[i].size();
		if (len1 != len2) {
			continue;
		}
		if (newinfo[i-1].compare(0, len1-1, newinfo[i], 0, len2-1) == 0) {
			newinfo[i-1] = "";
			newinfo[i] = newinfo[i].substr(1, len2-3);
		}
	}

	vector<string> newinfo2;
	for (i=0; i<(int)newinfo.size(); i++) {
		if (newinfo[i].empty()) {
			continue;
		}
		newinfo2.push_back(newinfo[i]);
	}

	for (i=1; i<(int)newinfo2.size(); i++) {
		int len1 = (int)newinfo2[i-1].size();
		int len2 = (int)newinfo2[i].size();
		if (len1 != len2) {
			continue;
		}
		if (newinfo2[i-1].compare(0, len1-1, newinfo2[i], 0, len2-1) == 0) {
			newinfo2[i-1] = "";
			newinfo2[i] = newinfo2[i].substr(1, len2-3);
		}
	}

	newinfo.resize(0);
	for (i=0; i<(int)newinfo2.size(); i++) {
		if (newinfo2[i].empty()) {
			continue;
		}
		newinfo.push_back(newinfo2[i]);
	}

	for (i=1; i<(int)newinfo.size(); i++) {
		int len1 = (int)newinfo[i-1].size();
		int len2 = (int)newinfo[i].size();
		if (len1 != len2) {
			continue;
		}
		if (newinfo[i-1].compare(0, len1-1, newinfo[i], 0, len2-1) == 0) {
			newinfo[i-1] = "";
			newinfo[i] = newinfo[i].substr(1, len2-3);
		}
	}

	newinfo2.resize(0);
	for (i=0; i<(int)newinfo.size(); i++) {
		if (newinfo[i].empty()) {
			continue;
		}
		newinfo2.push_back(newinfo[i]);
	}

	output = newinfo2[0];
	for (int i=1; i<(int)newinfo2.size(); i++) {
		output += " " + newinfo2.at(i);
	}
	return output;
}



//////////////////////////////
//
// HumdrumFileBase::analyzeNonNullDataTokens -- For null data tokens, indicate
//    the previous non-null token which the null token refers to.  After
//    a spine merger, there may be multiple previous tokens, so you would
//		have to decide on the actual source token on based on subtrack or
//    sub-spine information.  The function also gives links to the previous/next
//    non-null tokens, skipping over intervening null data tokens.
//

bool HumdrumFileBase::analyzeNonNullDataTokens(void) {
	vector<HTp> ptokens;

	// analyze forward tokens:
	for (int i=1; i<=getMaxTrack(); i++) {
		if (!processNonNullDataTokensForTrackForward(getTrackStart(i),
				ptokens)) {
			return false;
		}
	}

	ptokens.resize(0);

	// analyze backward tokens:
	for (int i=1; i<=getMaxTrack(); i++) {
		for (int j=0; j<getTrackEndCount(i); j++) {
			if (!processNonNullDataTokensForTrackBackward(getTrackEnd(i, j),
					ptokens)) {
				return false;
			}
		}
	}

	// Eventually set the foward and backward non-null data token for
	// tokens in spines for all types of line types  For now specify
	// the next non-null data token for the exclusive interpretation token.
	// Also this implementation does not consider that the first
	// non-null data tokens may be from multiple split tokens (fix later).

	// This algorithm is probably not right, but good enough for now.
	// There may be missing portions of the file for the analysis,
	// and/or the algorithm is probably retracking tokens in the case
	// of spine splits.

	vector<HTp> stops;
	getSpineStopList(stops);
	HTp nexts = NULL;

	for (int i=0; i<(int)stops.size(); i++) {
		if (stops[i] == NULL) {
			continue;
		}
		HTp token = stops[i];
		if (token->isData() && !token->isNull()) {
			nexts = token;
		}
		token = token->getPreviousToken();

		while (token) {
			if (nexts) {
				token->addNextNonNullToken(nexts);
			}
			if (token->isData() && !token->isNull()) {
				nexts = token;
			}
			token = token->getPreviousToken();
		}
	}

	return true;
}



//////////////////////////////
//
// HumdurmFile::processNonNullDataTokensForTrackBackward -- Helper function
//    for analyzeNonNullDataTokens.  Given any token, this function tells
//    you what is the next non-null data token(s) in the spine after the given
//    token.
//

bool HumdrumFileBase::processNonNullDataTokensForTrackBackward(
		HTp endtoken, vector<HTp> ptokens) {

	HTp token = endtoken;
	int tcount = token->getPreviousTokenCount();

	while (tcount > 0) {
		for (int i=1; i<tcount; i++) {
			if (!processNonNullDataTokensForTrackBackward(
					token->getPreviousToken(i), ptokens)) {
				return false;
			}
		}
		HTp prevtoken = token->getPreviousToken();
		if (prevtoken->isSplitInterpretation()) {
			addUniqueTokens(prevtoken->m_nextNonNullTokens, ptokens);
			if (token != prevtoken->m_nextTokens[0]) {
				// terminate if not most primary subspine
				return true;
			}
		} else if (token->isData()) {
			addUniqueTokens(token->m_nextNonNullTokens, ptokens);
			if (!token->isNull()) {
				ptokens.resize(0);
				ptokens.push_back(token);
			}
		}

		// Follow previous data token 0 since 1 and higher are handled above.
		token = token->getPreviousToken(0);
		tcount = token->getPreviousTokenCount();
	}

	return true;
}



//////////////////////////////
//
// HumdurmFile::processNonNullDataTokensForTrackForward -- Helper function
//    for analyzeNonNullDataTokens.  Given any token, this function tells
//    you what are the previous non-null data token(s) in the spine before
//    the given token.
//

bool HumdrumFileBase::processNonNullDataTokensForTrackForward(HTp starttoken,
		vector<HTp> ptokens) {

	HTp token = starttoken;
	int tcount = token->getNextTokenCount();
	while (tcount > 0) {
		if (token->isSplitInterpretation()) {
			for (int i=1; i<tcount; i++) {
				if (!processNonNullDataTokensForTrackForward(
						token->getNextToken(i), ptokens)) {
					return false;
				}
			}
		} else if (token->isMergeInterpretation()) {
			HTp nexttoken = token->getNextToken();
			addUniqueTokens(nexttoken->m_previousNonNullTokens, ptokens);
			if (token != nexttoken->m_previousTokens[0]) {
				// terminate if not most primary subspine
				return true;
			}
		} else {
			addUniqueTokens(token->m_previousNonNullTokens, ptokens);
			if (token->isData() && !token->isNull()) {
				ptokens.resize(0);
				ptokens.push_back(token);

			}
		}
		// Data tokens can only be followed by up to one next token,
		// so no need to check for more than one next token.
		token = token->getNextToken(0);
		tcount = token->getNextTokenCount();
	}

	return true;
}



//////////////////////////////
//
// HumdrumFileBase::addUniqueTokens -- Used for non-null token analysis.  The
//    analysis is recursive like rhythmic analysis in the HumdrumFileStructure
//    class, but this algorithm does not terminate secondary traversals when
//    recursing.  Perhaps that should be fixed (utilizing the "rhycheck"
//    variable in HumdrumTokens)
//

void HumdrumFileBase::addUniqueTokens(vector<HTp>& target,
		vector<HTp>& source) {
	int i, j;
	bool found;
	for (i=0; i<(int)source.size(); i++) {
		found = false;
		for (j=0; j<(int)target.size(); j++) {
			if (source[i] == target[i]) {
				found = true;
			}
		}
		if (!found) {
			target.push_back(source[i]);
		}
	}
}



//////////////////////////////
//
// HumdrumFileBase::adjustMergeSpineLines -- fix *v lines so that adjacent
//     tracks do not merge at the same time.  In other words, split the line
//     into two or more merge lines.
//

/* still to be implemented

void HumdrumFileBase::adjustMergeSpineLines(void) {
	HumdrumFileBase& infile = *this;
	// going backwards to not have to deal with line number updates
	// at the moment...
	for (int i=infile.getLineCount()-1; i>= 0; i--) {
		if (!infile[i].isManipulator()) {
			continue;
		}
		bool hasbadmerge = false;
		int track1;
		int track2;
		for (int j=1; j<infile[i].getFieldCount(); j++) {
			if (!infile[i].token(j)->equalTo("*v")) {
				continue;
			}
			if (!infile[i].token(j-1)->equalTo("*v")) {
				continue;
			}
			track1 = infile.token(i, j-1)->getTrack();
			track2 = infile.token(i, j)->getTrack();
			if (track1 != track2) {
				hasbadmerge = true;
				break;
			}
		}
		if (hasbadmerge) {
			cerr << "!! BADMERGE on line " << i + 1 << endl;
			fixMerges(i);
		}
	}
}

*/



//////////////////////////////
//
// HumdrumFileBase::fixMerges -- Split a line with merges into two
//    lines.  The line is presumed to have a bad merge which
//    means that two adjacent tracks have adjacent *v tokens.
//    This algorithm will create a new lines where everything
//    after the bad merge is placed on the newline.   Example:
//
// track:    1    2    2    3    3    4    5    5
//           *    *v   *v   *v   *v   *    *v   *v
//
// This is invalid because track 2 and track 3 have adjacent *v tokens.
// This function will create a new line and move everything after
// the bad position to a new line:
//
// track:    1    2    2    3    3    4    5    5
//           *    *v   *v   *v   *v   *    *v   *v
//           *    *    *v   *v   *    *    *
// track:    1    2    3    3    4    5    5
//
// This algorithm only fixes one bad boundary.  The calling function
// will presumably fix any bad boundaries on the newly created line.
//

/* Still to be implemented...
void HumdrumFileBase::fixMerges(int linei) {
	HumdrumFileBase& infile = *this;

	vector<vector<HTp> > linetoks;
	HTp tok;

	// linetoks: collect tokens on the current line by track groups.
	int track1 = -1;
	int track2 = -1;
	for (int j=0; j<infile[linei].getFieldCount(); j++) {
		tok = infile[linei].token(j);
		track2 = tok->getTrack();
		if (track2 != track1) {
			linetoks.resize(linetoks.size()+1);
			linetoks.back().push_back(tok);
		}
		track1 = track2;
	}

	// ptoks: collect the tokens on the previous line for stiching tokens
	// together after adding new line.
	vector<vector<HTp> > ptoks;
	track1 = -1;
	track2 = -1;
	for (int j=0; j<infile[linei-1].getFieldCount(); j++) {
		tok = infile[linei-1].token(j);
		track2 = tok->getTrack();
		if (track2 != track1) {
			ptoks.resize(ptoks.size()+1);
			ptoks.back().push_back(tok);
		}
		track1 = track2;
	}

	// ntoks: collect the tokens on the next line for stiching tokens
	// together after adding new line.
	vector<vector<HTp> > ntoks;
	track1 = -1;
	track2 = -1;
	for (int j=0; j<infile[linei+1].getFieldCount(); j++) {
		tok = infile[linei+1].token(j);
		track2 = tok->getTrack();
		if (track2 != track1) {
			ntoks.resize(ntoks.size()+1);
			ntoks.back().push_back(tok);
		}
		track1 = track2;
	}

	int maxt = infile.getMaxTrack();
	vector<vector<HTp> > newtokbytrack(maxt+1);

// track:    1    2    2    3    3    4    5    5
//           *    *v   *v   *v   *v   *    *v   *v
//
// o = new null tokens.
//
// original     *    *v   *v   o    o    o    o    o
// new          o    o         *v   *v   *    *v   *v
// track:       1    2         3    3    4    5    5

	HLp newline = new HumdrumLine;
	newline->setOwner(this);
	bool foundboundary = false;
	HTp token;
	int findex;
	// int swaptrack = -1;
	int difference = 0;  // decrease in token count on new line
	for (int i=0; i<linetoks.size()-1; i++) {
		if (foundboundary) {
			// transfer the track tokens to the new line, and put
			// new null tokens in their place on the old line.
			for (int j=0; j<(int)linetoks[i].size(); j++) {
				track1 = linetoks[i][j]->getTrack();
				findex = linetoks[i][j]->getFieldIndex();
            // move the token to the next line:
				newline->m_tokens.push_back(linetoks[i][j]);
				// put it in the list for later processing:
				newtokbytrack[track1].push_back(linetoks[i][j]);
				// replace the moved token with a null token:
				token = new HumdrumToken("*");
				infile[linei].m_tokens[findex] = token;
				// probably need to update the HumAddress of both tokens.
			}
		} else if ((!foundboundary) && linetoks[i].back()->equalTo("*v") &&
				linetoks[i+1][0]->equalTo("*v")) {
			// This is the bad boundary.  Keep track fields in the
			// original line, and create one new null token in
			// the newline.
// original     *    *v   *v   o    o    o    o    o
// new          o    o         *v   *v   *    *v   *v
// track:       1    2         3    3    4    5    5
			difference = linetoks[i].size() - 1;

			track1 = linetoks[i][0]->getTrack();
			token = new HumdrumToken("*");
			token->setTrack(track1);
			token->setSubtrack(track1);
			newline->m_tokens.push_back(token);
			// put new token in list for later processing:
			newtokbytrack[track1].push_back(token);

			foundboundary = true;
		} else {
			// add null tokens to the new line, and keep the
			// tokens on the original line as they were
			for (int j=0; j<(int)linetoks[i].size(); j++) {
				track1 = linetoks[i][j]->getTrack();
				token = new HumdrumToken("*");
				token->setTrack(track1);
				token->setSubtrack(track1);
				newline->m_tokens.push_back(token);
				// put new token in list for later processing:
				newtokbytrack[track1].push_back(token);
			}
		}
	}

	// for now the links between the tokens on successive lines
	// will not be updated.  For the most part it will not be
	// important.  Probably more important is to update line numbers
	// for HumdrumLines occurring on new lines.  Maybe need to set
	// the line type for the new line.

	// add the new line to the file:
	m_lines.insert(m_lines.begin() + linei + 1, newline);

}

*/


//////////////////////////////
//
// operator<< -- Default method of printing HumdrumFiles.  This printing method
//    assumes that the HumdrumLine string is correct.  If a token is changed
//    in the file, the HumdrumFileBase::createLinesFromTokens() before printing
//    the contents of the line.
//

ostream& operator<<(ostream& out, HumdrumFileBase& infile) {
	for (int i=0; i<infile.getLineCount(); i++) {
		out << infile[i] << '\n';
	}
	return out;
}


//////////////////////////////
//
// sortTokenParisByLineIndex -- Sort two tokens so that the one
//    on the smaller line is first.  If both are on the same line then
//    sort the left-most token first.
//

bool sortTokenPairsByLineIndex(const TokenPair& a, const TokenPair& b) {
	if (a.first->getLineIndex() < b.first->getLineIndex()) {
		return true;
	}
	if (a.first->getLineIndex() == b.first->getLineIndex()) {
		if (a.first->getFieldIndex() < b.first->getFieldIndex()) {
			return true;
		}
	}
	return false;
}



//////////////////////////////
//
// HumdrumFileBase::makeBooleanTrackList --
//

void HumdrumFileBase::makeBooleanTrackList(vector<bool>& spinelist,
		const string& spinestring) {
	Convert::makeBooleanTrackList(spinelist, spinestring, getMaxTrack());
}



//////////////////////////////
//
// HumdrumFileBase::getMeasureNumbers -- Return a list of measures numbers
//    in the file indexed by line. A value of -1 means no measure number.
//

vector<int> HumdrumFileBase::getMeasureNumbers(void) {
	HumdrumFileBase& infile = *this;
	vector<int> output(infile.getLineCount(), -1);
	int lastmeasure = -1;
	int current;
	for (int i=0; i<infile.getLineCount(); i++) {
		if (infile[i].isBarline()) {
			current = infile.getMeasureNumber(i);
			if (current >= 0) {
				lastmeasure = current;
			}
		}
		output[i] = lastmeasure;
	}
	return output;
}



//////////////////////////////
//
// HumdrumFileBase::getMeasureNumber -- If the current line is a
//      barline, then read the first integer found in the fields on the line.
//

int HumdrumFileBase::getMeasureNumber(int line) {
   HumdrumFileBase& infile = *this;
   int j;
   if (!infile[line].isBarline()) {
      // Return -1 if not a barline.  May be changed in the future
      // to return the measure number of the previous barline.
      return -1;
   }
   HumRegex hre;
   int measurenumber = -1;
   for (j=0; j<infile[line].getFieldCount(); j++) {
      if (hre.search(*infile.token(line, j), "^=[^\\d]*(\\d+)")) {
         measurenumber = hre.getMatchInt(1);
         return measurenumber;
      }
   }
	return -1;
}



//////////////////////////////
//
// HumdrumFileBase::getReferenceRecord --
//

std::string HumdrumFileBase::getReferenceRecord(const std::string& key) {
	HumdrumFileBase& infile = *this;
	for (int i=0; i<infile.getLineCount(); i++) {
		if (!infile[i].isReference()) {
			continue;
		}
		string refkey = infile[i].getReferenceKey();
		if (refkey == key) {
			string value = infile[i].getReferenceValue();
			return value;
		}
	}
	return "";
}



//////////////////////////////
//
// HumdrumFileBase::insertNullDataLine -- Add a null data line at
//     the given absolute quarter-note timestamp in the file.  If there
//     is already a data line at the given timestamp, then do not create
//     a line and instead return a pointer to the existing line.  Returns
//     NULL if there was a problem.
//

HLp HumdrumFileBase::insertNullDataLine(HumNum timestamp) {
	// for now do a linear search for the insertion point, but later
	// do something more efficient.
	HumdrumFileBase& infile = *this;
	HumNum beforet(-1);
	HumNum aftert(-1);
	int beforei = -1;
	// int afteri = -1;
	HumNum current;
	for (int i=0; i<infile.getLineCount(); i++) {
		if (!infile[i].isData()) {
			continue;
		}
		current = infile[i].getDurationFromStart();
		if (current == timestamp) {
			return &infile[i];
		} else if (current < timestamp) {
			beforet = current;
			beforei = i;
		} else if (current > timestamp) {
			aftert = current;
			// afteri = i;
			break;
		}
	}

	if (beforei < 0) {
		return NULL;
	}
	HLp newline = new HumdrumLine;
	// copyStructure will add null tokens automatically
	newline->copyStructure(&infile[beforei], ".");

	infile.insertLine(beforei+1, newline);

	// Set the timestamp information for inserted line:
	HumNum delta = timestamp - beforet;
	HumNum durationFromStart = infile[beforei].getDurationFromStart() + delta;
	HumNum durationFromBarline = infile[beforei].getDurationFromBarline() + delta;
	HumNum durationToBarline = infile[beforei].getDurationToBarline() - delta;

	newline->m_durationFromStart = durationFromStart;
	newline->m_durationFromBarline = durationFromBarline;
	newline->m_durationToBarline = durationToBarline;

	newline->m_duration = infile[beforei].m_duration - delta;
	infile[beforei].m_duration = delta;

	for (int i=0; i<infile[beforei].getFieldCount(); i++) {
		HTp token = infile.token(beforei, i);
		HTp newtoken = newline->token(i);
		token->insertTokenAfter(newtoken);
	}

	return newline;
}



//////////////////////////////
//
// HumdrumFileBase::insertNullInterpretationLine -- Add a null interpretation
//     line at the given absolute quarter-note timestamp in the file.  The line will
//     be added after any other interpretation lines at that timestamp, but before any
//     local comments that appear immediately before the data line(s) at that timestamp.
//     Returns NULL if there was a problem.
//

HLp HumdrumFileBase::insertNullInterpretationLine(HumNum timestamp) {
	// for now do a linear search for the insertion point, but later
	// do something more efficient.
	HumdrumFileBase& infile = *this;
	HumNum beforet(-1);
	HumNum aftert(-1);
	int beforei = -1;
	// int afteri = -1;
	HumNum current;
	for (int i=0; i<infile.getLineCount(); i++) {
		if (!infile[i].isData()) {
			continue;
		}
		current = infile[i].getDurationFromStart();
		if (current == timestamp) {
			beforei = i;
			break;
		} else if (current < timestamp) {
			beforet = current;
			beforei = i;
		} else if (current > timestamp) {
			aftert = current;
			// afteri = i;
			break;
		}
	}

	if (beforei < 0) {
		return NULL;
	}

	HLp target = getLineForInterpretationInsertion(beforei);

	HLp newline = new HumdrumLine;
	// copyStructure will add null tokens automatically
	newline->copyStructure(target, "*");

	int targeti = target->getLineIndex();

	// There will be problems with linking to previous line if it is
	// a manipulator.
	// infile.insertLine(targeti-1, newline);
	infile.insertLine(targeti, newline);

	// inserted line will increment beforei by one:
	beforei++;

	// Set the timestamp information for inserted line:
	HumNum durationFromStart = infile[beforei].getDurationFromStart();
	HumNum durationFromBarline = infile[beforei].getDurationFromBarline();
	HumNum durationToBarline = infile[beforei].getDurationToBarline();

	newline->m_durationFromStart = durationFromStart;
	newline->m_durationFromBarline = durationFromBarline;
	newline->m_durationToBarline = durationToBarline;

	newline->m_duration = 0;

	// Problems here if targeti line is a manipulator.
	for (int i=0; i<infile[targeti].getFieldCount(); i++) {
		HTp token = infile.token(targeti, i);
		HTp newtoken = newline->token(i);
		token->insertTokenAfter(newtoken);
	}

	return newline;
}



//////////////////////////////
//
// HumdrumFileBase::insertNullInterpretationLieAboveIndex --
//

HLp HumdrumFileBase::insertNullInterpretationLineAboveIndex(int index) {
	if (index < 1) {
		return NULL;
	}
	if (index >= this->getLineCount()) {
		return NULL;
	}

	HumdrumFileBase& infile = *this;

	HLp target = &infile[index];

	HLp newline = new HumdrumLine;
	// copyStructure will add null tokens automatically
	newline->copyStructure(target, "*");

	int targeti = target->getLineIndex();

	// There will be problems with linking to previous line if it is
	// a manipulator.
	// infile.insertLine(targeti-1, newline);
	infile.insertLine(targeti, newline);

	// inserted line will increment insertion line by one:
	int beforei = index + 1;

	// Set the timestamp information for inserted line:
	HumNum durationFromStart = infile[beforei].getDurationFromStart();
	HumNum durationFromBarline = infile[beforei].getDurationFromBarline();
	HumNum durationToBarline = infile[beforei].getDurationToBarline();

	newline->m_durationFromStart = durationFromStart;
	newline->m_durationFromBarline = durationFromBarline;
	newline->m_durationToBarline = durationToBarline;

	newline->m_duration = 0;

	// Problems here if targeti line is a manipulator.
	for (int i=0; i<infile[targeti].getFieldCount(); i++) {
		HTp token = infile.token(targeti, i);
		HTp newtoken = newline->token(i);
		token->insertTokenAfter(newtoken);
	}

	return newline;
}



//////////////////////////////
//
// HumdrumFileBase::insertNullInterpretationLineAbove -- Add a null interpretation
//     line at the given absolute quarter-note timestamp in the file.  The line will
//     be added before any other lines at that timestamp.
//     Returns NULL if there was a problem.
//

HLp HumdrumFileBase::insertNullInterpretationLineAbove(HumNum timestamp) {
	// for now do a linear search for the insertion point, but later
	// do something more efficient.
	HumdrumFileBase& infile = *this;
	HumNum beforet(-1);
	HumNum aftert(-1);
	int beforei = -1;
	// int afteri = -1;
	HumNum current;
	for (int i=0; i<infile.getLineCount(); i++) {
		current = infile[i].getDurationFromStart();
		if (current == timestamp) {
			beforei = i;
			break;
		} else if (current < timestamp) {
			beforet = current;
			beforei = i;
		} else if (current > timestamp) {
			aftert = current;
			// afteri = i;
			break;
		}
	}

	if (beforei < 0) {
		return NULL;
	}

	HLp target = getLineForInterpretationInsertionAbove(beforei);

	HLp newline = new HumdrumLine;
	// copyStructure will add null tokens automatically
	newline->copyStructure(target, "*");

	int targeti = target->getLineIndex();

	// There will be problems with linking to previous line if it is
	// a manipulator.
	// infile.insertLine(targeti-1, newline);
	infile.insertLine(targeti, newline);

	// inserted line will increment beforei by one:
	beforei++;

	// Set the timestamp information for inserted line:
	HumNum durationFromStart = infile[beforei].getDurationFromStart();
	HumNum durationFromBarline = infile[beforei].getDurationFromBarline();
	HumNum durationToBarline = infile[beforei].getDurationToBarline();

	newline->m_durationFromStart = durationFromStart;
	newline->m_durationFromBarline = durationFromBarline;
	newline->m_durationToBarline = durationToBarline;

	newline->m_duration = 0;

	// Problems here if targeti line is a manipulator.
	for (int i=0; i<infile[targeti].getFieldCount(); i++) {
		HTp token = infile.token(targeti, i);
		HTp newtoken = newline->token(i);
		token->insertTokenAfter(newtoken);
	}

	return newline;
}



//////////////////////////////
//
// HumdrumFileBase::getLineForInterpretationInsertion --  Search backwards
//    in the file for the first local comment immediately before a data line
//    index given as input.  If there are no local comments, then return the
//    data line.  If there are local comment lines immediately before the data
//    line, then keep searching for the first local comment.  Non-spined lines
//    (global or empty lines) are ignored.  This function is used to insert
//    an empty interpretation before a data line at a specific data line.
//

HLp HumdrumFileBase::getLineForInterpretationInsertion(int index) {
	HumdrumFileBase& infile = *this;
	int current = index - 1;
	int previous = index;
	while (current > 0) {
		if (!infile[current].hasSpines()) {
			current--;
			continue;
		}
		if (infile[current].isCommentLocal()) {
			previous = current;
			current--;
			continue;
		}
		return &infile[previous];
	}
	return &infile[index];
}



//////////////////////////////
//
// HumdrumFileBase::getLineForInterpretationInsertionAbove --  Search backwards
//    in the file for the first line at the same timestamp as the starting line.
//

HLp HumdrumFileBase::getLineForInterpretationInsertionAbove(int index) {
	HumdrumFileBase& infile = *this;
	HumNum timestamp = infile[index].getDurationFromStart();
	HumNum teststamp;
	int current = index - 1;
	int previous = index;
	while (current > 0) {
		if (!infile[current].hasSpines()) {
			current--;
			continue;
		}
		teststamp = infile[current].getDurationFromStart();
		if (teststamp == timestamp) {
			previous = current;
			current--;
			continue;
		}
		return &infile[previous];
	}
	return &infile[index];
}



//////////////////////////////
//
// HumdrumFileBase::clearTokenLinkInfo --
//

void HumdrumFileBase::clearTokenLinkInfo(void) {
	HumdrumFileBase& infile = *this;
	for (int i=0; i<infile.getLineCount(); i++) {
		if (!infile[i].isEmpty()) {
			infile[i].clearTokenLinkInfo();
		}
	}
}





//////////////////////////////
//
// HumdrumFileContent::analyzeAccidentals -- Analyze kern and mens accidentals.
//

bool HumdrumFileContent::analyzeAccidentals(void) {
	bool status = true;
	status &= analyzeKernAccidentals();
	status &= analyzeMensAccidentals();
	return status;
}



//////////////////////////////
//
// HumdrumFileContent::analyzeMensAccidentals -- Analyze kern and mens accidentals.
//

bool HumdrumFileContent::analyzeMensAccidentals(void) {
	return analyzeKernAccidentals("**mens");
}



//////////////////////////////
//
// HumdrumFileContent::analyzeKernAccidentals -- Identify accidentals that
//    should be printed (only in **kern spines) as well as cautionary
//    accidentals (accidentals which are forced to be displayed but otherwise
//    would not be printed.  Algorithm assumes that all secondary tied notes
//    will not display their accidental across a system break.  Consideration
//    about grace-note accidental display still needs to be done.
//

bool HumdrumFileContent::analyzeKernAccidentals(const string& dataType) {

	// ottava marks must be analyzed first:
	this->analyzeOttavas();

	HumdrumFileContent& infile = *this;
	int i, j, k;
	int kindex;
	int track;

	// ktracks == List of **kern spines in data.
	// rtracks == Reverse mapping from track to ktrack index (part/staff index).
	vector<HTp> ktracks;
	if ((dataType == "**kern") || dataType.empty()) {
		ktracks = getKernSpineStartList();
	} else if (dataType == "**mens") {
		getSpineStartList(ktracks, "**mens");
	} else {
		getSpineStartList(ktracks, dataType);
	}
	if (ktracks.empty()) {
		return true;
	}
	vector<int> rtracks(getMaxTrack()+1, -1);
	for (i=0; i<(int)ktracks.size(); i++) {
		track = ktracks[i]->getTrack();
		rtracks[track] = i;
	}
	int kcount = (int)ktracks.size();

	// keysigs == key signature spellings of diatonic pitch classes.  This array
	// is duplicated into dstates after each barline.
	vector<vector<int> > keysigs;
	keysigs.resize(kcount);
	for (i=0; i<kcount; i++) {
		keysigs[i].resize(7);
		std::fill(keysigs[i].begin(), keysigs[i].end(), 0);
	}

	// dstates == diatonic states for every pitch in a spine.
	// sub-spines are considered as a single unit, although there are
	// score conventions which would keep a separate voices on a staff
	// with different accidental states (i.e., two parts superimposed
	// on the same staff, but treated as if on separate staves).
	// Eventually this algorithm should be adjusted for dealing with
	// cross-staff notes, where the cross-staff notes should be following
	// the accidentals of a different spine...
	vector<vector<int> > dstates; // diatonic states
	dstates.resize(kcount);
	for (i=0; i<kcount; i++) {
		dstates[i].resize(70);     // 10 octave limit for analysis
			                        // may cause problems; fix later.
		std::fill(dstates[i].begin(), dstates[i].end(), 0);
	}

	// gdstates == grace note diatonic states for every pitch in a spine.
	vector<vector<int> > gdstates; // grace-note diatonic states
	gdstates.resize(kcount);
	for (i=0; i<kcount; i++) {
		gdstates[i].resize(70);
		std::fill(gdstates[i].begin(), gdstates[i].end(), 0);
	}

	// rhythmstart == keep track of first beat in measure.
	vector<int> firstinbar(kcount, 0);

	int lasttrack = -1;
	vector<int> concurrentstate(70, 0);

	for (i=0; i<infile.getLineCount(); i++) {
		if (!infile[i].hasSpines()) {
			continue;
		}
		if (infile[i].isInterpretation()) {
			for (j=0; j<infile[i].getFieldCount(); j++) {
				HTp token  = infile.token(i, j);
				if (!token->isKern()) {
					continue;
				}
				if (token->compare(0, 3, "*k[") == 0) {
					track = token->getTrack();
					kindex = rtracks[track];
					fillKeySignature(keysigs[kindex], *infile[i].token(j));
					// resetting key states of current measure.  What to do if this
					// key signature is in the middle of a measure?
					resetDiatonicStatesWithKeySignature(dstates[kindex], keysigs[kindex]);
					resetDiatonicStatesWithKeySignature(gdstates[kindex], keysigs[kindex]);
				}
			}
		} else if (infile[i].isBarline()) {
			for (j=0; j<infile[i].getFieldCount(); j++) {
				HTp token = infile.token(i, j);
				if (!token->isKern()) {
					continue;
				}
				if (token->isInvisible()) {
					continue;
				}
				std::fill(firstinbar.begin(), firstinbar.end(), 1);
				track = token->getTrack();
				kindex = rtracks[track];
				// reset the accidental states in dstates to match keysigs.
				resetDiatonicStatesWithKeySignature(dstates[kindex], keysigs[kindex]);
				resetDiatonicStatesWithKeySignature(gdstates[kindex], keysigs[kindex]);
			}
		}

		if (!infile[i].isData()) {
			continue;
		}
		fill(concurrentstate.begin(), concurrentstate.end(), 0);
		lasttrack = -1;

		for (j=0; j<infile[i].getFieldCount(); j++) {
			HTp token = infile.token(i, j);
			if (!token->isKern()) {
				continue;
			}
			if (token->isNull()) {
				continue;
			}
			if (token->isRest()) {
				continue;
			}

			int subcount = token->getSubtokenCount();
			track = token->getTrack();

			if (lasttrack != track) {
				fill(concurrentstate.begin(), concurrentstate.end(), 0);
			}
			lasttrack = track;
			int rindex = rtracks[track];
			for (k=0; k<subcount; k++) {
				// bool tienote = false;
				string subtok = token->getSubtoken(k);
				if (subcount > 1) {
					// Rests in chords represent unsounding notes.
					// Rests can have pitch, but this is treated as
					// Diatonic pitch which does not involve accidentals,
					// so convert to pitch-like so that accidentals are
					// processed on these notes.
					for (int m=0; m<(int)subtok.size(); m++) {
						if (subtok[m] == 'r') {
							subtok[m] = 'R';
						}
					}
				}
				int b40 = Convert::kernToBase40(subtok);
				int diatonic = Convert::kernToBase7(subtok);
				int octaveadjust = token->getValueInt("auto", "ottava");
				diatonic -= octaveadjust * 7;
				if (diatonic < 0) {
					// Deal with extra-low notes later.
					continue;
				}
				int graceQ = token->isGrace();
				int accid = Convert::kernToAccidentalCount(subtok);
				int hiddenQ = 0;
				if (subtok.find("yy") == string::npos) {
					if ((subtok.find("ny") != string::npos) ||
					    (subtok.find("#y") != string::npos) ||
					    (subtok.find("-y") != string::npos)) {
						hiddenQ = 1;
					}
				}

				if (((subtok.find("_") != string::npos) || (subtok.find("]") != string::npos))) {
					// tienote = true;
					// tied notes do not have accidentals, so skip them
					if ((accid != keysigs[rindex][diatonic % 7]) && firstinbar[rindex]) {
						// But first, prepare to force an accidental to be shown on
						// the note immediately following the end of a tied group
						// if the tied group crosses a barline.
						dstates[rindex][diatonic] = -1000 + accid;
						gdstates[rindex][diatonic] = -1000 + accid;
					}
					auto loc = subtok.find('X');
					if (loc == string::npos) {
						continue;
					} else if (loc == 0) {
						continue;
					} else {
						if (!((subtok[loc-1] == '#') || (subtok[loc-1] == '-') ||
								(subtok[loc-1] == 'n'))) {
							continue;
						} else {
							// an accidental should be fored at end of tie
						}
					}
				}

				size_t loc;
				// check for accidentals on trills, mordents and turns.
				if (subtok.find("t") != string::npos) {
					// minor second trill
					int trillnote     = b40 + 5;
					int trilldiatonic = Convert::base40ToDiatonic(trillnote);
					int trillaccid    = Convert::base40ToAccidental(trillnote);
					if (dstates[rindex][trilldiatonic] != trillaccid) {
						token->setValue("auto", to_string(k),
								"trillAccidental", to_string(trillaccid));
						dstates[rindex][trilldiatonic] = -1000 + trillaccid;
					}
				} else if (subtok.find("T") != string::npos) {
					// major second trill
					int trillnote     = b40 + 6;
					int trilldiatonic = Convert::base40ToDiatonic(trillnote);
					int trillaccid    = Convert::base40ToAccidental(trillnote);
					if (dstates[rindex][trilldiatonic] != trillaccid) {
						token->setValue("auto", to_string(k), "trillAccidental", to_string(trillaccid));
						dstates[rindex][trilldiatonic] = -1000 + trillaccid;
					}
				} else if (subtok.find("M") != string::npos) {
					// major second upper mordent
					int auxnote     = b40 + 6;
					int auxdiatonic = Convert::base40ToDiatonic(auxnote);
					int auxaccid    = Convert::base40ToAccidental(auxnote);
					if (dstates[rindex][auxdiatonic] != auxaccid) {
						token->setValue("auto", to_string(k), "mordentUpperAccidental", to_string(auxaccid));
						dstates[rindex][auxdiatonic] = -1000 + auxaccid;
					}
				} else if (subtok.find("m") != string::npos) {
					// minor second upper mordent
					int auxnote     = b40 + 5;
					int auxdiatonic = Convert::base40ToDiatonic(auxnote);
					int auxaccid    = Convert::base40ToAccidental(auxnote);
					if (dstates[rindex][auxdiatonic] != auxaccid) {
						token->setValue("auto", to_string(k), "mordentUpperAccidental", to_string(auxaccid));
						dstates[rindex][auxdiatonic] = -1000 + auxaccid;
					}
				} else if (subtok.find("W") != string::npos) {
					// major second upper mordent
					int auxnote     = b40 - 6;
					int auxdiatonic = Convert::base40ToDiatonic(auxnote);
					int auxaccid    = Convert::base40ToAccidental(auxnote);
					if (dstates[rindex][auxdiatonic] != auxaccid) {
						token->setValue("auto", to_string(k),
								"mordentLowerAccidental", to_string(auxaccid));
						dstates[rindex][auxdiatonic] = -1000 + auxaccid;
					}
				} else if (subtok.find("w") != string::npos) {
					// minor second upper mordent
					int auxnote     = b40 - 5;
					int auxdiatonic = Convert::base40ToDiatonic(auxnote);
					int auxaccid    = Convert::base40ToAccidental(auxnote);
					if (dstates[rindex][auxdiatonic] != auxaccid) {
						token->setValue("auto", to_string(k),
								"mordentLowerAccidental", to_string(auxaccid));
						dstates[rindex][auxdiatonic] = -1000 + auxaccid;
					}

				} else if ((loc = subtok.find("$")) != string::npos) {
					int turndiatonic = Convert::base40ToDiatonic(b40);
					// int turnaccid = Convert::base40ToAccidental(b40);
					// inverted turn
					int lowerint = 0;
					int upperint = 0;
					if (loc < subtok.size()-1) {
						if (subtok[loc+1] == 's') {
							lowerint = -5;
						} else if (subtok[loc+1] == 'S') {
							lowerint = -6;
						}
					}
					if (loc < subtok.size()-2) {
						if (subtok[loc+2] == 's') {
							upperint = +5;
						} else if (subtok[loc+2] == 'S') {
							upperint = +6;
						}
					}
					int lowerdiatonic = turndiatonic - 1;
					// Maybe also need to check for forced accidental state...
					int loweraccid = dstates[rindex][lowerdiatonic];
					int lowerb40 = Convert::base7ToBase40(lowerdiatonic) + loweraccid;
					int upperdiatonic = turndiatonic + 1;
					// Maybe also need to check for forced accidental state...
					int upperaccid = dstates[rindex][upperdiatonic];
					int upperb40 = Convert::base7ToBase40(upperdiatonic) + upperaccid;
					if (lowerint == 0) {
						// need to calculate lower interval (but it will not appear
						// below the inverted turn, just calculating for performance
						// rendering.
						lowerint = lowerb40 - b40;
						lowerb40 = b40 + lowerint;
					}
					if (upperint == 0) {
						// need to calculate upper interval (but it will not appear
						// above the inverted turn, just calculating for performance
						// rendering.
						upperint = upperb40 - b40;
						upperb40 = b40 + upperint;
					}
					int uacc = Convert::base40ToAccidental(b40 + upperint);
					int bacc = Convert::base40ToAccidental(b40 + lowerint);
					if (uacc != upperaccid) {
						token->setValue("auto", to_string(k),
								"turnUpperAccidental", to_string(uacc));
						dstates[rindex][upperdiatonic] = -1000 + uacc;
					}
					if (bacc != loweraccid) {
						token->setValue("auto", to_string(k),
								"turnLowerAccidental", to_string(bacc));
						dstates[rindex][lowerdiatonic] = -1000 + bacc;
					}
				} else if ((loc = subtok.find("S")) != string::npos) {
					int turndiatonic = Convert::base40ToDiatonic(b40);
					// int turnaccid = Convert::base40ToAccidental(b40);
					// regular turn
					int lowerint = 0;
					int upperint = 0;
					if (loc < subtok.size()-1) {
						if (subtok[loc+1] == 's') {
							upperint = +5;
						} else if (subtok[loc+1] == 'S') {
							upperint = +6;
						}
					}
					if (loc < subtok.size()-2) {
						if (subtok[loc+2] == 's') {
							lowerint = -5;
						} else if (subtok[loc+2] == 'S') {
							lowerint = -6;
						}
					}
					int lowerdiatonic = turndiatonic - 1;
					// Maybe also need to check for forced accidental state...
					int loweraccid = dstates[rindex][lowerdiatonic];
					int lowerb40 = Convert::base7ToBase40(lowerdiatonic) + loweraccid;
					int upperdiatonic = turndiatonic + 1;
					// Maybe also need to check for forced accidental state...
					int upperaccid = dstates[rindex][upperdiatonic];
					int upperb40 = Convert::base7ToBase40(upperdiatonic) + upperaccid;
					if (lowerint == 0) {
						// need to calculate lower interval (but it will not appear
						// below the inverted turn, just calculating for performance
						// rendering.
						lowerint = lowerb40 - b40;
						lowerb40 = b40 + lowerint;
					}
					if (upperint == 0) {
						// need to calculate upper interval (but it will not appear
						// above the inverted turn, just calculating for performance
						// rendering.
						upperint = upperb40 - b40;
						upperb40 = b40 + upperint;
					}
					int uacc = Convert::base40ToAccidental(b40 + upperint);
					int bacc = Convert::base40ToAccidental(b40 + lowerint);

					if (uacc != upperaccid) {
						token->setValue("auto", to_string(k), "turnUpperAccidental", to_string(uacc));
						dstates[rindex][upperdiatonic] = -1000 + uacc;
					}
					if (bacc != loweraccid) {
						token->setValue("auto", to_string(k), "turnLowerAccidental", to_string(bacc));
						dstates[rindex][lowerdiatonic] = -1000 + bacc;
					}
				}

				// if (tienote) {
				// 	continue;
				// }

				if (graceQ && (accid != gdstates[rindex][diatonic])) {
					// accidental is different from the previous state so should be
					// printed
					if (!hiddenQ) {
						token->setValue("auto", to_string(k), "visualAccidental", "true");
						if (gdstates[rindex][diatonic] < -900) {
							// this is an obligatory cautionary accidental
							// or at least half the time it is (figure that out later)
							token->setValue("auto", to_string(k), "obligatoryAccidental", "true");
							token->setValue("auto", to_string(k), "cautionaryAccidental", "true");
						}
					}
					gdstates[rindex][diatonic] = accid;
					// regular notes are not affected by grace notes accidental
					// changes, but should have an obligatory cautionary accidental,
					// displayed for clarification.
					dstates[rindex][diatonic] = -1000 + accid;

				} else if (!graceQ && ((concurrentstate[diatonic] && (concurrentstate[diatonic] == accid))
						|| (accid != dstates[rindex][diatonic]))) {
					// accidental is different from the previous state so should be
					// printed, but only print if not supposed to be hidden.
					if (!hiddenQ) {
						token->setValue("auto", to_string(k), "visualAccidental", "true");
						concurrentstate[diatonic] = accid;
						if (dstates[rindex][diatonic] < -900) {
							// this is an obligatory cautionary accidental
							// or at least half the time it is (figure that out later)
							token->setValue("auto", to_string(k), "obligatoryAccidental", "true");
							token->setValue("auto", to_string(k), "cautionaryAccidental", "true");
						}
					}
					dstates[rindex][diatonic] = accid;
					gdstates[rindex][diatonic] = accid;

				} else if ((accid == 0) && (subtok.find("n") != string::npos) && !hiddenQ) {
					token->setValue("auto", to_string(k), "cautionaryAccidental", "true");
					token->setValue("auto", to_string(k), "visualAccidental", "true");
				} else if (subtok.find("XX") == string::npos) {
					// The accidental is not necessary. See if there is a single "X"
					// immediately after the accidental which means to force it to
					// display.
					auto loc = subtok.find("X");
					if ((loc != string::npos) && (loc > 0)) {
						if (subtok[loc-1] == '#') {
							token->setValue("auto", to_string(k), "cautionaryAccidental", "true");
							token->setValue("auto", to_string(k), "visualAccidental", "true");
						} else if (subtok[loc-1] == '-') {
							token->setValue("auto", to_string(k), "cautionaryAccidental", "true");
							token->setValue("auto", to_string(k), "visualAccidental", "true");
						} else if (subtok[loc-1] == 'n') {
							token->setValue("auto", to_string(k), "cautionaryAccidental", "true");
							token->setValue("auto", to_string(k), "visualAccidental", "true");
						}
					}
				}
			}
		}
		std::fill(firstinbar.begin(), firstinbar.end(), 0);
	}

	// Indicate that the accidental analysis has been done:
	string dataTypeDone = "accidentalAnalysis" + dataType;
	infile.setValue("auto", dataTypeDone, "true");

	return true;
}



//////////////////////////////
//
// HumdrumFileContent::fillKeySignature -- Read key signature notes and
//    assign +1 to sharps, -1 to flats in the diatonic input array.  Used
//    only by HumdrumFileContent::analyzeKernAccidentals().
//

void HumdrumFileContent::fillKeySignature(vector<int>& states, const string& keysig) {
	if (states.size() < 7) {
		cerr << "In HumdrumFileContent::fillKeySignature, states is too small: " << states.size() << endl;
		return;
	}
	std::fill(states.begin(), states.end(), 0);
	if (keysig.find("f#") != string::npos) { states.at(3) = +1; }
	if (keysig.find("c#") != string::npos) { states.at(0) = +1; }
	if (keysig.find("g#") != string::npos) { states.at(4) = +1; }
	if (keysig.find("d#") != string::npos) { states.at(1) = +1; }
	if (keysig.find("a#") != string::npos) { states.at(5) = +1; }
	if (keysig.find("e#") != string::npos) { states.at(2) = +1; }
	if (keysig.find("b#") != string::npos) { states.at(6) = +1; }
	if (keysig.find("b-") != string::npos) { states.at(6) = -1; }
	if (keysig.find("e-") != string::npos) { states.at(2) = -1; }
	if (keysig.find("a-") != string::npos) { states.at(5) = -1; }
	if (keysig.find("d-") != string::npos) { states.at(1) = -1; }
	if (keysig.find("g-") != string::npos) { states.at(4) = -1; }
	if (keysig.find("c-") != string::npos) { states.at(0) = -1; }
	if (keysig.find("f-") != string::npos) { states.at(3) = -1; }
}



//////////////////////////////
//
// HumdrumFileContent::resetDiatonicStatesWithKeySignature -- Only used in
//     HumdrumFileContent::analyzeKernAccidentals().  Resets the accidental
//     states for notes
//

void HumdrumFileContent::resetDiatonicStatesWithKeySignature(vector<int>&
		states, vector<int>& signature) {
	for (int i=0; i<(int)states.size(); i++) {
		states[i] = signature[i % 7];
	}
}





//////////////////////////////
//
// HumdrumFileContent::analyzeBarlines --
//

void HumdrumFileContent::analyzeBarlines(void) {
	if (m_analyses.m_barlines_analyzed) {
		// Maybe allow forcing reanalysis.
		return;
	}
	m_analyses.m_barlines_analyzed = true;
	m_analyses.m_barlines_different = false;

	string baseline;
	string comparison;
	bool baseQ;

	HumdrumFileContent& infile = *this;
	for (int i=0; i<infile.getLineCount(); i++) {
		if (!infile[i].isBarline()) {
			continue;
		}
		bool allSame = true;
		int fieldcount = infile[i].getFieldCount();
		if (fieldcount <= 1) {
			continue;
		}
		baseQ = false;
		baseline = "";
		comparison = "";
		for (int j=0; j<infile[i].getFieldCount(); j++) {
			HTp token = infile[i].token(j);
			int subtrack = token->getSubtrack();
			if (subtrack > 1) {
				// ignore secondary barlines in subspines.
				continue;
			}
			if (!token->isStaff()) {
				// don't check non-staff barlines
				continue;
			}
			if (!baseQ) {
				baseline = "";
				for (int k=0; k<(int)token->size(); k++) {
					if (isdigit(token->at(k))) {
						// ignore barnumbers
						// maybe ignore fermatas
						continue;
					}
					if (token->at(k) == ';') {
						// ignore fermatas in comparison
						continue;
					}
					baseline += token->at(k);
				}
				baseQ = true;
			} else {
				comparison = "";
				for (int k=0; k<(int)token->size(); k++) {
					if (isdigit(token->at(k))) {
						// ignore barnumbers;
						// maybe ignore fermatas
						continue;
					}
					if (token->at(k) == ';') {
						// ignore fermatas in comparison
						continue;
					}
					comparison += token->at(k);
				}
				if (comparison != baseline) {
					allSame = false;
					break;
				}
			}
		}

		if (hasDataStraddle(i)) {
				infile[i].setValue("auto", "straddlingData", 1);
		} else {
				infile[i].setValue("auto", "straddlingData", 0);
		}

		if (!allSame) {
			infile[i].setValue("auto", "barlinesDifferent", 1);
			m_analyses.m_barlines_different = true;
		} else {
			infile[i].setValue("auto", "barlinesDifferent", 0);
		}
	}
}



//////////////////////////////
//
// HumdrumFileContent::hasDataStraddle -- Returns true if the next
//    data line after a barline has null tokens on isStaff() tokens.
//    If there are no data lines after the barline, then it will
//    return false;
//

bool HumdrumFileContent::hasDataStraddle(int line) {
	HumdrumFileContent& infile = *this;
	if (!infile[line].isBarline()) {
		return false;
	}
	for (int i=line+1; i<infile.getLineCount(); i++) {
		if (infile[i].isInterpretation()) {
			HTp token = infile.token(i, 0);
			if (*token == "*-") {
				return false;
			}
		}
		if (!infile[i].isData()) {
			continue;
		}
		if (infile[i].isGraceLine()) {
			continue;
		}

		for (int j=0; j<infile[i].getFieldCount(); j++) {
			HTp token = infile.token(i, j);
			if (!token->isStaff()) {
				continue;
			}
			if (token->isNull()) {
				return true;
			}
		}
		break;
	}
	return false;
}



//////////////////////////////
//
// HumdrumFileContent::hasDifferentBarlines --
//

bool HumdrumFileContent::hasDifferentBarlines(void) {
	if (!m_analyses.m_barlines_analyzed) {
		analyzeBarlines();
	}
	return m_analyses.m_barlines_different;
}







//////////////////////////////
//
// HumdrumFileContent::analyzeBeams -- Link start and ends of
//    beams to each other.
//

bool HumdrumFileContent::analyzeBeams(void) {
	if (m_analyses.m_beams_analyzed) {
		return false;
	}
	m_analyses.m_beams_analyzed = true;
	bool output = true;
	output &= analyzeKernBeams();
	output &= analyzeMensBeams();
	return output;
}



//////////////////////////////
//
// HumdrumFileContent::analyzeMensBeams -- Link start and ends of
//    beams to each other.  They are the same as **kern, so borrowing
//    analyzeKernBeams to do the analysis.
//

bool HumdrumFileContent::analyzeMensBeams(void) {
	vector<HTp> beamstarts;
	vector<HTp> beamends;

	vector<HTp> l;
	vector<pair<HTp, HTp>> labels; // first is previous label, second is next label
	HumdrumFileBase& infile = *this;
	labels.resize(infile.getLineCount());
	l.resize(infile.getLineCount());
	for (int i=0; i<infile.getLineCount(); i++) {
		labels[i].first = NULL;
		labels[i].second = NULL;
		l[i] = NULL;
	}
	for (int i=0; i<infile.getLineCount(); i++) {
		if (!infile[i].isInterpretation()) {
			continue;
		}
		HTp token = infile.token(i, 0);
		if ((token->compare(0, 2, "*>") == 0) && (token->find("[") == std::string::npos)) {
			l[i] = token;
		}
	}
	HTp current = NULL;
	for (int i=0; i<infile.getLineCount(); i++) {
		if (l[i] != NULL) {
			current = l[i];
		}
		labels[i].first = current;
	}
	current = NULL;
	for (int i=infile.getLineCount() - 1; i>=0; i--) {
		if (l[i] != NULL) {
			current = l[i];
		}
		labels[i].second = current;
	}

	vector<int> endings(infile.getLineCount(), 0);
	int ending = 0;
	for (int i=0; i<(int)endings.size(); i++) {
		if (l[i]) {
			char lastchar = l[i]->back();
			if (isdigit(lastchar)) {
				ending = lastchar - '0';
			} else {
				ending = 0;
			}
		}
		endings[i] = ending;
	}

	vector<HTp> mensspines;
	getSpineStartList(mensspines, "**mens");
	bool output = true;
	string linkSignifier = m_signifiers.getKernLinkSignifier();
	for (int i=0; i<(int)mensspines.size(); i++) {
		output = output && analyzeKernBeams(mensspines[i], beamstarts, beamends, labels, endings, linkSignifier);
	}
	createLinkedBeams(beamstarts, beamends);
	return output;
}



//////////////////////////////
//
// HumdrumFileContent::analyzeKernBeams -- Link start and ends of
//    beams to each other.
//

bool HumdrumFileContent::analyzeKernBeams(void) {
	vector<HTp> beamstarts;
	vector<HTp> beamends;

	vector<HTp> l;
	vector<pair<HTp, HTp>> labels; // first is previous label, second is next label
	HumdrumFileBase& infile = *this;
	labels.resize(infile.getLineCount());
	l.resize(infile.getLineCount());
	for (int i=0; i<infile.getLineCount(); i++) {
		labels[i].first = NULL;
		labels[i].second = NULL;
		l[i] = NULL;
	}
	for (int i=0; i<infile.getLineCount(); i++) {
		if (!infile[i].isInterpretation()) {
			continue;
		}
		HTp token = infile.token(i, 0);
		if ((token->compare(0, 2, "*>") == 0) && (token->find("[") == std::string::npos)) {
			l[i] = token;
		}
	}
	HTp current = NULL;
	for (int i=0; i<infile.getLineCount(); i++) {
		if (l[i] != NULL) {
			current = l[i];
		}
		labels[i].first = current;
	}
	current = NULL;
	for (int i=infile.getLineCount() - 1; i>=0; i--) {
		if (l[i] != NULL) {
			current = l[i];
		}
		labels[i].second = current;
	}

	vector<int> endings(infile.getLineCount(), 0);
	int ending = 0;
	for (int i=0; i<(int)endings.size(); i++) {
		if (l[i]) {
			char lastchar = l[i]->back();
			if (isdigit(lastchar)) {
				ending = lastchar - '0';
			} else {
				ending = 0;
			}
		}
		endings[i] = ending;
	}

	vector<HTp> kernspines;
	getSpineStartList(kernspines, "**kern");
	bool output = true;
	string linkSignifier = m_signifiers.getKernLinkSignifier();
	for (int i=0; i<(int)kernspines.size(); i++) {
		output = output && analyzeKernBeams(kernspines[i], beamstarts, beamends, labels, endings, linkSignifier);
	}

	createLinkedBeams(beamstarts, beamends);
	return output;
}


bool HumdrumFileContent::analyzeKernBeams(HTp spinestart,
		vector<HTp>& linkstarts, vector<HTp>& linkends, vector<pair<HTp, HTp>>& labels,
		vector<int>& endings, const string& linksig) {

	// linked beams handled separately, so generate an ignore sequence:
	string ignorebegin = linksig + "L";
	string ignoreend = linksig + "J";

	// tracktokens == the 2-D data list for the track,
	// arranged in layers with the second dimension.
	vector<vector<HTp> > tracktokens;
	this->getTrackSeq(tracktokens, spinestart, OPT_DATA | OPT_NOEMPTY);
	// printSequence(tracktokens);

	// beamopens == list of beam openings for each track and elision level
	// first dimension: elision level
	// second dimension: track number
	vector<vector<vector<HTp>>> beamopens;

	beamopens.resize(4); // maximum of 4 elision levels
	for (int i=0; i<(int)beamopens.size(); i++) {
		beamopens[i].resize(8);  // maximum of 8 layers
	}

	int opencount = 0;
	int closecount = 0;
	int elision = 0;
	HTp token;
	for (int row=0; row<(int)tracktokens.size(); row++) {
		for (int track=0; track<(int)tracktokens[row].size(); track++) {
			token = tracktokens[row][track];
			if (!token->isData()) {
				continue;
			}
			if (token->isNull()) {
				continue;
			}
			opencount = (int)count(token->begin(), token->end(), 'L');
			closecount = (int)count(token->begin(), token->end(), 'J');

			for (int i=0; i<closecount; i++) {
				bool isLinked = isLinkedBeamEnd(token, i, ignoreend);
				if (isLinked) {
					linkends.push_back(token);
					continue;
				}
				elision = token->getBeamEndElisionLevel(i);
				if (elision < 0) {
					continue;
				}
				if (beamopens[elision][track].size() > 0) {
					linkBeamEndpoints(beamopens[elision][track].back(), token);
					// remove beam opening from buffer
					beamopens[elision][track].pop_back();
				} else {
					// No starting beam marker to match to this beam end in the
					// given track.
					// search for an open beam in another track:
					bool found = false;
					for (int itrack=0; itrack<(int)beamopens[elision].size(); itrack++) {
						if (beamopens[elision][itrack].size() > 0) {
							linkBeamEndpoints(beamopens[elision][itrack].back(), token);
							// remove beam opening from buffer
							beamopens[elision][itrack].pop_back();
							found = true;
							break;
						}
					}
					if (!found) {
						int lineindex = token->getLineIndex();
						int endnum = endings[lineindex];
						int pindex = -1;
						if (labels[lineindex].first) {
							pindex = labels[lineindex].first->getLineIndex();
							pindex--;
						}
						int endnumpre = -1;
						if (pindex >= 0) {
							endnumpre = endings[pindex];
						}

						if ((endnumpre > 0) && (endnum > 0) && (endnumpre != endnum)) {
							// This is a beam in an ending that start at the start of an ending.
							HumNum duration = token->getDurationFromStart();
							if (labels[token->getLineIndex()].first) {
								duration -= labels[token->getLineIndex()].first->getDurationFromStart();
							}
							token->setValue("auto", "endingBeamBack", "true");
							token->setValue("auto", "beamSide", "stop");
							token->setValue("auto", "beamDuration",
								token->getDurationToEnd());
						} else {
							// This is a beam closing that does not have a matching opening.
							token->setValue("auto", "hangingBeam", "true");
							token->setValue("auto", "beamSide", "stop");
							token->setValue("auto", "beamOpenIndex", to_string(i));
							token->setValue("auto", "beamDuration",
								token->getDurationToEnd());
						}
					}
				}
			}

			for (int i=0; i<opencount; i++) {
				bool isLinked = isLinkedBeamBegin(token, i, ignorebegin);
				if (isLinked) {
					linkstarts.push_back(token);
					continue;
				}
				elision = token->getBeamStartElisionLevel(i);
				if (elision < 0) {
					continue;
				}
				beamopens[elision][track].push_back(token);
			}
		}
	}

	// Mark un-closed beam starts:
	for (int i=0; i<(int)beamopens.size(); i++) {
		for (int j=0; j<(int)beamopens[i].size(); j++) {
			for (int k=0; k<(int)beamopens[i][j].size(); k++) {
				beamopens[i][j][k]->setValue("", "auto", "hangingBeam", "true");
				beamopens[i][j][k]->setValue("", "auto", "beamSide", "start");
				beamopens[i][j][k]->setValue("", "auto", "beamDuration",
						beamopens[i][j][k]->getDurationFromStart());
			}
		}
	}

	return true;
}



//////////////////////////////
//
// HumdrumFileContent::createLinkedBeams --  Currently assume that
//    start and ends are matched.
//

void HumdrumFileContent::createLinkedBeams(vector<HTp>& linkstarts, vector<HTp>& linkends) {
	int max = (int)linkstarts.size();
	if ((int)linkends.size() < max) {
		max = (int)linkends.size();
	}
	if (max == 0) {
		// nothing to do
		return;
	}

	for (int i=0; i<max; i++) {
		linkBeamEndpoints(linkstarts[i], linkends[i]);
	}
}



//////////////////////////////
//
// HumdrumFileContent::isLinkedBeamEnd --
//

bool HumdrumFileContent::isLinkedBeamEnd(HTp token, int index, const string& pattern) {
	if (pattern.size() <= 1) {
		return false;
	}
	int counter = -1;
	for (int i=0; i<(int)token->size(); i++) {
		if (token->at(i) == 'J') {
			counter++;
		}
		if (i == 0) {
			// Can't have linked beam at starting index in string.
			continue;
		}
		if (counter != index) {
			continue;
		}

		int startindex = i - (int)pattern.size() + 1;
		auto loc = token->find(pattern, startindex);
		if ((loc != std::string::npos) && ((int)loc == startindex)) {
			return true;
		}
		return false;
	}
	return false;
}



//////////////////////////////
//
// HumdrumFileContent::isLinkedBeamBegin --
//

bool HumdrumFileContent::isLinkedBeamBegin(HTp token, int index, const string& pattern) {
	if (pattern.size() <= 1) {
		return false;
	}
	int counter = -1;
	for (int i=0; i<(int)token->size(); i++) {
		if (token->at(i) == 'L') {
			counter++;
		}
		if (i == 0) {
			continue;
		}
		if (counter != index) {
			continue;
		}
		if (token->find(pattern, i - (int)pattern.size() + 1) != std::string::npos) {
			return true;
		}
		return false;
	}
	return false;
}



//////////////////////////////
//
// HumdrumFileContent::linkBeamEndpoints --  Allow up to two beam starts/ends
//      on a note.
//

void HumdrumFileContent::linkBeamEndpoints(HTp beamstart, HTp beamend) {
	string durtag = "beamDuration";
	string endtag = "beamEndId";
	string starttag = "beamStartId";
	string beamstartnumbertag = "beamStartNumber";
	string beamendnumbertag = "beamEndNumber";

	int beamStartCount = beamstart->getValueInt("auto", "beamStartCount");
	int opencount = (int)count(beamstart->begin(), beamstart->end(), 'L');
	beamStartCount++;
	int openEnumeration = opencount - beamStartCount + 1;

	if (openEnumeration > 1) {
		endtag += to_string(openEnumeration);
		durtag += to_string(openEnumeration);
		beamendnumbertag += to_string(openEnumeration);
	}

	int beamEndNumber = beamend->getValueInt("auto", "beamEndCount");
	beamEndNumber++;
	int closeEnumeration = beamEndNumber;
	if (closeEnumeration > 1) {
		starttag += to_string(closeEnumeration);
		beamstartnumbertag += to_string(closeEnumeration);
	}

	HumNum duration = beamend->getDurationFromStart()
			- beamstart->getDurationFromStart();

	HumNum durToBar = beamstart->getDurationToBarline();

	if (duration >= durToBar) {
		beamstart->setValue("auto", "beamSpanStart", 1);
		beamend->setValue("auto", "beamSpanEnd", 1);
		markBeamSpanMembers(beamstart, beamend);
	}

	beamstart->setValue("auto", endtag,            beamend);
	beamstart->setValue("auto", "id",              beamstart);
	beamstart->setValue("auto", beamendnumbertag,  closeEnumeration);
	beamstart->setValue("auto", durtag,            duration);
	beamstart->setValue("auto", "beamStartCount",  beamStartCount);

	beamend->setValue("auto", starttag, beamstart);
	beamend->setValue("auto", "id", beamend);
	beamend->setValue("auto", beamstartnumbertag, openEnumeration);
	beamend->setValue("auto", "beamEndCount",  beamEndNumber);
}



//////////////////////////////
//
// HumdrumFileContent::markBeamSpanMembers --
//

void HumdrumFileContent::markBeamSpanMembers(HTp beamstart, HTp beamend) {
	int endindex = beamend->getLineIndex();
	beamstart->setValue("auto", "inBeamSpan", beamstart);
	beamend->setValue("auto", "inBeamSpan", beamstart);
	HTp current = beamstart->getNextToken();;
	while (current) {
      int line = current->getLineIndex();
		if (line > endindex) {
			// terminate search for end if getting lost
			break;
		}
		if (current == beamend) {
			break;
		}
		if (!current->isData()) {
			current = current->getNextToken();
			continue;
		}
		if (current->isNull()) {
			current = current->getNextToken();
			continue;
		}
		if (current->getDuration() == 0) {
			// ignore grace notes
			current = current->getNextToken();
			continue;
		}
		current->setValue("auto", "inBeamSpan", beamstart);
		current = current->getNextToken();
	}
}





//////////////////////////////
//
// HumdrumFileContent::doHandAnalysis -- Returns true if any **kern spine has hand markup.
//    default value:
//         attacksOnlyQ = false;
//

bool HumdrumFileContent::doHandAnalysis(bool attacksOnlyQ) {
	HumdrumFileContent& infile = *this;
	vector<HTp> kstarts = infile.getKernSpineStartList();
	bool status = 0;
	for (int i=0; i<(int)kstarts.size(); i++) {
		status |= doHandAnalysis(kstarts[i], attacksOnlyQ);
	}
	return status;
}


bool HumdrumFileContent::doHandAnalysis(HTp startSpine, bool attacksOnlyQ) {
	if (!startSpine->isKern()) {
		return false;
	}
	bool output = false;
	vector<string> states(20);
	states[0] = "none";
	HTp current = startSpine->getNextToken();
	while (current) {
		int subtrack = current->getSubtrack();
		if (subtrack == 0) {
			for (int i=1; i<(int)states.size(); i++) {
				states[i] = states[0];
			}
		}

		if (current->isInterpretation()) {
			if (subtrack == 0) {
				if (*current == "*LH") {
					states[0] = "LH";
					output = true;
					for (int i=1; i<(int)states.size(); i++) {
						states[i] = states[0];
					}
				} else if (*current == "*RH") {
					states[0] = "RH";
					output = true;
					for (int i=1; i<(int)states.size(); i++) {
						states[i] = states[0];
					}
				}
			} else {
				int ttrack = current->getTrack();
				HTp c2 = current;
				while (c2) {
					int track = c2->getTrack();
					if (track != ttrack) {
						break;
					}
					int sub = c2->getSubtrack();
					if (*c2 == "*LH") {
						states.at(sub) = "LH";
						if (sub == 1) {
							states.at(0) = "LH";
						}
					} else if (*c2 == "*RH") {
						states.at(sub) = "RH";
						if (sub == 1) {
							states.at(0) = "RH";
						}
					}
					c2 = c2->getNextFieldToken();
				}
			}
		}

		if (!current->isData()) {
			current = current->getNextToken();
			continue;
		}

		if (subtrack == 0) {
			// no subspines
			if (attacksOnlyQ && current->isNoteAttack()) {
				current->setValue("auto", "hand", states[0]);
			} else {
				current->setValue("auto", "hand", states[0]);
			}
		} else {
			int ttrack = current->getTrack();
			HTp c2 = current;
			while (c2) {
				int track = c2->getTrack();
				if (track != ttrack) {
					break;
				}
				if (attacksOnlyQ && !c2->isNoteAttack()) {
					c2 = c2->getNextFieldToken();
					continue;
				}
				int sub = c2->getSubtrack();
				if (states.at(sub).empty()) {
					c2->setValue("auto", "hand", states.at(0));
				} else {
					c2->setValue("auto", "hand", states.at(sub));
				}
				c2 = c2->getNextFieldToken();
			}
		}
		current = current->getNextToken();
		continue;
	}
	if (output) {
		startSpine->setValue("auto", "hand", 1);
	}
	return output;
}





//////////////////////////////
//
// HumdrumFileContent::getTrackToKernIndex -- return a list indexed by file track
//     numbers (first entry not used), with non-zero values in the vector being
//     the **kern index with the given track number.
//

vector<int> HumdrumFileContent::getTrackToKernIndex(void) {
	HumdrumFileContent& infile = *this;
	vector<HTp> ktracks = infile.getKernSpineStartList();
	vector<int> trackToKernIndex(infile.getMaxTrack() + 1, -1);
	for (int i=0; i<(int)ktracks.size(); i++) {
		int track = ktracks[i]->getTrack();
		trackToKernIndex[track] = i;
	}
	return trackToKernIndex;
}



//////////////////////////////
//
// HumdrumFileStructure::getMetricLevels -- Each line in the output
//     vector matches to the line of the metric analysis data.
//     undefined is the value to represent undefined analysis data
//     (for non-data spines).
//
//     default value: track = 0: 0 means use the time signature
//         of the first **kern spines in the file; otherwise, use the
//         time signatures found in the given track (indexed from 1
//         for the first spine on a line).
//     default value: undefined = NAN: The value to use for un-analyzed lines.
//

void HumdrumFileContent::getMetricLevels(vector<double>& output,
		int track, double undefined) {

	HumdrumFileStructure& infile = *this;
	int lineCount = infile.getLineCount();
	output.resize(lineCount);
	fill(output.begin(), output.end(), undefined);
	vector<HTp> kernspines = infile.getKernSpineStartList();
	if ((track == 0) && (kernspines.size() > 0)) {
		track = kernspines[0]->getTrack();
	}
	if (track == 0) {
		track = 1;
	}

	int top = 1;                // top number of time signature (0 for no meter)
	int bot = 4;                // bottom number of time signature
	bool compoundQ = false;     // test for compound meters, such as 6/8
	HumNum beatdur(1 * 4, bot); // duration of a beat in the measure
	HumNum measurepos;          // quarter notes from the start of barline
	HumNum combeatdur;          // for adjusting beat level in compound meters
	HumNum commeasurepos;       // for adjusting beat level in compound meters

	for (int i=0; i<lineCount; i++) {
		if (infile[i].isInterpretation()) {
			// check for time signature:
			HumdrumLine& line = *infile.getLine(i);
			for (int j=0; j<line.getFieldCount(); j++) {
				int ttrack = line.token(j)->getTrack();
				if (ttrack != track) {
					continue;
				}
				if (sscanf(infile.token(i,j)->c_str(), "*M%d/%d", &top, &bot)) {
					beatdur.setValue(1*4, bot); // converted to quarter-note units
					if ((top % 3 == 0) && (top != 3)) {
						// if meter top is a multiple of 3 but not 3, then compound
						// such as 6/8, 9/8, 6/4, but not 3/8, 3/4.
						compoundQ = true;
						beatdur *= 3;
					} else {
						compoundQ = false;
					}
					break;
				}
			}
		}
		if (!infile[i].isData()) {
				continue;
		}

		measurepos = infile[i].getDurationFromBarline();
		// Might want to handle cases where the time signature changes in
		// the middle or a measure...
		measurepos /= beatdur;
		int denominator = measurepos.getDenominator();
		if (compoundQ) {
			output[i] = Convert::nearIntQuantize(log(denominator) / log(3.0));
			if ((output[i] != 0.0) && (output[i] != 1.0)) {
				// if not the beat or first level, then calculate
				// levels above level 1.  In 6/8 this means
				// to move the 8th note level to be the "beat"
				// and then use binary levels for rhythmic levels
				// smaller than a beat.
				combeatdur.setValue(4,bot);
				commeasurepos = infile[i].getDurationFromBarline() / combeatdur;
				denominator = commeasurepos.getDenominator();
				output[i] = 1.0 + log(denominator)/log(2.0);
			}
		} else {
			output[i] = Convert::nearIntQuantize(log(denominator) / log(2.0));
		}
	}
}





/////////////////////////////////
//
// HumdrumFileContent::fillMidiInfo -- Create a data structure that
//     organizes tokens by track/midi note number.
//     Input object to fill is firsted indexed the **kern track
//     number, then by MIDI note number, and then an array of pairs
//     <HTp, int> where int is the subtoken number in the token
//     for the given MIDI note.


void HumdrumFileContent::fillMidiInfo(vector<vector<vector<pair<HTp, int>>>>& trackMidi) {
	HumdrumFileContent& infile = *this;
	vector<HTp> ktracks = infile.getKernSpineStartList();
	trackMidi.clear();
	trackMidi.resize(ktracks.size());
	for (int i=0; i<(int)trackMidi.size(); i++) {
		trackMidi[i].resize(128);   // 0 used for rests
	}
	// each entry is trackMidi[track][key] is an array of <token, subtoken> pairs.
	// using trackMidi[track][0] for rests;
	
	vector<int> trackToKernIndex = infile.getTrackToKernIndex();

	for (int i=0; i<infile.getStrandCount(); i++) {
		HTp sstart = infile.getStrandStart(i);
		if (!sstart->isKern()) {
			continue;
		}

		int track = sstart->getTrack();
		HTp send = infile.getStrandEnd(i);
		processStrandNotesForMidi(sstart, send, trackMidi[trackToKernIndex[track]]);
	}
}



/////////////////////////////////
//
// HumdrumFileContent::processStrandNotesForMidi -- store strand tokens/subtokens by MIDI note
//     in the midi track entry.
//
//     First index if track info is the MIDI note number, second is a list
//     of tokens for that note number, with the second value of the pair
//     giving the subtoken index of the note in the token.
//

void HumdrumFileContent::processStrandNotesForMidi(HTp sstart, HTp send, vector<vector<pair<HTp, int>>>& trackInfo) {
	HTp current = sstart->getNextToken();
	while (current && (current != send)) {
		if (!current->isData() || current->isNull()) {
			current = current->getNextToken();
			continue;
		}
		vector<string> subtokens = current->getSubtokens();
		for (int i=0; i<(int)subtokens.size(); i++) {
			if (subtokens[i] == ".") {
				// something strange happened (no null tokens expected)
				continue;
			}
			if (subtokens[i].find("r") != string::npos) {
				// rest, so store in MIDI[0]
				trackInfo.at(0).emplace_back(current, 0);
			} else if (subtokens[i].find("R") != string::npos) {
				// unpitched or quasi-pitched note, so store in MIDI[0]
				trackInfo.at(0).emplace_back(current, 0);
			} else {
				int keyno = Convert::kernToMidiNoteNumber(subtokens[i]);
				if ((keyno >= 0) && (keyno < 128)) {
					trackInfo.at(keyno).emplace_back(current, i);
				}
			}
		}
		current = current->getNextToken();
	}
}





//////////////////////////////
//
// HumdrumFileContent::analyzeCrossStaffStemDirections -- Calculate stem directions
//    for notes that are cross-staff, and the notes in the presence of cross-staff
//    notes.
//

void HumdrumFileContent::analyzeCrossStaffStemDirections(void) {
	string above = this->getKernAboveSignifier();
	string below = this->getKernBelowSignifier();

	if (above.empty() && below.empty()) {
		// no cross staff notes present in data
		return;
	}

	vector<HTp> kernstarts = getKernSpineStartList();
	for (int i=0; i<(int)kernstarts.size(); i++) {
		analyzeCrossStaffStemDirections(kernstarts[i]);
	}
}



//////////////////////////////
//
// HumdrumFileContent::analyzeCrossStaffStemDirections -- Check for cross-staff
//     notes, and assign stems if they do not have any.  Also assign stems to
//     notes in the target directory if there is only one layer active on that staff.
//

void HumdrumFileContent::analyzeCrossStaffStemDirections(HTp kernstart) {
	if (!kernstart) {
		return;
	}
	if (!kernstart->isKern()) {
		return;
	}
	string above = this->getKernAboveSignifier();
	string below = this->getKernBelowSignifier();
	if (above.empty() && below.empty()) {
		// no cross staff notes present in data
		return;
	}

	HTp current = kernstart;
	while (current) {
		if (current->isData()) {
			checkCrossStaffStems(current, above, below);
		}
		current = current->getNextToken();
	}
}



//////////////////////////////
//
// HumdrumFileContent::checkCrossStaffStems -- Check all notes in all
//     sub-spines of the current token (which should be in the top layer)
//     for cross-staff assignment.
//

void HumdrumFileContent::checkCrossStaffStems(HTp token, string& above, string& below) {
	int track = token->getTrack();

	HTp current = token;
	while (current) {
		int ttrack = current->getTrack();
		if (ttrack != track) {
			break;
		}
		checkDataForCrossStaffStems(current, above, below);
		current = current->getNextFieldToken();
	}
}



//////////////////////////////
//
// HumdrumFileContent::checkDataForCrossStaffStems -- Check a note or chord for
//    cross staff
//

void HumdrumFileContent::checkDataForCrossStaffStems(HTp token, string& above, string& below) {
	if (token->isNull()) {
		return;
	}
	if (token->isRest()) {
		// deal with cross-staff rests later
		return;
	}

	if (token->find('/') != std::string::npos) {
		// has a stem-up marker, so do not try to adjust stems;
		return;
	}

	if (token->find('\\') != std::string::npos) {
		// has a stem-down marker, so do not try to adjust stems;
		return;
	}


	HumRegex hre;
	bool hasaboveQ = false;
	bool hasbelowQ = false;

	if (!above.empty()) {
		string searchstring = "[A-Ga-g]+[#n-]*" + above;
		if (hre.search(*token, searchstring)) {
			// note/chord has staff-above signifier
			hasaboveQ = true;
		}
	}

	if (!below.empty()) {
		string searchstring = "[A-Ga-g]+[#n-]*" + below;
		if (hre.search(*token, searchstring)) {
			// note/chord has staff-below signifier
			hasbelowQ = true;
		}
	}

	if (!(hasaboveQ || hasbelowQ)) {
		// no above/below signifier, so nothing to do
		return;
	}
	if (hasaboveQ && hasbelowQ) {
		// strange complication of above and below, so ignore
		return;
	}

	if (hasaboveQ) {
		prepareStaffAboveNoteStems(token);
	} else if (hasbelowQ) {
		prepareStaffBelowNoteStems(token);
	}
}



//////////////////////////////
//
// HumdrumFileContent::prepareStaffAboveNoteStems --
//

void HumdrumFileContent::prepareStaffAboveNoteStems(HTp token) {
	token->setValue("auto", "stem.dir", "-1");
	int track = token->getTrack();
	HTp curr = token->getNextFieldToken();
	int ttrack;

	// Find the next higher **kern spine (if any):
	while (curr) {
		ttrack = curr->getTrack();
		if (!curr->isKern()) {
			curr = curr->getNextFieldToken();
			continue;
		}
		if (ttrack == track) {
			curr = curr->getNextFieldToken();
			continue;
		}
		// is kern data and in a different spine
		break;
	}
	if (!curr) {
		// no higher staff of **kern data.
		return;
	}
	if (!curr->isKern()) {
		// something strange happened
		return;
	}
	HumNum endtime = token->getDurationFromStart() + token->getDuration();
	HTp curr2 = curr;
	while (curr2) {
		if (curr2->getDurationFromStart() >= endtime) {
			// exceeded the duration of the cross-staff note, so stop looking
			break;
		}
		if (!curr2->isData()) {
			// ignore non-data tokens
			curr2 = curr2->getNextToken();
			continue;
		}
		if (curr2->isNull()) {
			curr2 = curr2->getNextToken();
			continue;
		}
		if (curr2->isRest()) {
			// ignore rests
			curr2 = curr2->getNextToken();
			continue;
		}
		if (!curr2->isNote()) {
			curr2 = curr2->getNextToken();
			continue;
		}
		if ((curr2->find('/') != std::string::npos) || (curr2->find('\\') != std::string::npos)) {
			// the note/chord has a stem direction, so ignore it
			curr2 = curr2->getNextToken();
			continue;
		}
		int layer = curr2->getSubtrack();
		// layer != 0 means there is more than one active layer at this point in the
		// above staff.  If so, then do not assign any stem directions.
		if (layer != 0) {
			curr2 = curr2->getNextToken();
			continue;
		}
		// set the stem to up for the current note/chord
		curr2->setValue("auto", "stem.dir", "1");
		curr2 = curr2->getNextToken();
	}
}



//////////////////////////////
//
// HumdrumFileContent::prepareStaffBelowNoteStems --
//

void HumdrumFileContent::prepareStaffBelowNoteStems(HTp token) {
	token->setValue("auto", "stem.dir", "1");
	int track = token->getTrack();
	HTp curr = token->getPreviousFieldToken();
	int ttrack;

	// Find the next lower **kern spine (if any):
	while (curr) {
		ttrack = curr->getTrack();
		if (!curr->isKern()) {
			curr = curr->getPreviousFieldToken();
			continue;
		}
		if (ttrack == track) {
			curr = curr->getPreviousFieldToken();
			continue;
		}
		// is kern data and in a different spine
		break;
	}
	if (!curr) {
		// no lower staff of **kern data.
		return;
	}
	if (!curr->isKern()) {
		// something strange happened
		return;
	}

	// Find the first subtrack of the identified spine
	int targettrack = curr->getTrack();
	while (curr) {
		HTp ptok = curr->getPreviousFieldToken();
		if (!ptok) {
			break;
		}
		ttrack = ptok->getTrack();
		if (targettrack != ttrack) {
			break;
		}
		curr = ptok;
		ptok = curr->getPreviousToken();
	}
	// Should now be at the first subtrack of the target staff.

	HumNum endtime = token->getDurationFromStart() + token->getDuration();
	HTp curr2 = curr;
	while (curr2) {
		if (curr2->getDurationFromStart() >= endtime) {
			// exceeded the duration of the cross-staff note, so stop looking
			break;
		}
		if (!curr2->isData()) {
			// ignore non-data tokens
			curr2 = curr2->getNextToken();
			continue;
		}
		if (curr2->isNull()) {
			curr2 = curr2->getNextToken();
			continue;
		}
		if (curr2->isRest()) {
			// ignore rests
			curr2 = curr2->getNextToken();
			continue;
		}
		if (!curr2->isNote()) {
			curr2 = curr2->getNextToken();
			continue;
		}
		if ((curr2->find('/') != std::string::npos) || (curr2->find('\\') != std::string::npos)) {
			// the note/chord has a stem direction, so ignore it
			curr2 = curr2->getNextToken();
			continue;
		}
		int layer = curr2->getSubtrack();
		// layer != 0 means there is more than one active layer at this point in the
		// above staff.  If so, then do not assign any stem directions.
		if (layer != 0) {
			curr2 = curr2->getNextToken();
			continue;
		}
		// set the stem to up for the current note/chord
		curr2->setValue("auto", "stem.dir", "-1");
		curr2 = curr2->getNextToken();
	}
}


//////////////////////////////
//
// HumdrumFileContent::getNoteCount -- Returns the number of notes in **kern spines.
//    could be expanded to **mens, and kern-like sorts of spines.  Also could be
//    expanded to all staff-like spines, or specific spines.
//

int HumdrumFileContent::getNoteCount(void) {
	HumdrumFileContent& infile = *this;
	int counter = 0;

	int scount = infile.getStrandCount();
	for (int i=0; i<scount; i++) {
		HTp sstart = infile.getStrandStart(i);
		if (!sstart->isKern()) {
			continue;
		}
		HTp send = infile.getStrandEnd(i);
		HTp current = sstart;
		while (current && (current != send)) {
			if (!current->isData()) {
				current = current->getNextToken();
				continue;
			}
			if (current->isNull()) {
				current = current->getNextToken();
				continue;
			}
			if (current->isRest()) {
				current = current->getNextToken();
				continue;
			}
			int subcount = current->getSubtokenCount();
			if (subcount == 1) {
				if (!current->isSecondaryTiedNote()) {
					counter++;
				}
			} else {
				vector<string> subtokens = current->getSubtokens();
				for (int i=0; i<(int)subtokens.size(); i++) {
					if (subtokens[i].find("_") != string::npos) {
						continue;
					}
					if (subtokens[i].find("]") != string::npos) {
						continue;
					}
					if (subtokens[i].find("r") != string::npos) {
						continue;
					}
					counter++;
				}
			}
			current = current->getNextToken();
		}
	}
	return counter;
}





//////////////////////////////
//
// HumdrumFileContent::analyzeOttavas --
//

void HumdrumFileContent::analyzeOttavas(void) {
	int tcount = getTrackCount();
	vector<int> activeOttava(tcount+1, 0);
	vector<int> octavestate(tcount+1, 0);
	for (int i=0; i<getLineCount(); i++) {
		HLp line = getLine(i);
		if (line->isInterpretation()) {
			int fcount = getLine(i)->getFieldCount();
			for (int j=0; j<fcount; j++) {
				HTp token = line->token(j);
				if (!token->isKern()) {
					continue;
				}
				int track = token->getTrack();
				if (*token == "*8va") {
					octavestate[track] = +1;
					activeOttava[track]++;
				} else if (*token == "*X8va") {
					octavestate[track] = 0;
					activeOttava[track]--;
				} else if (*token == "*8ba") {
					octavestate[track] = -1;
					activeOttava[track]++;
				} else if (*token == "*X8ba") {
					octavestate[track] = 0;
					activeOttava[track]--;
				} else if (*token == "*15ma") {
					octavestate[track] = +2;
					activeOttava[track]++;
				} else if (*token == "*X15ma") {
					octavestate[track] = 0;
					activeOttava[track]--;
				} else if (*token == "*15ba") {
					octavestate[track] = -2;
					activeOttava[track]++;
				} else if (*token == "*X15ba") {
					octavestate[track] = 0;
					activeOttava[track]--;
				}
			}
		}
		else if (line->isData()) {
			int fcount = getLine(i)->getFieldCount();
			for (int j=0; j<fcount; j++) {
				HTp token = line->token(j);
				if (!token->isKern()) {
					continue;
				}
				int track = token->getTrack();
				if (!activeOttava[track]) {
					continue;
				}
				if (octavestate[track] == 0) {
					continue;
				}
				if (token->isNull()) {
					continue;
				}
				if (token->isRest()) {
					// do not exclude rests, since the vertical placement
					// of the staff may need to be updated by the ottava mark.
				}
				token->setValue("auto", "ottava", to_string(octavestate[track]));
			}
		}
	}
}





//////////////////////////////
//
// HumdrumFileContent::analyzePhrasings -- Link start and ends of
//    phrases to each other.
//

bool HumdrumFileContent::analyzePhrasings(void) {
	if (m_analyses.m_phrases_analyzed) {
		return false;
	}
	m_analyses.m_phrases_analyzed = true;
	bool output = true;
	output &= analyzeKernPhrasings();
	return output;
}



//////////////////////////////
//
// HumdrumFileContent::analyzeKernPhrasings -- Link start and ends of
//    phrases to each other.
//

bool HumdrumFileContent::analyzeKernPhrasings(void) {
	vector<HTp> phrasestarts;
	vector<HTp> phraseends;

	vector<HTp> l;
	vector<pair<HTp, HTp>> labels; // first is previous label, second is next label
	HumdrumFileBase& infile = *this;
	labels.resize(infile.getLineCount());
	l.resize(infile.getLineCount());
	for (int i=0; i<infile.getLineCount(); i++) {
		labels[i].first = NULL;
		labels[i].second = NULL;
		l[i] = NULL;
	}
	for (int i=0; i<infile.getLineCount(); i++) {
		if (!infile[i].isInterpretation()) {
			continue;
		}
		HTp token = infile.token(i, 0);
		if ((token->compare(0, 2, "*>") == 0) && (token->find("[") == std::string::npos)) {
			l[i] = token;
		}
	}
	HTp current = NULL;
	for (int i=0; i<infile.getLineCount(); i++) {
		if (l[i] != NULL) {
			current = l[i];
		}
		labels[i].first = current;
	}
	current = NULL;
	for (int i=infile.getLineCount() - 1; i>=0; i--) {
		if (l[i] != NULL) {
			current = l[i];
		}
		labels[i].second = current;
	}

	vector<int> endings(infile.getLineCount(), 0);
	int ending = 0;
	for (int i=0; i<(int)endings.size(); i++) {
		if (l[i]) {
			char lastchar = l[i]->back();
			if (isdigit(lastchar)) {
				ending = lastchar - '0';
			} else {
				ending = 0;
			}
		}
		endings[i] = ending;
	}

	vector<HTp> kernspines;
	getSpineStartList(kernspines, "**kern");
	bool output = true;
	string linkSignifier = m_signifiers.getKernLinkSignifier();
	for (int i=0; i<(int)kernspines.size(); i++) {
		output = output && analyzeKernPhrasings(kernspines[i], phrasestarts, phraseends, labels, endings, linkSignifier);
	}

	createLinkedPhrasings(phrasestarts, phraseends);
	return output;
}


bool HumdrumFileContent::analyzeKernPhrasings(HTp spinestart,
		vector<HTp>& linkstarts, vector<HTp>& linkends, vector<pair<HTp, HTp>>& labels,
		vector<int>& endings, const string& linksig) {

	// linked phrases handled separately, so generate an ignore sequence:
	string ignorebegin = linksig + "{";
	string ignoreend = linksig + "}";

	// tracktokens == the 2-D data list for the track,
	// arranged in layers with the second dimension.
	vector<vector<HTp> > tracktokens;
	this->getTrackSeq(tracktokens, spinestart, OPT_DATA | OPT_NOEMPTY);
	// printSequence(tracktokens);

	// phraseopens == list of phrase openings for each track and elision level
	// first dimension: elision level
	// second dimension: track number
	vector<vector<vector<HTp>>> phraseopens;

	phraseopens.resize(4); // maximum of 4 elision levels
	for (int i=0; i<(int)phraseopens.size(); i++) {
		phraseopens[i].resize(8);  // maximum of 8 layers
	}

	int opencount = 0;
	int closecount = 0;
	int elision = 0;
	HTp token;
	for (int row=0; row<(int)tracktokens.size(); row++) {
		for (int track=0; track<(int)tracktokens[row].size(); track++) {
			token = tracktokens[row][track];
			if (!token->isData()) {
				continue;
			}
			if (token->isNull()) {
				continue;
			}
			opencount = (int)count(token->begin(), token->end(), '{');
			closecount = (int)count(token->begin(), token->end(), '}');

			for (int i=0; i<closecount; i++) {
				bool isLinked = isLinkedPhraseEnd(token, i, ignoreend);
				if (isLinked) {
					linkends.push_back(token);
					continue;
				}
				elision = token->getPhraseEndElisionLevel(i);
				if (elision < 0) {
					continue;
				}
				if (phraseopens[elision][track].size() > 0) {
					linkPhraseEndpoints(phraseopens[elision][track].back(), token);
					// remove phrase opening from buffer
					phraseopens[elision][track].pop_back();
				} else {
					// No starting phrase marker to match to this phrase end in the
					// given track.
					// search for an open phrase in another track:
					bool found = false;
					for (int itrack=0; itrack<(int)phraseopens[elision].size(); itrack++) {
						if (phraseopens[elision][itrack].size() > 0) {
							linkPhraseEndpoints(phraseopens[elision][itrack].back(), token);
							// remove phrase opening from buffer
							phraseopens[elision][itrack].pop_back();
							found = true;
							break;
						}
					}
					if (!found) {
						int lineindex = token->getLineIndex();
						int endnum = endings[lineindex];
						int pindex = -1;
						if (labels[lineindex].first) {
							pindex = labels[lineindex].first->getLineIndex();
							pindex--;
						}
						int endnumpre = -1;
						if (pindex >= 0) {
							endnumpre = endings[pindex];
						}

						if ((endnumpre > 0) && (endnum > 0) && (endnumpre != endnum)) {
							// This is a phrase in an ending that start at the start of an ending.
							HumNum duration = token->getDurationFromStart();
							if (labels[token->getLineIndex()].first) {
								duration -= labels[token->getLineIndex()].first->getDurationFromStart();
							}
							token->setValue("auto", "endingPhraseBack", "true");
							token->setValue("auto", "phraseSide", "stop");
							token->setValue("auto", "phraseDuration",
								token->getDurationToEnd());
						} else {
							// This is a phrase closing that does not have a matching opening.
							token->setValue("auto", "hangingPhrase", "true");
							token->setValue("auto", "phraseSide", "stop");
							token->setValue("auto", "phraseOpenIndex", to_string(i));
							token->setValue("auto", "phraseDuration",
								token->getDurationToEnd());
						}
					}
				}
			}

			for (int i=0; i<opencount; i++) {
				bool isLinked = isLinkedPhraseBegin(token, i, ignorebegin);
				if (isLinked) {
					linkstarts.push_back(token);
					continue;
				}
				elision = token->getPhraseStartElisionLevel(i);
				if (elision < 0) {
					continue;
				}
				phraseopens[elision][track].push_back(token);
			}
		}
	}

	// Mark un-closed phrase starts:
	for (int i=0; i<(int)phraseopens.size(); i++) {
		for (int j=0; j<(int)phraseopens[i].size(); j++) {
			for (int k=0; k<(int)phraseopens[i][j].size(); k++) {
				phraseopens[i][j][k]->setValue("", "auto", "hangingPhrase", "true");
				phraseopens[i][j][k]->setValue("", "auto", "phraseSide", "start");
				phraseopens[i][j][k]->setValue("", "auto", "phraseDuration",
						phraseopens[i][j][k]->getDurationFromStart());
			}
		}
	}

	return true;
}



//////////////////////////////
//
// HumdrumFileContent::createLinkedPhrasings --  Currently assume that
//    start and ends are matched.
//

void HumdrumFileContent::createLinkedPhrasings(vector<HTp>& linkstarts, vector<HTp>& linkends) {
	int max = (int)linkstarts.size();
	if ((int)linkends.size() < max) {
		max = (int)linkends.size();
	}
	if (max == 0) {
		// nothing to do
		return;
	}

	for (int i=0; i<max; i++) {
		linkPhraseEndpoints(linkstarts[i], linkends[i]);
	}
}



//////////////////////////////
//
// HumdrumFileContent::isLinkedPhraseEnd --
//

bool HumdrumFileContent::isLinkedPhraseEnd(HTp token, int index, const string& pattern) {
	if (pattern.size() <= 1) {
		return false;
	}
	int counter = -1;
	for (int i=0; i<(int)token->size(); i++) {
		if (token->at(i) == ')') {
			counter++;
		}
		if (i == 0) {
			// Can't have linked phrase at starting index in string.
			continue;
		}
		if (counter != index) {
			continue;
		}

		int startindex = i - (int)pattern.size() + 1;
		auto loc = token->find(pattern, startindex);
		if ((loc != std::string::npos) && ((int)loc == startindex)) {
			return true;
		}
		return false;
	}
	return false;
}



//////////////////////////////
//
// HumdrumFileContent::isLinkedPhraseBegin --
//

bool HumdrumFileContent::isLinkedPhraseBegin(HTp token, int index, const string& pattern) {
	if (pattern.size() <= 1) {
		return false;
	}
	int counter = -1;
	for (int i=0; i<(int)token->size(); i++) {
		if (token->at(i) == '(') {
			counter++;
		}
		if (i == 0) {
			continue;
		}
		if (counter != index) {
			continue;
		}
		if (token->find(pattern, i - (int)pattern.size() + 1) != std::string::npos) {
			return true;
		}
		return false;
	}
	return false;
}



//////////////////////////////
//
// HumdrumFileContent::linkPhraseEndpoints --  Allow up to two phrase starts/ends
//      on a note.
//

void HumdrumFileContent::linkPhraseEndpoints(HTp phrasestart, HTp phraseend) {
	string durtag = "phraseDuration";
	string endtag = "phraseEnd";
	int phraseEndCount = phrasestart->getValueInt("auto", "phraseEndCount");
	phraseEndCount++;
	if (phraseEndCount > 1) {
		endtag += to_string(phraseEndCount);
		durtag += to_string(phraseEndCount);
	}
	string starttag = "phraseStart";
	int phraseStartCount = phraseend->getValueInt("auto", "phraseStartCount");
	phraseStartCount++;
	if (phraseStartCount > 1) {
		starttag += to_string(phraseStartCount);
	}

	phrasestart->setValue("auto", endtag, phraseend);
	phrasestart->setValue("auto", "id", phrasestart);
	phraseend->setValue("auto", starttag, phrasestart);
	phraseend->setValue("auto", "id", phraseend);
	HumNum duration = phraseend->getDurationFromStart()
			- phrasestart->getDurationFromStart();
	phrasestart->setValue("auto", durtag, duration);
	phrasestart->setValue("auto", "phraseEndCount", to_string(phraseEndCount));
	phraseend->setValue("auto", "phraseStartCount", to_string(phraseStartCount));
}





//////////////////////////////
//
// HumdrumFileContent::analyzeRestPositions -- Calculate the vertical position
//    of rests on staves with two layers.
//

void HumdrumFileContent::analyzeRestPositions(void) {
	vector<HTp> kernstarts = getKernSpineStartList();

	// Now using verovio automatic rest positions, so not calcualting
	// by default anymore.  This code can be uncommented out if explicit
	// rest positions are needed for other purposes.
	//for (int i=0; i<(int)kernstarts.size(); i++) {
	//	assignImplicitVerticalRestPositions(kernstarts[i]);
	//}

	// Check for explicit positioning:
	checkForExplicitVerticalRestPositions();
}



//////////////////////////////
//
// HumdrumFileContent::checkForExplicitVerticalRestPositions -- Starting at the
//     current layer, check all rests in the same track for vertical positioning.
//

void HumdrumFileContent::checkForExplicitVerticalRestPositions(void) {
	HumdrumFileContent& infile = *this;
	vector<int> baselines(infile.getTrackCount() + 1, Convert::kernClefToBaseline("*clefG2"));
	for (int i=0; i<infile.getLineCount(); i++) {
		if (infile[i].isInterpretation()) {
			for (int j=0; j<infile[i].getFieldCount(); j++) {
				HTp tok = infile.token(i, j);
				if (!tok->isKern()) {
					continue;
				}
				if (!tok->isClef()) {
					continue;
				}
				int track = tok->getTrack();
				baselines[track] = Convert::kernClefToBaseline(tok);
			}
		}
		if (!infile[i].isData()) {
			continue;
		}
		for (int j=0; j<infile[i].getFieldCount(); j++) {
			HTp tok = infile.token(i, j);
			if (!tok->isKern()) {
				continue;
			}
			if (!tok->isRest()) {
				continue;
			}
			int track = tok->getTrack();
			checkRestForVerticalPositioning(tok, baselines[track]);
		}
	}
}



//////////////////////////////
//
// HumdrumFileContent::assignImplicitVerticalRestPositions -- Starting at the
//     current layer, check all rests in the same track for vertical positioning.
//     Only checks the first and second layers in a track.
//

void HumdrumFileContent::assignImplicitVerticalRestPositions(HTp kernstart) {
	if (!kernstart) {
		return;
	}

	int baseline = Convert::kernClefToBaseline("*clefG2");
	HTp current = kernstart;
	int track = kernstart->getTrack();

	while (current) {
		if (current->isClef()) {
			baseline = Convert::kernClefToBaseline(current);
			current = current->getNextToken();
			continue;
		}
		if (!current->isData()) {
			current = current->getNextToken();
			continue;
		}
		int strack = -1;
		HTp second = current->getNextFieldToken();
		if (second) {
			strack = second->getTrack();
		}
		if (track != strack) {
         if (current->isRest()) {
				checkRestForVerticalPositioning(current, baseline);
			}
			// only one layer in current spine.
			current = current->getNextToken();
			continue;
		}
		if (current->isNull()) {
			HTp resolve = current->resolveNull();
			if (resolve && resolve->isRest()) {
				if (second && second->isRest()) {
					if (checkRestForVerticalPositioning(second, baseline)) {
						current = current->getNextToken();
						continue;
					}
				}
			}
			current = current->getNextToken();
			continue;
		}
		if (current->isRest()) {
			// assign a default position for the rest, since
			// verovio will try to tweak it when there is
			// more than one layer on the staff.
			setRestOnCenterStaffLine(current, baseline);
		}
		if (current->isRest()) {
			if (checkRestForVerticalPositioning(current, baseline)) {
				if (second && second->isRest()) {
					if (checkRestForVerticalPositioning(second, baseline)) {
						current = current->getNextToken();
						continue;
					}
				}
				current = current->getNextToken();
				continue;
			}
		}
		if (second && second->isRest()) {
			if (checkRestForVerticalPositioning(second, baseline)) {
				current = current->getNextToken();
				continue;
			}
		}
		if (!second) {
			current = current->getNextToken();
			continue;
		}
		if (second->isRest()) {
			// assign a default position for the rest, since
			// verovio will try to tweak it when there is
			// more than one layer on the staff.
			setRestOnCenterStaffLine(current, baseline);
			setRestOnCenterStaffLine(second, baseline);
		}
		if (second->isNull()) {
			current = current->getNextToken();
			continue;
		}
		if (current->isRest() && second->isRest()) {
			// not dealing with rest against rest for now
			current = current->getNextToken();
			// what to do with vertical positions?  The are
			// current collapsed into a single rest
			// with the code above.
			continue;
		}
		if (current->isRest() || second->isRest()) {
			assignVerticalRestPosition(current, second, baseline);
		}
		current = current->getNextToken();
		continue;
	}
}



//////////////////////////////
//
// HumdrumFileContent::checkRestForVerticalPositioning -- Read any pitch information attached to
//     a rest and convert to ploc/oloc values.
//

bool HumdrumFileContent::checkRestForVerticalPositioning(HTp rest, int baseline) {
	HumRegex hre;
	if (!hre.search(rest, "([A-Ga-g]+)")) {
		return false;
	}
	string pitch = hre.getMatch(1);
	int b7 = Convert::kernToBase7(pitch);

	int diff = (b7 - baseline) + 100;
	if (diff % 2) {
		// force to every other diatonic step (stafflines)
		HumNum dur = rest->getDuration();
		if (dur > 1) {
			b7--;
		} else {
			b7++;
		}
	}

	int pc = b7 % 7;
	int oct = b7 / 7;

	string dname;
	switch (pc) {
		case 0: dname = "C"; break;
		case 1: dname = "D"; break;
		case 2: dname = "E"; break;
		case 3: dname = "F"; break;
		case 4: dname = "G"; break;
		case 5: dname = "A"; break;
		case 6: dname = "B"; break;
	}

	if (dname.empty()) {
		return false;
	}

	string oloc = to_string(oct);

	rest->setValue("auto", "ploc", dname);
	rest->setValue("auto", "oloc", oloc);

	return true;
}





//////////////////////////////
//
// HumdrumFileContent::setRestOnCenterStaffLine --
//

void HumdrumFileContent::setRestOnCenterStaffLine(HTp rest, int baseline) {
	int rpos = 4;
	int restdia = rpos + baseline;
	int pc = restdia % 7;
	int oct = restdia / 7;

	string dname;
	switch (pc) {
		case 0: dname = "C"; break;
		case 1: dname = "D"; break;
		case 2: dname = "E"; break;
		case 3: dname = "F"; break;
		case 4: dname = "G"; break;
		case 5: dname = "A"; break;
		case 6: dname = "B"; break;
	}

	if (dname.empty()) {
		return;
	}

	string oloc = to_string(oct);

	rest->setValue("auto", "ploc", dname);
	rest->setValue("auto", "oloc", oloc);
}



//////////////////////////////
//
//	HumdrumFileContents::assignVerticalRestPosition --
//

void HumdrumFileContent::assignVerticalRestPosition(HTp first, HTp second, int baseline) {
	vector<string> tokens;
	vector<int> vpos;

	int notepos = 0;
	HTp rest = NULL;
	HTp notes = NULL;
	if (first->isRest()) {
		rest = first;
		notes = second;
		notepos = -1;
	} else if (second->isRest()) {
		rest = second;
		notes = first;
		notepos = +1;
	} else {
		return;
	}

	int count = notes->getSubtokenCount();
	for (int i=0; i<count; i++) {
		vpos.push_back(Convert::kernToBase7(notes->getSubtoken(i)) - baseline);
	}

	int rpos = 0;
	if (notepos > 0) {
		rpos = getRestPositionBelowNotes(rest, vpos);
	} else if (notepos < 0) {
		rpos = getRestPositionAboveNotes(rest, vpos);
	} else {
		return;
	}

	int restdia = rpos + baseline;
	int pc = restdia % 7;
	int oct = restdia / 7;

	string dname;
	switch (pc) {
		case 0: dname = "C"; break;
		case 1: dname = "D"; break;
		case 2: dname = "E"; break;
		case 3: dname = "F"; break;
		case 4: dname = "G"; break;
		case 5: dname = "A"; break;
		case 6: dname = "B"; break;
	}

	if (dname.empty()) {
		return;
	}

	string oloc = to_string(oct);

	rest->setValue("auto", "ploc", dname);
	rest->setValue("auto", "oloc", oloc);
}



//////////////////////////////
//
// HumdrumFileContent::getRestPositionBelowNotes --
//

int HumdrumFileContent::getRestPositionBelowNotes(HTp rest, vector<int>& vpos) {
	if (vpos.empty()) {
		return 4;
	}
	int lowest = vpos[0];
	for (int i=1; i<(int)vpos.size(); i++) {
		if (lowest > vpos[i]) {
			lowest = vpos[i];
		}
	}
	int restint = 0;
	double resttype = log(rest->getDuration().getFloat()) / log(2.0);
	restint = int(resttype + 1000.0) - 1000;
	int output = 0;

	switch (restint) {

		case 0: // quarter-note rests
			output = 0;
			switch (lowest) {
				case -2: output = -8; break;
				case -1: output = -6; break;
				case  0: output = -6; break;
				case  1: output = -4; break;
				case  2: output = -4; break;
				case  3: output = -2; break;
				case  4: output = -2; break;
				case  5: output =  0; break;
				case  6: output =  0; break;
				case  7: output =  2; break;
				case  8: output =  2; break;
				case  9: output =  4; break;
				case 10: output =  4; break;
			}
			if (lowest > 10) {
				output = 4;
			}
			if (lowest < -2) {
				output = lowest - 6;
				if (lowest % 2) {
					output++;
				}
			}
			return output;
			break;

		case -1: // eighth-note rests
		case -2: // sixteenth-note rests
			output = 0;
			switch (lowest) {
				case -2: output = -6; break;
				case -1: output = -4; break;
				case  0: output = -4; break;
				case  1: output = -2; break;
				case  2: output = -2; break;
				case  3: output =  0; break;
				case  4: output =  0; break;
				case  5: output =  2; break;
				case  6: output =  2; break;
				case  7: output =  4; break;
				case  8: output =  4; break;
				case  9: output =  4; break;
				case 10: output =  4; break;
			}
			if (lowest > 10) {
				output = 4;
			}
			if (lowest < -2) {
				output = lowest - 4;
				if (lowest % 2) {
					output++;
				}
			}
			return output;
			break;

		case -3: // 32nd-note rests
		case -4: // 64h-note rests
			output = 0;
			switch (lowest) {
				case -2: output = -8; break;
				case -1: output = -6; break;
				case  0: output = -6; break;
				case  1: output = -4; break;
				case  2: output = -4; break;
				case  3: output = -2; break;
				case  4: output = -2; break;
				case  5: output =  0; break;
				case  6: output =  0; break;
				case  7: output =  2; break;
				case  8: output =  2; break;
				case  9: output =  4; break;
				case 10: output =  4; break;
			}
			if (lowest > 10) {
				output = 4;
			}
			if (lowest < -2) {
				output = lowest - 6;
				if (lowest % 2) {
					output++;
				}
			}
			return output;
			break;

		case -5: // 128th-note rests
		case -6: // 256th-note rests
			output = 0;
			switch (lowest) {
				case -2: output = -10; break;
				case -1: output = -8;  break;
				case  0: output = -8;  break;
				case  1: output = -6;  break;
				case  2: output = -6;  break;
				case  3: output = -4;  break;
				case  4: output = -4;  break;
				case  5: output = -2;  break;
				case  6: output = -2;  break;
				case  7: output =  0;  break;
				case  8: output =  0;  break;
				case  9: output =  2;  break;
				case 10: output =  2;  break;
			}
			if (lowest > 10) {
				output = 4;
			}
			if (lowest < -2) {
				output = lowest - 8;
				if (lowest % 2) {
					output++;
				}
			}
			return output;
			break;

		case 1: // half-note rests
		case 2: // whole-note rests
		case 3: // breve-note rests
			output = 0;
			switch (lowest) {
				case -2: output = -6; break;
				case -1: output = -6; break;
				case  0: output = -4; break;
				case  1: output = -4; break;
				case  2: output = -2; break;
				case  3: output = -2; break;
				case  4: output =  0; break;
				case  5: output =  0; break;
				case  6: output =  2; break;
				case  7: output =  2; break;
				case  8: output =  4; break;
				case  9: output =  4; break;
				case 10: output =  4; break;
			}
			if (lowest > 10) {
				output = 4;
			}
			if (lowest < -2) {
				output = lowest - 4;
				if (lowest % 2) {
					output--;
				}
			}
			return output;
			break;

	}
	return 0;
}



//////////////////////////////
//
// HumdrumFileContent::getRestPositionAboveNotes --
//

int HumdrumFileContent::getRestPositionAboveNotes(HTp rest, vector<int>& vpos) {
	if (vpos.empty()) {
		return 4;
	}
	int highest = vpos[0];
	for (int i=1; i<(int)vpos.size(); i++) {
		if (highest < vpos[i]) {
			highest = vpos[i];
		}
	}
	int restint = 0;
	double resttype = log(rest->getDuration().getFloat()) / log(2.0);
	restint = int(resttype + 1000.0) - 1000;
	int output = 8;

	switch (restint) {

		case 0: // quarter-note rests
			output = 0;
			switch (highest) {
				case -2: output =  4; break;
				case -1: output =  4; break;
				case  0: output =  4; break;
				case  1: output =  6; break;
				case  2: output =  6; break;
				case  3: output =  8; break;
				case  4: output =  8; break;
				case  5: output = 10; break;
				case  6: output = 10; break;
				case  7: output = 12; break;
				case  8: output = 12; break;
				case  9: output = 14; break;
				case 10: output = 14; break;
			}
			if (highest < -2) {
				output = 4;
			}
			if (highest > 10) {
				output = highest + 4;
				if (highest % 2) {
					output++;
				}
			}
			return output;
			break;

		case -1: // eighth-note rests
			output = 0;
			switch (highest) {
				case -2: output =  4; break;
				case -1: output =  4; break;
				case  0: output =  4; break;
				case  1: output =  4; break;
				case  2: output =  6; break;
				case  3: output =  6; break;
				case  4: output =  8; break;
				case  5: output =  8; break;
				case  6: output = 10; break;
				case  7: output = 10; break;
				case  8: output = 12; break;
				case  9: output = 12; break;
				case 10: output = 14; break;
			}
			if (highest < -2) {
				output = 4;
			}
			if (highest > 10) {
				output = highest + 4;
				if (highest % 2) {
					output--;
				}
			}
			return output;
			break;

		case -2: // sixteenth-note rests
		case -3: // 32nd-note rests
			output = 0;
			switch (highest) {
				case -2: output =  4; break;
				case -1: output =  4; break;
				case  0: output =  6; break;
				case  1: output =  6; break;
				case  2: output =  8; break;
				case  3: output =  8; break;
				case  4: output = 10; break;
				case  5: output = 10; break;
				case  6: output = 12; break;
				case  7: output = 12; break;
				case  8: output = 14; break;
				case  9: output = 14; break;
				case 10: output = 16; break;
			}
			if (highest < -2) {
				output = 4;
			}
			if (highest > 10) {
				output = highest + 6;
				if (highest % 2) {
					output--;
				}
			}
			return output;
			break;

		case -4: // 64th-note rests
		case -5: // 128th-note rests
			output = 0;
			switch (highest) {
				case -2: output =  6; break;
				case -1: output =  6; break;
				case  0: output =  8; break;
				case  1: output =  8; break;
				case  2: output = 10; break;
				case  3: output = 10; break;
				case  4: output = 12; break;
				case  5: output = 12; break;
				case  6: output = 14; break;
				case  7: output = 14; break;
				case  8: output = 16; break;
				case  9: output = 16; break;
				case 10: output = 18; break;
			}
			if (highest < -2) {
				output = 4;
			}
			if (highest > 10) {
				output = highest + 8;
				if (highest % 2) {
					output--;
				}
			}
			return output;
			break;

		case -6: // 256th-note rests
			output = 0;
			switch (highest) {
				case -4: output =  6; break;
				case -3: output =  6; break;
				case -2: output =  8; break;
				case -1: output =  8; break;
				case  0: output = 10; break;
				case  1: output = 10; break;
				case  2: output = 12; break;
				case  3: output = 12; break;
				case  4: output = 14; break;
				case  5: output = 14; break;
				case  6: output = 16; break;
				case  7: output = 16; break;
				case  8: output = 18; break;
				case  9: output = 18; break;
				case 10: output = 20; break;
			}
			if (highest < -4) {
				output = 4;
			}
			if (highest > 10) {
				output = highest + 10;
				if (highest % 2) {
					output--;
				}
			}
			return output;
			break;

		case 1: // half-note rests
		case 2: // whole-note rests
		case 3: // breve-note rests
			output = 0;
			switch (highest) {
				case -2: output =  4; break;
				case -1: output =  4; break;
				case  0: output =  4; break;
				case  1: output =  4; break;
				case  2: output =  6; break;
				case  3: output =  6; break;
				case  4: output =  8; break;
				case  5: output =  8; break;
				case  6: output = 10; break;
				case  7: output = 10; break;
				case  8: output = 12; break;
				case  9: output = 12; break;
				case 10: output = 14; break;
			}
			if (highest < -2) {
				output = 4;
			}
			if (highest > 10) {
				output = highest + 4;
				if (highest % 2) {
					output--;
				}
			}
			return output;
			break;

	}

	return output;
}





//////////////////////////////
//
// HumdrumFileContent::analyzeSlurs -- Link start and ends of
//    slurs to each other.
//

bool HumdrumFileContent::analyzeSlurs(void) {
	if (m_analyses.m_slurs_analyzed) {
		return false;
	}
	m_analyses.m_slurs_analyzed = true;
	bool output = true;
	output &= analyzeKernSlurs();
	output &= analyzeMensSlurs();
	return output;
}



//////////////////////////////
//
// HumdrumFileContent::analyzeMensSlurs -- Link start and ends of
//    slurs to each other.  They are the same as **kern, so borrowing
//    analyzeKernSlurs to do the analysis.
//

bool HumdrumFileContent::analyzeMensSlurs(void) {
	vector<HTp> slurstarts;
	vector<HTp> slurends;

	vector<HTp> l;
	vector<pair<HTp, HTp>> labels; // first is previous label, second is next label
	HumdrumFileBase& infile = *this;
	labels.resize(infile.getLineCount());
	l.resize(infile.getLineCount());
	for (int i=0; i<infile.getLineCount(); i++) {
		labels[i].first = NULL;
		labels[i].second = NULL;
		l[i] = NULL;
	}
	for (int i=0; i<infile.getLineCount(); i++) {
		if (!infile[i].isInterpretation()) {
			continue;
		}
		HTp token = infile.token(i, 0);
		if ((token->compare(0, 2, "*>") == 0) && (token->find("[") == std::string::npos)) {
			l[i] = token;
		}
	}
	HTp current = NULL;
	for (int i=0; i<infile.getLineCount(); i++) {
		if (l[i] != NULL) {
			current = l[i];
		}
		labels[i].first = current;
	}
	current = NULL;
	for (int i=infile.getLineCount() - 1; i>=0; i--) {
		if (l[i] != NULL) {
			current = l[i];
		}
		labels[i].second = current;
	}

	vector<int> endings(infile.getLineCount(), 0);
	int ending = 0;
	for (int i=0; i<(int)endings.size(); i++) {
		if (l[i]) {
			char lastchar = l[i]->back();
			if (isdigit(lastchar)) {
				ending = lastchar - '0';
			} else {
				ending = 0;
			}
		}
		endings[i] = ending;
	}

	vector<HTp> mensspines;
	getSpineStartList(mensspines, "**mens");
	bool output = true;
	string linkSignifier = m_signifiers.getKernLinkSignifier();
	for (int i=0; i<(int)mensspines.size(); i++) {
		output = output && analyzeKernSlurs(mensspines[i], slurstarts, slurends, labels, endings, linkSignifier);
	}
	createLinkedSlurs(slurstarts, slurends);
	return output;
}



//////////////////////////////
//
// HumdrumFileContent::analyzeKernSlurs -- Link start and ends of
//    slurs to each other.
//

bool HumdrumFileContent::analyzeKernSlurs(void) {
	vector<HTp> slurstarts;
	vector<HTp> slurends;

	vector<HTp> l;
	vector<pair<HTp, HTp>> labels; // first is previous label, second is next label
	HumdrumFileBase& infile = *this;
	labels.resize(infile.getLineCount());
	l.resize(infile.getLineCount());
	for (int i=0; i<infile.getLineCount(); i++) {
		labels[i].first = NULL;
		labels[i].second = NULL;
		l[i] = NULL;
	}
	for (int i=0; i<infile.getLineCount(); i++) {
		if (!infile[i].isInterpretation()) {
			continue;
		}
		HTp token = infile.token(i, 0);
		if ((token->compare(0, 2, "*>") == 0) && (token->find("[") == std::string::npos)) {
			l[i] = token;
		}
	}
	HTp current = NULL;
	for (int i=0; i<infile.getLineCount(); i++) {
		if (l[i] != NULL) {
			current = l[i];
		}
		labels[i].first = current;
	}
	current = NULL;
	for (int i=infile.getLineCount() - 1; i>=0; i--) {
		if (l[i] != NULL) {
			current = l[i];
		}
		labels[i].second = current;
	}

	vector<int> endings(infile.getLineCount(), 0);
	int ending = 0;
	for (int i=0; i<(int)endings.size(); i++) {
		if (l[i]) {
			char lastchar = l[i]->back();
			if (isdigit(lastchar)) {
				ending = lastchar - '0';
			} else {
				ending = 0;
			}
		}
		endings[i] = ending;
	}

	vector<HTp> kernspines;
	getSpineStartList(kernspines, "**kern");
	bool output = true;
	string linkSignifier = m_signifiers.getKernLinkSignifier();
	for (int i=0; i<(int)kernspines.size(); i++) {
		output = output && analyzeKernSlurs(kernspines[i], slurstarts, slurends, labels, endings, linkSignifier);
	}

	createLinkedSlurs(slurstarts, slurends);
	return output;
}


bool HumdrumFileContent::analyzeKernSlurs(HTp spinestart,
		vector<HTp>& linkstarts, vector<HTp>& linkends, vector<pair<HTp, HTp>>& labels,
		vector<int>& endings, const string& linksig) {

	// linked slurs handled separately, so generate an ignore sequence:
	string ignorebegin = linksig + "(";
	string ignoreend = linksig + ")";

	// tracktokens == the 2-D data list for the track,
	// arranged in layers with the second dimension.
	vector<vector<HTp> > tracktokens;
	this->getTrackSeq(tracktokens, spinestart, OPT_DATA | OPT_NOEMPTY);
	// printSequence(tracktokens);

	// sluropens == list of slur openings for each track and elision level
	// first dimension: elision level
	// second dimension: track number
	vector<vector<vector<HTp>>> sluropens;

	sluropens.resize(4); // maximum of 4 elision levels
	for (int i=0; i<(int)sluropens.size(); i++) {
		sluropens[i].resize(8);  // maximum of 8 layers
	}

	int opencount = 0;
	int closecount = 0;
	int elision = 0;
	HTp token;
	for (int row=0; row<(int)tracktokens.size(); row++) {
		for (int track=0; track<(int)tracktokens[row].size(); track++) {
			token = tracktokens[row][track];
			if (!token->isData()) {
				continue;
			}
			if (token->isNull()) {
				continue;
			}
			opencount = (int)count(token->begin(), token->end(), '(');
			closecount = (int)count(token->begin(), token->end(), ')');

			for (int i=0; i<closecount; i++) {
				bool isLinked = isLinkedSlurEnd(token, i, ignoreend);
				if (isLinked) {
					linkends.push_back(token);
					continue;
				}
				elision = token->getSlurEndElisionLevel(i);
				if (elision < 0) {
					continue;
				}
				if (sluropens[elision][track].size() > 0) {
					linkSlurEndpoints(sluropens[elision][track].back(), token);
					// remove slur opening from buffer
					sluropens[elision][track].pop_back();
				} else {
					// No starting slur marker to match to this slur end in the
					// given track.
					// search for an open slur in another track:
					bool found = false;
					for (int itrack=0; itrack<(int)sluropens[elision].size(); itrack++) {
						if (sluropens[elision][itrack].size() > 0) {
							linkSlurEndpoints(sluropens[elision][itrack].back(), token);
							// remove slur opening from buffer
							sluropens[elision][itrack].pop_back();
							found = true;
							break;
						}
					}
					if (!found) {
						int lineindex = token->getLineIndex();
						int endnum = endings[lineindex];
						int pindex = -1;
						if (labels[lineindex].first) {
							pindex = labels[lineindex].first->getLineIndex();
							pindex--;
						}
						int endnumpre = -1;
						if (pindex >= 0) {
							endnumpre = endings[pindex];
						}

						if ((endnumpre > 0) && (endnum > 0) && (endnumpre != endnum)) {
							// This is a slur in an ending that start at the start of an ending.
							HumNum duration = token->getDurationFromStart();
							if (labels[token->getLineIndex()].first) {
								duration -= labels[token->getLineIndex()].first->getDurationFromStart();
							}
							token->setValue("auto", "endingSlurBack", "true");
							token->setValue("auto", "slurSide", "stop");
							token->setValue("auto", "slurDuration",
								token->getDurationToEnd());
						} else {
							// This is a slur closing that does not have a matching opening.
							token->setValue("auto", "hangingSlur", "true");
							token->setValue("auto", "slurSide", "stop");
							token->setValue("auto", "slurOpenIndex", to_string(i));
							token->setValue("auto", "slurDuration",
								token->getDurationToEnd());
						}
					}
				}
			}

			for (int i=0; i<opencount; i++) {
				bool isLinked = isLinkedSlurBegin(token, i, ignorebegin);
				if (isLinked) {
					linkstarts.push_back(token);
					continue;
				}
				elision = token->getSlurStartElisionLevel(i);
				if (elision < 0) {
					continue;
				}
				sluropens[elision][track].push_back(token);
			}
		}
	}

	// Mark un-closed slur starts:
	for (int i=0; i<(int)sluropens.size(); i++) {
		for (int j=0; j<(int)sluropens[i].size(); j++) {
			for (int k=0; k<(int)sluropens[i][j].size(); k++) {
				sluropens[i][j][k]->setValue("", "auto", "hangingSlur", "true");
				sluropens[i][j][k]->setValue("", "auto", "slurSide", "start");
				sluropens[i][j][k]->setValue("", "auto", "slurDuration",
						sluropens[i][j][k]->getDurationFromStart());
			}
		}
	}

	return true;
}



//////////////////////////////
//
// HumdrumFileContent::createLinkedSlurs --  Currently assume that
//    start and ends are matched.
//

void HumdrumFileContent::createLinkedSlurs(vector<HTp>& linkstarts, vector<HTp>& linkends) {
	int max = (int)linkstarts.size();
	if ((int)linkends.size() < max) {
		max = (int)linkends.size();
	}
	if (max == 0) {
		// nothing to do
		return;
	}

	for (int i=0; i<max; i++) {
		linkSlurEndpoints(linkstarts[i], linkends[i]);
	}
}



//////////////////////////////
//
// HumdrumFileContent::isLinkedSlurEnd --
//

bool HumdrumFileContent::isLinkedSlurEnd(HTp token, int index, const string& pattern) {
	if (pattern.size() <= 1) {
		return false;
	}
	int counter = -1;
	for (int i=0; i<(int)token->size(); i++) {
		if (token->at(i) == ')') {
			counter++;
		}
		if (i == 0) {
			// Can't have linked slur at starting index in string.
			continue;
		}
		if (counter != index) {
			continue;
		}

		int startindex = i - (int)pattern.size() + 1;
		auto loc = token->find(pattern, startindex);
		if ((loc != std::string::npos) && ((int)loc == startindex)) {
			return true;
		}
		return false;
	}
	return false;
}



//////////////////////////////
//
// HumdrumFileContent::isLinkedSlurBegin --
//

bool HumdrumFileContent::isLinkedSlurBegin(HTp token, int index, const string& pattern) {
	if (pattern.size() <= 1) {
		return false;
	}
	int counter = -1;
	for (int i=0; i<(int)token->size(); i++) {
		if (token->at(i) == '(') {
			counter++;
		}
		if (i == 0) {
			continue;
		}
		if (counter != index) {
			continue;
		}
		if (token->find(pattern, i - (int)pattern.size() + 1) != std::string::npos) {
			return true;
		}
		return false;
	}
	return false;
}



//////////////////////////////
//
// HumdrumFileContent::linkSlurEndpoints --  Allow up to two slur starts/ends
//      on a note.
//

void HumdrumFileContent::linkSlurEndpoints(HTp slurstart, HTp slurend) {
	string durtag = "slurDuration";
	string endtag = "slurEndId";
	string starttag = "slurStartId";
	string slurstartnumbertag = "slurStartNumber";
	string slurendnumbertag = "slurEndNumber";

	int slurStartCount = slurstart->getValueInt("auto", "slurStartCount");
	int opencount = (int)count(slurstart->begin(), slurstart->end(), '(');
	slurStartCount++;
	int openEnumeration = opencount - slurStartCount + 1;

	if (openEnumeration > 1) {
		endtag += to_string(openEnumeration);
		durtag += to_string(openEnumeration);
		slurendnumbertag += to_string(openEnumeration);
	}

	int slurEndNumber = slurend->getValueInt("auto", "slurEndCount");
	slurEndNumber++;
	int closeEnumeration = slurEndNumber;
	if (closeEnumeration > 1) {
		starttag += to_string(closeEnumeration);
		slurstartnumbertag += to_string(closeEnumeration);
	}

	HumNum duration = slurend->getDurationFromStart()
			- slurstart->getDurationFromStart();

	slurstart->setValue("auto", endtag,            slurend);
	slurstart->setValue("auto", "id",              slurstart);
	slurstart->setValue("auto", slurendnumbertag,  closeEnumeration);
	slurstart->setValue("auto", durtag,            duration);
	slurstart->setValue("auto", "slurStartCount",  slurStartCount);

	slurend->setValue("auto", starttag, slurstart);
	slurend->setValue("auto", "id", slurend);
	slurend->setValue("auto", slurstartnumbertag, openEnumeration);
	slurend->setValue("auto", "slurEndCount",  slurEndNumber);
}





//////////////////////////////
//
// HumdrumFileContent::analyzeKernStemLengths --
//

bool HumdrumFileContent::analyzeKernStemLengths(void) {
	int scount = this->getStrandCount();
	bool output = true;

	vector<vector<int>> centerlines;
	getBaselines(centerlines);
	for (int i=0; i<scount; i++) {
		HTp sstart = this->getStrandStart(i);
		if (!sstart->isKern()) {
			continue;
		}
		HTp send = this->getStrandEnd(i);
		output = output && analyzeKernStemLengths(sstart, send, centerlines);
	}
	return output;
}


bool HumdrumFileContent::analyzeKernStemLengths(HTp stok, HTp etok, vector<vector<int>>& centerlines) {
	HTp tok = stok;
	while (tok && (tok != etok)) {
		if (!tok->isData()) {
			tok = tok->getNextToken();
			continue;
		}
		if (tok->isNull()) {
			tok = tok->getNextToken();
			continue;
		}
		if (tok->isChord()) {
			// don't deal with chords yet
			tok = tok->getNextToken();
			continue;
		}
		if (!tok->isNote()) {
			tok = tok->getNextToken();
			continue;
		}
		int subtrack = tok->getSubtrack();
		if (subtrack == 0) {
			// single voice on staff, so don't process unless it has a stem direction
			// deal with explicit stem direction later.
			tok = tok->getNextToken();
			continue;
		}
		if (subtrack > 2) {
			// 3rd and higher voices will not be processed without stem direction
			// deal with explicit stem direction later.
			tok = tok->getNextToken();
			continue;
		}
		HumNum dur = Convert::recipToDurationNoDots(tok, 8);
		// dur is in units of eighth notes
		if (dur <= 1) {
			// eighth-note or less (could be in beam, so deal with it later)
			tok = tok->getNextToken();
			continue;
		}
		if (dur > 4) {
			// greater than a half-note (no stem)
			tok = tok->getNextToken();
			continue;
		}
		int track = tok->getTrack();
		int b7 = Convert::kernToBase7(tok);
		int diff = b7 - centerlines[track][tok->getLineIndex()];
		if (subtrack == 1) {
			if (diff == 1) { // 0.5 stem length adjustment
				tok->setValue("auto", "stemlen", "6.5");
			} else if (diff == 2) { // 1.0 stem length adjustment
				tok->setValue("auto", "stemlen", "6");
			} else if (diff >= 3) { // 1.5 stem length adjustment
				tok->setValue("auto", "stemlen", "5.5");
			}
		} else if (subtrack == 2) {
			if (diff == -1) { // 0.5 stem length adjustment
				tok->setValue("auto", "stemlen", "6.5");
			} else if (diff == -2) { // 1.0 stem length adjustment
				tok->setValue("auto", "stemlen", "6");
			} else if (diff <= -3) { // 1.5 stem length adjustment
				tok->setValue("auto", "stemlen", "5.5");
			}

		}
		tok = tok->getNextToken();
	}

	return true;
}


//////////////////////////////
//
// HumdrumFileContent::getCenterlines --
//

void HumdrumFileContent::getBaselines(vector<vector<int>>& centerlines) {
	centerlines.resize(this->getTrackCount()+1);

	vector<HTp> kernspines;
	getSpineStartList(kernspines, "**kern");
	int treble = Convert::kernClefToBaseline("*clefG2") + 4;
	int track;

	for (int i=0; i<(int)kernspines.size(); i++) {
		track = kernspines[i]->getTrack();
		centerlines[track].resize(getLineCount());
		for (int j=0; j<getLineCount(); j++) {
			centerlines[track][j] = treble;
		}
	}

	for (int i=0; i<(int)kernspines.size(); i++) {
		HTp tok = kernspines[i];
		int clefcenter = treble;
		while (tok) {
			track = tok->getTrack();
			centerlines[track][tok->getLineIndex()] = clefcenter;
			if (!tok->isClef()) {
				tok = tok->getNextToken();
				continue;
			}
			int centerline = Convert::kernClefToBaseline(tok) + 4;
			centerlines[track][tok->getLineIndex()] = centerline;
			clefcenter = centerline;
			tok = tok->getNextToken();
		}
	}
}




//////////////////////////////
//
// HumdrumFileContent::analyzeTextRepetition -- Look for *ij and *Xij markers
//     that indicate repetition marks.  values added to text:
//      	auto/ij=true: the syllable is in an ij region.
//      	auto/ij-begin=true: the syllable is the first in an ij region.
//      	auto/ij-end=true: the syllable is the last in an ij region.
//
// Returns true if there are any *ij/*Xij markers in the data.
//
// Also consider *edit/*Xedit and *italic/*Xitalic as *ij/*Xij for printing.
//

bool HumdrumFileContent::analyzeTextRepetition(void) {
	HumdrumFileContent& infile = *this;
	vector<HTp> sstarts;
	infile.getSpineStartList(sstarts);

	bool output = false;
	bool ijstate;
	bool startij;  // true if at the first note in IJ
	HTp lastword;   // non-null if last syllable before *Xij

	for (int i=0; i<(int)sstarts.size(); i++) {
		ijstate = false;
		startij = false;
		lastword = NULL;
		HTp start = sstarts[i];
		if (!(start->isDataType("**text") || start->isDataType("**sylb"))) {
			continue;
		}
		HTp current = start;
		while (current) {
			if (current->isNull()) {
				current = current->getNextToken();
				continue;;
			}
			if (current->isInterpretation()) {
				if ((*current == "*ij") || (*current == "*edit") || (*current == "*italic")) {
					output = true;
					startij = true;
					ijstate = true;
				} else if ((*current == "*Xij") || (*current == "*Xedit") || (*current == "*Xitalic")) {
					output = true;
					startij = false;
					ijstate = false;
					if (lastword) {
						lastword->setValue("auto", "ij-end", "true");
						lastword = NULL;
					}
				}
				current = current->getNextToken();
				continue;
			}
			if (current->isData()) {
				if (ijstate == true) {
					current->setValue("auto", "ij", "true");
					if (startij) {
						current->setValue("auto", "ij-begin", "true");
						startij = false;
					}
					lastword = current;
				}
			}
			current = current->getNextToken();
			continue;
		}
	}

	return output;
}





//////////////////////////////
//
// HumdrumFileContent::analyzeKernTies -- Link start and ends of
//    ties to each other.
//

bool HumdrumFileContent::analyzeKernTies(void) {
	vector<pair<HTp, int>> linkedtiestarts;
	vector<pair<HTp, int>> linkedtieends;

	// vector<HTp> kernspines;
	// getSpineStartList(kernspines, "**kern");
	bool output = true;
	string linkSignifier = m_signifiers.getKernLinkSignifier();
	output = analyzeKernTies(linkedtiestarts, linkedtieends, linkSignifier);
	createLinkedTies(linkedtiestarts, linkedtieends);
	return output;
}


//
// Could be generalized to allow multiple grand-staff pairs by limiting
// the search spines for linking (probably with *part indications).
// Currently all spines are checked for linked ties.
//

bool HumdrumFileContent::analyzeKernTies(
		vector<pair<HTp, int>>& linkedtiestarts,
		vector<pair<HTp, int>>& linkedtieends,
		string& linkSignifier) {

	// Use this in the future to limit to grand-staff search (or 3 staves for organ):
	// vector<vector<HTp> > tracktokens;
	// this->getTrackSeq(tracktokens, spinestart, OPT_DATA | OPT_NOEMPTY);

	// Only analyzing linked ties for now (others ties are handled without analysis in
	// the hum2mei converter, for example.
	if (linkSignifier.empty()) {
		return true;
	}

	string lstart  = linkSignifier + "[";
	string lmiddle = linkSignifier + "_";
	string lend    = linkSignifier + "]";

	vector<pair<HTp, int>> startdatabase(400);

	for (int i=0; i<(int)startdatabase.size(); i++) {
		startdatabase[i].first  = NULL;
		startdatabase[i].second = -1;
	}

	HumdrumFileContent& infile = *this;
	for (int i=0; i<infile.getLineCount(); i++) {
		if (!infile[i].isData()) {
			continue;
		}
		for (int j=0; j<infile[i].getFieldCount(); j++) {
			HTp tok = infile.token(i, j);
			if (!tok->isKern()) {
				continue;
			}
			if (!tok->isData()) {
				continue;
			}
			if (tok->isNull()) {
				continue;
			}
			if (tok->isRest()) {
				continue;
			}
			int scount = tok->getSubtokenCount();
			int b40;
			for (int k=0; k<scount; k++) {
				int index = k;
				if (scount == 1) {
					index = -1;
				}
				std::string tstring = tok->getSubtoken(k);
				if (tstring.find(lstart) != std::string::npos) {
					b40 = Convert::kernToBase40(tstring);
					startdatabase[b40].first  = tok;
					startdatabase[b40].second = index;
					// linkedtiestarts.push_back(std::make_pair(tok, index));
				}
				if (tstring.find(lend) != std::string::npos) {
					b40 = Convert::kernToBase40(tstring);
					if (startdatabase.at(b40).first) {
						linkedtiestarts.push_back(startdatabase[b40]);
						linkedtieends.push_back(std::make_pair(tok, index));
						startdatabase[b40].first  = NULL;
						startdatabase[b40].second = -1;
					}
				}
				if (tstring.find(lmiddle) != std::string::npos) {
					b40 = Convert::kernToBase40(tstring);
					if (startdatabase[b40].first) {
						linkedtiestarts.push_back(startdatabase[b40]);
						linkedtieends.push_back(std::make_pair(tok, index));
					}
					startdatabase[b40].first  = tok;
					startdatabase[b40].second = index;
					// linkedtiestarts.push_back(std::make_pair(tok, index));
					// linkedtieends.push_back(std::make_pair(tok, index));
				}
			}
		}
	}

	return true;
}



//////////////////////////////
//
// HumdrumFileContent::createLinkedTies --
//

void HumdrumFileContent::createLinkedTies(vector<pair<HTp, int>>& linkstarts,
	vector<pair<HTp, int>>& linkends) {
   int max = (int)linkstarts.size();
   if ((int)linkends.size() < max) {
      max = (int)linkends.size();
   }
   if (max == 0) {
      // nothing to do
      return;
   }

   for (int i=0; i<max; i++) {
      linkTieEndpoints(linkstarts[i].first, linkstarts[i].second,
				linkends[i].first, linkends[i].second);
   }

}


//////////////////////////////
//
// HumdrumFileContent::linkTieEndpoints --
//

void HumdrumFileContent::linkTieEndpoints(HTp tiestart,
		int startindex, HTp tieend, int endindex) {

   string durtag   = "tieDuration";
   string starttag = "tieStart";
   string endtag   = "tieEnd";
	string startnum = "tieStartSubtokenNumber";
	string endnum   = "tieEndSubtokenNumber";

	int startnumber = startindex + 1;
	int endnumber   = endindex + 1;

	if (tiestart->isChord()) {
		if (startnumber > 0) {
			durtag   += to_string(startnumber);
			endnum   += to_string(startnumber);
			endtag   += to_string(startnumber);
		}
	}
	if (tieend->isChord()) {
		if (endnumber > 0) {
			starttag += to_string(endnumber);
			startnum += to_string(endnumber);
		}
	}

   tiestart->setValue("auto", endtag, tieend);
   tiestart->setValue("auto", "id", tiestart);
	if (endnumber > 0) {
		tiestart->setValue("auto", endnum, to_string(endnumber));
	}

   tieend->setValue("auto", starttag, tiestart);
   tieend->setValue("auto", "id", tieend);
	if (startnumber > 0) {
		tieend->setValue("auto", startnum, to_string(startnumber));
	}

   HumNum duration = tieend->getDurationFromStart()
         - tiestart->getDurationFromStart();
   tiestart->setValue("auto", durtag, duration);
}




//////////////////////////////
//
// HumdrumFileStructure::getTimeSigs -- Return the prevailing time signature
//     top and bottom for a particular spine for each line in the HumdrumFile.
//     This version does not handle mulimeters such as 2+3/4 or 3/4+6/8.
//     Only checks the primary strand of a spine/track for time signatures.
//
//     default value: track = 0: 0 means use the time signature
//         of the first **kern spine in the file; otherwise, use the
//         time signatures found in the given track (indexed from 1
//         for the first spine on a line).  A value of <0, 0> is used for
//         unassigned time signature lines.
//

void HumdrumFileContent::getTimeSigs(vector<pair<int, HumNum> >& output,
		int track) {
	HumdrumFileStructure& infile = *this;
	int lineCount = infile.getLineCount();
	output.resize(lineCount);
	pair<int, HumNum> current(0, 0);
	fill(output.begin(), output.end(), current);
	if (track == 0) {
		vector<HTp> kernspines = infile.getKernLikeSpineStartList();
		if (kernspines.size() > 0) {
			track = kernspines[0]->getTrack();
		}
	}
	if (track == 0) {
		track = 1;
	}

	int top  = 0;   // top number of time signature (0 for no meter)
	int bot  = 0;   // bottom number of time signature
	int bot2 = 0;   // such as the 2 in 3%2.

	int firstsig  = -1;
	int firstdata = -1;

	HTp token = getTrackStart(track);
	while (token) {
		if (token->isData()) {
			if (firstdata < 0) {
				firstdata = token->getLineIndex();
			}
			token = token->getNextToken();
			continue;
		}
		if (!token->isInterpretation()) {
			token = token->getNextToken();
			continue;
		}
		// check for time signature:
		if (sscanf(token->c_str(), "*M%d/%d%%%d", &top, &bot, &bot2) == 3) {
			current.first = top;
			current.second.setValue(bot, bot2);
			if (firstsig < 0) {
				firstsig = token->getLineIndex();
			}
		} else if (sscanf(token->c_str(), "*M%d/%d", &top, &bot) == 2) {
			current.first = top;
			current.second = bot;
			if (firstsig < 0) {
				firstsig = token->getLineIndex();
			}
		}
		output[token->getLineIndex()] = current;
		token = token->getNextToken();
	}

	// Back-fill the list if the first time signature occurs before
	// the start of the data:
	if ((firstsig > 0) && (firstdata >= firstsig)) {
		current = output[firstsig];
		for (int i=0; i<firstsig; i++) {
			output[i] = current;
		}
	}

	// In-fill the list:
	int starti = firstsig;
	if (starti < 0) {
		starti = 0;
	}
	current = output[starti];
	for (int i=starti+1; i<(int)output.size(); i++) {
		if (output[i].first == 0) {
			output[i] = current;
		} else {
			current = output[i];
		}
	}
}



//////////////////////////////
//
// HumdrumFileContent::HumdrumFileContent --
//

HumdrumFileContent::HumdrumFileContent(void) : HumdrumFileStructure() {
	// do nothing
}


HumdrumFileContent::HumdrumFileContent(const string& filename) :
		HumdrumFileStructure() {
	read(filename);
}


HumdrumFileContent::HumdrumFileContent(istream& contents) :
		HumdrumFileStructure() {
	read(contents);
}



//////////////////////////////
//
// HumdrumFileContent::~HumdrumFileContent --
//

HumdrumFileContent::~HumdrumFileContent() {
	// do nothing
}



//////////////////////////////
//
// HumdrumFileContent::analyzeRScale --
//

bool HumdrumFileContent::analyzeRScale(void) {
	int active = 0; // number of tracks currently having an active rscale parameter
	HumdrumFileBase& infile = *this;
	vector<HumNum> rscales(infile.getMaxTrack() + 1, 1);
	HumRegex hre;
	for (int i=0; i<infile.getLineCount(); i++) {
		if (infile[i].isInterpretation()) {
			int fieldcount = infile[i].getFieldCount();
			for (int j=0; j<fieldcount; j++) {
				HTp token = infile[i].token(j);
				if (token->compare(0, 8, "*rscale:") != 0) {
					continue;
				}
				if (!token->isKern()) {
					continue;
				}
				int track = token->getTrack();
				HumNum value = 1;
				if (hre.search(*token, "\\*rscale:(\\d+)/(\\d+)")) {
					int top = hre.getMatchInt(1);
					int bot = hre.getMatchInt(2);
					value.setValue(top, bot);
				} else if (hre.search(*token, "\\*rscale:(\\d+)")) {
					int top = hre.getMatchInt(1);
					value.setValue(top, 1);
				}
				if (value == 1) {
					if (rscales[track] != 1) {
						rscales[track] = 1;
						active--;
					}
				} else {
					if (rscales[track] == 1) {
						active++;
					}
					rscales[track] = value;
				}
			}
			continue;
		}
		if (!active) {
			continue;
		}
		if (!infile[i].isData()) {
			continue;
		}
		int fieldcount = infile[i].getFieldCount();
		for (int j=0; j<fieldcount; j++) {
			HTp token = infile.token(i, j);
			int track = token->getTrack();
			if (rscales[track] == 1) {
				continue;
			}
			if (!token->isKern()) {
				continue;
			}
			if (token->isNull()) {
				continue;
			}

			int dots = token->getDots();
			HumNum dur = token->getDurationNoDots();
			dur *= rscales[track];
			string vis = Convert::durationToRecip(dur);
			for (int k=0; k<dots; k++) {
				vis += '.';
			}
			token->setValue("LO", "N", "vis", vis);
			string rvalue = to_string(rscales[track].getNumerator());
			rvalue += '/';
			rvalue += to_string(rscales[track].getDenominator());
			token->setValue("auto", "rscale", rvalue);
		}
	}

	return true;
}



//////////////////////////////
//
// HumdrumFileContent::hasPickup -- Return false if there is no pickup measure.
//   Return the barline index number if there is a pickup measure.  A pickup measure
//   is identified when the duration from the start of the file to the first
//   barline is not zero or equal to the duration of the starting time signature.
//   if there is not starting time signature, then there cannot be an identified
//   pickup measure.
//

int HumdrumFileContent::hasPickup(void) {
	HumdrumFileContent& infile = *this;
	int barline = -1;
	HTp tsig = NULL;
	for (int i=0; i<infile.getLineCount(); i++) {
		if (infile[i].isBarline()) {
			if (barline > 0) {
				// second barline found, so stop looking for time signature
				break;
			}
			barline = i;
			continue;
		}
		if (!infile[i].isInterpretation()) {
			continue;
		}
		if (tsig != NULL) {
			continue;
		}
		for (int j=0; j<infile[i].getFieldCount(); j++) {
			HTp token = infile.token(i, j);
			if (token->isTimeSignature()) {
				tsig = token;
				break;
			}
		}
	}
	if (tsig == NULL) {
		// no time signature so return 0
		return 0;
	}
	if (barline < 0) {
		// no barlines in music
		return 0;
	}
	HumNum mdur = infile[barline].getDurationFromStart();
	HumNum tdur = Convert::timeSigToDurationInQuarter(tsig);
	if (mdur == tdur) {
		return 0;
	}
	return barline;
}




//////////////////////////////
//
// HumdrumFileSet::HumdrumFileSet --
//

HumdrumFileSet::HumdrumFileSet(void) {
	// do nothing
}

HumdrumFileSet::HumdrumFileSet(Options& options) {
	read(options);
}

HumdrumFileSet::HumdrumFileSet(const string& contents) {
	readString(contents);
}



//////////////////////////////
//
// HumdrumFileSet::~HumdrumFileSet --
//

HumdrumFileSet::~HumdrumFileSet() {
	clear();
}



//////////////////////////////
//
// HumdrumFileSet::clear -- Remove all Humdrum file content from set.
//

void HumdrumFileSet::clear(void) {
	for (int i=0; i<(int)m_data.size(); i++) {
		delete m_data[i];
		m_data[i] = NULL;
	}
	m_data.resize(0);
}



//////////////////////////////
//
// HumdrumFileSet::clearNoFree -- Remove all Humdrum file content from set
//    but do not delete the contents (since it should be handled externally).
//

void HumdrumFileSet::clearNoFree(void) {
	for (int i=0; i<(int)m_data.size(); i++) {
		m_data[i] = NULL;
	}
	m_data.resize(0);
}



//////////////////////////////
//
// HumdrumFileSet::getSize -- Return the number of Humdrum files in the
//     set.
//

int HumdrumFileSet::getSize(void) {
	return (int)m_data.size();
}



//////////////////////////////
//
// HumdrumFileSet::operator[] -- Return a HumdrumFile.
//

HumdrumFile& HumdrumFileSet::operator[](int index) {
	return *(m_data.at(index));
}



//////////////////////////////
//
// HumdrumFileSet::swap -- Switch position of two scores in the set.
//

bool HumdrumFileSet::swap(int index1, int index2) {
	if (index1 < 0) { return false; }
	if (index2 < 0) { return false; }
	if (index1 >= (int)m_data.size()) { return false; }
	if (index2 >= (int)m_data.size()) { return false; }

	HumdrumFile* temp = m_data[index1];
	m_data[index1] = m_data[index2];
	m_data[index2] = temp;

	return true;
}



//////////////////////////////
//
// HumdrumFileSet::read -- Returns the total number of segments
//

int HumdrumFileSet::readFile(const string& filename) {
	clear();
	return readAppendFile(filename);
}

int HumdrumFileSet::readString(const string& contents) {
	clear();
	return readAppendString(contents);
}

int HumdrumFileSet::readStringCsv(const string& contents) {
	clear();
	return readAppendStringCsv(contents);
}

int HumdrumFileSet::read(istream& inStream) {
	clear();
	return readAppend(inStream);
}

int HumdrumFileSet::read(Options& options) {
	clear();
	return readAppend(options);
}

int HumdrumFileSet::read(HumdrumFileStream& instream) {
	clear();
	return readAppend(instream);
}




//////////////////////////////
//
// HumdrumFileSet::readAppend -- Returns the total number of segments
//    Adds each new HumdrumFile segment to the end of the current data.
//

int HumdrumFileSet::readAppendFile(const string& filename) {
	ifstream indata;
	indata.open(filename);
	string contents((istreambuf_iterator<char>(indata)), istreambuf_iterator<char>());
	HumdrumFileStream instream(contents);
	return readAppend(instream);
}


int HumdrumFileSet::readAppendString(const string& contents) {
	HumdrumFileStream instream(contents);
	return readAppend(instream);
}

int HumdrumFileSet::readAppendStringCsv(const string& contents) {
	cerr << "NOT implemented yet" << endl;
	return 0;
}


int HumdrumFileSet::readAppend(istream& inStream) {
	string contents((istreambuf_iterator<char>(inStream)), istreambuf_iterator<char>());
	HumdrumFileStream instream(contents);
	return readAppend(instream);
}


int HumdrumFileSet::readAppend(Options& options) {
	HumdrumFileStream instream(options);
	return readAppend(instream);
}


int HumdrumFileSet::readAppend(HumdrumFileStream& instream) {
	HumdrumFile* pfile = new HumdrumFile;
	while (instream.read(*pfile)) {
		m_data.push_back(pfile);
		pfile = new HumdrumFile;
	}
	delete pfile;
	return (int)m_data.size();
}


int HumdrumFileSet::readAppendHumdrum(HumdrumFile& infile) {
	stringstream ss;
	ss << infile;
	return readAppendString(ss.str());
}


//////////////////////////////
//
// appendHumdrumPointer --  The infile will be deleted by the object
//    (so do not delete outside of the object or allow it to be inserted
//    from a stack pointer). I.e., this function is dangerous if you do
//    no know what you are doing.
//

int HumdrumFileSet::appendHumdrumPointer(HumdrumFile* infile) {
	m_data.push_back(infile);
	return 1;
}



//////////////////////////////
//
// HumdrumFileSet::hasFilters -- Returns true if has any
//    reference records starting with "!!!!filter:"
//    (universal filters).
//

bool HumdrumFileSet::hasFilters(void) {
	HumdrumFileSet& infiles = *this;
	for (int i=0; i<infiles.getCount(); i++) {
		int lcount = infiles[i].getLineCount();
		for (int j=0; j<lcount; j++) {
			if (!infiles[i][j].isComment()) {
				continue;
			}
			HTp token = infiles[i].token(j, 0);
			if (token->compare(0, 11, "!!!!filter:") == 0) {
				return true;
			}
			if (token->compare(0, 10, "!!!filter:") == 0) {
				return true;
			}
		}
	}
	return false;
}



//////////////////////////////
//
// HumdrumFileSet::hasGlobalFilters -- Returns true if has any
//    reference records starting with "!!!!filter:"
//    (universal filters).
//

bool HumdrumFileSet::hasGlobalFilters(void) {
	HumdrumFileSet& infiles = *this;
	for (int i=0; i<infiles.getCount(); i++) {
		int lcount = infiles[i].getLineCount();
		for (int j=0; j<lcount; j++) {
			if (!infiles[i][j].isComment()) {
				continue;
			}
			HTp token = infiles[i].token(j, 0);
			if (token->compare(0, 10, "!!!filter:") == 0) {
				return true;
			}
		}
	}
	return false;
}



//////////////////////////////
//
// HumdrumFileSet::hasUniversalFilters -- Returns true if has any
//    reference records starting with "!!!!filter:"
//    (universal filters).
//

bool HumdrumFileSet::hasUniversalFilters(void) {
	HumdrumFileSet& infiles = *this;
	for (int i=0; i<infiles.getCount(); i++) {
		int lcount = infiles[i].getLineCount();
		for (int j=0; j<lcount; j++) {
			if (!infiles[i][j].isComment()) {
				continue;
			}
			HTp token = infiles[i].token(j, 0);
			if (token->compare(0, 11, "!!!!filter:") == 0) {
				return true;
			}
		}
	}
	return false;
}



//////////////////////////////
//
// HumdrumFileSet::getUniversalReferenceRecords --
//

vector<HLp> HumdrumFileSet::getUniversalReferenceRecords(void) {
	vector<HLp> hlps;
	hlps.reserve(32);
	HLp hlp;
	HumdrumFileSet& infiles = *this;
	for (int i=0; i<infiles.getCount(); i++) {
		HumdrumFileBase& infile = infiles[i];
		for (int j=0; j<infile.getLineCount(); j++) {
			if (infile[j].isUniversalReference()) {
				hlp = &infile[j];
				hlps.push_back(hlp);
			}
		}
	}
	return hlps;
}





//////////////////////////////
//
// HumdrumFileStream::HumdrumFileStream --
//

HumdrumFileStream::HumdrumFileStream(void) {
	m_curfile = -1;
}

HumdrumFileStream::HumdrumFileStream(char** list) {
	m_curfile = -1;
	setFileList(list);
}

HumdrumFileStream::HumdrumFileStream(const vector<string>& list) {
	m_curfile = -1;
	setFileList(list);
}

HumdrumFileStream::HumdrumFileStream(Options& options) {
	m_curfile = -1;
	vector<string> list;
	options.getArgList(list);
	setFileList(list);
}

HumdrumFileStream::HumdrumFileStream(const string& datastring) {
	m_curfile = -1;
	m_stringbuffer << datastring;
}



//////////////////////////////
//
// HumdrumFileStream::clear -- reset the contents of the class.
//

void HumdrumFileStream::clear(void) {
	m_curfile = 0;
	m_filelist.resize(0);
	m_universals.resize(0);
	m_newfilebuffer.resize(0);
	// m_stringbuffer.clear(0);
	m_stringbuffer.str("");
}



//////////////////////////////
//
// HumdrumFileStream::setFileList --
//

int HumdrumFileStream::setFileList(char** list) {
	m_filelist.reserve(1000);
	m_filelist.resize(0);
	int i = 0;
	while (list[i] != NULL) {
		m_filelist.push_back(list[i]);
		i++;
	}
	return i;
}


int HumdrumFileStream::setFileList(const vector<string>& list) {
	m_filelist = list;
	return (int)list.size();
}



//////////////////////////////
//
// HumdrumFileStream::loadString --
//

void HumdrumFileStream::loadString(const string& data) {
	m_curfile = -1;
	m_stringbuffer << data;
}



//////////////////////////////
//
// HumdrumFileStream::read -- alias for getFile.
//

int HumdrumFileStream::read(HumdrumFile& infile) {
	return getFile(infile);
}


int HumdrumFileStream::read(HumdrumFileSet& infiles) {
	infiles.clear();
	HumdrumFile* infile = new HumdrumFile;
	while (getFile(*infile)) {
		infiles.appendHumdrumPointer(infile);
		infile = new HumdrumFile;
	}
	delete infile;
	return 0;
}



//////////////////////////////
//
// HumdrumFileStream::readSingleSegment -- Get a single file for a set structure.
//

int HumdrumFileStream::readSingleSegment(HumdrumFileSet& infiles) {
	infiles.clear();
	HumdrumFile* infile = new HumdrumFile;
	int status = getFile(*infile);
	if (!status) {
		delete infile;
	} else {
		infiles.appendHumdrumPointer(infile);
	}
	return status;
}



//////////////////////////////
//
// HumdrumFileStream::eof -- returns true if there is no more segements
//     to read from the input source(s).
//

int HumdrumFileStream::eof(void) {
	istream* newinput = NULL;

	// Read HumdrumFile contents from:
	// (1) Current ifstream if open
	// (2) Next filename if ifstream is done
	// (3) cin if no ifstream open and no filenames

	// (1) Is an ifstream open?, then yes, there is more data to read.
	if (m_instream.is_open() && !m_instream.eof()) {
		return 0;
	}

	// (1b) Is the URL data buffer open?
	else if (m_urlbuffer.str() != "") {
		return 0;
	}

	// (2) If ifstream is closed but there is a file to be processed,
	// load it into the ifstream and start processing it immediately.
	else if ((m_filelist.size() > 0) && (m_curfile < (int)m_filelist.size()-1)) {
		return 0;
	} else {
		// no input fstream open and no list of files to process, so
		// start (or continue) reading from standard input.
		if (m_curfile < 0) {
			// but only read from cin if no files have previously been read
			newinput = &cin;
		}
		if ((newinput != NULL) && newinput->eof()) {
			return 1;
		}
	}

	return 1;
}



//////////////////////////////
//
// HumdrumFileStream::getFile -- fills a HumdrumFile class with content
//    from the input stream or next input file in the list.  Returns
//    true if content was extracted, fails if there is no more HumdrumFiles
//    in the input stream.
//

int HumdrumFileStream::getFile(HumdrumFile& infile) {
	infile.clear();
	istream* newinput = NULL;

restarting:

	stringstream buffer;
	string templine;
	if (!m_newfilebuffer.empty()) {
		buffer << m_newfilebuffer << endl;
		m_newfilebuffer = "";
	}

	newinput = NULL;

	if (m_urlbuffer.eof()) {
		// If the URL buffer is at its end, clear the buffer.
		m_urlbuffer.str("");
	}

	// Read HumdrumFile contents from:
	// (1) Read from string buffer
	// (2) Current ifstream if open
	// (3) Next filename if ifstream is done
	// (4) cin if no ifstream open and no filenames

	// (1) Is there content in the string buffer?
	if (!m_stringbuffer.str().empty()) {
		newinput = &m_stringbuffer;
	}

	// (2) Is an ifstream open?
	else if (m_instream.is_open() && !m_instream.eof()) {
		newinput = &m_instream;
	}

	// (2b) Is the URL data buffer open?
	else if (m_urlbuffer.str() != "") {
		m_urlbuffer.clear();
		newinput = &m_urlbuffer;
	}

	// (3) If ifstream is closed but there is a file to be processed,
	// load it into the ifstream and start processing it immediately.
	else if (((int)m_filelist.size() > 0) &&
	      (m_curfile < (int)m_filelist.size()-1)) {
		m_curfile++;
		if (m_instream.is_open()) {
			m_instream.close();
		}
		if (m_filelist[m_curfile].find("://") != string::npos) {
			// The next file to read is a URL/URI, so buffer the
			// data from the internet and start reading that instead
			// of reading from a file on the hard disk.
			fillUrlBuffer(m_urlbuffer, m_filelist[m_curfile]);
			infile.setFilename(m_filelist[m_curfile]);
			goto restarting;
		}
		m_instream.open(m_filelist[m_curfile]);
		infile.setFilename(m_filelist[m_curfile]);
		if (!m_instream.is_open()) {
			// file does not exist or cannot be opened close
			// the file and try luck with next file in the list
			// (perhaps given an error or warning?).
			infile.setFilename("");
			m_instream.close();
			goto restarting;
		}
		newinput = &m_instream;
	} else {
		// no input fstream open and no list of files to process, so
		// start (or continue) reading from standard input.
		if (m_curfile < 0) {
			// but only read from cin if no files have previously been read
			newinput = &cin;
		}
	}

	// At this point the newinput istream is set to read from the given
	// file or from standard input, so start reading Humdrum content.
	// If there is "m_newfilebuffer" content, then set the filename of the
	// HumdrumFile to that value.

	if (m_newfilebuffer.size() > 0) {
		// store the filename for the current HumdrumFile being read:
		HumRegex hre;
		if (hre.search(m_newfilebuffer, R"(^!!!!SEGMENT\s*([+-]?\d+)?\s*:\s*(.*)\s*$)")) {
			if (hre.getMatchLength(1) > 0) {
				infile.setSegmentLevel(hre.getMatchInt(1));
			} else {
				infile.setSegmentLevel(0);
			}
			infile.setFilename(hre.getMatch(2));
		} else if ((m_curfile >=0) && (m_curfile < (int)m_filelist.size())
		      && (m_filelist.size() > 0)) {
			infile.setFilename(m_filelist[m_curfile]);
		} else {
			// reading from standard input, but no name.
		}
	}

	if (newinput == NULL) {
		// something strange happened, or no more files to read.
		return 0;
	}

	int foundUniversalQ = 0;

	// Start reading the input stream.  If !!!!SEGMENT: universal comment
	// is found, then store that line in m_newfilebuffer and return the
	// newly read HumdrumFile.  If other universal comments are found, then
	// overwrite the old universal comments here.

	// int addedFilename = 0;
	int dataFoundQ = 0;
	int starstarFoundQ = 0;
	int starminusFoundQ = 0;
	if (m_newfilebuffer.size() < 4) {
		//searchName = 1;
	}

	if (newinput->eof()) {
		if (m_curfile < (int)m_filelist.size()-1) {
			m_curfile++;
			goto restarting;
		}
		// input stream is close and there is no more files to process.
		return 0;
	}

	istream& input = *newinput;

	// if the previous line from the last read starts with "**"
	// then treat it as part of the current file.
	if ((m_newfilebuffer.size() > 1) && (m_newfilebuffer.compare(0, 2, "**") == 0)) {
		buffer << m_newfilebuffer << "\n";
		m_newfilebuffer = "";
		starstarFoundQ = 1;
	}

	while (!input.eof()) {
		getline(input, templine);
		if (templine.compare(0, strlen("!!!!SEGMENT"), "!!!!SEGMENT") == 0) {
			// Store the current segment line in the buffer before breaking.
			if (!buffer.str().empty()) {
				m_newfilebuffer = templine;
				break;
			}
			m_newfilebuffer = templine;
		}

		if (templine.compare(0, 2, "**") == 0) {
			if (starstarFoundQ == 1) {
				m_newfilebuffer = templine;
				// already found a **, so this one is defined as a file
				// segment.  Exit from the loop and process the previous
				// content, waiting until the next read to start with
				// this line.
				break;
			}
			starstarFoundQ = 1;
		}

		if (input.eof() && templine.empty()) {
			// No more data coming from current stream, so this is
			// the end of the HumdrumFile.  Break from the while loop
			// and then store the read contents of the stream in the
			// HumdrumFile.
			break;
		}

		int len = templine.length();
		if ((len > 4) && (templine.compare(0, 4, "!!!!") == 0) &&
		    (templine[4] != '!') &&
		    (dataFoundQ == 0) &&
		    (templine.compare(0, strlen("!!!!filter:"), "!!!!filter:") != 0) &&
		    (templine.compare(0, strlen("!!!!SEGMENT:"), "!!!!SEGMENT:") != 0)) {
			// This is a universal comment.  Should it be appended
			// to the list or should the current list be erased and
			// this record placed into the first entry?
			if (foundUniversalQ) {
				// already found a previous universal, so append.
				m_universals.push_back(templine);
			} else {
				// new universal comment, to delete all previous
				// universal comments and store this one.
				m_universals.reserve(1000);
				m_universals.resize(1);
				m_universals[0] = templine;
				foundUniversalQ = 1;
			}
			continue;
		}

		if (templine.compare(0, 2, "*-") == 0) {
			starminusFoundQ = 1;
		}

		if (((starminusFoundQ == 1) || (starstarFoundQ == 0)) && (templine[0] != '*') && (templine[0] != '!')) {
			if ((!templine.empty()) && (templine[0] != ' ')) {
				int found = 0;
				for (int mm = 0; mm < (int)m_filelist.size(); mm++) {
					if (m_filelist[mm] == templine) {
						found = 1;
					}
				}
				if (!found) {
					m_filelist.push_back(templine);
					// addedFilename = 1;
				}
				continue;
			}
		}

		dataFoundQ = 1; // found something other than universal comments

		// store the data line for later parsing into HumdrumFile record:
		buffer << templine << "\n";
	}

/*
	if (dataFoundQ == 0) {
		// never found anything for some strange reason.
		if (addedFilename) {
			goto restarting;
		}
		return 0;
	}
*/

	// Arriving here means that reading of the data stream is complete.
	// The string stream variable "buffer" contains the HumdrumFile
	// content, so send it to the HumdrumFile variable.  Also, prepend
	// Universal comments (demoted into Global comments) at the start
	// of the data stream (maybe allow for postpending Universal comments
	// in the future).
	stringstream contents;
	contents.str(""); // empty any contents in buffer
	contents.clear(); // reset error flags in buffer

	for (int i=0; i < (int)m_universals.size(); i++) {
		if (m_universals[i].compare(0, 11, "!!!!filter:") == 0) {
			continue;
		}
		contents << &(m_universals[i][1]) << "\n";
	}

	contents << buffer.str();
	string oldfilename = infile.getFilename();
	infile.readNoRhythm(contents);
	string newfilename = infile.getFilename();
	if (newfilename.empty() && !oldfilename.empty()) {
		infile.setFilename(oldfilename);
	}
	infile.setFilenameFromSegment();

	return 1;
}




//////////////////////////////
//
// HumdrumFileStream::fillUrlBuffer --
//

void HumdrumFileStream::fillUrlBuffer(stringstream& uribuffer,
		const string& uriname) {
	#ifdef USING_URI
		uribuffer.str(""); // empty any contents in buffer
		uribuffer.clear(); // reset error flags in buffer
		string webaddress = HumdrumFileBase::getUriToUrlMapping(uriname);
		HumdrumFileBase::readStringFromHttpUri(uribuffer, webaddress);
	#endif
}





//////////////////////////////
//
// HumdrumFileStructure::analyzeStropheMarkers -- Merge this
//    with analyzeStrophes (below) at some point.
//

void HumdrumFileStructure::analyzeStropheMarkers(void) {
	m_analyses.m_strophes_analyzed = true;

	m_strophes1d.clear();
	m_strophes2d.clear();
	int spines = getSpineCount();
	m_strophes2d.resize(spines);

	map<string, HTp> laststrophe;

	HumdrumFileStructure& infile = *this;

	for (int i=0; i<infile.getLineCount(); i++) {
		if (!infile[i].isInterpretation()) {
			continue;
		}
		for (int j=0; j<infile[i].getFieldCount(); j++) {
			HTp token = infile.token(i, j);
			if (*token == "*strophe") {
				string spineinfo = token->getSpineInfo();
	 			HTp lastone = laststrophe[spineinfo];
				if (lastone) {
					// Improperly terminated strophe,
					// ending here and starting a new one.
					TokenPair tp;
					tp.first = lastone;
					tp.last = token;
					m_strophes1d.push_back(tp);
					int spine = token->getTrack() - 1;
					m_strophes2d.at(spine).push_back(tp);
					laststrophe[spineinfo] = token;
				} else {
					// Store the new strophe.
					laststrophe[spineinfo] = token;
				}
			} else if ((*token == "*Xstrophe") || (*token == "*S-")) {
				string spineinfo = token->getSpineInfo();
	 			HTp lastone = laststrophe[spineinfo];
				if (lastone) {
					TokenPair tp;
					tp.first = lastone;
					tp.last = token;
					m_strophes1d.push_back(tp);
					int spine = token->getTrack() - 1;
					m_strophes2d.at(spine).push_back(tp);
					laststrophe[spineinfo] = NULL;
				} else {
					// Improperly placed *Xstrophe, so ignore
					cerr << "WARNING: unmatched strophe end: " << token << " ON LINE " << token->getLineNumber() << endl;
				}
			}
		}
	}

	// Warn about any improperly terminated *strophe:
	for (auto it = laststrophe.begin(); it != laststrophe.end(); ++it) {
		HTp token = it->second;
		if (token != NULL) {
			cerr << "WARNING: unmatched strophe begin: " << token << " ON LINE " << token->getLineNumber() << endl;
		}
	}
}



//////////////////////////////
//
// HumdrumFileStructure::analyzeStrophes --
//

bool HumdrumFileStructure::analyzeStrophes(void) {
	if (!m_analyses.m_strands_analyzed) {
		analyzeStrands();
	}
	analyzeStropheMarkers();

	int scount = (int)m_strand1d.size();
	// bool dataQ;
	vector<HTp> strophestarts;
	strophestarts.reserve(100);
	for (int i=0; i<scount; i++) {
		// dataQ = false;
		HTp current = m_strand1d.at(i).first;
		HTp send = m_strand1d.at(i).last;
		if (!send) {
			continue;
		}
		while (current && (current != send)) {
			if (!current->isInterpretation()) {
				// not a strophe (data not allowed in subspine before strophe marker).
				break;
			}

			if (current->compare(0, 3, "*S/") == 0) {
				int track = current->getTrack();
				HTp first = current->getPreviousFieldToken();
				if (first) {
					int trackp = first->getTrack();
					if (track == trackp) {
						if (first->compare(0, 3, "*S/") == 0) {
							bool found = false;
							for (int j=0; j<(int)strophestarts.size(); j++) {
								if (strophestarts[j] == first) {
									found = true;
									break;
								}
							}
							if (!found) {
								strophestarts.push_back(first);
							}
						}
					}
				}
				bool found = false;
				for (int j=0; j<(int)strophestarts.size(); j++) {
					if (strophestarts[j] == current) {
						found = true;
						break;
					}
				}
				if (!found) {
					strophestarts.push_back(current);
				}
				break;
			}
			current = current->getNextToken();
		}
	}

	// Now store strophe information in tokens.  Currently
	// spine splits are not allowed in strophes.  Spine merges
	// are OK: the first strophe will dominate in a merge.
	for (int i=0; i<(int)strophestarts.size(); i++) {
		HTp current = strophestarts[i];
		if (current->hasStrophe()) {
			continue;
		}
		current->setStrophe(strophestarts[i]);
		current = current->getNextToken();
		while (current) {
			if (current->hasStrophe()) {
				break;
			}
			if (*current == "*Xstrophe") {
				break;
			}
			if (*current == "*S-") {
				// Alternate for *Xstrophe
				break;
			}
			current->setStrophe(strophestarts[i]);
			current = current->getNextToken();
		}
	}

	return true;
}



//////////////////////////////
//
// HumdrumFileStructure::getStropheCount --  Return the number of
//    strophes in the file (for void input), or return the number
//    of strophes for a particular spine (for int input).
//

int HumdrumFileStructure::getStropheCount(void) {
	return (int)m_strophes1d.size();
}


int HumdrumFileStructure::getStropheCount(int spineindex) {
	if ((spineindex < 0) || (spineindex >= (int)m_strophes2d.size())) {
		return 0;
	}
	return (int)m_strophes2d.at(spineindex).size();
}



//////////////////////////////
//
// HumdrumFileStructure::getStropheStart --
//

HTp HumdrumFileStructure::getStropheStart(int index) {
	if ((index < 0) || (index >= (int)m_strophes1d.size())) {
		return NULL;
	}
	return m_strophes1d.at(index).first;
}

HTp HumdrumFileStructure::getStropheStart(int spine, int index) {
		if ((spine < 0) || (index < 0)) {
			return NULL;
		}
		if (spine >= (int)m_strophes2d.size()) {
			return NULL;
		}
		if (index >= (int)m_strophes2d.at(spine).size()) {
			return NULL;
		}
		return m_strophes2d.at(spine).at(index).first;
}



//////////////////////////////
//
// HumdrumFileStructure::getStropheEnd --
//

HTp HumdrumFileStructure::getStropheEnd(int index) {
	if ((index < 0) || (index >= (int)m_strophes1d.size())) {
		return NULL;
	}
	return m_strophes1d.at(index).last;
}


HTp HumdrumFileStructure::getStropheEnd(int spine, int index) {
		if ((spine < 0) || (index < 0)) {
			return NULL;
		}
		if (spine >= (int)m_strophes2d.size()) {
			return NULL;
		}
		if (index >= (int)m_strophes2d.at(spine).size()) {
			return NULL;
		}
		return m_strophes2d.at(spine).at(index).last;
}




//////////////////////////////
//
// HumdrumFileStructure::HumdrumFileStructure -- HumdrumFileStructure
//     constructor.
//

HumdrumFileStructure::HumdrumFileStructure(void) : HumdrumFileBase() {
	// do nothing
}

HumdrumFileStructure::HumdrumFileStructure(const string& filename) :
		HumdrumFileBase() {
	read(filename);
}

HumdrumFileStructure::HumdrumFileStructure(istream& contents) :
		HumdrumFileBase() {
	read(contents);
}



//////////////////////////////
//
// HumdrumFileStructure::~HumdrumFileStructure -- HumdrumFileStructure
//     deconstructor.
//

HumdrumFileStructure::~HumdrumFileStructure() {
	// do nothing
}



//////////////////////////////
//
// HumdrumFileStructure::read --  Read the contents of a file from a file or
//   istream.  The file's structure is analyzed, and then the rhythmic structure
//   is calculated.
//


bool HumdrumFileStructure::read(istream& contents) {
	m_displayError = false;
	if (!readNoRhythm(contents)) {
		return isValid();
	}
	return analyzeStructure();
}


bool HumdrumFileStructure::read(const char* filename) {
	m_displayError = false;
	if (!readNoRhythm(filename)) {
		return isValid();
	}
	return analyzeStructure();
}


bool HumdrumFileStructure::read(const string& filename) {
	m_displayError = false;
	if (!readNoRhythm(filename)) {
		return isValid();
	}
	return analyzeStructure();
}



//////////////////////////////
//
// HumdrumFileStructure::readCsv --  Read the contents of a file from a file or
//   istream in CSV format.  The file's structure is analyzed, and then the
//   rhythmic structure is calculated.
// default value: separator = ","
//


bool HumdrumFileStructure::readCsv(istream& contents,
		const string& separator) {
	m_displayError = false;
	if (!readNoRhythmCsv(contents, separator)) {
		return isValid();
	}
	return analyzeStructure();
}


bool HumdrumFileStructure::readCsv(const char* filename,
		const string& separator) {
	m_displayError = false;
	if (!readNoRhythmCsv(filename, separator)) {
		return isValid();
	}
	return analyzeStructure();
}


bool HumdrumFileStructure::readCsv(const string& filename,
		const string& separator) {
	m_displayError = false;
	if (!readNoRhythmCsv(filename, separator)) {
		return isValid();
	}
	return analyzeStructure();
}



//////////////////////////////
//
// HumdrumFileStructure::readString -- Read the contents from a string.
//    Similar to HumdrumFileStructure::read, but for string data.
//

bool HumdrumFileStructure::readString(const char* contents) {
	m_displayError = false;
	if (!HumdrumFileBase::readString(contents)) {
		return isValid();
	}
	return analyzeStructure();
}


bool HumdrumFileStructure::readString(const string& contents) {
	m_displayError = false;
	if (!HumdrumFileBase::readString(contents)) {
		return isValid();
	}
	return analyzeStructure();
}



//////////////////////////////
//
// HumdrumFileStructure::readStringCsv -- Read the contents from a string.
//    Similar to HumdrumFileStructure::read, but for string data.
// default value: separator = ","
//

bool HumdrumFileStructure::readStringCsv(const char* contents,
		const string& separator) {
	m_displayError = false;
	if (!HumdrumFileBase::readStringCsv(contents, separator)) {
		return isValid();
	}
	return analyzeStructure();
}


bool HumdrumFileStructure::readStringCsv(const string& contents,
		const string& separator) {
	m_displayError = false;
	if (!HumdrumFileBase::readStringCsv(contents, separator)) {
		return isValid();
	}
	return analyzeStructure();
}



//////////////////////////////
//
// HumdrumFileStructure::analyzeStructure -- Analyze global/local
//    parameters and rhythmic structure.
//

bool HumdrumFileStructure::analyzeStructure(void) {
	m_analyses.m_structure_analyzed = false;
	if (!m_analyses.m_strands_analyzed) {
		if (!analyzeStrands()       ) { return isValid(); }
	}
	if (!analyzeGlobalParameters() ) { return isValid(); }
	if (!analyzeLocalParameters()  ) { return isValid(); }
	if (!analyzeTokenDurations()   ) { return isValid(); }
	if (!analyzeTokenDurations()   ) { return isValid(); }
	m_analyses.m_structure_analyzed = true;
	if (!analyzeRhythmStructure()  ) { return isValid(); }
	analyzeSignifiers();
	return isValid();
}



//////////////////////////////
//
// HumdrumFileStructure::analyzeStructureNoRhythm -- Analyze global/local
//    parameters but not rhythmic structure.
//

bool HumdrumFileStructure::analyzeStructureNoRhythm(void) {
	m_analyses.m_structure_analyzed = true;
	if (!m_analyses.m_strands_analyzed) {
		if (!analyzeStrands()          ) { return isValid(); }
	}
	if (!analyzeGlobalParameters() ) { return isValid(); }
	if (!analyzeLocalParameters()  ) { return isValid(); }
	if (!analyzeTokenDurations()   ) { return isValid(); }
	analyzeSignifiers();
	return isValid();
}



/////////////////////////////
//
// HumdrumFileStructure::analyzeRhythmStructure --
//

bool HumdrumFileStructure::analyzeRhythmStructure(void) {
	m_analyses.m_rhythm_analyzed = true;
	setLineRhythmAnalyzed();
	if (!isStructureAnalyzed()) {
		if (!analyzeStructureNoRhythm()) { return isValid(); }
	}

	HTp firstspine = getSpineStart(0);
	if (firstspine && firstspine->isDataType("**recip")) {
		assignRhythmFromRecip(firstspine);
	} else {
		if (!analyzeRhythm()           ) { return isValid(); }
		if (!analyzeDurationsOfNonRhythmicSpines()) { return isValid(); }
	}
	return isValid();
}



//////////////////////////////
//
// HumdrumFileStructure::assignRhythmFromRecip --
//

bool HumdrumFileStructure::assignRhythmFromRecip(HTp spinestart) {
	HTp current = spinestart;

	HumNum duration;
	while (current) {
		if (!current->isData()) {
			current = current->getNextToken();
			continue;
		}
		if (current->isNull()) {
			// This should not occur in a well-formed **recip spine, but
			// treat as a zero duration.
			current = current->getNextToken();
			continue;
		}

		if (strchr(current->c_str(), 'q') != NULL) {
			duration = 0;
		} else {
			duration = Convert::recipToDuration((string)*current);
		}
		current->getLine()->setDuration(duration);
		current = current->getNextToken();
	}

	// now go back and set the absolute position from the start of
	// the file.
	HumNum sum = 0;
	HumdrumFileStructure& hfile = *this;
	for (int i=0; i<getLineCount(); i++) {
		hfile[i].setDurationFromStart(sum);
		if (hfile[i].getDuration() < 0) {
			hfile[i].setDuration(0);
		}
		sum += hfile[i].getDuration();
	}

	// Analyze durations to/from barlines:
	if (!analyzeMeter()) { return false; }
	if (!analyzeNonNullDataTokens()) { return false; }
	return true;
}



//////////////////////////////
//
// HumdrumFileStructure::readNoRhythm -- Similar to the read() functions, but
//    does not parse rhythm (or parameters).
//

bool HumdrumFileStructure::readNoRhythm(istream& infile) {
	return HumdrumFileBase::read(infile);
}


bool HumdrumFileStructure::readNoRhythm(const char* filename) {
	return HumdrumFileBase::read(filename);
}


bool HumdrumFileStructure::readNoRhythm(const string& filename) {
	return HumdrumFileBase::read(filename);
}



//////////////////////////////
//
// HumdrumFileStructure::readNoRhythmCsv -- Similar to the readCsv()
//    functions, but does not parse rhythm (or parameters).
// default value: separator = ","
//

bool HumdrumFileStructure::readNoRhythmCsv(istream& infile,
		const string& seperator) {
	return HumdrumFileBase::readCsv(infile);
}


bool HumdrumFileStructure::readNoRhythmCsv(const char* filename,
		const string& seperator) {
	return HumdrumFileBase::readCsv(filename);
}


bool HumdrumFileStructure::readNoRhythmCsv(const string& filename,
		const string& seperator) {
	return HumdrumFileBase::readCsv(filename);
}



//////////////////////////////
//
// HumdrumFileStructure::readStringNoRhythm -- Read a string, but
//   do not analyze the rhythm (or parameters) in the read data.
//


bool HumdrumFileStructure::readStringNoRhythm(const char* contents) {
	return HumdrumFileBase::readString(contents);
}


bool HumdrumFileStructure::readStringNoRhythm(const string& contents) {
	return HumdrumFileBase::readString(contents);
}



//////////////////////////////
//
// HumdrumFileStructure::readStringNoRhythmCsv -- Read a string, but
//   do not analyze the rhythm (or parameters) in the read data.
// default value: separator = ","
//


bool HumdrumFileStructure::readStringNoRhythmCsv(const char* contents,
		const string& separator) {
	return HumdrumFileBase::readStringCsv(contents);
}


bool HumdrumFileStructure::readStringNoRhythmCsv(const string& contents,
		const string& separator) {
	return HumdrumFileBase::readStringCsv(contents);
}



//////////////////////////////
//
// HumdrumFileStructure::getScoreDuration -- Return the total duration
//    of the score in quarter note units.  Returns zero if no lines in the
//    file, or -1 if there are lines, but no rhythmic analysis has been done.
//

HumNum HumdrumFileStructure::getScoreDuration(void) const {
	if (m_lines.size() == 0) {
		return 0;
	}
	return m_lines.back()->getDurationFromStart();
}



//////////////////////////////
//
// HumdrumFileStructure::tpq -- "Ticks per Quarter-note".  Returns the minimal
//    number of integral time units that divide a quarter note into equal
//    subdivisions.  This value is needed to convert Humdrum data into
//    MIDI file data, MuseData, and MusicXML data.  Also useful for timebase
//    type of operations on the data and describing the durations in terms
//    of integers rather than with fractions.  This function will also
//    consider the implicit durations of non-rhythmic spine data.
//

int HumdrumFileStructure::tpq(void) {
	if (m_ticksperquarternote > 0) {
		return m_ticksperquarternote;
	}
	set<HumNum> durlist = getPositiveLineDurations();
	vector<int> dems;
	for (auto& it : durlist) {
		if (it.getDenominator() > 1) {
			dems.push_back(it.getDenominator());
		}
	}
	int lcm = 1;
	if (dems.size() > 0) {
		lcm = Convert::getLcm(dems);
	}
	m_ticksperquarternote = lcm;
	return m_ticksperquarternote;
}



//////////////////////////////
//
// HumdrumFileStructure::getPositiveLineDurations -- Return a list of all
//    unique token durations in the file.  This function could be expanded
//    to limit the search to a range of lines or to a specific track.
//

set<HumNum> HumdrumFileStructure::getPositiveLineDurations(void) {
	set<HumNum> output;
	for (auto& line : m_lines) {
		if (line->getDuration().isPositive()) {
			output.insert(line->getDuration());
		}
	}
	return output;
}



//////////////////////////////
//
// HumdrumFileStructure::printDurationInfo -- Print the assigned duration
//    of each line in a file.  Useful for debugging.
//

ostream& HumdrumFileStructure::printDurationInfo(ostream& out) {
	for (int i=0; i<getLineCount(); i++) {
		m_lines[i]->printDurationInfo(out) << '\n';
	}
	return out;
}



//////////////////////////////
//
// HumdrumFileStructure::getBarline -- Return the given barline from the file
//   based on the index number.  Negative index accesses from the end of the
//   list.  If the first barline is a pickup measure, then the returned
//   HLp will not be an actual barline line.
//

HLp HumdrumFileStructure::getBarline(int index) const {
	if (index < 0) {
		index += (int)m_barlines.size();
	}
	if (index < 0) {
		return NULL;
	}
	if (index >= (int)m_barlines.size()) {
		return NULL;
	}
	return m_barlines[index];
}



//////////////////////////////
//
// HumdrumFileStructure::getBarlineCount -- Return the number of barlines in
//   the file.  If there is a pickup beat, then the count includes an imaginary
//   barline before the first pickup (and the start of the file will be returned
//   for barline(0).
//

int HumdrumFileStructure::getBarlineCount(void) const {
	return (int)m_barlines.size();
}



///////////////////////////////
//
// HumdrumFileStructure::getBarlineDuration --  Return the duration from the
//    current barline to the next barline in the data.  For the last barline,
//    the duration will be calculated from the end of the data;  The final
//    will have a duration of 0 if there are not notes after the barline
//    in the data.
//

HumNum HumdrumFileStructure::getBarlineDuration(int index) const {
	if (index < 0) {
		index += (int)m_barlines.size();
	}
	if (index < 0) {
		return 0;
	}
	if (index >= (int)m_barlines.size()) {
		return 0;
	}
	HumNum startdur = m_barlines[index]->getDurationFromStart();
	HumNum enddur;
	if (index + 1 < (int)m_barlines.size() - 1) {
		enddur = m_barlines[index+1]->getDurationFromStart();
	} else {
		enddur = getScoreDuration();
	}
	return enddur - startdur;
}



///////////////////////////////
//
// HumdrumFileStructure::getBarlineDurationFromStart -- Return the duration
//    between the start of the Humdrum file and the given barline.
//

HumNum HumdrumFileStructure::getBarlineDurationFromStart(int index) const {
	if (index < 0) {
		index += (int)m_barlines.size();
	}
	if (index < 0) {
		return 0;
	}
	if (index >= (int)m_barlines.size()) {
		return getScoreDuration();
	}
	return m_barlines[index]->getDurationFromStart();
}



///////////////////////////////
//
// HumdrumFileStructure::getBarlineDurationToEnd -- Return the duration
//    between barline and the end of the HumdrumFileStructure.
//

HumNum HumdrumFileStructure::getBarlineDurationToEnd(int index) const {
	if (index < 0) {
		index += (int)m_barlines.size();
	}
	if (index < 0) {
		return 0;
	}
	if (index >= (int)m_barlines.size()) {
		return getScoreDuration();
	}
	return m_barlines[index]->getDurationToEnd();
}


//////////////////////////////
//
// HumdrumFileStructure::setLineRhythmAnalyzed --
//

void HumdrumFileStructure::setLineRhythmAnalyzed(void) {
	for (int i=0; i<(int)m_lines.size(); i++) {
		m_lines[i]->m_rhythm_analyzed = true;
	}
}



//////////////////////////////
//
// HumdrumFileStructure::analyzeRhythm -- Analyze the rhythmic structure
//     of the data.  Returns false if there was a parse error.
//

bool HumdrumFileStructure::analyzeRhythm(void) {
	setLineRhythmAnalyzed();
	if (getMaxTrack() == 0) {
		return true;
	}
	int startline = getTrackStart(1)->getLineIndex();
	int testline;
	HumNum zero(0);

	int i;
	for (int i=1; i<=getMaxTrack(); i++) {
		if (!getTrackStart(i)->hasRhythm()) {
			// Can't analyze rhythm of spines that do not have rhythm.
			continue;
		}
		testline = getTrackStart(i)->getLineIndex();
		if (testline == startline) {
			if (!assignDurationsToTrack(getTrackStart(i), zero)) {
				return false;
			}
		} else {
			// Spine does not start at beginning of data, so
			// the starting position of the spine has to be
			// determined before continuing.  Search for a token
			// which is on a line with assigned duration, then work
			// outwards from that position.
			continue;
		}
	}

	// Go back and analyze spines that do not start at the
	// beginning of the data stream.
	for (i=1; i<=getMaxTrack(); i++) {
		if (!getTrackStart(i)->hasRhythm()) {
			// Can't analyze rhythm of spines that do not have rhythm.
			continue;
		}
		testline = getTrackStart(i)->getLineIndex();
		if (testline > startline) {
			if (!analyzeRhythmOfFloatingSpine(getTrackStart(i))) { return false; }
		}
	}

	if (!analyzeNullLineRhythms()) { return false; }
	fillInNegativeStartTimes();
	assignLineDurations();
	if (!analyzeMeter()) { return false; }
	if (!analyzeNonNullDataTokens()) { return false; }

	return true;
}



//////////////////////////////
//
// HumdrumFileStructure::analyzeMeter -- Store the times from the last barline
//     to the current line, as well as the time to the next barline.
//     the sum of these two will be the duration of the barline, except
//     for barlines, where the getDurationToBarline() will store the
//     duration of the measure staring at that barline.  To get the
//     beat, you will have to figure out the current time signature.
//

bool HumdrumFileStructure::analyzeMeter(void) {

	m_barlines.resize(0);

	int i;
	HumNum sum = 0;
	bool foundbarline = false;
	for (i=0; i<getLineCount(); i++) {
		m_lines[i]->setDurationFromBarline(sum);
		sum += m_lines[i]->getDuration();
		if (m_lines[i]->isBarline()) {
			foundbarline = true;
			m_barlines.push_back(m_lines[i]);
			sum = 0;
		}
		if (m_lines[i]->isData() && !foundbarline) {
			// pickup measure, so set the first measure to the start of the file.
			m_barlines.push_back(m_lines[0]);
			foundbarline = 1;
		}
	}

	sum = 0;
	for (i=getLineCount()-1; i>=0; i--) {
		sum += m_lines[i]->getDuration();
		m_lines[i]->setDurationToBarline(sum);
		if (m_lines[i]->isBarline()) {
			sum = 0;
		}
	}

	return true;
}



//////////////////////////////
//
// HumdrumFileStructure::analyzeTokenDurations -- Calculate the duration of
//   all tokens in spines which posses duration in a file.
//

bool HumdrumFileStructure::analyzeTokenDurations (void) {
	prepareMensurationInformation();
	for (int i=0; i<getLineCount(); i++) {
		if (!m_lines[i]->analyzeTokenDurations(m_parseError)) {
			return isValid();
		}
	}
	return isValid();
}



//////////////////////////////
//
// HumdrumFileStructure::prepareMensurationInformation --
//

bool HumdrumFileStructure::prepareMensurationInformation(void) {
	vector<HTp> menstarts;
	getSpineStartList(menstarts, "**mens");
	if (menstarts.empty()) {
		return true;
	}
	int tracks = getMaxTrack();
	HumdrumFileStructure& infile = *this;
	vector<int> menlev(tracks+1, 2222);
	for (int i=0; i<infile.getLineCount(); i++) {
		if (infile[i].isInterpretation()) {
			for (int j=0; j<infile[i].getFieldCount(); j++) {
				HTp token = infile.token(i, j);
				if (!token->isMens()) {
					continue;
				}
				if (!token->isMensurationSymbol()) {
					continue;
				}
				int track = token->getTrack();
				int mlev = Convert::metToMensurationLevels(*token);
				if (mlev > 0) {
					menlev[track] = mlev;
				}
			}
		}
		if (!infile[i].isData()) {
			continue;
		}
		for (int j=0; j<infile[i].getFieldCount(); j++) {
			HTp token = infile.token(i, j);
			if (!token->isMens()) {
				continue;
			}
			if (token->isNull()) {
				continue;
			}
			int track = token->getTrack();
			token->setValue("auto", "mensuration", "levels", menlev.at(track));
		}
	}
	return true;
}



///////////////////////////////
//
// HumdrumFileStructure::analyzeGlobalParameters -- only allowing layout
//    parameters at the moment.  Global parameters affect the next
//    line which is either a barline, dataline or an interpretation
//    other than a spine manipulator.  Null lines are also not
//    considered.
//

bool HumdrumFileStructure::analyzeGlobalParameters(void) {
	vector<HLp> globals;

//	for (int i=0; i<(int)m_lines.size(); i++) {
//		if (m_lines[i]->isCommentGlobal()) {
//			m_lines[i]->setLayoutParameters();
//		}
//	}

	for (int i=0; i<(int)m_lines.size(); i++) {
		if (m_lines[i]->isCommentGlobal() && (m_lines[i]->find("!!LO:") != string::npos)) {
			m_lines[i]->storeGlobalLinkedParameters();
			globals.push_back(m_lines[i]);
			continue;
		}
		if (!m_lines[i]->hasSpines()) {
			continue;
		}
		if (m_lines[i]->isAllNull())  {
			continue;
		}
		if (m_lines[i]->isCommentLocal()) {
			continue;
		}
		if (globals.empty()) {
			continue;
		}

		// Filter manipulators or not?  At the moment allow
		// global parameters to pass through manipulators.
		// if (m_lines[i]->isManipulator()) {
		// 	continue;
		// }

		for (int j=0; j<(int)m_lines[i]->getFieldCount(); j++) {
			for (int k=0; k<(int)globals.size(); k++) {
				m_lines[i]->token(j)->addLinkedParameterSet(globals[k]->token(0));
			}
		}
		globals.clear();
	}

	return isValid();
}



///////////////////////////////
//
// HumdrumFileStructure::analyzeLocalParameters -- Parses any
//    local comments before a non-null token.
//

bool HumdrumFileStructure::analyzeLocalParameters(void) {
	// analyze backward tokens:

	for (int i=0; i<getStrandCount(); i++) {
		processLocalParametersForStrand(i);
	}

//	for (int i=1; i<=getMaxTrack(); i++) {
//		for (int j=0; j<getTrackEndCount(i); j++) {
//			if (!processLocalParametersForTrack(getTrackEnd(i, j),
//					getTrackEnd(i, j), getTrackStart(i, j))) {
//				return isValid();
//			}
//		}
//	}

	return isValid();
}



//////////////////////////////
//
// HumdrumFileStructure::analyzeDurationsOfNonRhythmicSpines -- Calculate the
//    duration of non-null data token in non-rhythmic spines.
//

bool HumdrumFileStructure::analyzeDurationsOfNonRhythmicSpines(void) {
	// analyze tokens backwards:
	for (int i=1; i<=getMaxTrack(); i++) {
		for (int j=0; j<getTrackEndCount(i); j++) {
			if (getTrackEnd(i, j)->hasRhythm()) {
				continue;
			}
			if (!assignDurationsToNonRhythmicTrack(getTrackEnd(i, j),
					getTrackEnd(i, j))) {
				return isValid();
			}
		}
	}
	return isValid();
}



//////////////////////////////
//
// HumdrumFileStructure::getMinDur -- Return the smallest duration on the
//   line.  If all durations are zero, then return zero; otherwise,
//   return the smallest positive duration.
//

HumNum HumdrumFileStructure::getMinDur(vector<HumNum>& durs,
		vector<HumNum>& durstate) {
	HumNum mindur = 0;
	bool allzero = true;

	for (int i=0; i<(int)durs.size(); i++) {
		if (durs[i].isPositive()) {
			allzero = false;
			if (mindur.isZero()) {
				mindur = durs[i];
			} else if (mindur > durs[i]) {
				mindur = durs[i];
			}
		}
	}
	if (allzero) {
		return mindur;
	}

	for (int i=0; i<(int)durs.size(); i++) {
		if (durstate[i].isPositive() && mindur.isZero()) {
			if (durstate[i].isZero()) {
				// mindur = durstate[i];
			} else if (mindur > durstate[i]) {
				mindur = durstate[i];
			}
		}
	}
	return mindur;
}



//////////////////////////////
//
// HumdrumFileStructure::getTokenDurations -- Extract the duration of rhythmic
//    tokens on the line.
//

bool HumdrumFileStructure::getTokenDurations(vector<HumNum>& durs, int line) {
	durs.resize(0);
	for (int i=0; i<m_lines[line]->getTokenCount(); i++) {
		HumNum dur = m_lines[line]->token(i)->getDuration();
		durs.push_back(dur);
	}
	if (!cleanDurs(durs, line)) {
		return isValid();
	}
	return isValid();
}



//////////////////////////////
//
// HumdrumFileStructure::cleanDurs -- Check if there are grace note and regular
//    notes on a line (not allowed).  Leaves negative durations which
//    indicate undefined durations (needed for keeping track of null
//    tokens in rhythmic spines.
//

bool HumdrumFileStructure::cleanDurs(vector<HumNum>& durs, int line) {
	bool zero     = false;
	bool positive = false;
	for (int i=0; i<(int)durs.size(); i++) {
		if      (durs[i].isPositive()) { positive = true; }
		else if (durs[i].isZero())     { zero     = true; }
	}
	if (zero && positive) {
		stringstream err;
		err << "Error on line " << (line+1) << " grace note and "
		    << " regular note cannot occur on same line." << endl;
		err << "Line: " << *m_lines[line] << endl;
		return setParseError(err);
	}
	return isValid();
}



//////////////////////////////
//
// HumdrumFileStructure::decrementDurStates -- Subtract the line duration from
//   the current line of running durations.  If any duration is less
//   than zero, then a rhythm error exists in the data.
//

bool HumdrumFileStructure::decrementDurStates(vector<HumNum>& durs,
		HumNum linedur, int line) {
	if (linedur.isZero()) {
		return isValid();
	}
	for (int i=0; i<(int)durs.size(); i++) {
		if (!m_lines[line]->token(i)->hasRhythm()) {
			continue;
		}
		durs[i] -= linedur;
		if (durs[i].isNegative()) {
			stringstream err;
			err << "Error: rhythmic error on line " << (line+1)
			    << " field index " << i << endl;
			err << "Duration state is: " << durs[i] << endl;
			return setParseError(err);
		}
	}
	return isValid();
}



//////////////////////////////
//
// HumdrumFileStructure::assignDurationsToTrack -- Assign duration from starts
//    for each rhythmic spine in the file.  Analysis is done recursively, one
//    sub-spine at a time.  Duplicate analyses are prevented by the state
//    variable in the HumdrumToken (currently called rhycheck because it is only
//    used in this function).  After the durationFromStarts have been assigned
//    for the rhythmic analysis of non-data tokens and non-rhythmic spines is
//    done elsewhere.
//

bool HumdrumFileStructure::assignDurationsToTrack(HTp starttoken,
		HumNum startdur) {
	if (!starttoken->hasRhythm()) {
		return isValid();
	}
	int state = starttoken->getState();
	if (!prepareDurations(starttoken, state, startdur)) {
		return isValid();
	}
	return isValid();
}



//////////////////////////////
//
// HumdrumFileStructure::prepareDurations -- Helper function for
//     HumdrumFileStructure::assignDurationsToTrack() which does all of the
//     work for assigning durationFromStart values.
//

bool HumdrumFileStructure::prepareDurations(HTp token, int state,
		HumNum startdur) {
	if (state != token->getState()) {
		return isValid();
	}

	HumNum dursum = startdur;
	token->incrementState();

	if (!setLineDurationFromStart(token, dursum)) { return isValid(); }
	if (token->getDuration().isPositive()) {
		dursum += token->getDuration();
	}
	int tcount = token->getNextTokenCount();

	vector<HTp> reservoir;
	vector<HumNum> startdurs;

	// Assign line durationFromStarts for primary track first.
	while (tcount > 0) {
		for (int t=1; t<tcount; t++) {
			reservoir.push_back(token->getNextToken(t));
			startdurs.push_back(dursum);
		}
		token = token->getNextToken(0);
		if (state != token->getState()) {
			break;
		}
		token->incrementState();
		if (!setLineDurationFromStart(token, dursum)) { return isValid(); }
		if (token->getDuration().isPositive()) {
			dursum += token->getDuration();
		}
		tcount = token->getNextTokenCount();
	}

	if ((tcount == 0) && (token->isTerminateInterpretation())) {
		if (!setLineDurationFromStart(token, dursum)) { return isValid(); }
	}

	// Process secondary tracks next:
	int newstate = state;

	for (int i=(int)reservoir.size()-1; i>=0; i--) {
		prepareDurations(reservoir[i], newstate, startdurs[i]);
	}

	return isValid();
}



//////////////////////////////
//
// HumdrumFileStructure::setLineDurationFromStart -- Set the duration of
//      a line based on the analysis of tokens in the spine.
//

bool HumdrumFileStructure::setLineDurationFromStart(HTp token,
		HumNum dursum) {
	if ((!token->isTerminateInterpretation()) &&
			token->getDuration().isNegative()) {
		// undefined rhythm, so don't assign line duration information:
		return isValid();
	}
	HLp line = token->getOwner();
	if (line->getDurationFromStart().isNegative()) {
		line->setDurationFromStart(dursum);
	} else if (line->getDurationFromStart() != dursum) {
		stringstream err;
		err << "Error: Inconsistent rhythm analysis occurring near line "
		    << token->getLineNumber() << endl;
		err << "Expected durationFromStart to be: " << dursum
		    << " but found it to be " << line->getDurationFromStart() << endl;
		err << "Line: " << *line << endl;
		return setParseError(err);
	}

	return isValid();
}



//////////////////////////////
//
// HumdrumFileStructure::analyzeRhythmOfFloatingSpine --  This analysis
//    function is used to analyze the rhythm of spines which do not start at
//    the beginning of the data.  The function searches for the first line
//    which has an assigned durationFromStart value, and then uses that
//    as the basis for assigning the initial durationFromStart position
//    for the spine.
//

bool HumdrumFileStructure::analyzeRhythmOfFloatingSpine(
		HTp spinestart) {
	HumNum dursum = 0;
	HumNum founddur = 0;
	HTp token = spinestart;
	int tcount = token->getNextTokenCount();

	// Find a known durationFromStart for a line in the Humdrum file, then
	// use that to calculate the starting duration of the floating spine.
	if (token->getDurationFromStart().isNonNegative()) {
		founddur = token->getLine()->getDurationFromStart();
	} else {
		tcount = token->getNextTokenCount();
		while (tcount > 0) {
			if (token->getDurationFromStart().isNonNegative()) {
				founddur = token->getLine()->getDurationFromStart();
				break;
			}
			if (token->getDuration().isPositive()) {
				dursum += token->getDuration();
			}
			token = token->getNextToken(0);
		}
	}

	if (founddur.isZero()) {
		return setParseError("Error cannot link floating spine to score.");
	}

	if (!assignDurationsToTrack(spinestart, founddur - dursum)) {
		return isValid();
	}

	return isValid();
}



//////////////////////////////
//
// HumdrumFileStructure::analyzeNullLineRhythms -- When a series of null-token
//    data line occur between two data lines possessing a start duration,
//    then split the duration between those two lines amongst the null-token
//    lines.  For example if a data line starts at time 15, and there is one
//    null-token line before another data line at time 16, then the null-token
//    line will be assigned to the position 15.5 in the score.
//

bool HumdrumFileStructure::analyzeNullLineRhythms(void) {
	vector<HLp> nulllines;
	HLp previous = NULL;
	HLp next = NULL;
	HumNum dur;
	HumNum startdur;
	HumNum enddur;
	int i, j;
	for (i=0; i<(int)m_lines.size(); i++) {
		if (!m_lines[i]->hasSpines()) {
			continue;
		}
		if (m_lines[i]->isAllRhythmicNull()) {
			if (m_lines[i]->isData()) {
				nulllines.push_back(m_lines[i]);
			}
			continue;
		}
		dur = m_lines[i]->getDurationFromStart();
		if (dur.isNegative()) {
			if (m_lines[i]->isData()) {
				stringstream err;
				err << "Error: found an unexpected negative duration on line "
			       << m_lines[i]->getDurationFromStart()<< endl;
				err << "Line: " << *m_lines[i] << endl;
				return setParseError(err);
			} else {
				continue;
			}
		}
		next = m_lines[i];
		if (previous == NULL) {
			previous = next;
			nulllines.resize(0);
			continue;
		}
		startdur = previous->getDurationFromStart();
		enddur   = next ->getDurationFromStart();
		HumNum gapdur = enddur - startdur;
		HumNum nulldur = gapdur / ((int)nulllines.size() + 1);
		for (j=0; j<(int)nulllines.size(); j++) {
			nulllines[j]->setDurationFromStart(startdur + (nulldur * (j+1)));
		}
		previous = next;
		nulllines.resize(0);
	}
	return isValid();
}



//////////////////////////////
//
// HumdrumFileStructure::fillInNegativeStartTimes -- Negative line durations
//    after the initial rhythmAnalysis mean that the lines are not data line.
//    Duplicate the duration of the next non-negative duration for all negative
//    durations.
//

void HumdrumFileStructure::fillInNegativeStartTimes(void) {
	int i;
	HumNum lastdur = -1;
	HumNum dur;
	for (i=(int)m_lines.size()-1; i>=0; i--) {
		dur = m_lines[i]->getDurationFromStart();
		if (dur.isNegative() && lastdur.isNonNegative()) {
			m_lines[i]->setDurationFromStart(lastdur);
		}
		if (dur.isNonNegative()) {
			lastdur = dur;
			continue;
		}
	}

	// fill in start times for ending comments
	for (i=0; i<(int)m_lines.size(); i++) {
		dur = m_lines[i]->getDurationFromStart();
		if (dur.isNonNegative()) {
			lastdur = dur;
		} else {
			m_lines[i]->setDurationFromStart(lastdur);
		}
	}
}



//////////////////////////////
//
// HumdrumFileStructure::assignLineDurations --  Calculate the duration of lines
//   based on the durationFromStart of the current line and the next line.
//

void HumdrumFileStructure::assignLineDurations(void) {
	HumNum startdur;
	HumNum enddur;
	HumNum dur;
	for (int i=0; i<(int)m_lines.size()-1; i++) {
		startdur = m_lines[i]->getDurationFromStart();
		enddur = m_lines[i+1]->getDurationFromStart();
		dur = enddur - startdur;
		m_lines[i]->setDuration(dur);
	}
	if (m_lines.size() > 0) {
		m_lines.back()->setDuration(0);
	}
}



//////////////////////////////
//
// HumdrumFileStructure::assignDurationsToNonRhythmicTrack --  After the basic
//   rhythmAnalysis has been done, go back and assign durations to non-rhythmic
//   spine tokens based on the lineFromStart values of the lines that they
//   occur on as well as the distance in the file to the next non-null token for
//   that spine.
//

bool HumdrumFileStructure::assignDurationsToNonRhythmicTrack(
		HTp endtoken, HTp current) {

	string spineinfo = endtoken->getSpineInfo();
	HTp token = endtoken;

	while (token) {
		if (token->getSpineInfo() != spineinfo) {
			if (token->getSpineInfo().find("b") != std::string::npos) {
				break;
			}
			if (spineinfo.find("b") != std::string::npos) {
				break;
			}
		}
		int tcount = token->getPreviousTokenCount();
		if (tcount == 0) {
			break;
		}
		if (tcount > 1) {
			for (int i=1; i<tcount; i++) {
				HTp ptok = token->getPreviousToken(i);
				if (!assignDurationsToNonRhythmicTrack(ptok, current)) {
					return isValid();
				}
			}
		}
		if (token->isData()) {
			if (!token->isNull()) {
				token->setDuration(current->getDurationFromStart() -
					token->getDurationFromStart());
				current = token;
			}
		}
		token = token->getPreviousToken(0);
	}

	return isValid();
}



//////////////////////////////
//
// HumdrumFileStructure::processLocalParametersForStrand --
//

void HumdrumFileStructure::processLocalParametersForStrand(int index) {
	HTp sstart = getStrandStart(index);
	HTp send = getStrandEnd(index);
	HTp tok = send;
	HTp dtok = NULL;
	while (tok) {
		if (tok->isData()) {
			dtok = tok;
		} else if (tok->isBarline()) {
			// layout parameters allowed for barlines
			dtok = tok;
		} else if (tok->isInterpretation() && (*tok != "*")) {
			// layout parameters allowed for non-null interpretations
			dtok = tok;
		} else if (tok->isCommentLocal()) {
			if (tok->find("!LO:") == 0) {
				tok->storeParameterSet();
				if (dtok) {
					dtok->addLinkedParameterSet(tok);
				}
			}
		}
		if (tok == sstart) {
			break;
		}
		tok = tok->getPreviousToken();
	}
}




//////////////////////////////
//
// HumdrumFileStructure::processLocalParametersForTrack --  Search for
//   local parameters backwards in each spine and fill in the HumHash
//   for the token to which the parameter is to be applied.
//
// No longer used.
//

bool HumdrumFileStructure::processLocalParametersForTrack(
		HTp starttok, HTp current) {

	HTp token = starttok;
	int tcount = token->getPreviousTokenCount();

	while (tcount > 0) {
		for (int i=1; i<tcount; i++) {
			if (!processLocalParametersForTrack(
					token->getPreviousToken(i), current)) {
				return isValid();
			}
		}
		HTp prevtoken = token->getPreviousToken();
		if (prevtoken->isSplitInterpretation()) {
			if (token != prevtoken->m_nextTokens[0]) {
				// terminate if not most primary subspine
				return true;
			}
		} else if (!(token->isNull() && token->isManipulator())) {
			if (token->isCommentLocal()) {
				checkForLocalParameters(token, current);
			} else {
				current = token;
			}
		}

		// Follow previous data token 0 since 1 and higher are handled above.
		token = token->getPreviousToken(0);
		tcount = token->getPreviousTokenCount();
	}

	return isValid();
}



//////////////////////////////
//
// HumdrumFileStructure::checkForLocalParameters -- Helper function for
//     HumdrumFileStructure::processLocalParametersForTrack.  Only allowing
//     layout parameters currently.
//

void HumdrumFileStructure::checkForLocalParameters(HTp token,
		HTp current) {
	if (token->size() < 1) {
		return;
	}
	int loc1 = (int)token->find(":");
	if (loc1 == (int)string::npos) {
		return;
	}
	int loc2 = (int)token->substr(loc1).find(":");
	if (loc2 == (int)string::npos) {
		return;
	}
	loc2 += loc1 + 1;
	int sloc = (int)token->find(" ");
	if (sloc != (int)string::npos) {
		if ((sloc < loc1) || (sloc < loc2)) {
			return;
		}
	}
	sloc = (int)token->find("\t");
	if (sloc != (int)string::npos) {
		if ((sloc < loc1) || (sloc < loc2)) {
			return;
		}
	}
	// Looks like a parameter so parse the comment:
	current->setParameters(token);
}



//////////////////////////////
//
// HumdrumFileStructure::analyzeStrands -- Analyze spine strands.
//

bool HumdrumFileStructure::analyzeStrands(void) {
	m_analyses.m_strands_analyzed = true;
	int spines = getSpineCount();
	m_strand1d.clear();
	m_strand2d.clear();
	int i, j;
	for (i=0; i<spines; i++) {
		HTp tok = getSpineStart(i);
		m_strand2d.resize(m_strand2d.size()+1);
		analyzeSpineStrands(m_strand2d.back(), tok);
	}

	for (i=0; i<(int)m_strand2d.size(); i++) {
		std::sort(m_strand2d[i].begin(), m_strand2d[i].end(),
				sortTokenPairsByLineIndex);
		for (j=0; j<(int)m_strand2d[i].size(); j++) {
			m_strand1d.push_back(m_strand2d[i][j]);
		}
	}

	assignStrandsToTokens();

	resolveNullTokens();

	analyzeLocalParameters();

	analyzeStrophes();

	return isValid();
}



///////////////////////////////
//
// HumdrumFileStructure::resolveNullTokens --
//

void HumdrumFileStructure::resolveNullTokens(void) {
	if (m_analyses.m_nulls_analyzed) {
		return;
	}
	m_analyses.m_nulls_analyzed = true;
	if (!areStrandsAnalyzed()) {
		analyzeStrands();
	}

	HTp token;
	HTp data = NULL;
	HTp strandend;
	for (int s=0; s<(int)m_strand1d.size(); s++) {
		token = getStrandStart(s);
		strandend = getStrandEnd(s);
		while (token != strandend) {
			if (!token->isData()) {
				token = token->getNextToken();
				continue;
			}
			if (data == NULL) {
				data = token;
				token->setNullResolution(data);
				token = token->getNextToken();
				continue;
			}
			if (token->isNull()) {
				token->setNullResolution(data);
			} else {
				data = token;
			}
			token = token->getNextToken();
		}
	}
}



//////////////////////////////
//
// HumdrumFileStructure::assignStrandsToTokens -- Store the 1D strand
//    index number for each token in the file.  Global tokens will have
//    strand index set to -1.
//

void HumdrumFileStructure::assignStrandsToTokens(void) {
	HTp tok;
	for (int i=0; i<(int)m_strand1d.size(); i++) {
		tok = m_strand1d[i].first;
		while (tok != NULL) {
			tok->setStrandIndex(i);
			tok = tok->getNextToken();
		}
	}
}



//////////////////////////////
//
// HumdrumFileStructure::analyzeSpineStrands -- Fill in the list of
//   strands in a single spine.
//

void HumdrumFileStructure::analyzeSpineStrands(vector<TokenPair>& ends,
		HTp starttok) {

	ends.resize(ends.size()+1);
	int index = (int)ends.size()-1;
	ends[index].first = starttok;
	HTp tok = starttok;
	HTp lasttok = starttok;
	while (tok != NULL) {
		lasttok = tok;
		if ((tok->getSubtrack() > 1) && (tok->isMerge())) {
			// check to the left: if the left primary/sub spine also has
			// a *v, then this is the end of this strand; otherwise, the
			// strand continues.
			if (tok->getPreviousFieldToken()->isMerge()) {
				ends[index].last = tok;
				return;
			} else {
				tok = tok->getNextToken();
				continue;
			}
		}
		if (tok->isTerminator()) {
			ends[index].last = tok;
			return;
		}
		if (tok->getNextTokenCount() > 1) {
			// should only be 2, but allow for generalizing in the future.
			for (int j=1; j<tok->getNextTokenCount(); j++) {
				analyzeSpineStrands(ends, tok->getNextToken(j));
			}
		}
		tok = tok->getNextToken();
	}

	cerr << "!!WARNING: spine " << starttok->getSpineInfo() << " is not terminated by *-" << endl;
	// Maybe set last to null, but then programs would have to also
	// check for null for end of track.
	ends[index].last = lasttok;
}


//////////////////////////////
//
// HumdrumFileStructure::getStrandCount --
//

int HumdrumFileStructure::getStrandCount(void) {
	if (!areStrandsAnalyzed()) {
		analyzeStrands();
	}
	return (int)m_strand1d.size();
}


int HumdrumFileStructure::getStrandCount(int spineindex) {
	if (!areStrandsAnalyzed()) {
		analyzeStrands();
	}
	if (spineindex < 0) {
		return 0;
	}
	if (spineindex >= (int)m_strand2d.size()) {
		return 0;
	}
	return (int)m_strand2d[spineindex].size();
}



//////////////////////////////
//
// HumdrumFileStructure::getStrandStart -- Return the first token
//    in the a strand.
//

HTp HumdrumFileStructure::getStrandStart(int index) {
	if (!areStrandsAnalyzed()) {
		analyzeStrands();
	}
	return m_strand1d[index].first;
}


HTp HumdrumFileStructure::getStrandEnd(int index) {
	if (!areStrandsAnalyzed()) {
		analyzeStrands();
	}
	return m_strand1d[index].last;
}


HTp HumdrumFileStructure::getStrandStart(int sindex,
		int index) {
	if (!areStrandsAnalyzed()) {
		analyzeStrands();
	}
	return m_strand2d[sindex][index].first;
}


HTp HumdrumFileStructure::getStrandEnd(int sindex, int index) {
	if (!areStrandsAnalyzed()) {
		analyzeStrands();
	}
	return m_strand2d[sindex][index].last;
}



//////////////////////////////
//
// HumdrumFileStructure::hasFilters -- Returns true if has any
//    reference records starting with "!!!filter:" or "!!!!filter:".
//

bool HumdrumFileStructure::hasFilters(void) {
	HumdrumFileBase& infile = *this;
	vector<HLp> refs  = infile.getGlobalReferenceRecords();
	for (int i=0; i<(int)refs.size(); i++) {
		if (refs[i]->getGlobalReferenceKey() == "filter") {
			return true;
		}
	}
	return false;
}



//////////////////////////////
//
// HumdrumFileStructure::hasGlobalFilters -- Returns true if has any
//    reference records starting with "!!!filter:".
//

bool HumdrumFileStructure::hasGlobalFilters(void) {
	HumdrumFileBase& infile = *this;
	for (int i=0; i<infile.getLineCount(); i++) {
		if (!infile[i].isComment()) {
			continue;
		}
		HTp token = infile.token(i, 0);
		if (token->compare(0, 10, "!!!filter:") == 0) {
			return true;
		}
	}
	return false;
}



//////////////////////////////
//
// HumdrumFileStructure::hasUniversalFilters -- Returns true if has any
//    reference records starting with "!!!!filter:".
//

bool HumdrumFileStructure::hasUniversalFilters(void) {
	HumdrumFileBase& infile = *this;
	vector<HLp> refs  = infile.getUniversalReferenceRecords();
	for (int i=0; i<(int)refs.size(); i++) {
		if (refs[i]->getUniversalReferenceKey() == "filter") {
			return true;
		}
	}
	return false;
}



//////////////////////////////
//
// HumdrumFileStructure::analyzeSignifiers --
//

void HumdrumFileStructure::analyzeSignifiers(void) {
	HumdrumFileStructure& infile = *this;
	for (int i=0; i<getLineCount(); i++) {
		if (!infile[i].isSignifier()) {
			continue;
		}
		m_signifiers.addSignifier(infile[i].getText());
	}
}



//////////////////////////////
//
// HumdrumFileStructure::getKernLinkSignifier -- used for linking two
//     non-standard slur/tie ends together.
//

std::string HumdrumFileStructure::getKernLinkSignifier(void) {
	return m_signifiers.getKernLinkSignifier();
}



//////////////////////////////
//
// HumdrumFileStructure::getKernAboveSignifier -- used to place things
//     "above" (note on staff above, slurs/ties with an "above" orientation,
//     etc.
//

std::string HumdrumFileStructure::getKernAboveSignifier(void) {
	return m_signifiers.getKernAboveSignifier();
}



//////////////////////////////
//
// HumdrumFileStructure::getKernBelowSignifier -- Used to place things
//     "below" (note on staff above, slurs/ties with an "below" orientation,
//     etc.
//

std::string HumdrumFileStructure::getKernBelowSignifier(void) {
	return m_signifiers.getKernBelowSignifier();
}





//////////////////////////////
//
// HumdrumLine::getMidiPitches: Get MIDI note numbers for **kern pitches on line.
// 0 = rest, negative values are tied notes from previously in the score.
//

void HumdrumLine::getMidiPitches(std::vector<int>& output) {

	HumdrumLine& line = *this;
	output.clear();
	if (!line.isData()) {
		return;
	}
	vector<int> tnotes;
	for (int i=0; i<line.getFieldCount(); i++) {
		HTp token = line.token(i);
		if (!token->isKern()) {
			continue;
		}
		if (token->isNull()) {
			return;
		}
		token->getMidiPitches(tnotes);
		output.insert(output.end(), tnotes.begin(), tnotes.end());
	}

}


std::vector<int> HumdrumLine::getMidiPitches(void) {
	vector<int> output;
	this->getMidiPitches(output);
	return output;
}


void HumdrumLine::getMidiPitchesSortHL(std::vector<int>& output) {
	output.clear();
	this->getMidiPitches(output);
	sort(output.begin(), output.end(),
		[](const int& a, const int& b) {
			return abs(a) > abs(b);
		}
	);
}


std::vector<int> HumdrumLine::getMidiPitchesSortHL(void) {
	vector<int> output;
	this->getMidiPitchesSortHL(output);
	return output;
}


void HumdrumLine::getMidiPitchesSortLH(std::vector<int>& output) {
	output.clear();
	this->getMidiPitches(output);
	sort(output.begin(), output.end(),
		[](const int& a, const int& b) {
			return abs(a) < abs(b);
		}
	);
}


std::vector<int> HumdrumLine::getMidiPitchesSortLH(void) {
	vector<int> output;
	this->getMidiPitchesSortLH(output);
	return output;
}



//////////////////////////////
//
// HumdrumLine::getMidiPitches: Get MIDI note numbers for **kern pitches on line.
// Null tokens are resulved to the token which is being sustained.
// 0 = rest, negative values are tied notes from previously in the score.
//

void HumdrumLine::getMidiPitchesResolveNull(std::vector<int>& output) {
	HumdrumLine& line = *this;
	output.clear();
	if (!line.isData()) {
		return;
	}
	vector<int> tnotes;
	for (int i=0; i<line.getFieldCount(); i++) {
		HTp token = line.token(i);
		if (!token->isKern()) {
			continue;
		}
		token->getMidiPitchesResolveNull(tnotes);
		output.insert(output.end(), tnotes.begin(), tnotes.end());
	}
}


std::vector<int> HumdrumLine::getMidiPitchesResolveNull(void) {
	vector<int> output;
	this->getMidiPitchesResolveNull(output);
	return output;
}


void HumdrumLine::getMidiPitchesResolveNullSortHL(std::vector<int>& output) {
	output.clear();
	this->getMidiPitchesResolveNull(output);
	sort(output.begin(), output.end(),
		[](const int& a, const int& b) {
			return abs(a) > abs(b);
		}
	);
}


std::vector<int> HumdrumLine::getMidiPitchesResolveNullSortHL (void) {
	vector<int> output;
	this->getMidiPitchesResolveNullSortHL(output);
	return output;
}


void HumdrumLine::getMidiPitchesResolveNullSortLH(std::vector<int>& output) {
	output.clear();
	this->getMidiPitchesResolveNull(output);
	sort(output.begin(), output.end(),
		[](const int& a, const int& b) {
			return abs(a) < abs(b);
		}
	);
}


std::vector<int> HumdrumLine::getMidiPitchesResolveNullSortLH (void) {
	vector<int> output;
	this->getMidiPitchesResolveNullSortLH(output);
	return output;
}



//////////////////////////////
//
// HumdrumLine::HumdrumLine -- HumdrumLine constructor.
//

HumdrumLine::HumdrumLine(void) : string() {
	m_owner = NULL;
	m_duration = -1;
	m_durationFromStart = -1;
	setPrefix("!!");
}


HumdrumLine::HumdrumLine(const string& aString) : string(aString) {
	m_owner = NULL;
	if ((this->size() > 0) && (this->back() == 0x0d)) {
		this->resize(this->size() - 1);
	}
	m_duration = -1;
	m_durationFromStart = -1;
	setPrefix("!!");
	createTokensFromLine();
}


HumdrumLine::HumdrumLine(const char* aString) : string(aString) {
	m_owner = NULL;
	if ((this->size() > 0) && (this->back() == 0x0d)) {
		this->resize(this->size() - 1);
	}
	m_duration = -1;
	m_durationFromStart = -1;
	setPrefix("!!");
	createTokensFromLine();
}


HumdrumLine::HumdrumLine(HumdrumLine& line)  : string((string)line) {
	m_lineindex           = line.m_lineindex;
	m_duration            = line.m_duration;
	m_durationFromStart   = line.m_durationFromStart;
	m_durationFromBarline = line.m_durationFromBarline;
	m_durationToBarline   = line.m_durationToBarline;
	m_tokens.resize(line.m_tokens.size());
	for (int i=0; i<(int)m_tokens.size(); i++) {
		m_tokens[i] = new HumdrumToken(*line.m_tokens[i], this);
	}
	m_tabs.resize(line.m_tabs.size());
	for (int i=0; i<(int)m_tabs.size(); i++) {
		m_tabs.at(i) = line.m_tabs.at(i);
	}
	m_owner = NULL;
}


HumdrumLine::HumdrumLine(HumdrumLine& line, void* owner) : string((string)line) {
	m_lineindex           = line.m_lineindex;
	m_duration            = line.m_duration;
	m_durationFromStart   = line.m_durationFromStart;
	m_durationFromBarline = line.m_durationFromBarline;
	m_durationToBarline   = line.m_durationToBarline;
	m_tokens.resize(line.m_tokens.size());
	for (int i=0; i<(int)m_tokens.size(); i++) {
		m_tokens[i] = new HumdrumToken(*line.m_tokens[i], this);
	}
	m_tabs.resize(line.m_tabs.size());
	for (int i=0; i<(int)m_tabs.size(); i++) {
		m_tabs.at(i) = line.m_tabs.at(i);
	}
	m_owner = owner;
}



//////////////////////////////
//
// HumdrumLine::operator= --
//

HumdrumLine& HumdrumLine::operator=(HumdrumLine& line) {
	m_lineindex           = line.m_lineindex;
	m_duration            = line.m_duration;
	m_durationFromStart   = line.m_durationFromStart;
	m_durationFromBarline = line.m_durationFromBarline;
	m_durationToBarline   = line.m_durationToBarline;
	m_tokens.resize(line.m_tokens.size());
	for (int i=0; i<(int)m_tokens.size(); i++) {
		m_tokens[i] = new HumdrumToken(*line.m_tokens[i], this);
	}
	m_tabs.resize(line.m_tabs.size());
	for (int i=0; i<(int)m_tabs.size(); i++) {
		m_tabs.at(i) = line.m_tabs.at(i);
	}
	m_owner = NULL;
	return *this;
}



//////////////////////////////
//
// HumdrumLine::~HumdrumLine -- HumdrumLine deconstructor.
//

HumdrumLine::~HumdrumLine() {
	// free stored HumdrumTokens:
	for (int i=0; i<(int)m_tokens.size(); i++) {
		if (m_tokens[i] != NULL) {
			delete m_tokens[i];
			m_tokens[i] = NULL;
		}
	}
}



//////////////////////////////
//
// HumdrumLine::setLineFromCsv -- Read a HumdrumLine from a CSV line.
// default value: separator = ","
//

void HumdrumLine::setLineFromCsv(const char* csv, const string& separator) {
	string temp = csv;
	setLineFromCsv(temp);
}



void HumdrumLine::setLineFromCsv(const string& csv, const string& separator) {
	if (csv.size() < 1) {
		return;
	}
	string newcsv = csv;
	if ((newcsv.size() > 0) && (newcsv.back() == 0x0d)) {
		newcsv.resize(newcsv.size() - 1);
	}
	// construct tab-delimited string
	string output;
	bool inquote = false;

	if ((newcsv.size() >= 2) && (newcsv[0] == '!') && (newcsv[1] == '!')) {
		// Global commands and reference records which do not start with a
		// quote are considered to be literal.
		this->setText(newcsv);
		return;
	}

	for (int i=0; i<(int)newcsv.size(); i++) {
		if ((newcsv[i] == '"') && !inquote) {
			inquote = true;
			continue;
		}
		if (inquote && (newcsv[i] == '"') && (newcsv[i+1] == '"')
				&& (i < (int)newcsv.length()-1)) {
			output += '"';
			i++;
			continue;
		}
		if (newcsv[i] == '"') {
			inquote = false;
			continue;
		}
		if ((!inquote) && (newcsv.substr(i, separator.size()) == separator)) {
			output += '\t';
			i += (int)separator.size() - 1;
			continue;
		}
		output += newcsv[i];
	}
	string& value = *this;
	value = output;
}



//////////////////////////////
//
// HumdrumLine::setText -- Get the textual content of the line.  Note that
//    you may need to run HumdrumLine::createLineFromTokens() if the tokens
//    of the line have changed.
//

void HumdrumLine::setText(const string& text) {
	string::assign(text);
}



//////////////////////////////
//
// HumdrumLine::getText --
//

string HumdrumLine::getText(void) {
	return string(*this);
}



//////////////////////////////
//
// HumdrumLine::clear -- Remove stored tokens.
//

void HumdrumLine::clear(void) {
	for (int i=0; i<(int)m_tokens.size(); i++) {
		if (m_tokens[i] != NULL) {
			delete m_tokens[i];
			m_tokens[i] = NULL;
		}
	}
	m_tokens.clear();
	m_tabs.clear();
	m_rhythm_analyzed = false;
}



//////////////////////////////
//
// HumdrumLine::equalChar -- return true if the character at the given
//     index is the given char.
//

bool HumdrumLine::equalChar(int index, char ch) const {
	if ((int)size() <= index) {
		return false;
	}
	if (index < 0) {
		return false;
	}
	if (((string)(*this))[index] == ch) {
		return true;
	} else {
		return false;
	}
}



//////////////////////////////
//
// HumdrumLine::isKernBoundaryStart -- Return true if the
//    line does not have any null tokens in **kern data which
//    refer to data tokens above the line.
//

bool HumdrumLine::isKernBoundaryStart(void) const {
	if (!isData()) {
		return false;
	}
	for (int i=0; i<getFieldCount(); i++) {
		if (!token(i)->isDataType("**kern")) {
			continue;
		}
		if (token(i)->isNull()) {
			return false;
		}
	}
	return true;
}



//////////////////////////////
//
// HumdrumLine::isGraceLine --
//

bool HumdrumLine::isGraceLine(void) {
	if (!this->isData()) {
		return false;
	}
	if (this->getDuration() == 0) {
		return true;
	} else {
		return false;
	}
}



//////////////////////////////
//
// HumdrumLine::isKernBoundaryEnd -- Return true if the next
//    data line contains no null tokens in the **kern spines.
//    Assuming that a **kern spine split always starts with
//    a non-null token.
//

bool HumdrumLine::isKernBoundaryEnd(void) const {
	if (!isData()) {
		return false;
	}
	HTp ntok;
	for (int i=0; i<getFieldCount(); i++) {
		if (!token(i)->isDataType("**kern")) {
			continue;
		}
		ntok = token(i)->getNextToken();
		if (ntok == NULL) {
			continue;
		}
		while ((ntok != NULL) && !ntok->isData()) {
			ntok = ntok->getNextToken();
		}
		if (ntok == NULL) {
			continue;
		}
		if (ntok->isNull()) {
			return false;
		}
	}
	return true;
}



//////////////////////////////
//
// HumdrumLine::isComment -- Returns true if the first character
//   in the string is '!'. Could be local, global, or a reference record.
//

bool HumdrumLine::isComment(void) const {
	return equalChar(0, '!');
}



//////////////////////////////
//
// HumdrumLine::isCommentLocal -- Returns true if a local comment.
//

bool HumdrumLine::isCommentLocal(void) const {
	return equalChar(0, '!') && !equalChar(1, '!');
}



//////////////////////////////
//
// HumdrumLine::isCommentGlobal -- Returns true if a local comment.
//

bool HumdrumLine::isCommentGlobal(void) const {
	return equalChar(0, '!') && equalChar(1, '!');
}



//////////////////////////////
//
// HumdrumLine::isCommentUniversal -- Returns true if a universal comment.
//

bool HumdrumLine::isCommentUniversal(void) const {
	return equalChar(3, '!') && equalChar(2, '!') && equalChar(1, '!') && equalChar(0, '!');
}



//////////////////////////////
//
// HumdrumLine::isReference -- Returns true if a reference record.
//

bool HumdrumLine::isReference(void) const {
	return isGlobalReference() || isUniversalReference();
}



//////////////////////////////
//
// HumdrumLine::isGlobalReference -- Returns true if a global reference record.
//   Meaning that it is in the form:
//     !!!KEY: VALUE
//

bool HumdrumLine::isGlobalReference(void) const {
	if (this->size() < 5) {
		return false;
	}
	if (this->compare(0, 3, "!!!") != 0) {
		return false;
	}
	if (this->at(3) == '!') {
		return false;
	}
	size_t spaceloc = this->find(" ");
	size_t tabloc = this->find("\t");
	size_t colloc = this->find(":");
	if (colloc == string::npos) {
		return false;
	}
	if ((spaceloc != string::npos) && (spaceloc < colloc)) {
		return false;
	}
	if ((tabloc != string::npos) && (tabloc < colloc)) {
		return false;
	}
	return true;
}



//////////////////////////////
//
// HumdrumLine::isUniversalReference -- Returns true if
//     a universal reference record.
//

bool HumdrumLine::isUniversalReference(void) const {

	if (this->size() < 5) {
		return false;
	}
	if (this->substr(0, 4) != "!!!!") {
		return false;
	}
	if ((*this)[4] == '!') {
		return false;
	}
	size_t spaceloc = this->find(" ");
	size_t tabloc = this->find("\t");
	size_t colloc = this->find(":");
	if (colloc == string::npos) {
		return false;
	}
	if ((spaceloc != string::npos) && (spaceloc < colloc)) {
		return false;
	}
	if ((tabloc != string::npos) && (tabloc < colloc)) {
		return false;
	}
	return true;
}




//////////////////////////////
//
// HumdrumLine::isSignifier -- Returns true if a !!!RDF reference record.
//

bool HumdrumLine::isSignifier(void) const {
	if (this->size() < 9) {
		return false;
	}
	if (this->substr(0, 8) != "!!!RDF**") {
		return false;
	}
	return true;
}



//////////////////////////////
//
// HumdrumLine::getReferenceKey -- Return reference key if a reference
//     record.  Otherwise returns an empty string.
//

string HumdrumLine::getReferenceKey(void) const {
	if (this->size() < 4) {
		return "";
	}
	if (this->substr(0, 3) != "!!!") {
		return "";
	}
	if (this->at(3) != '!') {
		return getGlobalReferenceKey();
	} else {
		return getUniversalReferenceKey();
	}
}



//////////////////////////////
//
// HumdrumLine::getReferenceValue -- Return reference value if a reference
//     record.  Otherwise returns an empty string.
//

string HumdrumLine::getReferenceValue(void) const {
	if (this->size() < 4) {
		return "";
	}
	if (this->substr(0, 3) != "!!!") {
		return "";
	}
	if (this->at(3) != '!') {
		return getGlobalReferenceValue();
	} else {
		return getUniversalReferenceValue();
	}
}



//////////////////////////////
//
// HumdrumLine::getUniversalReferenceValue -- Return reference value if a
//     universal reference record.  Otherwise returns an empty string.
//

string HumdrumLine::getUniversalReferenceValue(void) const {
	if (this->size() < 6) {
		return "";
	}
	if (this->substr(0, 4) != "!!!!") {
		return "";
	}
	if ((*this)[4] == '!') {
		return "";
	}

	size_t colloc = this->find(":");
	if (colloc == string::npos) {
		return "";
	}
	int index = (int)colloc + 1;
	for (int i=index; i<(int)this->size(); i++) {
		if (isspace(this->at(i))) {
			index++;
			continue;
		}
		break;
	}
	if (index >= (int)this->size()) {
		return "";
	}
	string output = this->substr(index);
	for (int i=(int)output.size() - 1; i>=0; i--) {
		if (isspace(output.at(i))) {
			output.resize((int)output.size() - 1);
			continue;
		}
		break;
	}
	return output;
}



//////////////////////////////
//
// HumdrumLine::getGlobalReferenceValue -- Return reference value if a
//     global reference record.  Otherwise returns an empty string.
//

string HumdrumLine::getGlobalReferenceValue(void) const {
	if (this->size() < 6) {
		return "";
	}
	if (this->substr(0, 3) != "!!!") {
		return "";
	}
	if ((*this)[4] == '!') {
		return "";
	}

	size_t colloc = this->find(":");
	if (colloc == string::npos) {
		return "";
	}
	int index = (int)colloc + 1;
	for (int i=index; i<(int)this->size(); i++) {
		if (isspace(this->at(i))) {
			index++;
			continue;
		}
		break;
	}
	if (index >= (int)this->size()) {
		return "";
	}
	string output = this->substr(index);
	for (int i=(int)output.size() - 1; i>=0; i--) {
		if (isspace(output.at(i))) {
			output.resize((int)output.size() - 1);
			continue;
		}
		break;
	}
	return output;
}



//////////////////////////////
//
// HumdrumLine::getGlobalReferenceKey -- Return reference key if a reference
//     record.  Otherwise returns an empty string.
//

string HumdrumLine::getGlobalReferenceKey(void) const {

	if (this->size() < 5) {
		return "";
	}
	if (this->substr(0, 3) != "!!!") {
		return "";
	}
	if ((*this)[3] == '!') {
		return "";
	}
	size_t colloc = this->find(":");
	if (colloc == string::npos) {
		return "";
	}
	int index = 3;
	for (int i=index; i<(int)colloc; i++) {
		if (isspace(this->at(i))) {
			index++;
			continue;
		}
		break;
	}
	if (index >= (int)colloc) {
		return "";
	}
	int length = (int)colloc - index;
	string output = this->substr(index, length);
	for (int i=(int)output.size()-1; i>=0; i--) {
		if (isspace(output.at(i))) {
			output.resize((int)output.size() - 1);
		}
	}
	return output;

}



//////////////////////////////
//
// HumdrumLine::getUniversalReferenceKey -- Return reference key if a reference
//     record.  Otherwise returns an empty string.
//

string HumdrumLine::getUniversalReferenceKey(void) const {

	if (this->size() < 6) {
		return "";
	}
	if (this->substr(0, 4) != "!!!!") {
		return "";
	}
	if ((*this)[4] == '!') {
		return "";
	}
	size_t colloc = this->find(":");
	if (colloc == string::npos) {
		return "";
	}
	int index = 4;
	for (int i=index; i<(int)colloc; i++) {
		if (isspace(this->at(i))) {
			index++;
			continue;
		}
		break;
	}
	if (index >= (int)colloc) {
		return "";
	}
	int length = (int)colloc - index;
	string output = this->substr(index, length);
	for (int i=(int)output.size()-1; i>=0; i--) {
		if (isspace(output.at(i))) {
			output.resize((int)output.size() - 1);
		}
	}
	return output;

}



//////////////////////////////
//
// HumdrumLine::isExclusive -- Returns true if the first two characters
//     are "**".
//

bool HumdrumLine::isExclusive(void) const {
	return equalChar(1, '*') && equalChar(0, '*');
}



//////////////////////////////
//
// HumdrumLine::isTerminator -- Returns true if all tokens on the line
//    are terminators.
//

bool HumdrumLine::isTerminator(void) const {
	if (getTokenCount() == 0) {
		// if tokens have not been parsed, check line text
		return equalChar(1, '!') && equalChar(0, '*');
	}
	for (int i=0; i<getTokenCount(); i++) {
		if (!token(i)->isTerminator()) {
			return false;
		}
	}
	return true;
}



//////////////////////////////
//
// HumdrumLine::isInterp -- Returns true if starts with '*' character.
//

bool HumdrumLine::isInterp(void) const {
	return equalChar(0, '*');
}



//////////////////////////////
//
// HumdrumLine::isBarline -- Returns true if starts with '=' character.
//

bool HumdrumLine::isBarline(void) const {
	return equalChar(0, '=');
}



//////////////////////////////
//
// HumdrumLine::isData -- Returns true if data (but not measure).
//

bool HumdrumLine::isData(void) const {
	if (isComment() || isInterp() || isBarline() || isEmpty()) {
		return false;
	} else {
		return true;
	}
}



//////////////////////////////
//
// HumdrumLine::isAllNull -- Returns true if all tokens on the line
//    are null ("." if a data line, "*" if an interpretation line, "!"
//    if a local comment line).
//

bool HumdrumLine::isAllNull(void) const {
	if (!hasSpines()) {
		return false;
	}
	for (int i=0; i<getTokenCount(); i++) {
		if (!token(i)->isNull()) {
			return false;
		}
	}
	return true;
}



//////////////////////////////
//
// HumdrumLine::isAllRhythmicNull -- Returns true if all rhythmic
//    data-type tokens on the line are null ("." if a data line,
//    "*" if an interpretation line, "!" if a local comment line).
//

bool HumdrumLine::isAllRhythmicNull(void) const {
	if (!hasSpines()) {
		return false;
	}
	for (int i=0; i<getTokenCount(); i++) {
		if (!token(i)->hasRhythm()) {
			continue;
		}
		if (!token(i)->isNull()) {
			return false;
		}
	}
	return true;
}



//////////////////////////////
//
// HumdrumLine::setLineIndex -- Used by the HumdrumFileBase class to set the
//   index number of the line in the data storage for the file.
//

void HumdrumLine::setLineIndex(int index) {
	m_lineindex = index;
}



//////////////////////////////
//
// HumdrumLine::getLineIndex -- Returns the index number of the line in the
//    HumdrumFileBase storage for the lines.
//

int HumdrumLine::getLineIndex(void) const {
	return m_lineindex;
}



//////////////////////////////
//
// HumdrumLine::getLineNumber -- Returns the line index plus one.
//

int HumdrumLine::getLineNumber(void) const {
	return m_lineindex + 1;
}



//////////////////////////////
//
// HumdrumLine::getDuration -- Get the duration of the line.  The duration will
//    be negative one if rhythmic analysis in HumdrumFileStructure has not been
//    done on the owning HumdrumFile object.  Otherwise this is the duration of
//    the current line in the file.
//

HumNum HumdrumLine::getDuration(void) {
	if (!m_rhythm_analyzed) {
		if (getOwner()) {
			getOwner()->analyzeRhythmStructure();
		}
	}
	return m_duration;
}


HumNum HumdrumLine::getDuration(HumNum scale) {
	if (!m_rhythm_analyzed) {
		if (getOwner()) {
			getOwner()->analyzeRhythmStructure();
		}
	}
	return m_duration * scale;
}



//////////////////////////////
//
// HumdrumLine::getBarlineDuration -- Return the duration following a barline,
//    or the duration of the previous barline in the data.
//

HumNum HumdrumLine::getBarlineDuration(void) {
	if (!m_rhythm_analyzed) {
		if (getOwner()) {
			getOwner()->analyzeRhythmStructure();
		}
	}
	if (isBarline()) {
		return getDurationToBarline();
	} else {
		return getDurationFromBarline() + getDurationToBarline();
	}
}


HumNum HumdrumLine::getBarlineDuration(HumNum scale) {
	if (!m_rhythm_analyzed) {
		if (getOwner()) {
			getOwner()->analyzeRhythmStructure();
		}
	}
	if (isBarline()) {
		return getDurationToBarline(scale);
	} else {
		return getDurationFromBarline(scale) + getDurationToBarline(scale);
	}
}



//////////////////////////////
//
// HumdrumLine::setDurationFromStart -- Sets the duration from the start of the
//    file to the start of the current line.  This is used in rhythmic
//    analysis done in the HumdrumFileStructure class.
//

void HumdrumLine::setDurationFromStart(HumNum dur) {
	m_durationFromStart = dur;
}



//////////////////////////////
//
// HumdrumLine::getDurationFromStart -- Get the duration from the start of the
//    file to the start of the current line.  This will be -1 if rhythmic
//    analysis has not been done in the HumdrumFileStructure class.
//

HumNum HumdrumLine::getDurationFromStart(void) {
	if (!m_rhythm_analyzed) {
		if (getOwner()) {
			getOwner()->analyzeRhythmStructure();
		}
	}
	return m_durationFromStart;
}


HumNum HumdrumLine::getDurationFromStart(HumNum scale) {
	if (!m_rhythm_analyzed) {
		if (getOwner()) {
			getOwner()->analyzeRhythmStructure();
		}
	}
	return m_durationFromStart * scale;
}



//////////////////////////////
//
// HumdrumLine::getDurationToEnd -- Returns the duration from the start of the
//    line to the end of the HumdrumFile which owns this HumdrumLine.  The
//    rhythm of the HumdrumFile must be analyze before using this function;
//    otherwise a 0 will probably be returned.
//

HumNum HumdrumLine::getDurationToEnd(void) {
	if (!m_rhythm_analyzed) {
		if (getOwner()) {
			getOwner()->analyzeRhythmStructure();
		}
	} else {
		return 0;
	}
	return ((HumdrumFile*)getOwner())->getScoreDuration() -  m_durationFromStart;
}


HumNum HumdrumLine::getDurationToEnd(HumNum scale) {
	if (!m_rhythm_analyzed) {
		if (getOwner()) {
			getOwner()->analyzeRhythmStructure();
		}
	} else {
		return 0;
	}
	return scale * (((HumdrumFile*)getOwner())->getScoreDuration() -
		m_durationFromStart);
}



//////////////////////////////
//
// HumdrumLine::getDurationFromBarline -- Returns the duration from the start
//    of the given line to the first barline occurring before the given line.
//    Analysis of this data is found in HumdrumFileStructure::metricAnalysis.
//

HumNum HumdrumLine::getDurationFromBarline(void) {
	if (!m_rhythm_analyzed) {
		if (getOwner()) {
			getOwner()->analyzeRhythmStructure();
		}
	}
	return m_durationFromBarline;
}


HumNum HumdrumLine::getDurationFromBarline(HumNum scale) {
	if (!m_rhythm_analyzed) {
		if (getOwner()) {
			getOwner()->analyzeRhythmStructure();
		}
	}
	return m_durationFromBarline * scale;
}



//////////////////////////////
//
// HumdrumLine::getTrackStart --  Returns the starting exclusive interpretation
//    for the given spine/track.
//

HTp HumdrumLine::getTrackStart(int track) const {
	if (!m_owner) {
		return NULL;
	} else {
		return ((HumdrumFile*)m_owner)->getTrackStart(track);
	}
}



//////////////////////////////
//
// HumdrumLine::getTrackEnd --  Returns the ending exclusive interpretation
//    for the given spine/track.
//

HTp HumdrumLine::getTrackEnd(int track, int subspine) const {
	if (!m_owner) {
		return NULL;
	} else {
		return ((HumdrumFile*)m_owner)->getTrackEnd(track, subspine);
	}
}



//////////////////////////////
//
// HumdrumLine::setDurationFromBarline -- Time from the previous
//    barline to the current line.  This function is used in analyzeMeter in
//    the HumdrumFileStructure class.
//

void HumdrumLine::setDurationFromBarline(HumNum dur) {
	m_durationFromBarline = dur;
}



//////////////////////////////
//
// HumdrumLine::getDurationToBarline -- Time from the starting of the
//   current note to the next barline.
//

HumNum HumdrumLine::getDurationToBarline(void) {
	if (!m_rhythm_analyzed) {
		if (getOwner()) {
			getOwner()->analyzeRhythmStructure();
		}
	}
	return m_durationToBarline;
}


HumNum HumdrumLine::getDurationToBarline(HumNum scale) {
	if (!m_rhythm_analyzed) {
		if (getOwner()) {
			getOwner()->analyzeRhythmStructure();
		}
	}
	return m_durationToBarline * scale;
}



//////////////////////////////
//
// HumdrumLine::getBeat -- Returns the beat number for the data on the
//     current line given the input **recip representation for the duration
//     of a beat.  The beat in a measure is offset from 1 (first beat is
//     1 rather than 0).
//  Default value: beatrecip = "4".
//  Default value: beatdur   = 1.
//

HumNum HumdrumLine::getBeat(HumNum beatdur) {
	if (beatdur.isZero()) {
		return beatdur;
	}
	HumNum beat = (getDurationFromBarline() / beatdur) + 1;
	return beat;
}


HumNum HumdrumLine::getBeatStr(string beatrecip) {
	HumNum beatdur = Convert::recipToDuration(beatrecip);
	if (beatdur.isZero()) {
		return beatdur;
	}
	HumNum beat = (getDurationFromBarline() / beatdur) + 1;
	return beat;
}



//////////////////////////////
//
// HumdrumLine::setDurationToBarline -- Sets the duration from the current
//     line to the next barline in the score.  This function is used by
//     analyzeMeter in the HumdrumFileStructure class.
//

void HumdrumLine::setDurationToBarline(HumNum dur) {
	m_durationToBarline = dur;
}



//////////////////////////////
//
// HumdrumLine::setDuration -- Sets the duration of the line.  This is done
//   in the rhythmic analysis for the HumdurmFileStructure class.
//

void HumdrumLine::setDuration(HumNum aDur) {
	if (aDur.isNonNegative()) {
		m_duration = aDur;
	} else {
		m_duration = 0;
	}
}



//////////////////////////////
//
// HumdrumLine::hasSpines -- Returns true if the line contains spines.  This
//   means the the line is not empty or a global comment (which can include
//   reference records.
//

bool HumdrumLine::hasSpines(void) const {
	return (isEmpty() || isCommentGlobal()) ? false : true;
}



//////////////////////////////
//
// HumdrumLine::isGlobal -- Returns true if the line is a global record: either
//   and empty record, a global comment or a reference record.
//

bool HumdrumLine::isGlobal(void) const {
	return !hasSpines();
}



//////////////////////////////
//
// HumdrumLine::equalFieldsQ --
//

bool HumdrumLine::equalFieldsQ(const string& exinterp, const string& value) {
	for (int i=0; i<getFieldCount(); i++) {
		HTp token = this->token(i);
		if (!token->isDataType(exinterp)) {
			return false;
		}
		if (*token != value) {
			return false;
		}
	}
	return true;
}



//////////////////////////////
//
// HumdrumLine::isManipulator -- Returns true if any tokens on the line are
//   manipulator interpretations.  Only null interpretations are allowed on
//   lines which contain manipulators, but the parser currently does not
//   enforce this rule.
//

bool HumdrumLine::isManipulator(void) const {
	for (int i=0; i<(int)m_tokens.size(); i++) {
		if (m_tokens[i]->isManipulator()) {
			return true;
		}
	}
	return false;
}



//////////////////////////////
//
// HumdrumLine::isEmpty -- Returns true if no characters on line.  A blank line
//   is technically disallowed in the classic Humdrum Toolkit programs, but it
//   is usually tolerated.  In humlib (and HumdrumExtras) empty lines with
//   no content (not even space characters) are allowed and treated as a
//   special class of line.
//

bool HumdrumLine::isEmpty(void) const {
	return (size() == 0) ? true : false;
}



//////////////////////////////
//
// HumdrumLine::getTokenCount --  Returns the number of tokens on the line.
//     This value is set by HumdrumFileBase in analyzeTokens.
//

int HumdrumLine::getTokenCount(void) const {
	return (int)m_tokens.size();
}



//////////////////////////////
//
// HumdrumLine::token -- Returns a reference to the given token on the line.
//    An invalid token index would be bad to give to this function as it
//    returns a reference rather than a pointer (which could be set to
//    NULL if invalid).  Perhaps this function will eventually throw an
//    error if the index is out of bounds.
//

HTp HumdrumLine::token(int index) const {
	return m_tokens[index];
}



//////////////////////////////
//
// HumdrumLine::getTokenString -- Returns a copy of the string component of
//     a token.  This code will return a segmentation fault if index is out of
//     range...
//

string HumdrumLine::getTokenString(int index) const {
	return (string(*m_tokens[index]));
}


//////////////////////////////
//
// HumdrumLine::createTokensFromLine -- Chop up a HumdrumLine string into
//     individual tokens.
//

int HumdrumLine::createTokensFromLine(void) {
	// delete previous tokens (will need to re-analyze structure
	// of file after this).
	for (int i=0; i < (int)m_tokens.size(); i++) {
		delete m_tokens[i];
		m_tokens[i] = NULL;
	}
	m_tokens.clear();
	m_tabs.clear();
	HTp token;
	char ch = 0;
	char lastch = 0;
	string tstring;

	if (this->size() == 0) {
		token = new HumdrumToken();
		token->setOwner(this);
		m_tokens.push_back(token);
		m_tabs.push_back(0);
	} else if (this->compare(0, 2, "!!") == 0) {
		token = new HumdrumToken(this->c_str());
		token->setOwner(this);
		m_tokens.push_back(token);
		m_tabs.push_back(0);
	} else {
		for (int i=0; i<(int)size(); i++) {
			lastch = ch;
			ch = getChar(i);
			if (ch == '\t') {
				// Parser now allows multiple tab characters in a
				// row to represent a single tab.
				if (lastch != '\t') {
					token = new HumdrumToken(tstring);
					token->setOwner(this);
					m_tokens.push_back(token);
					m_tabs.push_back(1);
					tstring.clear();
				} else {
					if (m_tabs.size() > 0) {
						m_tabs.back()++;
					}
				}
			} else {
				tstring += ch;
			}
		}
	}
	if (tstring.size() > 0) {
		token = new HumdrumToken(tstring);
		token->setOwner(this);
		m_tokens.push_back(token);
		m_tabs.push_back(0);
		tstring.clear();
	}

	return (int)m_tokens.size();
}



//////////////////////////////
//
// HumdrumLine::createLineFromTokens --  Re-generate a HumdrumLine string from
//    individual tokens on the line.  This function will be necessary to
//    run before printing a HumdrumFile if you have changed any tokens on the
//    line.  Otherwise, changes in the tokens will not be passed on to the
///   printing of the line.
//

void HumdrumLine::createLineFromTokens(void) {
	string& iline = *this;
	iline = "";
	// needed for empty lines for some reason:
	if (m_tokens.size()) {
		if (m_tokens.back() == NULL) {
			m_tokens.resize(m_tokens.size() - 1);
		}
	}
	for (int i=0; i<(int)m_tokens.size(); i++) {
		iline += (string)(*m_tokens.at(i));
		if (i < (int)m_tokens.size() - 1) {
			if ((int)m_tabs.size() <= i) {
				for (int j=0; j<(int)m_tokens.size() - (int)m_tabs.size(); j++) {
					m_tabs.push_back(1);
				}
			}
			if (m_tabs.at(i) == 0) {
				m_tabs.at(i) = 1;
			}
			for (int j=0; j<m_tabs.at(i); j++) {
				iline += '\t';
			}
		}
	}
}



//////////////////////////////
//
// HumdrumLine::removeExtraTabs -- Allow only for one tab between spine fields.
//    The tab counts are set to 0, but the line creation function knows to use
//    a minimum of one tab between fields.  The HumdrumFile::createLinesFromtTokens()
//    function should be called after running this function and before printing
//    the data so that the tabs in the text line can be updated.
//

void HumdrumLine::removeExtraTabs(void) {
	fill(m_tabs.begin(), m_tabs.end(), 0);
}



//////////////////////////////
//
// HumdrumLine::addExtraTabs -- Adds extra tabs between primary spines so that the
//    first token of a spine is vertically aligned.  The input array to this
//    function is a list of maximum widths.  This is typically caluclated by
//    HumdrumFileBase::getTrackWidths().  The first indexed value is unused,
//    since there is no track 0.
//

void HumdrumLine::addExtraTabs(vector<int>& trackWidths) {
	if (!this->hasSpines()) {
		return;
	}

	fill(m_tabs.begin(), m_tabs.end(), 1);
	vector<int> local(trackWidths.size(), 0);

	int lasttrack = 0;
	int track = 0;
	for (int j=0; j<getFieldCount(); j++) {
		lasttrack = track;
		HTp token = this->token(j);
		track = token->getTrack();
		if ((track != lasttrack) && (lasttrack > 0)) {
			int diff = trackWidths.at(lasttrack) - local.at(lasttrack);
			if ((diff > 0) && (j > 0)) {
				m_tabs.at(j-1) += diff;
			}
		}
		local.at(track)++;
	}
}



//////////////////////////////
//
// HumdrumLine::getTokens -- Returns an array of tokens pointers for a
//   Humdrum line.  This function should not be called on global comments,
//   reference records (which are a sub-cateogry of global comments).  This
//   is because a line's type may contain tabs which are not representing
//   token separators.  Empty lines are ok to input: the output token
//   list will contain one empty string.
//

void HumdrumLine::getTokens(vector<HTp>& list) {
	if (m_tokens.size() == 0) {
		createTokensFromLine();
	}
	list = m_tokens;
}



//////////////////////////////
//
// HumdrumLine::getChar -- Returns character at given index in string, or
//    null if out of range.
//

char HumdrumLine::getChar(int index) const {
	if (index < 0) {
		return '\0';
	}
	if (index >= (int)size()) {
		return '\0';
	}
	return (((string)(*this))[index]);
}



//////////////////////////////
//
// HumdrumLine::printSpineInfo -- Print the spine state information of
//    each token in a file.  Useful for debugging.  The spine info
//    is the track number, such as "1".  When the track splits into
//    subtracks, then there will be two subtracks: "(1)a" and "(1)b".
//    If the second of those subtracks splits again, then its subtracks
//    will be "((1)b)a" and "((1)b)b". If two different tracks merge, such
//    as "1" and "(2)a", then the spine info will be "1 (2)a".
//
// default value: out = cout
//

ostream& HumdrumLine::printSpineInfo(ostream& out) {
	if (isManipulator()) {
		out << *this;
	} else {
		for (int i=0; i<(int)m_tokens.size(); i++) {
			out << m_tokens[i]->getSpineInfo();
			if (i < (int)m_tokens.size() - 1) {
				out << '\t';
			}
		}
	}
	return out;
}



//////////////////////////////
//
// HumdrumLine::printDataTypeInfo -- Print the datatype of each token in
//     the file.  Useful for debugging.  The datatype prefix "**" is removed;
//     otherwise, it is given when you call HumdrumToken::getDataType().
//
// default value: out = cout
//

ostream& HumdrumLine::printDataTypeInfo(ostream& out) {
	if (isManipulator()) {
		out << *this;
	} else {
		for (int i=0; i<(int)m_tokens.size(); i++) {
			out << m_tokens[i]->getDataType().substr(2, string::npos);
			if (i < (int)m_tokens.size() - 1) {
				out << '\t';
			}
		}
	}
	return out;
}



//////////////////////////////
//
// HumdrumLine::analyzeTokenDurations -- Calculate the duration of
//    all tokens on a line.
//

bool HumdrumLine::analyzeTokenDurations(string& err) {
	if (!hasSpines()) {
		return !err.size();
	}
	for (int i=0; i<(int)m_tokens.size(); i++) {
		m_tokens[i]->analyzeDuration();
	}
	return !err.size();
}



//////////////////////////////
//
// HumdrumLine::analyzeTracks -- Calculate the subtrack info for subspines.
//   Subtracks index subspines strictly from left to right on the line.
//   Subspines can be exchanged and be represented left to right out of
//   original order.
//

bool HumdrumLine::analyzeTracks(string& err) {
	if (!hasSpines()) {
		return !err.size();
	}

	string info;
	int track;
	int maxtrack = 0;
	int i, j, k;

	for (i=0; i<(int)m_tokens.size(); i++) {
		info = m_tokens[i]->getSpineInfo();
		track = 0;
		for (j=0; j<(int)info.size(); j++) {
			if (!isdigit(info[j])) {
				continue;
			}
			track = info[j] - '0';
			for (k=j+1; k<(int)info.size(); k++) {
				if (isdigit(info[k])) {
					track = track * 10 + (info[k] - '0');
				} else {
					break;
				}
			}
			break;
		}
		if (maxtrack < track) {
			maxtrack = track;
		}
		m_tokens[i]->setTrack(track);
	}

	int subtrack;
	vector<int> subtracks;
	vector<int> cursub;

	subtracks.resize(maxtrack+1);
	cursub.resize(maxtrack+1);
	fill(subtracks.begin(), subtracks.end(), 0);
	fill(cursub.begin(), cursub.end(), 0);

	for (i=0; i<(int)m_tokens.size(); i++) {
		subtracks[m_tokens[i]->getTrack()]++;
	}
	for (i=0; i<(int)m_tokens.size(); i++) {
		track = m_tokens[i]->getTrack();
		subtrack = subtracks[track];
		if (subtrack > 1) {
			m_tokens[i]->setSubtrack(++cursub[m_tokens[i]->getTrack()]);
		} else {
			m_tokens[i]->setSubtrack(0);
		}
		m_tokens[i]->setSubtrackCount(subtracks[track]);
	}
	return !err.size();
}



//////////////////////////////
//
// HumdrumLine::printDurationInfo -- Print the analyzed duration of each
//     token in a file (for debugging).  If a token has an undefined
//     duration, then its duration is -1.  If a token represents
//     a grace note, then its duration is 0 (regardless of whether it
//     includes a visual duration).
// default value: out = cout
//

ostream& HumdrumLine::printDurationInfo(ostream& out) {
	if (isManipulator()) {
		out << *this;
	} else {
		for (int i=0; i<(int)m_tokens.size(); i++) {
			m_tokens[i]->getDuration().printMixedFraction(out);
			if (i < (int)m_tokens.size() - 1) {
				out << '\t';
			}
		}
	}
	return out;
}


//////////////////////////////
//
// HumdrumLine::printCsv -- print the line as a CSV
//    (comma separate value) line.
// default value: out = std::cout;
// default value: separator = ","
//

ostream& HumdrumLine::printCsv(ostream& out, const string& separator) {
	for (int i=0; i<getFieldCount(); i++) {
		token(i)->printCsv(out);
		if (i<getFieldCount()-1) {
			out << separator;
		}
	}
	out << endl;
	return out;
}



//////////////////////////////
//
// HumdrumLine::printGlobalXmlParameterInfo --
//

ostream& HumdrumLine::printGlobalXmlParameterInfo(ostream& out, int level,
		const string& indent) {
	token(0)->printGlobalXmlParameterInfo(out, level, indent);
	return out;
}



//////////////////////////////
//
// HumdrumLine::printXmlParameterInfo --
//

ostream& HumdrumLine::printXmlParameterInfo(ostream& out, int level,
		const string& indent) {
	((HumHash*)this)->printXml(out, level, indent);
	return out;
}



//////////////////////////////
//
// HumdrumLine::printXmlGlobalLinkedParameterInfo --
//

ostream&	HumdrumLine::printXmlGlobalLinkedParameterInfo(ostream& out, int level,
		const string& indent) {
	return out;
	// return token(0)->printXmlLinkedParameterInfo(out, level, indent);
}


//////////////////////////////
//
// HumdrumLine::printXmlGlobalLinkedParameters --
//

ostream& HumdrumLine::printXmlGlobalLinkedParameters(ostream& out, int level, const string& indent) {
	return out;
	// return token(0)->printXmlLinkedParameters(out, level, indent);
}


//////////////////////////////
//
// HumdrumLine::printXml -- Print the HumdrumLine as a XML element.
//

ostream& HumdrumLine::printXml(ostream& out, int level, const string& indent) {

	if (hasSpines()) {
		out << Convert::repeatString(indent, level) << "<frame";
		out << " n=\"" << getLineIndex() << "\"";
		out << " xml:id=\"" << getXmlId() << "\"";
		out << ">\n";
		level++;

		out << Convert::repeatString(indent, level) << "<frameInfo>\n";
		level++;

		out << Convert::repeatString(indent, level) << "<fieldCount>";
		out << getTokenCount() << "</fieldCount>\n";

		out << Convert::repeatString(indent, level);
		out << "<frameStart";
		out << Convert::getHumNumAttributes(getDurationFromStart());
		out << "/>\n";

		out << Convert::repeatString(indent, level);
		out << "<frameDuration";
		out << Convert::getHumNumAttributes(getDuration());
		out << "/>\n";

		out << Convert::repeatString(indent, level) << "<frameType>";
		if (isData()) {
			out << "data";
		} else if (isBarline()) {
			out << "barline";
		} else if (isInterpretation()) {
			out << "interpretation";
		} else if (isLocalComment()) {
			out << "local-comment";
		}
		out << "</frameType>\n";

		if (isBarline()) {
			// print the duration to the next barline or to the end of the score
			// if there is no barline at the end of the score.
			out << Convert::repeatString(indent, level);
			out << "<barlineDuration";
			out << Convert::getHumNumAttributes(getBarlineDuration());
			out << "/>\n";
		}

		bool bstart = isKernBoundaryStart();
		bool bend   = isKernBoundaryEnd();
		if (bstart || bend) {
			out << Convert::repeatString(indent, level);
			cout << "<kernBoundary";
			cout << " start=\"";
			if (bstart) {
				cout << "true";
			} else {
				cout << "false";
			}
			cout << "\"";
			cout << " end=\"";
			if (bend) {
				cout << "true";
			} else {
				cout << "false";
			}
			cout << "\"";
			cout << "/>\n";
		}

		level--;
		out << Convert::repeatString(indent, level) << "</frameInfo>\n";

		out << Convert::repeatString(indent, level) << "<fields>\n";
		level++;
		for (int i=0; i<getFieldCount(); i++) {
			token(i)->printXml(out, level, indent);
		}
		level--;
		out << Convert::repeatString(indent, level) << "</fields>\n";

		printGlobalXmlParameterInfo(out, level, indent);
		printXmlParameterInfo(out, level, indent);
		printXmlGlobalLinkedParameterInfo(out, level, indent);
		printXmlGlobalLinkedParameters(out, level, indent);

		level--;
		out << Convert::repeatString(indent, level) << "</frame>\n";

	} else {
		// global comments, reference records, or blank lines print here.
		out << Convert::repeatString(indent, level) << "<metaFrame";
		out << " n=\"" << getLineIndex() << "\"";
		out << " token=\"" << Convert::encodeXml(((string)(*this))) << "\"";
		out << " xml:id=\"" << getXmlId() << "\"";
		out << ">\n";
		level++;

		out << Convert::repeatString(indent, level) << "<frameInfo>\n";
		level++;

		out << Convert::repeatString(indent, level);
		out << "<startTime";
		out << Convert::getHumNumAttributes(getDurationFromStart());
		out << "/>\n";

		out << Convert::repeatString(indent, level) << "<frameType>";
		if (isGlobalReference()) {
			out << "reference";
		} else if (isUniversalReference()) {
			out << "ureference";
		} else if (isBlank()) {
			out << "empty";
		} else {
			out << "global-comment";
		}
		out << "</frameType>\n";

		if (isReference()) {
			out << Convert::repeatString(indent, level);
			string key = getReferenceKey();
			string language;
			string primaryLanguage;
			auto loc = key.find("@@");
			if (loc != string::npos) {
				language = key.substr(loc+2);
				key = key.substr(0, loc);
				primaryLanguage = "true";
			} else {
				loc = key.find("@");
				if (loc != string::npos) {
					language = key.substr(loc+1);
					key = key.substr(0, loc);
				}
			}

			out << "<referenceKey";
			if (language.size() > 0) {
				out << " language=\"" << Convert::encodeXml(language) << "\"";
			}
			if (primaryLanguage.size() > 0) {
				out << " primary=\"" << Convert::encodeXml(primaryLanguage) << "\"";
			}
			out << ">" << Convert::encodeXml(key);
			out << "</referenceKey>\n";

			out << Convert::repeatString(indent, level);
			out << "<referenceValue>" << Convert::encodeXml(getGlobalReferenceValue());
			out << "</referenceValue>\n";
		}

		level--;
		out << Convert::repeatString(indent, level) << "</frameInfo>\n";

		printGlobalXmlParameterInfo(out, level-2, indent);
		printXmlParameterInfo(out, level-2, indent);
		printXmlGlobalLinkedParameterInfo(out, level-2, indent);
		printXmlGlobalLinkedParameters(out, level, indent);

		level--;
		out << Convert::repeatString(indent, level) << "</metaFrame>\n";
	}


	return out;
}



//////////////////////////////
//
// HumdrumLine::getXmlId -- Return a unique ID for the current line.
//

string HumdrumLine::getXmlId(const string& prefix) const {
	string output;
	if (prefix.size() > 0) {
		output = prefix;
	} else {
		output = getXmlIdPrefix();
	}
	output += "L" + to_string(getLineIndex() + 1);
	return output;
}



//////////////////////////////
//
// HumdrumLine::getXmlIdPrefix -- Return the pre-set XML ID attribute
//     prefix from the owning HumdrumFile object.
//

string HumdrumLine::getXmlIdPrefix(void) const {
	if (!m_owner) {
		return "";
	}
	return ((HumdrumFileBase*)m_owner)->getXmlIdPrefix();
}



//////////////////////////////
//
// HumdrumLine::printTrackInfo -- Print the analyzed track information.
//     The first (left-most) spine in a Humdrum file is track 1, the
//     next is track 2, etc.  The track value is shared by all subspines,
//     so there may be duplicate track numbers on a line if the spine
//     has split.  When the spine splits, a subtrack number is given
//     after a "." character in the printed output from this function.
//     Subtrack==0 means that there is only one subtrack.
//     Examples:
//         "1"  == Track 1, subtrack 1 (and there are no more subtracks)
//	        "1.1" == Track 1, subtrack 1 (and there are more subtracks)
//	        "1.2" == Track 1, subtrack 2 (and there may be more subtracks)
//	        "1.10" == Track 1, subtrack 10 (and there may be subtracks)
//     Each starting exclusive interpretation is assigned to a unique
//     track number.  When a *+ manipulator is given, the new exclusive
//     interpretation on the next line is give the next higher track
//     number.
//
// default value: out = cout
//

ostream& HumdrumLine::printTrackInfo(ostream& out) {
	if (isManipulator()) {
		out << *this;
	} else {
		for (int i=0; i<(int)m_tokens.size(); i++) {
			out << m_tokens[i]->getTrackString();
			if (i < (int)m_tokens.size() - 1) {
				out << '\t';
			}
		}
	}
	return out;
}



//////////////////////////////
//
// HumdrumLine::setOwner -- store a pointer to the HumdrumFile which
//    manages (owns) this object.
//

void HumdrumLine::setOwner(void* hfile) {
	m_owner = hfile;
}



//////////////////////////////
//
// HumdrumLine::getOwner -- Return the HumdrumFile which manages
//   (owns) this line.
//

HumdrumFile* HumdrumLine::getOwner(void) {
	return (HumdrumFile*)m_owner;
}



//////////////////////////////
//
// HumdrumLine::addLinkedParameter --
//

int HumdrumLine::addLinkedParameter(HTp token) {
	for (int i=0; i<(int)m_linkedParameters.size(); i++) {
		if (m_linkedParameters[i] == token) {
			return i;
		}
	}

	m_linkedParameters.push_back(token);
	return (int)m_linkedParameters.size() - 1;
}



//////////////////////////////
//
// HumdrumLine::setLayoutParameters -- Takes a global comment with
//     the structure:
//        !!LO:NS2:key1=value1:key2=value2:key3=value3
//     and stores it in the HumHash parent class of the line.
//

void HumdrumLine::setLayoutParameters(void) {
	if (this->find("!!LO:") == string::npos) {
		return;
	}
	string pdata = this->substr(2, string::npos);
	setParameters(pdata);
}


//////////////////////////////
//
// HumdrumLine::setParameters -- Store global parameters in the first token
//    of the line.  Also add a marker at ("","","global","true") to indicate
//    that the parameters are global rather than local.  (Global text directions
//    will behave differently from local text directions, for example).
//

void HumdrumLine::setParameters(const string& pdata) {
	vector<string> pieces = Convert::splitString(pdata, ':');
	if (pieces.size() < 3) {
		return;
	}
	string ns1 = pieces[0];
	string ns2 = pieces[1];
	string key;
	string value;
	int loc;
	for (int i=2; i<(int)pieces.size(); i++) {
		Convert::replaceOccurrences(pieces[i], "&colon;", ":");
		loc = (int)pieces[i].find("=");
		if (loc != (int)string::npos) {
			key   = pieces[i].substr(0, loc);
			value = pieces[i].substr(loc+1, pieces[i].size());
		} else {
			key   = pieces[i];
			value = "true";
		}
		token(0)->setValue(ns1, ns2, key, value);
	}
	token(0)->setValue("global", "true");
}



//////////////////////////////
//
// HumdrumLine::appendToken -- add a token at the end of the current
//      list of tokens in the line.
//

void HumdrumLine::appendToken(HTp token, int tabcount) {
	// deletion will be handled by class.
	m_tokens.push_back(token);
	m_tabs.push_back(tabcount);
}


void HumdrumLine::appendToken(const HumdrumToken& token, int tabcount) {
	HTp newtok = new HumdrumToken(token);
	m_tokens.push_back(newtok);
	m_tabs.push_back(tabcount);
}


void HumdrumLine::appendToken(const string& token, int tabcount) {
	HTp newtok = new HumdrumToken(token);
	m_tokens.push_back(newtok);
	m_tabs.push_back(tabcount);
}


void HumdrumLine::appendToken(const char* token, int tabcount) {
	HTp newtok = new HumdrumToken(token);
	m_tokens.push_back(newtok);
	m_tabs.push_back(tabcount);
}



//////////////////////////////
//
// HumdrumLine::getKernNoteAttacks -- Return the number of kern notes
//    that attack on a line.
//

int HumdrumLine::getKernNoteAttacks(void) {
	int output = 0;
	for (int i=0; i<getFieldCount(); i++) {
		if (!token(i)->isKern()) {
			continue;
		}
		if (token(i)->isNoteAttack()) {
			output++;
		}
	}
	return output;
}



//////////////////////////////
//
// HumdrumLine::insertToken -- Add a token before the given token position.
//

void HumdrumLine::insertToken(int index, HTp token, int tabcount) {
	// Warning: deletion will be handled by class.  Don't insert if it
	// already belongs to another HumdrumLine or HumdrumFile.
	m_tokens.insert(m_tokens.begin() + index, token);
	m_tabs.insert(m_tabs.begin() + index, tabcount);
}


void HumdrumLine::insertToken(int index, const HumdrumToken& token, int tabcount) {
	HTp newtok = new HumdrumToken(token);
	m_tokens.insert(m_tokens.begin() + index, newtok);
	m_tabs.insert(m_tabs.begin() + index, tabcount);
}


void HumdrumLine::insertToken(int index, const string& token, int tabcount) {
	HTp newtok = new HumdrumToken(token);
	m_tokens.insert(m_tokens.begin() + index, newtok);
	m_tabs.insert(m_tabs.begin() + index, tabcount);
}


void HumdrumLine::insertToken(int index, const char* token, int tabcount) {
	HTp newtok = new HumdrumToken(token);
	m_tokens.insert(m_tokens.begin() + index, newtok);
	m_tabs.insert(m_tabs.begin() + index, tabcount);
}



//////////////////////////////
//
// HumdrumLine::appendToken -- Add a token after the given token position.
//

void HumdrumLine::appendToken(int index, HTp token, int tabcount) {
	HumdrumLine::insertToken(index+1, token, tabcount);
}


void HumdrumLine::appendToken(int index, const HumdrumToken& token, int tabcount) {
	HumdrumLine::insertToken(index+1, token, tabcount);
}


void HumdrumLine::appendToken(int index, const string& token, int tabcount) {
	HumdrumLine::insertToken(index+1, token, tabcount);
}


void HumdrumLine::appendToken(int index, const char* token, int tabcount) {
	HumdrumLine::insertToken(index+1, token, tabcount);
}



//////////////////////////////
//
// HumdrumLine::storeGlobalLinkedParameters --
//

void HumdrumLine::storeGlobalLinkedParameters(void) {
	token(0)->storeParameterSet();
}



//////////////////////////////
//
// HumdrumLine::getBarNumber -- return the bar number on the line.
//    If the line is not a bar line, then return -1.  If there is
//    no number at any token position on the line then return -1.
//

int HumdrumLine::getBarNumber(void) {
	if (!isBarline()) {
		return -1;
	}
	int output = -1;
	for (int i=0; i<getFieldCount(); i++) {
		HTp tok = token(i);
		if (tok->size() < 2) {
			return -1;
		}
		if (isdigit(tok->at(1))) {
			sscanf(tok->c_str(), "=%d", &output);
			if (output >= 0) {
				return output;
			}
		}
	}
	return -1;
}



//////////////////////////////
//
// HumdrumLine::copyStructure -- For data lines only at the moment.
//

void HumdrumLine::copyStructure(HLp line, const string& empty) {
		m_tokens.resize(line->m_tokens.size());
		for (int i=0; i<(int)m_tokens.size(); i++) {
			m_tokens[i] = new HumdrumToken(empty);
			m_tokens[i]->setOwner(this);
			m_tokens[i]->copyStructure(line->m_tokens[i]);
		}
		createLineFromTokens();

		m_tabs = line->m_tabs;
		m_linkedParameters.clear();
		m_rhythm_analyzed = line->m_rhythm_analyzed;
		m_owner = line->m_owner;

		// Other information that should be set later:
		//    int m_lineindex;
		//    HumNum m_durationFromStart;
		//    HumNum m_durationFromBarline;
		//    HumNum m_durationToBarline;
}



/////////////////////////////
//
// HumdrumLine::allSameStyle -- return true if barlines through all
//     staves are the same. Requires HumdrumFile::analyzeBarlines() to be
//     run first.
//

bool HumdrumLine::allSameBarlineStyle(void) {
	return !this->getValueInt("auto", "barlinesDifferent");
}



/////////////////////////////
//
// HumdrumLine::hasDataStraddle -- return true if barlines has any staff
//     that has data straddling it (the next measure starts with a null
//     data token (excluding grace-note lines).
//

bool HumdrumLine::hasDataStraddle(void) {
	return (this->getValueInt("auto", "straddlingData") ? true : false);
}



//////////////////////////////
//
// HumdrumLine::clearTokenLinkInfo --
//

void HumdrumLine::clearTokenLinkInfo(void) {
	if (this->isEmpty()) {
		return;
	}
	for (int i=0; i<getFieldCount(); i++) {
		if (token(i)) {
			token(i)->clearLinkInfo();
		} else {
			cerr << "Warning: non-existent token at field " << (i+1) << " on line " << (m_lineindex+1) << endl;
		}
	}
}


//////////////////////////////
//
// HumdrumLine::isKeySignature -- Returns 0 if no key signature on line, otherwise returns
//     the field index + 1 of the first key signature found in the given range.  If startTrack == 0,
//     then start at first field, if stopTrack == 0, then end at last field.
//     Default values:
//        startTrack = 0
//        stopTrack  = 0
//

int HumdrumLine::isKeySignature(int startTrack, int stopTrack) {
	HumdrumLine& line = *this;
	if (!line.isInterpretation()) {
		return 0;
	}
	for (int i=0; i<line.getFieldCount(); i++) {
		HTp token = line.token(i);
		int track = token->getTrack();
		if ((startTrack == 0) || (track >= startTrack)) {
			if ((stopTrack == 0) || (track <= stopTrack)) {
				if (token->isKeySignature()) {
					return i+1;
				}
			}
		}
	}
	return 0;
}



//////////////////////////////
//
// HumdrumLine::isKeyDesignation -- Returns 0 if no key designation on line, otherwise returns
//     the field index + 1 of the first key designation found in the given range.  If startTrack == 0,
//     then start at first field, if stopTrack == 0, then end at last field.
//     Default values:
//        startTrack = 0
//        stopTrack  = 0
//

int HumdrumLine::isKeyDesignation(int startTrack, int stopTrack) {
	HumdrumLine& line = *this;
	if (!line.isInterpretation()) {
		return 0;
	}
	for (int i=0; i<line.getFieldCount(); i++) {
		HTp token = line.token(i);
		int track = token->getTrack();
		if ((startTrack == 0) || (track >= startTrack)) {
			if ((stopTrack == 0) || (track <= stopTrack)) {
				if (token->isKeyDesignation()) {
					return i+1;
				}
			}
		}
	}
	return 0;
}



//////////////////////////////
//
// HumdrumLine::isTempo -- Returns 0 if no tempo on line, otherwise returns
//     the field index + 1 of the first tempo found in the given range.  If startTrack == 0,
//     then start at first field, if stopTrack == 0, then end at last field.
//     Default values:
//        startTrack = 0
//        stopTrack  = 0
//

int HumdrumLine::isTempo(int startTrack, int stopTrack) {
	HumdrumLine& line = *this;
	if (!line.isInterpretation()) {
		return 0;
	}
	for (int i=0; i<line.getFieldCount(); i++) {
		HTp token = line.token(i);
		int track = token->getTrack();
		if ((startTrack == 0) || (track >= startTrack)) {
			if ((stopTrack == 0) || (track <= stopTrack)) {
				if (token->isTempo()) {
					return i+1;
				}
			}
		}
	}
	return 0;
}



//////////////////////////////
//
// HumdrumLine::isTimeSignature -- Returns 0 if no time signature on line, otherwise returns
//     the field index + 1 of the first time signature found in the given range.  If startTrack == 0,
//     then start at first field, if stopTrack == 0, then end at last field.
//     Default values:
//        startTrack = 0
//        stopTrack  = 0
//

int HumdrumLine::isTimeSignature(int startTrack, int stopTrack) {
	HumdrumLine& line = *this;
	if (!line.isInterpretation()) {
		return 0;
	}
	for (int i=0; i<line.getFieldCount(); i++) {
		HTp token = line.token(i);
		int track = token->getTrack();
		if ((startTrack == 0) || (track >= startTrack)) {
			if ((stopTrack == 0) || (track <= stopTrack)) {
				if (token->isTimeSignature()) {
					return i+1;
				}
			}
		}
	}
	return 0;
}



//////////////////////////////
//
// HumdrumLine::isLabel -- Returns 0 if no label on line, otherwise returns
//     the field index + 1 of the first label found in the given range.  If startTrack == 0,
//     then start at first field, if stopTrack == 0, then end at last field.
//     Default values:
//        startTrack = 0
//        stopTrack  = 0
//

int HumdrumLine::isExpansionLabel(int startTrack, int stopTrack) {
	HumdrumLine& line = *this;
	if (!line.isInterpretation()) {
		return 0;
	}
	for (int i=0; i<line.getFieldCount(); i++) {
		HTp token = line.token(i);
		int track = token->getTrack();
		if ((startTrack == 0) || (track >= startTrack)) {
			if ((stopTrack == 0) || (track <= stopTrack)) {
				if (token->isExpansionLabel()) {
					return i+1;
				}
			}
		}
	}
	return 0;
}



//////////////////////////////
//
// HumdrumLine::isLabelExpansionList -- Returns 0 if no label expansion list on line, otherwise returns
//     the field index + 1 of the first label expansion list found in the given range.  If startTrack == 0,
//     then start at first field, if stopTrack == 0, then end at last field.
//     Default values:
//        startTrack = 0
//        stopTrack  = 0
//

int HumdrumLine::isExpansionList(int startTrack, int stopTrack) {
	HumdrumLine& line = *this;
	if (!line.isInterpretation()) {
		return 0;
	}
	for (int i=0; i<line.getFieldCount(); i++) {
		HTp token = line.token(i);
		int track = token->getTrack();
		if ((startTrack == 0) || (track >= startTrack)) {
			if ((stopTrack == 0) || (track <= stopTrack)) {
				if (token->isExpansionList()) {
					return i+1;
				}
			}
		}
	}
	return 0;
}



//////////////////////////////
//
// operator<< -- Print a HumdrumLine. Needed to avoid interaction with
//     HumHash parent class.
//

ostream& operator<<(ostream& out, HumdrumLine& line) {
	out << (string)line;
	return out;
}

ostream& operator<< (ostream& out, HLp line) {
	out << (string)(*line);
	return out;
}




//////////////////////////////
//
// HumdrumToken::getBase40Pitches -- Returns 0 if a rest.
//    (This will be a problem for very low notes).
//    Not checking to see if input data type is not **kern.
//    Negative value means a sustained pitch.
//

void HumdrumToken::getBase40Pitches(vector<int>& output) {
	if (*this == ".") {
		// Not resolving null tokens in this function.
		output.clear();
		return;
	}
	vector<string> pieces = this->getSubtokens();
	output.resize(pieces.size());
	for (int i=0; i<(int)pieces.size(); i++) {
		if (pieces[i].find("r") != string::npos) {
			output[i] = 0;
		} else {
			output[i] = Convert::kernToBase40(pieces[i]);
			// sustained notes are negative values:
			if (pieces[i].find("_") != string::npos) {
				output[i] = -output[i];
			} else if (pieces[i].find("]") != string::npos) {
				output[i] = -output[i];
			}
		}
	}
}


vector<int> HumdrumToken::getBase40Pitches(void) {
	vector<int> output;
	this->getBase40Pitches(output);
	return output;
}


int HumdrumToken::getBase40Pitch(void) {
	vector<int> pitches = getBase40Pitches();
	if (pitches.size() > 0) {
		return pitches[0];
	} else {
		return 0;
	}
}



//////////////////////////////
//
// HumdrumToken::getBase40PitchesSortHL -- Sort extracted Base-40 pitches from
//    high to low (when there is a chord).  Does not check for valid Base-40
//    note number range of 0-127.  Also not checking to see if input data
//    type is not **kern.  Sustained notes are negative values, but pitches
//    are sorted by absolute value.
//

void HumdrumToken::getBase40PitchesSortHL(vector<int>& output) {
	this->getBase40Pitches(output);
	if (output.size() <= 1) {
		return;
	}
	sort(output.begin(), output.end(),
		[](const int& a, const int& b) {
			return abs(a) > abs(b);
		}
	);
}


vector<int> HumdrumToken::getBase40PitchesSortHL(void) {
	vector<int> output;
	this->getBase40PitchesSortHL(output);
	return output;
}



//////////////////////////////
//
// HumdrumToken::getBase40PitchesSortLH -- Sort extracted Base-40 pitches from
//    low to high (when there is a chord).  Does not check for valid Base-40
//    note number range of 0-127.  Also not checking to see if input data
//    type is not **kern.  Sustained notes are negative values, but pitches
//    are sorted by absolute value.
//

void HumdrumToken::getBase40PitchesSortLH(vector<int>& output) {
	this->getBase40Pitches(output);
	if (output.size() <= 1) {
		return;
	}
	sort(output.begin(), output.end(),
		[](const int& a, const int& b) {
			return abs(a) < abs(b);
		}
	);
}


vector<int> HumdrumToken::getBase40PitchesSortLH(void) {
	vector<int> output;
	this->getBase40PitchesSortLH(output);
	return output;
}



//////////////////////////////
//
// getBase40PitchesResolveNull -- same as getBase40Pitches*() functions
//     but resolves null tokens (get the last non-null token if null).
//

void HumdrumToken::getBase40PitchesResolveNull(vector<int>& output) {
	bool nullQ = (*this == ".");
	HTp token = this;
	if (nullQ) {
		token = this->resolveNull();
	}
	output.clear();
	if (token == NULL) {
		return;
	}
	if (*token == ".") {
		return;
	}
	vector<string> pieces = token->getSubtokens();
	output.resize(pieces.size());
	for (int i=0; i<(int)pieces.size(); i++) {
		if (pieces[i].find("r") != string::npos) {
			output[i] = 0;
		} else {
			output[i] = Convert::kernToBase40(pieces[i]);
			// sustained notes are negative values:
			if (nullQ) {
				output[i] = -output[i];
			} else if (pieces[i].find("_") != string::npos) {
				output[i] = -output[i];
			} else if (pieces[i].find("]") != string::npos) {
				output[i] = -output[i];
			}
		}
	}
}


vector<int> HumdrumToken::getBase40PitchesResolveNull(void) {
	vector<int> output;
	this->getBase40PitchesResolveNull(output);
	return output;
}


int HumdrumToken::getBase40PitchResolveNull(void) {
	vector<int> pitches = getBase40PitchesResolveNull();
	if (pitches.size() > 0) {
		return pitches[0];
	} else {
		return 0;
	}
}


void HumdrumToken::getBase40PitchesResolveNullSortHL(vector<int>& output) {
	this->getBase40PitchesResolveNull(output);
	if (output.size() <= 1) {
		return;
	}
	sort(output.begin(), output.end(),
		[](const int& a, const int& b) {
			return abs(a) > abs(b);
		}
	);
}


vector<int> HumdrumToken::getBase40PitchesResolveNullSortHL(void) {
	vector<int> output;
	this->getBase40PitchesResolveNullSortHL(output);
	return output;
}


void HumdrumToken::getBase40PitchesResolveNullSortLH(vector<int>& output) {
	this->getBase40PitchesResolveNull(output);
	if (output.size() <= 1) {
		return;
	}
	sort(output.begin(), output.end(),
		[](const int& a, const int& b) {
			return abs(a) < abs(b);
		}
	);
}


vector<int> HumdrumToken::getBase40PitchesResolveNullSortLH (void) {
	vector<int> output;
	this->getBase40PitchesResolveNullSortLH(output);
	return output;
}





//////////////////////////////
//
// HumdrumToken::getMidiPitches -- Returns 0 if a rest.
//    Does not check for valid MIDI note number range of 0-127.
//    Also not checking to see if input data type is not **kern.
//    Negative value means a sustained pitch.
//

void HumdrumToken::getMidiPitches(vector<int>& output) {
	if (*this == ".") {
		// Not resolving null tokens in this function.
		output.clear();
		return;
	}
	vector<string> pieces = this->getSubtokens();
	output.resize(pieces.size());
	for (int i=0; i<(int)pieces.size(); i++) {
		if (pieces[i].find("r") != string::npos) {
			output[i] = 0;
		} else {
			output[i] = Convert::kernToMidiNoteNumber(pieces[i]);
			// sustained notes are negative values:
			if (pieces[i].find("_") != string::npos) {
				output[i] = -output[i];
			} else if (pieces[i].find("]") != string::npos) {
				output[i] = -output[i];
			}
		}
	}
}


vector<int> HumdrumToken::getMidiPitches(void) {
	vector<int> output;
	this->getMidiPitches(output);
	return output;
}


int HumdrumToken::getMidiPitch(void) {
	vector<int> pitches = getMidiPitches();
	if (pitches.size() > 0) {
		return pitches[0];
	} else {
		return 0;
	}
}



//////////////////////////////
//
// HumdrumToken::getMidiPitchesSortHL -- Sort extracted MIDI pitches from
//    high to low (when there is a chord).  Does not check for valid MIDI
//    note number range of 0-127.  Also not checking to see if input data
//    type is not **kern.  Sustained notes are negative values, but pitches
//    are sorted by absolute value.
//

void HumdrumToken::getMidiPitchesSortHL(vector<int>& output) {
	this->getMidiPitches(output);
	if (output.size() <= 1) {
		return;
	}
	sort(output.begin(), output.end(),
		[](const int& a, const int& b) {
			return abs(a) > abs(b);
		}
	);
}


vector<int> HumdrumToken::getMidiPitchesSortHL(void) {
	vector<int> output;
	this->getMidiPitchesSortHL(output);
	return output;
}



//////////////////////////////
//
// HumdrumToken::getMidiPitchesSortLH -- Sort extracted MIDI pitches from
//    low to high (when there is a chord).  Does not check for valid MIDI
//    note number range of 0-127.  Also not checking to see if input data
//    type is not **kern.  Sustained notes are negative values, but pitches
//    are sorted by absolute value.
//

void HumdrumToken::getMidiPitchesSortLH(vector<int>& output) {
	this->getMidiPitches(output);
	if (output.size() <= 1) {
		return;
	}
	sort(output.begin(), output.end(),
		[](const int& a, const int& b) {
			return abs(a) < abs(b);
		}
	);
}


vector<int> HumdrumToken::getMidiPitchesSortLH(void) {
	vector<int> output;
	this->getMidiPitchesSortLH(output);
	return output;
}



//////////////////////////////
//
// getMidiPitchesResolveNull -- same as getMidiPitches*() functions
//     but resolves null tokens (get the last non-null token if null).
//

void HumdrumToken::getMidiPitchesResolveNull(vector<int>& output) {
	bool nullQ = (*this == ".");
	HTp token = this;
	if (nullQ) {
		token = this->resolveNull();
	}
	output.clear();
	if (token == NULL) {
		return;
	}
	if (*token == ".") {
		return;
	}
	vector<string> pieces = token->getSubtokens();
	output.resize(pieces.size());
	for (int i=0; i<(int)pieces.size(); i++) {
		if (pieces[i].find("r") != string::npos) {
			output[i] = 0;
		} else {
			output[i] = Convert::kernToMidiNoteNumber(pieces[i]);
			// sustained notes are negative values:
			if (nullQ) {
				output[i] = -output[i];
			} else if (pieces[i].find("_") != string::npos) {
				output[i] = -output[i];
			} else if (pieces[i].find("]") != string::npos) {
				output[i] = -output[i];
			}
		}
	}
}


vector<int> HumdrumToken::getMidiPitchesResolveNull(void) {
	vector<int> output;
	this->getMidiPitchesResolveNull(output);
	return output;
}


int HumdrumToken::getMidiPitchResolveNull(void) {
	vector<int> pitches = getMidiPitchesResolveNull();
	if (pitches.size() > 0) {
		return pitches[0];
	} else {
		return 0;
	}
}


void HumdrumToken::getMidiPitchesResolveNullSortHL(vector<int>& output) {
	this->getMidiPitchesResolveNull(output);
	if (output.size() <= 1) {
		return;
	}
	sort(output.begin(), output.end(),
		[](const int& a, const int& b) {
			return abs(a) > abs(b);
		}
	);
}


vector<int> HumdrumToken::getMidiPitchesResolveNullSortHL(void) {
	vector<int> output;
	this->getMidiPitchesResolveNullSortHL(output);
	return output;
}


void HumdrumToken::getMidiPitchesResolveNullSortLH(vector<int>& output) {
	this->getMidiPitchesResolveNull(output);
	if (output.size() <= 1) {
		return;
	}
	sort(output.begin(), output.end(),
		[](const int& a, const int& b) {
			return abs(a) < abs(b);
		}
	);
}


vector<int> HumdrumToken::getMidiPitchesResolveNullSortLH (void) {
	vector<int> output;
	this->getMidiPitchesResolveNullSortLH(output);
	return output;
}




// spine mainipulators:
#define SPLIT_TOKEN       "*^"
#define MERGE_TOKEN       "*v"
#define EXCHANGE_TOKEN    "*x"
#define TERMINATE_TOKEN   "*-"
#define ADD_TOKEN         "*+"
// Also exclusive interpretations which start "**" followed by the data type.

// other special tokens:
#define NULL_DATA            "."
#define NULL_INTERPRETATION  "*"
#define NULL_COMMENT_LOCAL   "!"
#define NULL_COMMENT_GLOBAL  "!!"



//////////////////////////////
//
// HumdrumToken::HumdrumToken -- Constructor for HumdrumToken.
//

HumdrumToken::HumdrumToken(void) : string() {
	m_rhycheck = 0;
	setPrefix("!");
	m_strand = -1;
	m_nullresolve = NULL;
	m_strophe     = NULL;
}


HumdrumToken::HumdrumToken(const string& aString) : string(aString) {
	m_rhycheck = 0;
	setPrefix("!");
	m_strand = -1;
	m_nullresolve = NULL;
	m_strophe     = NULL;
}


HumdrumToken::HumdrumToken(const char* aString) : string(aString) {
	m_rhycheck = 0;
	setPrefix("!");
	m_strand = -1;
	m_nullresolve = NULL;
	m_strophe     = NULL;
}


HumdrumToken::HumdrumToken(const HumdrumToken& token) :
		string((string)token), HumHash((HumHash)token) {
	m_address         = token.m_address;
	m_address.m_owner = NULL;
	m_duration        = token.m_duration;
	m_nextTokens      = token.m_nextTokens;
	m_previousTokens.clear();
	m_nextNonNullTokens.clear();
	m_previousNonNullTokens.clear();
	m_rhycheck        = token.m_rhycheck;
	m_strand          = -1;
	m_nullresolve     = NULL;
	m_strophe         = NULL;
	setPrefix(token.getPrefix());
}


HumdrumToken::HumdrumToken(HumdrumToken* token) :
		string((string)(*token)), HumHash((HumHash)(*token)) {
	m_address         = token->m_address;
	m_address.m_owner = NULL;
	m_duration        = token->m_duration;
	m_nextTokens      = token->m_nextTokens;
	m_previousTokens.clear();
	m_nextNonNullTokens.clear();
	m_previousNonNullTokens.clear();
	m_rhycheck        = token->m_rhycheck;
	m_strand          = -1;
	m_nullresolve     = NULL;
	m_strophe         = NULL;
	setPrefix(token->getPrefix());
}



HumdrumToken::HumdrumToken(const HumdrumToken& token, HLp owner) :
		string((string)token), HumHash((HumHash)token) {
	m_address         = token.m_address;
	m_address.m_owner = owner;
	m_duration        = token.m_duration;
	m_nextTokens      = token.m_nextTokens;
	m_previousTokens.clear();
	m_nextNonNullTokens.clear();
	m_previousNonNullTokens.clear();
	m_rhycheck        = token.m_rhycheck;
	m_strand          = -1;
	m_nullresolve     = NULL;
	m_strophe         = NULL;
	setPrefix(token.getPrefix());
}


HumdrumToken::HumdrumToken(HumdrumToken* token, HLp owner) :
		string((string)(*token)), HumHash((HumHash)(*token)) {
	m_address         = token->m_address;
	m_address.m_owner = owner;
	m_duration        = token->m_duration;
	m_nextTokens      = token->m_nextTokens;
	m_previousTokens.clear();
	m_nextNonNullTokens.clear();
	m_previousNonNullTokens.clear();
	m_rhycheck        = token->m_rhycheck;
	m_strand          = -1;
	m_nullresolve     = NULL;
	m_strophe         = NULL;
	setPrefix(token->getPrefix());
}



//////////////////////////////
//
// HumdrumToken::operator= -- Copy operator.
//

HumdrumToken& HumdrumToken::operator=(HumdrumToken& token) {
	if (this == &token) {
		return *this;
	}
	(string)(*this)   = (string)token;
	(HumHash)(*this)  = (HumHash)token;

	m_address         = token.m_address;
	m_address.m_owner = NULL;
	m_duration        = token.m_duration;
	m_nextTokens      = token.m_nextTokens;
	m_previousTokens.clear();
	m_nextNonNullTokens.clear();
	m_previousNonNullTokens.clear();
	m_rhycheck        = token.m_rhycheck;
	m_strand          = -1;
	m_nullresolve     = NULL;
	m_strophe         = NULL;
	setPrefix(token.getPrefix());

	return *this;
}


HumdrumToken& HumdrumToken::operator=(const string& token) {
	(string)(*this) = token;

	m_address.m_owner = NULL;
	m_duration        = 0;
	m_nextTokens.clear();
	m_previousTokens.clear();
	m_nextNonNullTokens.clear();
	m_previousNonNullTokens.clear();
	m_rhycheck        = -1;
	m_strand          = -1;
	m_nullresolve     = NULL;
	m_strophe         = NULL;
	setPrefix("!");

	return *this;
}


HumdrumToken& HumdrumToken::operator=(const char* token) {
	(string)(*this) = token;

	m_address.m_owner = NULL;
	m_duration        = 0;
	m_nextTokens.clear();
	m_previousTokens.clear();
	m_nextNonNullTokens.clear();
	m_previousNonNullTokens.clear();
	m_rhycheck        = -1;
	m_strand          = -1;
	m_nullresolve     = NULL;
	m_strophe         = NULL;
	setPrefix("!");

	return *this;
}



//////////////////////////////
//
// HumdrumToken::~HumdrumToken -- Deconstructor for HumdrumToken.
//

HumdrumToken::~HumdrumToken() {
	if (m_parameterSet) {
		delete m_parameterSet;
		m_parameterSet = NULL;
	}
}


//////////////////////////////
//
// HumdrumToken::equalChar -- Returns true if the character at the given
//     index is the given char.
//

bool HumdrumToken::equalChar(int index, char ch) const {
	if ((int)size() <= index) {
		return false;
	}
	if (index < 0) {
		return false;
	}
	if (((string)(*this))[index] == ch) {
		return true;
	} else {
		return false;
	}
}



//////////////////////////////
//
// HumdrumToken::getPreviousNonNullDataTokenCount -- Returns the number of
//   previous tokens in the spine which is not a null token.  For null
//   tokens, this will be a count of the number of non-null tokens which
//   the null represents.
// @SEEALSO: getPreviousNonNullDataToken
//

int HumdrumToken::getPreviousNonNullDataTokenCount(void) {
	return (int)m_previousNonNullTokens.size();
}


//////////////////////////////
//
// HumdrumToken::insertTokenAfter -- Insert the given token after the this token.
//    This will sever the link from this token to its next token.  There is only
//    presumed to be one next token, at least for the moment.
//
//

void HumdrumToken::insertTokenAfter(HTp newtoken) {
	if (m_nextTokens.empty()) {
		m_nextTokens.push_back(newtoken);
	} else {
		HTp oldnexttoken = m_nextTokens[0];
		m_nextTokens[0] = newtoken;
		newtoken->m_previousTokens.clear();
		newtoken->m_previousTokens.push_back(this);
		newtoken->m_nextTokens.clear();
		newtoken->m_nextTokens.push_back(oldnexttoken);
		if (oldnexttoken->m_previousTokens.empty()) {
			oldnexttoken->m_previousTokens.push_back(newtoken);
		} else {
			oldnexttoken->m_previousTokens[0] = newtoken;
		}
	}
}



//////////////////////////////
//
// HumdrumToken::getPreviousNonNullDataToken -- Returns the non-null
//    data token which occurs before this token in the data in the same
//    spine.  The default value is index 0, since mostly there will only
//    be one previous token.
//

HumdrumToken* HumdrumToken::getPreviousNonNullDataToken(int index) {
	if (index < 0) {
		index += (int)m_previousNonNullTokens.size();
	}
	if (index < 0) {
		return NULL;
	}
	if (index >= (int)m_previousNonNullTokens.size()) {
		return NULL;
	}
	return m_previousNonNullTokens[index];
}



//////////////////////////////
//
// HumdrumToken::getNextNonNullDataTokenCount -- Returns the number of non-null
//     data tokens which follow this token in the spine.
//

int HumdrumToken::getNextNonNullDataTokenCount(void) {
	return (int)m_nextNonNullTokens.size();
}



//////////////////////////////
//
// HumdrumToken::getNextNonNullDataToken -- Returns the given next non-null token
//    following this one in the spine.  The default value for index is 0 since
//    the next non-null data token count will typically be 1.
// default value: index = 0
//

HumdrumToken* HumdrumToken::getNextNonNullDataToken(int index) {
	if (index < 0) {
		index += (int)m_nextNonNullTokens.size();
	}
	if (index < 0) {
		return NULL;
	}
	if (index >= (int)m_nextNonNullTokens.size()) {
		return NULL;
	}
	return m_nextNonNullTokens[index];
}



//////////////////////////////
//
// HumdrumToken::getSlurDuration -- If the note has a slur start, then
//    returns the duration until the endpoint; otherwise, returns 0;
//    Expand later to handle slur ends and elided slurs.  The function
//    HumdrumFileContent::analyzeSlurs() should be called before accessing
//    this function.  If the slur duruation was already calculated, return
//    that value; otherwise, calculate from the location of a matching
//    slur end.
//

HumNum HumdrumToken::getSlurDuration(HumNum scale) {
	if (!isDataTypeLike("**kern")) {
		return 0;
	}
	if (isDefined("auto", "slurDuration")) {
		return getValueFraction("auto", "slurDuration");
	} else if (isDefined("auto", "slurEnd")) {
		HTp slurend = getValueHTp("auto", "slurEnd");
		return slurend->getDurationFromStart(scale) -
				getDurationFromStart(scale);
	} else {
		return 0;
	}
}



//////////////////////////////
//
// HumdrumToken::getDataType -- Get the exclusive interpretation type for
//     the token.
// @SEEALSO: isDataType
//

const string& HumdrumToken::getDataType(void) const {
	return m_address.getDataType();
}


/////////////////////////////
//
// HumdrumToken::getExclusiveInterpretation -- Get the exclusive
//      interpretation token that owns the given token.
//

HTp HumdrumToken::getExclusiveInterpretation(void) {
	return m_address.getExclusiveInterpretation();
}


//////////////////////////////
//
// HumdrumToken::isDataType -- Returns true if the data type of the token
//   matches the test data type.
// @SEEALSO: getDataType getKern
//

bool HumdrumToken::isDataType(const string& dtype) const {
	if (dtype.compare(0, 2, "**") == 0) {
		return dtype == getDataType();
	} else {
		return getDataType().compare(2, string::npos, dtype) == 0;
	}
}



//////////////////////////////
//
// HumdrumToken::isDataTypeLike -- Returns true if the data type of the token
//   matches the test data type plus a dash followed by any text.
// @SEEALSO: getDataType getKern
//

bool HumdrumToken::isDataTypeLike(const string& dtype) const {
	if (isDataType(dtype)) {
		return true;
	}
	if (dtype.compare(0, 2, "**") == 0) {
		string comparison = dtype;
		comparison += "-";
		string tokentype = getDataType();
		if (tokentype.compare(0, comparison.size(), comparison) == 0) {
			return true;
		} else {
			return false;
		}
		return dtype == getDataType();
	} else {
		string comparison = "**";
		comparison += dtype;
		comparison += "-";
		string tokentype = getDataType();
		if (tokentype.compare(0, comparison.size(), comparison) == 0) {
			return true;
		} else {
			return false;
		}
	}
}



//////////////////////////////
//
// HumdrumToken::isKern -- Returns true if the data type of the token
//    is **kern.
// @SEEALSO: isDataType
//

bool HumdrumToken::isKern(void) const {
	return isDataType("**kern");
}



//////////////////////////////
//
// HumdrumToken::isKernLike -- Returns true if the data type of the token
//    is **kern, or **kern- plus a tag.  This Allows for **kern-tag to be
//    treated as a staff for printing in verovio.  This can be used to separate
//    analysis spines that are output as **kern data to be prevented for use
//    as input to another analysis as real **kern data.
// @SEEALSO: isDataType
//

bool HumdrumToken::isKernLike(void) const {
	string dtype = getDataType();
	if (dtype == "**kern") {
		return true;
	} else if (dtype.compare(0, 7, "**kern-") == 0) {
		return true;
	} else if (dtype == "**kernyy") {
		return true;
	}
	return false;
}



//////////////////////////////
//
// HumdrumToken::isMens -- Returns true if the data type of the token
//    is **mens.
// @SEEALSO: isDataType
//

bool HumdrumToken::isMens(void) const {
	return isDataType("**mens");
}



//////////////////////////////
//
// HumdrumToken::isMensLike -- Returns true if the data type of the token
//    is **mens, or **mens- plus a tag.  This Allows for **mens-tag to be
//    treated as a staff for printing in verovio.  This can be used to separate
//    analysis spines that are output as **mens data to be prevented for use
//    as input to another analysis as real **mens data.
// @SEEALSO: isDataType
//

bool HumdrumToken::isMensLike(void) const {
	string dtype = getDataType();
	if (dtype == "**mens") {
		return true;
	} else if (dtype.compare(0, 7, "**mens-") == 0) {
		return true;
	}
	return false;
}



//////////////////////////////
//
// HumdrumToken::setSpineInfo -- Sets the spine manipulation history string.
// @SEEALTO: getSpineInfo
//

void HumdrumToken::setSpineInfo(const string& spineinfo) {
	m_address.setSpineInfo(spineinfo);
}



//////////////////////////////
//
// HumdrumToken::getSpineInfo -- Returns the spine split/merge history
//    for the token.
// @SEEALTO: setSpineInfo
//

string HumdrumToken::getSpineInfo(void) const {
	return m_address.getSpineInfo();
}



//////////////////////////////
//
// HumdrumToken::getLineIndex -- Returns the line index of the owning
//    HumdrumLine for this token.
// @SEEALTO: getLineNumber
//

int HumdrumToken::getLineIndex(void) const {
	return m_address.getLineIndex();
}



//////////////////////////////
//
// HumdrumToken::getFieldIndex -- Returns the index of the token the line.
// @SEEALSO: getFieldIndex
//

int HumdrumToken::getFieldIndex(void) const {
	return m_address.getFieldIndex();
}



//////////////////////////////
//
// HumdrumToken::getFieldNumber -- Returns the index of the token the line.
// @SEEALSO: getFieldNumber
//

int HumdrumToken::getFieldNumber(void) const {
	return m_address.getFieldIndex() + 1;
}



//////////////////////////////
//
// HumdrumToken::getTokenIndex -- Returns the index of the token the line.
// @SEEALSO: getTokenIndex
//

int HumdrumToken::getTokenIndex(void) const {
	return m_address.getFieldIndex();
}



//////////////////////////////
//
// HumdrumToken::getTokenNumber -- Returns the index of the token the line.
// @SEEALSO: getFieldNumber
//

int HumdrumToken::getTokenNumber(void) const {
	return m_address.getFieldIndex() + 1;
}



//////////////////////////////
//
// HumdrumToken::getLineNumber -- Returns the line index plus 1.
// @SEEALTO: getLineIndex
//

int HumdrumToken::getLineNumber(void) const {
	return m_address.getLineNumber();
}



//////////////////////////////
//
// HumdrumToken::setFieldIndex -- Sets the field index of the token on the
//   owning HumdrumLine object.
// @SEEALSO: getFieldIndex
//

void HumdrumToken::setFieldIndex(int index) {
	m_address.setFieldIndex(index);
}



//////////////////////////////
//
// HumdrumToken::setTrack -- Sets the track number (similar to a staff in MEI).
//     The two-parameter version will set the track and sub-track at the same
//     time (subtrack is similar to a staff and layer in MEI).
//

void HumdrumToken::setTrack(int aTrack) {
	m_address.setTrack(aTrack);
}


void HumdrumToken::setTrack(int aTrack, int aSubtrack) {
	setTrack(aTrack);
	setSubtrack(aSubtrack);
}



//////////////////////////////
//
// HumdrumToken::getTrack -- Get the track (similar to a staff in MEI).
//

int HumdrumToken::getTrack(void) const {
	return m_address.getTrack();
}



//////////////////////////////
//
// HumdrumToken::getSpineIndex -- Similar to getTrack() but indexed from 0
//      rather than 1.  Non-spined tokens should return -1 since they
//      are not in the spine structure.
//

int HumdrumToken::getSpineIndex(void) const {
	return m_address.getTrack() - 1;
}



//////////////////////////////
//
// HumdrumToken::setSubtrack -- Sets the subtrack (similar to a layer
//    in MEI).
//

void HumdrumToken::setSubtrack(int aSubtrack) {
	m_address.setSubtrack(aSubtrack);
}



//////////////////////////////
//
// HumdrumToken::setSubtrackCount -- Sets the subtrack count in the
//    HumdrumLine for all tokens in the same track as the current
//    token.
//

void HumdrumToken::setSubtrackCount(int count) {
	m_address.setSubtrackCount(count);
}



//////////////////////////////
//
// HumdrumToken::setPreviousToken --
//

void HumdrumToken::setPreviousToken(HumdrumToken* token) {
	m_previousTokens.resize(1);
	m_previousTokens[0] = token;
}



//////////////////////////////
//
// HumdrumToken::setNextToken --
//

void HumdrumToken::setNextToken(HumdrumToken* token) {
	m_nextTokens.resize(1);
	m_nextTokens[0] = token;
}



//////////////////////////////
//
// HumdrumToken::addNextNonNullToken --
//

void HumdrumToken::addNextNonNullToken(HTp token) {
	if (token == NULL) {
		return;
	}
	for (int i=0; i<(int)m_nextNonNullTokens.size(); i++) {
		if (token == m_nextNonNullTokens[i]) {
			return;
		}
	}
	m_nextNonNullTokens.push_back(token);
	// maybe should sort by track/subspine order...
}



//////////////////////////////
//
// HumdrumToken::getNextToken -- Returns the next token in the
//    spine.  Since the next token count is usually one, the default
//    index value is zero.  When there is no next token (when the current
//    token is a spine terminaor), then NULL will be returned.
// default value: index = 0
// @SEEALSO: getNextTokens, getPreviousToken
//

HTp HumdrumToken::getNextToken(int index) const {
	if ((index >= 0) && (index < (int)m_nextTokens.size())) {
		return m_nextTokens[index];
	} else {
		return NULL;
	}
}



//////////////////////////////
//
// HumdrumToken::getNextTokens -- Returns a list of the next
//   tokens in the spine after this token.
// @SEEALSO: getNextToken
//

vector<HumdrumToken*> HumdrumToken::getNextTokens(void) const {
	return m_nextTokens;
}



//////////////////////////////
//
// HumdrumToken::getPreviousTokens -- Returns a list of the previous
//    tokens in the spine before this token.
//

vector<HumdrumToken*> HumdrumToken::getPreviousTokens(void) const {
	return m_previousTokens;
}



//////////////////////////////
//
// HumdrumToken::getPreviousToken -- Returns the previous token in the
//    spine.  Since the previous token count is usually one, the default
//    index value is zero.
// default value: index = 0
//

HumdrumToken* HumdrumToken::getPreviousToken(int index) const {
	if ((index >= 0) && (index < (int)m_previousTokens.size())) {
		return m_previousTokens[index];
	} else {
		return NULL;
	}
}


//////////////////////////////
//
// HumdrumToken::getNextFieldToken --
//

HTp HumdrumToken::getNextFieldToken(void) const {
	HLp line = getLine();
	if (!line) {
		return NULL;
	}
	int field = getFieldIndex();
	if (field >= line->getFieldCount()  - 1) {
		return NULL;
	}
	return line->token(field+1);
}



//////////////////////////////
//
// HumdrumToken::getPreviousFieldToken --
//

HTp HumdrumToken::getPreviousFieldToken(void) const {
	HLp line = getLine();
	if (!line) {
		return NULL;
	}
	int field = getFieldIndex();
	if (field < 1) {
		return NULL;
	}
	return line->token(field-1);
}



//////////////////////////////
//
// HumdrumToken::analyzeDuration -- Currently reads the duration of
//   **kern and **recip data.  Add more data types here such as **koto.
//

bool HumdrumToken::analyzeDuration(void) {
	m_rhythm_analyzed = true;
	if ((*this) == NULL_DATA) {
		m_duration.setValue(-1);
		return true;
	}
	if (equalChar(0 ,'!')) {
		m_duration.setValue(-1);
		return true;
	}
	if (equalChar(0 ,'*')) {
		m_duration.setValue(-1);
		return true;
	}
	if (equalChar(0 ,'=')) {
		m_duration.setValue(-1);
		return true;
	}
	if (hasRhythm()) {
		if (isData()) {
			if (!isNull()) {
				if (isKernLike()) {
					if (strchr(this->c_str(), 'q') != NULL) {
						m_duration = 0;
					} else {
						m_duration = Convert::recipToDuration((string)(*this));
					}
				} else if (isMensLike()) {
					int rlev = this->getValueInt("auto", "mensuration", "levels");
					if (rlev < 2222) {
						cerr << "Warning: mensuration levels not analyzed yet" << endl;
						rlev = 2222;
					}
					m_duration = Convert::mensToDuration((string)(*this), rlev);
				}
			} else {
				m_duration.setValue(-1);
			}
		} else {
			m_duration.setValue(-1);
		}

	} else {
		m_duration.setValue(-1);
	}
	return true;
}



///////////////////////////////
//
// HumdrumToken::isManipulator -- Returns true if token is one of:
//    SPLIT_TOKEN     = "*^"  == spine splitter
//    MERGE_TOKEN     = "*v"  == spine merger
//    EXCHANGE_TOKEN  = "*x"  == spine exchanger
//    ADD_TOKEN       = "*+"  == spine adder
//    TERMINATE_TOKEN = "*-"  == spine terminator
//    **...  == exclusive interpretation
//

bool HumdrumToken::isManipulator(void) const {
	if (isSplitInterpretation())     { return true; }
	if (isMergeInterpretation())     { return true; }
	if (isExchangeInterpretation())  { return true; }
	if (isAddInterpretation())       { return true; }
	if (isTerminateInterpretation()) { return true; }
	if (isExclusiveInterpretation()) { return true; }
	return false;
}



//////////////////////////////
//
// HumdrumToken::getDuration -- Returns the duration of the token.  The token
//    does not necessarily need to have any explicit duration, as the returned
//    value will also include implicit duration calculated in analyzeRhythm
//    in the HumdrumFileStructure class.
//

HumNum HumdrumToken::getDuration(void) {
	if (!m_rhythm_analyzed) {
		analyzeDuration();
	}
	return m_duration;
}


HumNum HumdrumToken::getDuration(HumNum scale) {
	if (!m_rhythm_analyzed) {
		analyzeDuration();
	}
	return m_duration * scale;
}



//////////////////////////////
//
// HumdrumToken::getTiedDuration -- Returns the duration of the token and any
//    tied notes attached to it.  Does not work well which chords that have
//    notes with different tie states.  Does not work well with secondary spine splits
//    (only follows left side of spine split).  Does not deal with disjunct ties or
//    linked ties (ties across staves).   Does handle enharmonic spelling of tied notes.
//

HumNum HumdrumToken::getTiedDuration(void) {
	if (!m_rhythm_analyzed) {
		analyzeDuration();
	}
	HumNum output = m_duration;

	if (this->find("[") == string::npos) {
		return output;
	}

	// start of a tied group so add the durations of the other notes.
	int b12 = Convert::kernToBase12(this);
	HTp note = this->getNextToken();
	while (note) {
		if (!note->isData()) {
         // The algorithm should be improved here, since only a lefthand
			// spine split will be followed (but the tie could be going to
			// a note on the right side of the split).
			note = note->getNextToken();
			continue;
		}
		if (note->isNull()) {
			note = note->getNextToken();
			continue;
		}
		if (!note->isSecondaryTiedNote()) {
			break;
		}
		int nb12 = Convert::kernToBase12(note);
		if (nb12 != b12) {
			// bad or incomplete tie (or cross-spine complexity, or disjunct tie):
			break;
		}
		// Note is tied to previous one, so add its curation to output.
		output += note->getDuration();
		note = note->getNextToken();
	}

	return output;
}


HumNum HumdrumToken::getTiedDuration(HumNum scale) {
	return getTiedDuration() * scale;
}



//////////////////////////////
//
// HumdrumToken::getDots -- Count the number of '.' characters in token string.
//    Terminating the count at the first occurrence of the separator character,
//    which is by default a space character.
//

int HumdrumToken::getDots(char separator) const {
	int count = 0;
	for (int i=0; i<(int)this->size()-1; i++) {
		if (this->at(i) == '.') {
			count++;
		}
		if (this->at(i) == separator) {
			break;
		}
	}
	return count;
}



//////////////////////////////
//
// HumdrumToken::getDurationNoDots -- Return the duration of the
//   note excluding any dots.
//

HumNum HumdrumToken::getDurationNoDots(void) {

	int dots = getDots();
	if (dots == 0) {
		return getDuration();
	}
	int bot = (int)pow(2.0, dots + 1) - 1;
	int top = (int)pow(2.0, dots);
	HumNum factor(top, bot);
	return getDuration() * factor;

}


HumNum HumdrumToken::getDurationNoDots(HumNum scale) {
	int dots = getDots();
	if (dots == 0) {
		return getDuration(scale);
	}
	int top = (int)pow(2.0, dots + 1) - 1;
	int bot = (int)pow(2.0, dots);
	HumNum factor(top, bot);
	return getDuration(scale) * factor;
}



//////////////////////////////
//
// HumdrumToken::setDuration -- Sets the duration of the token.  This is done in
//    HumdrumFileStructure::analyzeTokenDurations().
//

void HumdrumToken::setDuration(const HumNum& dur) {
	m_duration = dur;
}



//////////////////////////////
//
// HumdrumToken::getDurationFromStart -- Returns the duration from the
//   start of the owning HumdrumFile to the starting time of the
//   owning HumdrumLine for the token.  The durationFromStart is
//   in reference to the start of the token, not the end of the token,
//   which may be on another HumdrumLine.
//

HumNum HumdrumToken::getDurationFromStart(void) {
	return getLine()->getDurationFromStart();
}


HumNum HumdrumToken::getDurationFromStart(HumNum scale) {
	return getLine()->getDurationFromStart() * scale;
}



//////////////////////////////
//
// HumdrumToken::getDurationToEnd -- Returns the duration from the
//   start of the current line to the start of the last line
//   (the duration of the last line is always zero, so the duration
//   to end is always the duration to the end of the last non-zero
//   duration line.
//

HumNum HumdrumToken::getDurationToEnd(void) {
	return getLine()->getDurationToEnd();
}


HumNum HumdrumToken::getDurationToEnd(HumNum scale) {
	return getLine()->getDurationToEnd() * scale;
}



//////////////////////////////
//
// HumdrumToken::getDurationFromNoteStart -- Returns the duration from the start
//   of the resolved HumdrumToken to the starting time of this token. This is
//   useful to get the pased duration of a null token.
//

HumNum HumdrumToken::getDurationFromNoteStart(void) {
	HumNum dur = HumNum();
	HTp resolvedToken = this->resolveNull();
	HumdrumFile* infile = getLine()->getOwner();
	int startLineIndex = resolvedToken->getLineIndex();
	int thisLineIndex = getLineIndex();
	for (int i = startLineIndex; i < thisLineIndex; i++) {
		dur += infile->getLine(i)->getDuration();
	}
	// TODO handle tied notes
	return dur;
}


HumNum HumdrumToken::getDurationFromNoteStart(HumNum scale) {
	return this->getDurationFromNoteStart() * scale;
}



//////////////////////////////
//
// HumdrumToken::getDurationToNoteEnd -- Returns the duration from this token to
//   the end of the resolved HumdrumToken duration. This is useful to get the
//   remaining duration of a null token.
//

HumNum HumdrumToken::getDurationToNoteEnd(void) {
	// TODO handle tied notes
	return this->resolveNull()->getDuration() - getDurationFromNoteStart();
}


HumNum HumdrumToken::getDurationToNoteEnd(HumNum scale) {
	return this->getDurationToNoteEnd() * scale;
}



//////////////////////////////
//
// HumdrumToken::getBarlineDuration -- Returns the duration between
//   the next and previous barline.  If the token is a barline token,
//   then return the duration to the next barline.  The barline duration data
//   is filled in automatically when reading a file with the
//   HumdrumFileStructure::analyzeMeter() function.  The duration
//   will always be non-positive if the file is read with HumdrumFileBase and
//   analyzeMeter() is not run to analyze the data.
//

HumNum HumdrumToken::getBarlineDuration(void) {
	HLp own = getOwner();
	if (own == NULL) {
		return 0;
	}
	return own->getBarlineDuration();
}


HumNum HumdrumToken::getBarlineDuration(HumNum scale) {
	HLp own = getOwner();
	if (own == NULL) {
		return 0;
	}
	return own->getBarlineDuration(scale);
}



//////////////////////////////
//
// HumdrumToken::getDurationToBarline -- Get duration from start of token to
//      the start of the next barline. Units are quarter notes, unless scale
//      is set to a value other than 1.
//

HumNum HumdrumToken::getDurationToBarline(void) {
	HLp own = getOwner();
	if (own == NULL) {
		return 0;
	}
	return own->getDurationToBarline();
}

HumNum HumdrumToken::getDurationToBarline(HumNum scale) {
	HLp own = getOwner();
	if (own == NULL) {
		return 0;
	}
	return own->getDurationToBarline(scale);
}



//////////////////////////////
//
// HumdrumToken::getDurationFromBarline -- Get duration from start of token to
//      the previous barline. Units are quarter notes, unless scale
//      is set to a value other than 1.
//

HumNum HumdrumToken::getDurationFromBarline(void) {
	HLp own = getOwner();
	if (own == NULL) {
		return 0;
	}
	return own->getDurationFromBarline();
}

HumNum HumdrumToken::getDurationFromBarline(HumNum scale) {
	HLp own = getOwner();
	if (own == NULL) {
		return 0;
	}
	return own->getDurationFromBarline(scale);
}



//////////////////////////////
//
// HumdrumToken::hasRhythm -- Returns true if the exclusive interpretation
//    contains rhythmic data which will be used for analyzing the
//    duration of a HumdrumFile, for example.
//

bool HumdrumToken::hasRhythm(void) const {
	string type = getDataType();
	if (type == "**kern") {
		return true;
	}
	if (type.compare(0, 7, "**kern-") == 0) {
		return true;
	}
	if (type == "**recip") {
		return true;
	}
	if (type == "**mens") {
		return true;
	}
	return false;
}



//////////////////////////////
//
// HumdrumToken::hasBeam -- True if **kern has L, J, K, or k.
//

bool HumdrumToken::hasBeam(void) const {
	for (int i=0; i<(int)this->size(); i++) {
		switch (this->at(i)) {
			case 'L':
			case 'J':
			case 'k':
			case 'K':
				return true;
		}
	}
	return false;
}



//////////////////////////////
//
// HumdrumToken::hasFermata --
//

bool HumdrumToken::hasFermata(void) const {
	if (this->find(';') != string::npos) {
		return true;
	} else {
		return false;
	}
}



//////////////////////////////
//
// HumdrumToken::equalTo --
//

bool HumdrumToken::equalTo(const string& pattern) {
	if ((string)(*this) == pattern) {
		return true;
	} else {
		return false;
	}
}



//////////////////////////////
//
// HumdrumToken::isStaff -- Returns true if the spine type represents
//   a notated staff.
//

bool HumdrumToken::isStaff(void) const {
	if (isKernLike()) {
		return true;
	}
	if (isMensLike()) {
		return true;
	}

	return false;
}



//////////////////////////////
//
// HumdrumToken::isRest -- Returns true if the token is a (kern) rest.
//

bool HumdrumToken::isRest(void) {
	if (isKernLike()) {
		if (isChord()) {
			// rests are not allowed in chords, so return false if
			// token is a chord (rests in chords are used for non-sounding
			// notes in artificial harmonics).
			return false;
		} else if (isNull() && Convert::isKernRest((string)(*resolveNull()))) {
			return true;
		} else if (Convert::isKernRest((string)(*this))) {
			return true;
		}
	} else if (isMensLike()) {
		if (isNull() && Convert::isMensRest((string)(*resolveNull()))) {
			return true;
		} else if (Convert::isMensRest((string)(*this))) {
			return true;
		}
	}
	return false;
}



//////////////////////////////
//
// HumdrumToken::isNote -- Returns true if the token is a (kern) note
//     (possessing a pitch).
//

bool HumdrumToken::isNote(void) {
	if (!isData()) {
		return false;
	}
	if (isNull()) {
		return false;
	}
	if (isKernLike()) {
		if (Convert::isKernNote((string)(*this))) {
			return true;
		}
	} else if (isMensLike()) {
		if (Convert::isMensNote((string)(*this))) {
			return true;
		}
	}
	return false;
}



//////////////////////////////
//
// HumdrumToken::isPitched -- True if not a rest or an unpitched note.
//

bool HumdrumToken::isPitched(void) {
	if (this->isKernLike()) {
		for (int i=0; i<(int)this->size(); i++) {
			if ((this->at(i) == 'r') || (this->at(i) == 'R')) {
				return false;
			}
		}
		return true;
	}
	// Don't know data type so return false for now:
	return false;
}



//////////////////////////////
//
// HumdrumToken::isPitched -- True if has an unpitched marker (could be a rest)
//

bool HumdrumToken::isUnpitched(void) {
	if (this->isKernLike()) {
		if (this->find('R') != string::npos) {
			return 1;
		} else {
			return 0;
		}
	}
	// Don't know data type so return false for now:
	return false;
}




//////////////////////////////
//
// HumdrumToken::isSustainedNote -- Returns true if the token represents
//     a sounding note, but not the attack portion.  Should only be
//     applied to **kern data.
//

bool HumdrumToken::isSustainedNote(void) {
	HTp token = this;
	if (isNull()) {
		token = resolveNull();
	}
	return token->isSecondaryTiedNote();
}



//////////////////////////////
//
// HumdrumToken::isNoteAttack -- Returns true if the token represents
//     the attack of a note.  Should only be applied to **kern data, but
//     this function does not check for that for efficiency reasons.
//

bool HumdrumToken::isNoteAttack(void) {
	HTp token = this;
	if (isNull()) {
		token = resolveNull();
	}
	if (token->isRest()) {
		return false;
	}
	return !token->isSecondaryTiedNote();
}



//////////////////////////////
//
// HumdrumToken::isInvisible -- True if a barline and is invisible (contains
//     a "-" styling), or a note/rest contains the string "yy" which is
//     interpreted as meaning make it invisible.
//
//

bool HumdrumToken::isInvisible(void) {
	if (!isDataType("**kern")) {
			return false;
	}
	if (isBarline()) {
		if (find("-") != string::npos) {
			return true;
		}
	} else if (isData()) {
		if (find("yy") != string::npos) {
			return true;
		}
	}

	return false;
}



//////////////////////////////
//
// HumdrumToken::isGrace -- True if a **kern note has no duration.
//

bool HumdrumToken::isGrace(void) {
	if (!isDataType("**kern")) {
			return false;
	}
	if (!isData()) {
		return false;
	} else if (this->find("q") != string::npos) {
		return true;
	}

	return false;
}



//////////////////////////////
//
// HumdrumToken::isClef -- True if a clef.
//

bool HumdrumToken::isClef(void) {
	if (!(isDataType("**kern") || isDataType("**mens"))) {
			return false;
	}
	if (!isInterpretation()) {
		return false;
	} else if (this->compare(0, 5, "*clef") == 0) {
		return true;
	}

	return false;
}



//////////////////////////////
//
// HumdrumToken::isModernClef -- True if a modern clef.
//

bool HumdrumToken::isModernClef(void) {
	if (!(isDataType("**kern") || isDataType("**mens"))) {
			return false;
	}
	if (!isInterpretation()) {
		return false;
	} else if (this->compare(0, 6, "*mclef") == 0) {
		return true;
	}

	return false;
}



//////////////////////////////
//
// HumdrumToken::isOriginalClef -- True if an original clef.
//

bool HumdrumToken::isOriginalClef(void) {
	if (!(isDataType("**kern") || isDataType("**mens"))) {
			return false;
	}
	if (!isInterpretation()) {
		return false;
	} else if (this->compare(0, 6, "*oclef") == 0) {
		return true;
	}

	return false;
}



//////////////////////////////
//
// HumdrumToken::isKeySignature -- True if a key signature.
//

bool HumdrumToken::isKeySignature(void) {
	if (this->compare(0, 3, "*k[") != 0) {
		return false;
	}
	if (this->back() != ']') {
		return false;
	}
	return true;
}



//////////////////////////////
//
// HumdrumToken::isOriginalKeySignature -- True if an original key signature.
//

bool HumdrumToken::isOriginalKeySignature(void) {
	if (this->compare(0, 4, "*ok[") != 0) {
		return false;
	}
	if (this->back() != ']') {
		return false;
	}
	return true;
}



//////////////////////////////
//
// HumdrumToken::isModernKeySignature -- True if a modern key signature.
//

bool HumdrumToken::isModernKeySignature(void) {
	if (this->compare(0, 4, "*mk[") != 0) {
		return false;
	}
	if (this->back() != ']') {
		return false;
	}
	return true;
}



//////////////////////////////
//
// HumdrumToken::isKeyDesignation -- True if a **kern key designation.
//   *C:
//   *A-:
//   *c#:
//   *d:dor
//

bool HumdrumToken::isKeyDesignation(void) {
	if (this->size() < 3) {
		return false;
	}
	size_t pos = this->find(":");
	if (pos == string::npos) {
		return false;
	}
	if (pos > 4) {
		return false;
	}
	if (pos < 2) {
		return false;
	}
	char diatonic = tolower((*this)[1]);

	if (!((diatonic >= 'a') && (diatonic <= 'g'))) {
		return false;
	}
	if (pos >= 3) {
		char accidental1 = (*this)[2];
		if (!((accidental1 == '#') || (accidental1 == '-') || (accidental1 == 'n'))) {
			return false;
		}
	}
	if (pos >= 4) {
		char accidental2 = (*this)[3];
		if (!((accidental2 == '#') || (accidental2 == '-') || (accidental2 == 'n'))) {
			return false;
		}
	}
	// maybe check for modal cases here
	return true;
}



//////////////////////////////
//
// HumdrumToken::isTimeSignature -- True if a **kern time signature.
//

bool HumdrumToken::isTimeSignature(void) {
	if (this->size() < 3) {
		return false;
	}
	if (this->compare(0, 2, "*M") != 0) {
		return false;
	}
	if (!isdigit((*this)[2])) {
		return false;
	}
	if (this->find("/") == string::npos) {
		return false;
	}
	return true;
}



//////////////////////////////
//
// HumdrumToken::isMetricSymbol -- True if a **kern metric symbol
//

bool HumdrumToken::isMetricSymbol(void) {
	if (this->size() < 6) {
		return false;
	}
	if (this->compare(0, 5, "*met(") != 0) {
		return false;
	}
	if (this->back() != ')') {
		return false;
	}
	return true;
}



//////////////////////////////
//
// HumdrumToken::isTempo -- True if a **kern tempo.
//

bool HumdrumToken::isTempo(void) {
	if (this->size() < 4) {
		return false;
	}
	if (this->compare(0, 3, "*MM") != 0) {
		return false;
	}
	if (!isdigit((*this)[3])) {
		return false;
	}
	return true;
}



//////////////////////////////
//
// HumdrumToken::isMensurationSymbol -- True if a **kern mensuration Symbol.
//

bool HumdrumToken::isMensurationSymbol(void) {
	if (this->compare(0, 5, "*met(") == 0) {
		return true;
	}
	// extended mensuration symbol allows for an underscore and up to four digits after it.
	return false;
}



//////////////////////////////
//
// HumdrumToken::isOriginalMensurationSymbol -- True if a **kern mensuration symbol, original form.
//

bool HumdrumToken::isOriginalMensurationSymbol(void) {
	if (this->compare(0, 6, "*omet(") != 0) {
		return false;
	}
	if ((*this)[this->size()-1] != ')') {
		return false;
	}
	return true;
}



//////////////////////////////
//
// HumdrumToken::isModernMensurationSymbol -- True if a **kern mensuration symbol, modern form.
//

bool HumdrumToken::isModernMensurationSymbol(void) {
	if (this->compare(0, 6, "*mmet(") != 0) {
		return false;
	}
	if ((*this)[this->size()-1] != ')') {
		return false;
	}
	return true;
}



//////////////////////////////
//
// HumdrumToken::isInstrumentDesignation -- Such as *Iclars for B-flat clarinet.
//   Instrument codes can also contain "_" characters and capital letters
//   as long as they are not at the start of the code after "I".
//   This algorithm does not check past the first letter of the code,
//   which has to be lower case letter.
//

bool HumdrumToken::isInstrumentDesignation(void) {
	if (this->compare(0, 2, "*I") != 0) {
		return false;
	}
	if (this->size() < 3) {
		return false;
	}
	if (!islower(this->at(2))) {
		return false;
	}
	return true;
}



//////////////////////////////
//
// HumdrumToken::isInstrumentClass -- Instrument classes start with "*IC",
//   such as "*ICww" for woodwinds.
//

bool HumdrumToken::isInstrumentClass(void) {
	if (this->compare(0, 3, "*IC") == 0) {
		return true;
	} else {
		return false;
	}
}



//////////////////////////////
//
// HumdrumToken::isInstrumentGroup -- Instrument groups start with "*IG",
//   such as "*IGsolo" for the soloist(s).
//

bool HumdrumToken::isInstrumentGroup(void) {
	if (this->compare(0, 3, "*IG") == 0) {
		return true;
	} else {
		return false;
	}
}



//////////////////////////////
//
// HumdrumToken::isInstrumentName -- True if an instrument name token,
//    such as *I"Flute 1
//

bool HumdrumToken::isInstrumentName(void) {
	if (this->compare(0, 3, "*I\"") != 0) {
		return false;
	} else {
		return true;
	}
}



//////////////////////////////
//
// HumdrumToken::isInstrumentNumber -- True if an instrument number token,
//    such as *I#2 for a second instrument (Such as Flute 2)
//

bool HumdrumToken::isInstrumentNumber(void) {
	HumRegex hre;
	if (hre.search(this, "^\\*I#\\d+$")) {
		return true;
	} else {
		return false;
	}
}



//////////////////////////////
//
// HumdrumToken::isModernInstrumentName -- True if a modern instrument name token.
//

bool HumdrumToken::isModernInstrumentName(void) {
	if (this->compare(0, 4, "*mI\"") != 0) {
		return false;
	} else {
		return true;
	}
}



//////////////////////////////
//
// HumdrumToken::isOriginalInstrumentName -- True if an original instrument name token.
//

bool HumdrumToken::isOriginalInstrumentName(void) {
	if (this->compare(0, 4, "*oI\"") != 0) {
		return false;
	} else {
		return true;
	}
}



//////////////////////////////
//
// HumdrumToken::isInstrumentAbbreviation -- True if an instrument abbreviation token.
//    Checks to ensure the first three letters are *I' .
//

bool HumdrumToken::isInstrumentAbbreviation(void) {
	if (this->compare(0, 3, "*I'") != 0) {
		return false;
	} else {
		return true;
	}
}



//////////////////////////////
//
// HumdrumToken::isModernInstrumentAbbreviation -- True if a modern instrument abbreviation token.
//

bool HumdrumToken::isModernInstrumentAbbreviation(void) {
	if (this->compare(0, 4, "*mI'") != 0) {
		return false;
	} else {
		return true;
	}
}



//////////////////////////////
//
// HumdrumToken::isOriginalInstrumentAbbreviation -- True if a original instrument abbreviation token.
//

bool HumdrumToken::isOriginalInstrumentAbbreviation(void) {
	if (this->compare(0, 4, "*oI'") != 0) {
		return false;
	} else {
		return true;
	}
}



//////////////////////////////
//
// HumdrumToken::isStria -- True if a staff-line count.
//

bool HumdrumToken::isStria(void) {
	if (this->compare(0, 6, "*stria'") != 0) {
		return false;
	} else {
		return true;
	}
}



//////////////////////////////
//
// HumdrumToken::getInstrumentName --
//

string HumdrumToken::getInstrumentName(void) {
	if (this->size() < 3) {
		return "";
	} else if (this->compare(0, 3, "*I\"") != 0) {
		return "";
	} else {
		return this->substr(3);
	}
}



//////////////////////////////
//
// HumdrumToken::getInstrumentAbbreviation --
//

string HumdrumToken::getInstrumentAbbreviation(void) {
	if (this->size() < 3) {
		return "";
	} else if (this->compare(0, 3, "*I'") != 0) {
		return "";
	} else {
		return this->substr(3);
	}
}



//////////////////////////////
//
// HumdrumToken::hasSlurStart -- Returns true if the **kern token has
//     a '(' character.
//

bool HumdrumToken::hasSlurStart(void) {
	if (isDataType("**kern")) {
		if (Convert::hasKernSlurStart((string)(*this))) {
			return true;
		}
	}
	return false;
}



//////////////////////////////
//
// HumdrumToken::hasSlurEnd -- Returns true if the **kern token has
//     a ')' character.
//

bool HumdrumToken::hasSlurEnd(void) {
	if (isDataType("**kern")) {
		if (Convert::hasKernSlurEnd((string)(*this))) {
			return true;
		}
	}
	return false;
}



//////////////////////////////
//
// HumdrumToken::hasVisibleAccidental -- Returns true if the accidental
//    of a **kern note is viewable if rendered to graphical notation.
// 	return values:
//      0  = false;
//      1  = true;
//      -1 = undefined;
//

int HumdrumToken::hasVisibleAccidental(int subtokenIndex) const {
	HLp humrec = getOwner();
	if (humrec == NULL) {
		return -1;
	}
	HumdrumFile* humfile = humrec->getOwner();
	if (humfile == NULL) {
		return -1;
	}
	if (isKern()) {
		if (!humfile->getValueBool("auto", "accidentalAnalysis**kern")) {
			int status = humfile->analyzeKernAccidentals();
			if (!status) {
				return -1;
			}
		}
	} else if (isMens()) {
		if (!humfile->getValueBool("auto", "accidentalAnalysis**mens")) {
			int status = humfile->analyzeMensAccidentals();
			if (!status) {
				return -1;
			}
		}
	}
	return getValueBool("auto", to_string(subtokenIndex), "visualAccidental");
}



//////////////////////////////
//
// HumdrumToken::hasVisibleAccidental -- Returns true if the accidental
//    of a **kern note is viewable if rendered to graphical notation.
// 	return values:
//      0  = false;
//      1  = true;
//      -1 = undefined;
//

int HumdrumToken::hasCautionaryAccidental(int subtokenIndex) const {
	HLp humrec = getOwner();
	if (humrec == NULL) {
		return -1;
	}
	HumdrumFile* humfile = humrec->getOwner();
	if (humfile == NULL) {
		return -1;
	}
	if (isKern()) {
		if (!humfile->getValueBool("auto", "accidentalAnalysis**kern")) {
			int status = humfile->analyzeKernAccidentals();
			if (!status) {
				return -1;
			}
		}
	} else if (isMens()) {
		if (!humfile->getValueBool("auto", "accidentalAnalysis**mens")) {
			int status = humfile->analyzeMensAccidentals();
			if (!status) {
				return -1;
			}
		}
	}
	return getValueBool("auto", to_string(subtokenIndex), "cautionaryAccidental");
}



//////////////////////////////
//
// HumdrumToken::hasLigatureBegin --
//

bool HumdrumToken::hasLigatureBegin(void) {
	if (isMensLike()) {
		return Convert::hasLigatureBegin(*this);
	} else {
		return false;
	}
}



//////////////////////////////
//
// HumdrumToken::hasRectaLigatureBegin --
//

bool HumdrumToken::hasRectaLigatureBegin(void) {
	if (isMensLike()) {
		return Convert::hasRectaLigatureBegin(*this);
	} else {
		return false;
	}
}



//////////////////////////////
//
// HumdrumToken::hasObliquaLigatureBegin --
//

bool HumdrumToken::hasObliquaLigatureBegin(void) {
	if (isMensLike()) {
		return Convert::hasObliquaLigatureBegin(*this);
	} else {
		return false;
	}
}



//////////////////////////////
//
// HumdrumToken::hasStemDirection --
//

char HumdrumToken::hasStemDirection(void) {
	if (isKernLike()) {
		return Convert::hasKernStemDirection(*this);
	} else {
		// don't know what a stem in this datatype is
		return '\0';
	}
}



//////////////////////////////
//
// HumdrumToken::allSameBarlineStyle --
//

bool HumdrumToken::allSameBarlineStyle(void) {
	HLp owner = getOwner();
	if (!owner) {
		return true;
	}
	return owner->allSameBarlineStyle();
}



//////////////////////////////
//
// HumdrumToken::hasLigatureEnd --
//

bool HumdrumToken::hasLigatureEnd(void) {
	if (isMensLike()) {
		return Convert::hasLigatureEnd(*this);
	} else {
		return false;
	}
}



//////////////////////////////
//
// HumdrumToken::hasRectaLigatureEnd --
//

bool HumdrumToken::hasRectaLigatureEnd(void) {
	if (isMensLike()) {
		return Convert::hasRectaLigatureEnd(*this);
	} else {
		return false;
	}
}



//////////////////////////////
//
// HumdrumToken::hasObliquaLigatureEnd --
//

bool HumdrumToken::hasObliquaLigatureEnd(void) {
	if (isMensLike()) {
		return Convert::hasObliquaLigatureEnd(*this);
	} else {
		return false;
	}
}



//////////////////////////////
//
// HumdrumToken::isSecondaryTiedNote -- Returns true if the token
//     is a (kern) note (possessing a pitch) and has '_' or ']' characters.
//

bool HumdrumToken::isSecondaryTiedNote(void) {
	if (isDataType("**kern")) {
		if (Convert::isKernSecondaryTiedNote((string)(*this))) {
			return true;
		}
	}
	return false;
}



//////////////////////////////
//
// HumdrumToken::isBarline -- Returns true if the first character is an
//   equals sign.
//

bool HumdrumToken::isBarline(void) const {
	if (size() == 0) {
		return false;
	}
	if ((*this)[0] == '=') {
		return true;
	} else {
		return false;
	}
}



//////////////////////////////
//
// HumdrumToken::isCommentGlobal -- Returns true of the token starts with "!!".
//    Currently confused with reference records.
//

bool HumdrumToken::isCommentGlobal(void) const {
	if (size() == 0) {
		return false;
	}
	if ((*this)[0] == '!') {
		if (size() > 1) {
			if ((*this)[1] == '!') {
				// global comment
				return true;
			}
		}
	}
	return false;
}



//////////////////////////////
//
// HumdrumToken::isCommentLocal -- Returns true of the token start with "!",
//   but not "!!" which is for global comments.
//

bool HumdrumToken::isCommentLocal(void) const {
	if (size() == 0) {
		return false;
	}
	if ((*this)[0] == '!') {
		if (size() > 1) {
			if ((*this)[1] == '!') {
				// global comment
				return false;
			}
		}
		return true;
	} else {
		return false;
	}
}



//////////////////////////////
//
// HumdrumToken::isComment -- Returns true of the token start with "!".
//

bool HumdrumToken::isComment(void) const {
	if (size() == 0) {
		return false;
	}
	if ((*this)[0] == '!') {
		return true;
	} else {
		return false;
	}
}



//////////////////////////////
//
// HumdrumToken::isData -- Returns true if not an interpretation, barline
//      or local comment.  This will not work on synthetic tokens generated
//      from an empty line.  So this function should be called only on tokens
//      in lines which pass the HumdrumLine::hasSpines() test.
//

bool HumdrumToken::isData(void) const {
	if (size() == 0) {
		return false;
	}
	int firstchar = (*this)[0];
	if ((firstchar == '*') || (firstchar == '!') || (firstchar == '=')) {
		return false;
	}
	return true;
}



//////////////////////////////
//
// HumdrumToken::isInterpretation -- Returns true if an interpretation.
//

bool HumdrumToken::isInterpretation(void) const {
	if (size() == 0) {
		return false;
	}
	int firstchar = (*this)[0];
	if (firstchar == '*') {
		return true;
	}
	return false;
}



//////////////////////////////
//
// HumdrumToken::isNonNullData -- Returns true if the token is a data token
//    that is not a null token.
//

bool HumdrumToken::isNonNullData(void) const {
	return isData() && !isNull();
}



//////////////////////////////
//
// HumdrumToken::isNullData -- Returns true if the token is a null
//     data token.
//

bool HumdrumToken::isNullData(void) const {
	return isData() && isNull();
}



//////////////////////////////
//
// HumdrumToken::isLabel -- Returns true if a thru label (such as *>A).
//

bool HumdrumToken::isLabel(void) const {
	if (this->compare(0, 2, "*>") != 0) {
		return false;
	}
	if (this->find("[") != string::npos) {
		return false;
	}
	return true;
}



//////////////////////////////
//
// HumdrumToken::isExpansionList -- Returns true if a thru expansion list (such as *>[A,A,B], or *>norep[A,B]).
//

bool HumdrumToken::isExpansionList(void) const {
	if (this->compare(0, 2, "*>") != 0) {
		return false;
	}
	if ((this->find("[") != string::npos) && (this->back() == ']')) {
		return true;
	}
	return false;
}



/////////////////////////////
//
// HumdrumToken::isChord -- True if is a chord.  Presuming you know what
//     data type you are accessing.
//     Default value:
//          separate = " "   (**kern note separator)
//

bool HumdrumToken::isChord(const string& separator) {
	return (this->find(separator) != string::npos) ? true : false;
}



//////////////////////////////
//
// HumdrumToken::isExclusiveInterpretation -- Returns true if first two
//     characters are "**".
//

bool HumdrumToken::isExclusiveInterpretation(void) const {
	const string& tok = (string)(*this);
	return tok.substr(0, 2) == "**";
}



//////////////////////////////
//
// HumdrumToken::isSplitInterpretation -- True if the token is "*^".
//

bool HumdrumToken::isSplitInterpretation(void) const {
	return ((string)(*this)) == SPLIT_TOKEN;
}



//////////////////////////////
//
// HumdrumToken::isMergeInterpretation -- True if the token is "*v".
//

bool HumdrumToken::isMergeInterpretation(void) const {
	// [20200331] GCC 6+ will print a compiler warning when checking this against NULL.
	//if ((void*)this == NULL) {
	//	// This was added perhaps due to a new bug [20100125] that is checking a null pointer
	//	return false;
	//}
	return ((string)(*this)) == MERGE_TOKEN;
}



//////////////////////////////
//
// HumdrumToken::isExchangeInterpretation -- True if the token is "*x".
//

bool HumdrumToken::isExchangeInterpretation(void) const {
	return ((string)(*this)) == EXCHANGE_TOKEN;
}



//////////////////////////////
//
// HumdrumToken::isTerminateInterpretation -- True if the token is "*-".
//

bool HumdrumToken::isTerminateInterpretation(void) const {
	return ((string)(*this)) == TERMINATE_TOKEN;
}



//////////////////////////////
//
// HumdrumToken::isAddInterpretation -- True if the token is "*+".
//

bool HumdrumToken::isAddInterpretation(void) const {
	return ((string)(*this)) == ADD_TOKEN;
}



//////////////////////////////
//
// HumdrumToken::isNull -- Returns true if the token is a null token,
//   either for data, comments, or interpretations.  Does not consider
//   null global comments since they are not part of the spine structure.
//

bool HumdrumToken::isNull(void) const {
	const string& tok = (string)(*this);
	if (tok == NULL_DATA)           { return true; }
	if (tok == NULL_INTERPRETATION) { return true; }
	if (tok == NULL_COMMENT_LOCAL)  { return true; }
	return false;
}



//////////////////////////////
//
// HumdrumToken::getSubtrack -- Get the subtrack (similar to a layer
//    in MEI).
//

int HumdrumToken::getSubtrack(void) const {
	return m_address.getSubtrack();
}



//////////////////////////////
//
// HumdrumToken::noteInLowerSubtrack -- Return true if the note
//     is attacked or sustained with another note in a lower layer.
//     This is for using in hum2mei conversion to avoid a bug in
//     verovio related to lyrics in layers where the notes are a
//     second apart.
//

bool HumdrumToken::noteInLowerSubtrack(void) {
	int subtrack = this->getSubtrack();
	if (subtrack <= 1) {
		return false;
	}
	int field = this->getFieldIndex();
	int track = this->getTrack();

	HLp owner = this->getOwner();
	if (owner == NULL) {
		return false;
	}

	for (int i=field-1; i>=0; i--) {
		HTp xtoken = owner->token(i);
		int xtrack = xtoken->getTrack();
		if (xtrack != track) {
			return false;
		}
		if (xtoken->isNull()) {
			continue;
		}
		if (xtoken->find("r") != string::npos) {
			continue;
		}
		return true;
	}

	return false;
}



//////////////////////////////
//
// HumdrumToken::getTrackString -- Gets "track.subtrack" as a string.  The
//     track and subtrack are integers.  The getTrackString function will
//     return a string with the track and subtrack separated by an dot.  The
//     Dot is not a decimal point, but if the subtrack count does not exceed
//     9, then the returned string can be treated as a floating-point number
//     where the subtrack is the fractional part.
// @SEEALSO: getTrack, getSubtrack
//

string HumdrumToken::getTrackString(void) const {
	return m_address.getTrackString();
}



/////////////////////////////
//
// HumdrumToken::getSubtokenCount -- Returns the number of sub-tokens in
//     a token.  The input parameter is the sub-token separator.  If the
//     separator comes at the start or end of the token, then there will
//     be empty sub-token(s) included in the count.
// default value: separator = " "
// @SEEALSO: getSubtoken
//

int HumdrumToken::getSubtokenCount(const string& separator) const {
	int count = 0;
	string::size_type start = 0;
	while ((start = string::find(separator, start)) != string::npos) {
		count++;
		start += separator.size();
	}
	return count+1;
}



/////////////////////////////
//
// HumdrumToken::getSubtoken -- Extract the specified sub-token from the token.
//    Tokens usually are separated by spaces in Humdrum files, but this will
//    depened on the data type (so therefore, the tokens are not presplit into
//    sub-tokens when reading in the file).
// default value: separator = " "
// @SEEALSO: getSubtokenCount, getTrackString
//

string HumdrumToken::getSubtoken(int index, const string& separator) const {
	if (index < 0) {
		return "";
	}

	string output;
	const string& token = *this;
	if (separator.size() == 0) {
		output = token[index];
		return output;
	}

	int count = 0;
	for (int i=0; i<(int)size(); i++) {
		if (this->compare(i, separator.size(), separator) == 0) {
			count++;
			if (count > index) {
				break;
			}
			i += (int)separator.size() - 1;
		} else if (count == index) {
			output += token[i];
		}
	}
	return output;
}



//////////////////////////////
//
// HumdrumToken::getSubtokens -- Return the list of subtokens as an array
//     of strings.
//     default value: separator = " "
//

std::vector<std::string> HumdrumToken::getSubtokens (const std::string& separator) const {
	std::vector<std::string> output;
	const string& token = *this;
	HumRegex hre;
	hre.split(output, token, separator);
	return output;
}



//////////////////////////////
//
// HumdrumToken::replaceSubtoken --
//     default value: separator = " "
//

void HumdrumToken::replaceSubtoken(int index, const std::string& newsubtok,
		const std::string& separator) {
	if (index < 0) {
		return;
	}
	std::vector<std::string> subtokens = getSubtokens(separator);
	if (index >= (int)subtokens.size()) {
		return;
	}
	subtokens[index] = newsubtok;
	string output;
	for (int i=0; i<(int)subtokens.size(); i++) {
		output += subtokens[i];
		if (i < (int)subtokens.size() - 1) {
			output += separator;
		}
	}
	this->setText(output);
}



//////////////////////////////
//
// HumdrumToken::setParameters -- Process a local comment with
//     the structure:
//        !NS1:NS2:key1=value1:key2=value2:key3=value3
//     and store the parameter in the HumHash parent class component of the
//     HumdrumToken object.
// default value for 2-parameter version: ptok = NULL
//

void HumdrumToken::setParameters(HumdrumToken* ptok) {
	HumdrumToken& pl = *ptok;
	if (pl.size() <= 1) {
		return;
	}
	string pdata = pl.substr(1, pl.size()-1);
	setParameters(pdata, ptok);
}


void HumdrumToken::setParameters(const string& pdata, HumdrumToken* ptok) {
	vector<string> pieces = Convert::splitString(pdata, ':');
	if (pieces.size() < 3) {
		return;
	}
	string ns1 = pieces[0];
	string ns2 = pieces[1];
	string key;
	string value;
	int loc;
	for (int i=2; i<(int)pieces.size(); i++) {
		Convert::replaceOccurrences(pieces[i], "&colon;", ":");
		loc = (int)pieces[i].find("=");
		if (loc != (int)string::npos) {
			key   = pieces[i].substr(0, loc);
			value = pieces[i].substr(loc+1, pieces[i].size());
		} else {
			key   = pieces[i];
			value = "true";
		}
		setValue(ns1, ns2, key, value);
		setOrigin(ns1, ns2, key, ptok);
	}
}



//////////////////////////////
//
// HumdrumToken::setText --
//

void HumdrumToken::setText(const string& text) {
	string::assign(text);
}



//////////////////////////////
//
// HumdrumToken::getText --
//

string HumdrumToken::getText(void) const {
	return string(*this);
}



//////////////////////////////
//
// HumdrumToken::addLinkedParamter --
//

int HumdrumToken::addLinkedParameterSet(HTp token) {
	if (token->find(":ignore") != string::npos) {
		// Ignore layout command (store layout command but
		// do not use it.  This is particularly for adding
		// layout parameters for notation, but the parameters
		// currently cause problems in verovio (so they should
		// be unignored at a future date when the layout
		// parameter is handled better).  Note that any
		// parameter starting with "ignore" such as "ignored"
		// will also be suppressed by this if statement.
		return -1;
	}
	for (int i=0; i<(int)m_linkedParameterTokens.size(); i++) {
		if (m_linkedParameterTokens[i] == token) {
			return i;
		}
	}

	if (m_linkedParameterTokens.empty()) {
		m_linkedParameterTokens.push_back(token);
	} else {
		int lineindex = token->getLineIndex();
		if (lineindex >= m_linkedParameterTokens.back()->getLineIndex()) {
			m_linkedParameterTokens.push_back(token);
		} else {
			// Store sorted by line number
			for (auto it = m_linkedParameterTokens.begin(); it != m_linkedParameterTokens.end(); it++) {
				if (lineindex < (*it)->getLineIndex()) {
					m_linkedParameterTokens.insert(it, token);
					break;
				}
			}
		}

	}

	return (int)m_linkedParameterTokens.size() - 1;
}



//////////////////////////////
//
// HumdrumToken::linkedParameterIsGlobal --
//

bool HumdrumToken::linkedParameterIsGlobal(int index) {
	return m_linkedParameterTokens.at(index)->isCommentGlobal();
}



//////////////////////////////
//
// HumdrumToken::getLinkedParameterSetCount --
//

int HumdrumToken::getLinkedParameterSetCount(void) {
	return (int)m_linkedParameterTokens.size();
}



//////////////////////////////
//
// HumdrumToken::getParameterSet --
//

HumParamSet* HumdrumToken::getParameterSet(void) {
	return m_parameterSet;
}



//////////////////////////////
//
// HumdrumToken::getLinkedParameterSet --
//

HumParamSet* HumdrumToken::getLinkedParameterSet(int index) {
	return m_linkedParameterTokens.at(index)->getParameterSet();
}



//////////////////////////////
//
// HumdrumToken::storeParameterSet -- Store the contents of the token
//    in the linked parameter storage.  Used for layout parameters.
//

void HumdrumToken::storeParameterSet(void) {
	if (m_parameterSet) {
		delete m_parameterSet;
		m_parameterSet = NULL;
	}
	if (this->isCommentLocal() && (this->find(':') != string::npos)) {
		m_parameterSet = new HumParamSet(this);
	} else if (this->isCommentGlobal() && (this->find(':') != string::npos)) {
		m_parameterSet = new HumParamSet(this);
	}
}



//////////////////////////////
//
// HumdrumToken::clearLinkInfo -- clear structural analyses so that they
//      can be recalculated.
//

void HumdrumToken::clearLinkInfo(void) {
	// Possibly clear parameter set (but this info is not typically
	// dependent on links to other tokens).
	//
	// if (m_parameterSet) {
	// 	delete m_parameterSet;
	// 	m_parameterSet = NULL;
	// }

	// also clear linked parameters
	m_linkedParameterTokens.clear();

	// clear pointers to adjacent tokens
	m_nextTokens.clear();
	m_previousTokens.clear();
	m_nextNonNullTokens.clear();
	m_previousNonNullTokens.clear();
}



//////////////////////////////
//
// HumdrumToken::makeForwardLink -- Line a following spine token to this one.
//    Used by the HumdrumFileBase::analyzeLinks function.
//

void HumdrumToken::makeForwardLink(HumdrumToken& nextToken) {
	m_nextTokens.push_back(&nextToken);
	nextToken.m_previousTokens.push_back(this);
}



//////////////////////////////
//
// HumdrumToken::makeBackwarddLink -- Link a previous spine token to this one.
//    Used by the HumdrumFileBase::analyzeLinks function.
//

void HumdrumToken::makeBackwardLink(HumdrumToken& previousToken) {
	m_previousTokens.push_back(&previousToken);
	previousToken.m_nextTokens.push_back(this);
}



//////////////////////////////
//
// HumdrumToken::getVisualDuration -- Returns LO:N:vis parameter if it is attached
//    to a token directly or indirectly through a linked parameter.  Returns empty string
//    if no explicit visual durtation (so the visual duration is same as the logical duration).
//

string HumdrumToken::getVisualDuration(int subtokenindex) {
	// direct storage of the layout parameter is possible, but currently disabled:
	//string parameter = this->getValue("LO", "N", "vis");
	//if (!parameter.empty()) {
	//	return parameter;
	//}
	return this->getLayoutParameter("N", "vis", subtokenindex);
}



//////////////////////////////
//
// HumdrumToken::getVisualDurationChord -- only return the chord-level visual duration
//    parameter (not if it is specific to certain note(s) in the chord).
//

string HumdrumToken::getVisualDurationChord(void) {
	return this->getLayoutParameterChord("N", "vis");
}



//////////////////////////////
//
// HumdrumToken::getVisualDurationNote -- only return the note-level visual duration
//    parameter (not if it is general to the entire chord.
//

string HumdrumToken::getVisualDurationNote(int subtokenindex) {
	return this->getLayoutParameterNote("N", "vis", subtokenindex);
}



//////////////////////////////
//
// HumdrumToken::getLayoutParameter -- Returns requested layout parameter
//     if it is attached to a token directly or indirectly through a linked
//     parameter.  Returns empty string if no explicit visual durtation (so
//     the visual duration is same as the logical duration).  If subtokenindex
//     is less than -1 (the default value for the paramter), then ignore the
//     @n parameter control for indexing the layout parameter to chord notes.
//     The subtokenindex (0 indexed) is converted to note number (1 indexed)
//     for checking @n.  @n is currently only allowed to be a single integer
//     (eventually allow ranges and multiple values).
//

std::string HumdrumToken::getLayoutParameter(const std::string& category,
		const std::string& keyname, int subtokenindex) {

	// First check for any local layout parameter:
	std::string testoutput = this->getValue("LO", category, keyname);
	if (!testoutput.empty()) {
		if (subtokenindex >= 0) {
			int n = this->getValueInt("LO", category, "n");
			if (n == subtokenindex + 1) {
				return testoutput;
			}
		} else {
			return testoutput;
		}
	}

	std::string output;
	int lcount = this->getLinkedParameterSetCount();
	if (lcount == 0) {
		return output;
	}

	std::string nparam;
	for (int p = 0; p < this->getLinkedParameterSetCount(); ++p) {
		hum::HumParamSet *hps = this->getLinkedParameterSet(p);
		if (hps == NULL) {
			continue;
		}
		if (hps->getNamespace1() != "LO") {
			continue;
		}
		if (hps->getNamespace2() != category) {
			continue;
		}

		output = "";
		for (int q = 0; q < hps->getCount(); ++q) {
			string key = hps->getParameterName(q);
			if (key == keyname) {
				output = hps->getParameterValue(q);
				if (subtokenindex < 0) {
					return output;
				}
			}
			if (key == "n") {
				nparam = hps->getParameterValue(q);
			}
		}
		if (nparam.empty()) {
			// No subtoken selection for this parameter,
			// so return if not empty:
			if (!output.empty()) {
				return output;
			}
		} else if (subtokenindex < 0) {
			// No subtoken selection so return output if not empty
			if (!output.empty()) {
				return output;
			}
		} else {
			// There is a subtoken selection number, so
			// return output if n matches it (minus one)

			// currently @n requires a single value
			// (should allow a range or multiple values
			// later).  Also not checking validity of
			// string first (needs to start with a digit);

			int n = stoi(nparam);
			if (n == subtokenindex + 1) {
				return output;
			} else {
				// not the output that is required,
				// so suppress for end of loop:
				output = "";
			}
		}
	}

	return output;
}



//////////////////////////////
//
// HumdrumToken::getSlurLayoutParameter --
//

std::string HumdrumToken::getSlurLayoutParameter(const std::string& keyname,
		int subtokenindex) {
	std::string category = "S";
	std::string output;

	// First check for any local layout parameter:
	std::string testoutput = this->getValue("LO", category, keyname);
	if (!testoutput.empty()) {
		if (subtokenindex >= 0) {
			int s = this->getValueInt("LO", category, "s");
			if (s == subtokenindex + 1) {
				return testoutput;
			}
		} else {
			return testoutput;
		}
	}

	int lcount = this->getLinkedParameterSetCount();
	if (lcount == 0) {
		return output;
	}

	std::string sparam;
	for (int p = 0; p < this->getLinkedParameterSetCount(); ++p) {
		hum::HumParamSet *hps = this->getLinkedParameterSet(p);
		if (hps == NULL) {
			continue;
		}
		if (hps->getNamespace1() != "LO") {
			continue;
		}
		if (hps->getNamespace2() != category) {
			continue;
		}
		for (int q = 0; q < hps->getCount(); ++q) {
			string key = hps->getParameterName(q);
			if (key == "s") {
				sparam = hps->getParameterValue(q);
			}
			if (key == keyname) {
				output = hps->getParameterValue(q);
			}
		}
	}
	if (subtokenindex < 0) {
		// do not filter by s parameter
		return output;
	} else if (sparam.empty()) {
		// parameter is not qualified by a note number, so applies to whole token
		return output;
	}

	// currently @s requires a single value (should allow a range or multiple values later)
	// also not checking validity of string first (needs to start with a digit);
	int s = stoi(sparam);
	if (s == subtokenindex + 1) {
		return output;
	} else {
		return "";
	}
}



//////////////////////////////
//
// HumdrumToken::getPhraseLayoutParameter --
//

std::string HumdrumToken::getPhraseLayoutParameter(const std::string& keyname,
		int subtokenindex) {
	std::string category = "P";
	std::string output;

	// First check for any local layout parameter:
	std::string testoutput = this->getValue("LO", category, keyname);
	if (!testoutput.empty()) {
		if (subtokenindex >= 0) {
			int s = this->getValueInt("LO", category, "s");
			if (s == subtokenindex + 1) {
				return testoutput;
			}
		} else {
			return testoutput;
		}
	}

	int lcount = this->getLinkedParameterSetCount();
	if (lcount == 0) {
		return output;
	}

	std::string sparam;
	for (int p = 0; p < this->getLinkedParameterSetCount(); ++p) {
		hum::HumParamSet *hps = this->getLinkedParameterSet(p);
		if (hps == NULL) {
			continue;
		}
		if (hps->getNamespace1() != "LO") {
			continue;
		}
		if (hps->getNamespace2() != category) {
			continue;
		}
		for (int q = 0; q < hps->getCount(); ++q) {
			string key = hps->getParameterName(q);
			if (key == "s") {
				sparam = hps->getParameterValue(q);
			}
			if (key == keyname) {
				output = hps->getParameterValue(q);
			}
		}
	}
	if (subtokenindex < 0) {
		// do not filter by s parameter
		return output;
	} else if (sparam.empty()) {
		// parameter is not qualified by a note number, so applies to whole token
		return output;
	}

	// currently @s requires a single value (should allow a range or multiple values later)
	// also not checking validity of string first (needs to start with a digit);
	int s = stoi(sparam);
	if (s == subtokenindex + 1) {
		return output;
	} else {
		return "";
	}
}



//////////////////////////////
//
// HumdrumToken::getLayoutParameterChord -- Returns requested layout
// parameter if it is attached to a token directly or indirectly through
// a linked parameter.  The parameter must apply to the entire chord, so
// no @n qualification parameters can be given (even if they include all
// notes in the chord).

std::string HumdrumToken::getLayoutParameterChord(const std::string& category,
		const std::string& keyname) {

	// First check for any local layout parameter:
	std::string testoutput = this->getValue("LO", category, keyname);
	if (!testoutput.empty()) {
		std::string n = this->getValue("LO", category, "n");
		if (n.empty()) {
			return testoutput;
		}
	}

	std::string output;
	int lcount = this->getLinkedParameterSetCount();
	if (lcount == 0) {
		return output;
	}

	std::string nparam;
	for (int p = 0; p < this->getLinkedParameterSetCount(); ++p) {
		hum::HumParamSet *hps = this->getLinkedParameterSet(p);
		if (hps == NULL) {
			continue;
		}
		if (hps->getNamespace1() != "LO") {
			continue;
		}
		if (hps->getNamespace2() != category) {
			continue;
		}
		for (int q = 0; q < hps->getCount(); ++q) {
			string key = hps->getParameterName(q);
			if (key == "n") {
				nparam = hps->getParameterValue(q);
			}
			if (key == keyname) {
				output = hps->getParameterValue(q);
			}
		}
	}

	if (!nparam.empty()) {
		// parameter is qualified by a note number, so does not apply to whole token
		return "";
	} else {
		return output;
	}
}



//////////////////////////////
//
// HumdrumToken::getLayoutParameterNote -- Returns requested layout
//     parameter if it is attached to a token directly or indirectly through a
//     linked parameter.  The parameter must apply to a single note or specific
//     note in a chord.


std::string HumdrumToken::getLayoutParameterNote(const std::string& category,
		const std::string& keyname, int subtokenindex) {

	// First check for any local layout parameter:
	std::string testoutput = this->getValue("LO", category, keyname);
	if (!testoutput.empty()) {
		if (subtokenindex >= 0) {
			int n = this->getValueInt("LO", category, "n");
			if (n == subtokenindex + 1) {
				return testoutput;
			}
		}
	}

	std::string output;
	int lcount = this->getLinkedParameterSetCount();
	if (lcount == 0) {
		return output;
	}

	std::string nparam;
	for (int p = 0; p < this->getLinkedParameterSetCount(); ++p) {
		hum::HumParamSet *hps = this->getLinkedParameterSet(p);
		if (hps == NULL) {
			continue;
		}
		if (hps->getNamespace1() != "LO") {
			continue;
		}
		if (hps->getNamespace2() != category) {
			continue;
		}
		for (int q = 0; q < hps->getCount(); ++q) {
			string key = hps->getParameterName(q);
			if (key == "n") {
				nparam = hps->getParameterValue(q);
			}
			if (key == keyname) {
				output = hps->getParameterValue(q);
			}
		}
	}

	if (!nparam.empty()) {
		// a number number is specified from the parameter(s)
		int n = stoi(nparam);
		if (n == subtokenindex + 1) {
			return output;
		} else {
			// wrong note
			return "";
		}
	}

	if ((subtokenindex < 0) && isChord()) {
		// in chord, and no specific note is selected by @n.
		return "";
	} else {
		// single note, so return parameter:
		return output;
	}
}



//////////////////////////////
//
// HumdrumToken::setOwner -- Sets the HumdrumLine owner of this token.
//

void HumdrumToken::setOwner(HLp aLine) {
	m_address.setOwner(aLine);
}



//////////////////////////////
//
// HumdrumToken::getOwner -- Returns a pointer to the HumdrumLine that
//    owns this token.
//

HLp HumdrumToken::getOwner(void) const {
	return m_address.getOwner();
}



//////////////////////////////
//
// HumdrumToken::getState -- Returns the rhythm state variable.
//

int HumdrumToken::getState(void) const {
	return m_rhycheck;
}



//////////////////////////////
//
// HumdrumToken::getStrandIndex -- Returns the 1-D strand index
//    that the token belongs to in the owning HumdrumFile.
//    Returns -1 if there is no strand assignment.
//

int  HumdrumToken::getStrandIndex(void) const {
	return m_strand;
}



//////////////////////////////
//
// HumdrumToken::getBeamStartElisionLevel -- Returns the count of
//   elision marks ('&') preceding a slur start character '('.
//   Returns -1 if there is no slur start character.
//   Default value: index = 0
//

int HumdrumToken::getBeamStartElisionLevel(int index) const {
	if (isDataType("**kern") || isDataType("**mens")) {
		return Convert::getKernBeamStartElisionLevel((string)(*this), index);
	} else {
		return -1;
	}
}



//////////////////////////////
//
// HumdrumToken::getSlurStartElisionLevel -- Returns the count of
//   elision marks ('&') preceding a slur start character '('.
//   Returns -1 if there is no slur start character.
//   Default value: index = 0
//

int HumdrumToken::getSlurStartElisionLevel(int index) const {
	if (isDataType("**kern") || isDataType("**mens")) {
		return Convert::getKernSlurStartElisionLevel((string)(*this), index);
	} else {
		return -1;
	}
}



//////////////////////////////
//
// HumdrumToken::getPhraseStartElisionLevel -- Returns the count of
//   elision marks ('&') preceding a phrase start character '{'.
//   Returns -1 if there is no phrase start character.
//   Default value: index = 0
//

int HumdrumToken::getPhraseStartElisionLevel(int index) const {
	if (isDataType("**kern") || isDataType("**mens")) {
		return Convert::getKernPhraseStartElisionLevel((string)(*this), index);
	} else {
		return -1;
	}
}



//////////////////////////////
//
// HumdrumToken::getBeamEndElisionLevel -- Returns the count of
//   elision marks ('&') preceding a slur end character ')'.
//   Returns -1 if there is no slur end character.
//   Default value: index = 0
//

int HumdrumToken::getBeamEndElisionLevel(int index) const {
	if (isDataType("**kern") || isDataType("**mens")) {
		return Convert::getKernBeamEndElisionLevel((string)(*this), index);
	} else {
		return -1;
	}
}



//////////////////////////////
//
// HumdrumToken::getSlurEndElisionLevel -- Returns the count of
//   elision marks ('&') preceding a slur end character ')'.
//   Returns -1 if there is no slur end character.
//   Default value: index = 0
//

int HumdrumToken::getSlurEndElisionLevel(int index) const {
	if (isDataType("**kern") || isDataType("**mens")) {
		return Convert::getKernSlurEndElisionLevel((string)(*this), index);
	} else {
		return -1;
	}
}



//////////////////////////////
//
// HumdrumToken::getPhraseEndElisionLevel -- Returns the count of
//   elision marks ('&') preceding a slur end character '}'.
//   Returns -1 if there is no phrase end character.
//   Default value: index = 0
//

int HumdrumToken::getPhraseEndElisionLevel(int index) const {
	if (isDataType("**kern")) {
		return Convert::getKernPhraseEndElisionLevel((string)(*this), index);
	} else {
		return -1;
	}
}



//////////////////////////////
//
// HumdrumToken::setStrandIndex -- Sets the 1-D strand index
//    that the token belongs to in the owning HumdrumFile.
//    By default the strand index is set to -1 when a HumdrumToken
//    is created.
//

void  HumdrumToken::setStrandIndex(int index) {
	m_strand = index;
}



//////////////////////////////
//
// HumdrumToken::incrementState -- update the rhythm analysis state variable.
//    This will prevent redundant recursive analysis in analyzeRhythm of
//    the HumdrumFileStructure class.
//

void HumdrumToken::incrementState(void) {
	m_rhycheck++;
}



//////////////////////////////
//
// HumdrumToken::getNextTokenCount -- Returns the number of tokens in the
//   spine/sub spine which follow this token.  Typically this will be 1,
//   but will be zero for a terminator interpretation (*-), and will be
//   2 for a split interpretation (*^).
//

int HumdrumToken::getNextTokenCount(void) const {
	return (int)m_nextTokens.size();
}



//////////////////////////////
//
// HumdrumToken::getPreviousTokenCount -- Returns the number of tokens
//   in the spine/sub-spine which precede this token.  Typically this will
//   be 1, but will be zero for an exclusive interpretation (starting with
//   "**"), and will be greater than one for a token which follows a
//   spine merger (using *v interpretations).
//

int HumdrumToken::getPreviousTokenCount(void) const {
	return (int)m_previousTokens.size();
}



//////////////////////////////
//
// HumdrumToken::printCsv -- print token in CSV format.
// default value: out = std::cout
//

ostream& HumdrumToken::printCsv(ostream& out) {
	string& value = *this;
	int loc = (int)this->find(",");
	if (loc == (int)string::npos) {
		out << value;
	} else {
		out << '"';
		for (int i=0; i<(int)value.size(); i++) {
		   if (value[i] == '"') {
				out << '"' << '"';
			} else {
				out << value[i];
			}
		}
		out << '"';
	}
	return out;
}



//////////////////////////////
//
// HumdrumToken::printXml -- Print a HumdrumToken in XML format.
// default value: out = cout
// default value: level = 0
// default value: indent = "\t"
//
//

ostream& HumdrumToken::printXml(ostream& out, int level, const string& indent) {

	out << Convert::repeatString(indent, level);
	out << "<field";
	out << " n=\"" << getTokenIndex() << "\"";

	out << " track=\"" << getTrack() << "\"";
	if (getSubtrack() > 0) {
		out << " subtrack=\"" << getSubtrack() << "\"";
	}
	out << " token=\"" << Convert::encodeXml(((string)(*this))) << "\"";
	out << " xml:id=\"" << getXmlId() << "\"";
	out << ">\n";

	printXmlBaseInfo(out, level+1, indent);
	printXmlStructureInfo(out, level+1, indent);

	if (isData()) {
		if (isNote()) {
			out << Convert::repeatString(indent, level+1) << "<pitch";
			out << Convert::getKernPitchAttributes(((string)(*this)));
			out << "/>\n";
		}
	}

	printXmlContentInfo(out, level+1, indent);
	printXmlParameterInfo(out, level+1, indent);
	printXmlLinkedParameterInfo(out, level+1, indent);
	printXmlLinkedParameters(out, level+1, indent);

	out << Convert::repeatString(indent, level) << "</field>\n";
	return out;
}



//////////////////////////////
//
// HumdrumToken::printXmlLinkedParameters --
//

ostream&	HumdrumToken::printXmlLinkedParameters(ostream& out, int level, const string& indent) {
	if (m_parameterSet) {
		m_parameterSet->printXml(out, level, indent);
	}
	return out;
}



//////////////////////////////
//
// HumdrumToken::printXmlLinkedParameterInfo --
//

ostream& HumdrumToken::printXmlLinkedParameterInfo(ostream& out, int level, const string& indent) {
	if (m_linkedParameterTokens.empty()) {
		return out;
	}

	out << Convert::repeatString(indent, level);
	out << "<parameters-linked>\n";

	level++;
	for (int i=0; i<(int)m_linkedParameterTokens.size(); i++) {
		out << Convert::repeatString(indent, level);
		out << "<linked-parameter";
		out << " idref=\"";
		HLp owner = m_linkedParameterTokens[i]->getOwner();
		if (owner && owner->isGlobalComment()) {
			out << owner->getXmlId();
		} else {
			out << m_linkedParameterTokens[i]->getXmlId();
		}
		out << "\"";
		out << ">\n";
	}
	level--;

	out << Convert::repeatString(indent, level);
	out << "</parameters-linked>\n";

	return out;
}



//////////////////////////////
//
// HumdrumToken::printXmlBaseInfo -- print data type and spine info.
// default value: out = cout
// default value: level = 0
// default value: indent = "\t"
//

ostream& HumdrumToken::printXmlBaseInfo(ostream& out, int level,
		const string& indent) {

	// <dataType> redundant with
	// sequence/sequenceInfo/trackInfo/track@dataType
	out << Convert::repeatString(indent, level);
	out << "<dataType>" << getDataType().substr(2) << "</dataType>\n";

	out << Convert::repeatString(indent, level) << "<tokenType>";
	if (isNull()) {
		out << "null";
	} else if (isManipulator()) {
		out << "manipulator";
	} else if (isCommentLocal()) {
		out << "local-comment";
	} else if (isBarline()) {
		out << "barline";
	} else if (isData()) {
		out << "data";
	} else {
		out << "interpretation";
	}
	out << "</tokenType>\n";

	// <tokenFunction>
	if (isDataType("**kern")) {
		if (isNote()) {
			out << Convert::repeatString(indent, level) << "<tokenFunction>";
			out << "note" << "</tokenFunction>\n";
		} else if (isRest()) {
			out << Convert::repeatString(indent, level) << "<tokenFunction>";
			out << "note" << "</tokenFunction>\n";
		}
	}

	if (isNull()) {
		HumdrumToken* previous = getPreviousNonNullDataToken(0);
		if (previous != NULL) {
			out << Convert::repeatString(indent, level) << "<nullResolve";
			out << " text=\"";
			out << Convert::encodeXml(((string)(*previous))) << "\"";
			out << " idref=\"";
			out << previous->getXmlId();
			out << "\"/>\n";
		}
	}

	return out;
}



//////////////////////////////
//
// HumdrumToken::printXmlStructureInfo -- Prints structural information
//    other than spine analysis.
// default value: out = cout
// default value: level = 0
// default value: indent = "\t"
//

ostream& HumdrumToken::printXmlStructureInfo(ostream& out, int level,
		const string& indent) {

	if (getDuration().isNonNegative()) {
		out << Convert::repeatString(indent, level);
		out << "<duration" << Convert::getHumNumAttributes(getDuration());
		out << "/>\n";
	}

	return out;
}



//////////////////////////////
//
// HumdrumToken::getBeat -- Return the beat (1 indexed)
//

HumNum HumdrumToken::getBeat(HumNum scale) {
	if (!m_address.hasOwner()) {
		return 0;
	} else {
		return m_address.getOwner()->getBeat(scale);
	}
}



//////////////////////////////
//
// HumdrumToken::printXmlContentInfo -- Print content analysis information.
// default value: out = cout
// default value: level = 0
// default value: indent = "\t"
//

ostream& HumdrumToken::printXmlContentInfo(ostream& out, int level,
		const string& indent) {
	if (hasSlurStart()) {
		out << Convert::repeatString(indent, level) << "<slur";
		if (isDefined("auto", "hangingSlur")) {
			out << " hanging=\"" << getValue("auto", "hangingSlur") << "\"";
		}
		out << ">" << endl;
		out << Convert::repeatString(indent, level+1);
		out << "<duration" << Convert::getHumNumAttributes(getSlurDuration());
		out << "/>\n";
		out << Convert::repeatString(indent, level) << "</slur>" << endl;
	}
	return out;
}



//////////////////////////////
//
// HumdrumToken::printGlobalXmlParameterInfo --
//

ostream& HumdrumToken::printGlobalXmlParameterInfo(ostream& out, int level, const string& indent) {
	((HumHash*)this)->printXmlAsGlobal(out, level, indent);
	return out;
}



//////////////////////////////
//
// HumdrumToken::printXmlParameterInfo -- Print contents of HumHash for token.
// default value: out = cout
// default value: level = 0
// default value: indent = "\t"
//

ostream& HumdrumToken::printXmlParameterInfo(ostream& out, int level,
		const string& indent) {
	((HumHash*)this)->printXml(out, level, indent);
	return out;
}



//////////////////////////////
//
// HumdrumToken::getXmlId -- Returns an XML id attribute based on the line
//     and field index for the location of the token in the HumdrumFile.
//     An optional parameter for a prefix can be given.  If this parameter
//     is an empty string, then the prefix set in the owning HumdrumFile
//     will instead be used.  The prefix cannot start with a digit, and
//     should not include a space charcter.
//

string HumdrumToken::getXmlId(const string& prefix) const {
	string output;
	if (prefix.size() > 0) {
		output = prefix;
	} else {
		output = getXmlIdPrefix();
	}
	output += "loc" + to_string(getLineIndex()) + "_";
	output += to_string(getFieldIndex());
	// subtoken IDS could be added here.
	return output;
}



//////////////////////////////
//
// HumdrumToken::getXmlIdPrefix -- Returns the XML ID prefix from the HumdrumFile
//   structure via the HumdrumLine on which the token resides.
//

string HumdrumToken::getXmlIdPrefix(void) const {
	auto own = getOwner();
	if (own == NULL) {
		return "";
	}
	return own->getXmlIdPrefix();
}



//////////////////////////////
//
// operator<< -- Needed to avoid interaction with the HumHash parent class.
//

ostream& operator<<(ostream& out, const HumdrumToken& token) {
	out << token.c_str();
	return out;
}


ostream& operator<<(ostream& out, HumdrumToken* token) {
	if (token) {
		out << token->c_str();
	} else {
		out << "{NULL}";
	}
	return out;
}



//////////////////////////////
//
// printSequence --
//    default value: out = cout;
//

ostream& printSequence(vector<vector<HTp> >& sequence, ostream& out) {
	for (int i=0; i<(int)sequence.size(); i++) {
		for (int j=0; j<(int)sequence[i].size(); j++) {
			out << sequence[i][j];
			if (j < (int)sequence[i].size() - 1) {
				out << '\t';
			}
		}
		out << endl;
	}
	return out;
}


ostream& printSequence(vector<HTp>& sequence, ostream& out) {
	for (int i=0; i<(int)sequence.size(); i++) {
		out << sequence[i] << endl;
	}
	return out;
}



//////////////////////////////
//
// HumdrumToken::getSlurStartToken -- Return a pointer to the token
//     which starts the given slur.  Returns NULL if no start.  Assumes that
//     HumdrumFileContent::analyzeKernSlurs() has already been run.
//				<parameter key="slurEnd" value="HT_140366146702320" idref=""/>
//

HTp HumdrumToken::getSlurStartToken(int number) {
	string tag = "slurStartId";
	if (number > 1) {
		tag += to_string(number);
	}
	HTp value = getValueHTp("auto", tag);
	return value;
}


//////////////////////////////
//
// HumdrumToken::getSlurStartNumber -- Given a slur ending number,
//    return the slur start number that it pairs with.
//

int HumdrumToken::getSlurStartNumber(int endnumber) {
	string tag = "slurStartNumber";
	if (endnumber > 1) {
		tag += to_string(endnumber);
	}
	int value = getValueInt("auto", tag);
	return value;
}



//////////////////////////////
//
// HumdrumToken::getSlurEndToken -- Return a pointer to the token
//     which ends the given slur.  Returns NULL if no end.  Assumes that
//     HumdrumFileContent::analyzeKernSlurs() has already been run.
//				<parameter key="slurStart" value="HT_140366146702320" idref=""/>
//

HTp HumdrumToken::getSlurEndToken(int number) {
	string tag = "slurEnd";
	if (number > 1) {
		tag += to_string(number);
	}
	return getValueHTp("auto", tag);
}




//////////////////////////////
//
// HumdrumToken::getPhraseStartToken -- Return a pointer to the token
//     which starts the given phrase.  Returns NULL if no start.  Assumes that
//     HumdrumFileContent::analyzeKernPhrasings() has already been run.
//				<parameter key="phraseEnd" value="HT_140366146702320" idref=""/>
//

HTp HumdrumToken::getPhraseStartToken(int number) {
	string tag = "phraseStart";
	if (number > 1) {
		tag += to_string(number);
	}
	return getValueHTp("auto", tag);
}



//////////////////////////////
//
// HumdrumToken::getPhraseEndToken -- Return a pointer to the token
//     which ends the given phrase.  Returns NULL if no end.  Assumes that
//     HumdrumFileContent::analyzeKernPhrasings() has already been run.
//				<parameter key="phraseStart" value="HT_140366146702320" idref=""/>
//

HTp HumdrumToken::getPhraseEndToken(int number) {
	string tag = "phraseEnd";
	if (number > 1) {
		tag += to_string(number);
	}
	return getValueHTp("auto", tag);
}



//////////////////////////////
//
// HumdrumToken::resolveNull --
//

HTp HumdrumToken::resolveNull(void) {
	if (m_nullresolve == NULL) {
		HLp hline = getOwner();
		if (hline) {
			HumdrumFile* infile = hline->getOwner();
			infile->resolveNullTokens();
		}
		if (m_nullresolve == NULL) {
			return this;
		} else {
			return m_nullresolve;
		}
		return this;
	} else {
		return m_nullresolve;
	}
}



//////////////////////////////
//
// HumdrumToken::setNullResolution --
//

void HumdrumToken::setNullResolution(HTp resolution) {
	m_nullresolve = resolution;
}



//////////////////////////////
//
// HumdrumToken::copyStucture --
//

void HumdrumToken::copyStructure(HTp token) {
	m_strand = token->m_strand;
	HLp temp_owner = m_address.m_owner;
	m_address = token->m_address;
	m_address.m_owner = NULL;  // This will in general be different, so do not copy.
	m_address.m_owner = temp_owner; // But preserve in case already set.
	// m_nullresolve: set this?
}



//////////////////////////////
//
// HumdrumToken::getStrophe -- return the strophe that the token belongs to,
//    or NULL if it is not in a strophe.
//

HTp HumdrumToken::getStrophe(void) {
	return m_strophe;
}



//////////////////////////////
//
// HumdrumToken::setStrophe -- Set the *S/ line of the strophe
//    or NULL if it is not formatted correctly.
//

void HumdrumToken::setStrophe(HTp strophe) {
	if (!strophe) {
		clearStrophe();
		return;
	}
	if (strophe->compare(0, 3, "*S/") != 0) {
		// invalid strophe marker.
		clearStrophe();
		return;
	}
	m_strophe = strophe;
}



//////////////////////////////
//
// HumdrumToken::hasStrophe -- return true if the token is in a strophe; otherwise,
//    return false.
//

bool HumdrumToken::hasStrophe(void) {
	return m_strophe ? true : false;
}



//////////////////////////////
//
// HumdrumToken::clearStrophe -- return true if the token is in a strophe; otherwise,
//    return false.
//

void HumdrumToken::clearStrophe(void) {
	m_strophe = NULL;
}



//////////////////////////////
//
// HumdrumToken::getStropheStartIndex -- return the starting line of the strophe
//    sequence.  Returns -1 if not in a strophe.
//

int HumdrumToken::getStropheStartIndex(void) {
	if (!m_strophe) {
		return -1;
	}
	return m_strophe->getLineIndex();
}



//////////////////////////////
//
// HumdrumToken::isFirstStrophe -- Returns true if the token is in the first
//    strophe variant.  Returns true if not in a strophe.
//

bool HumdrumToken::isFirstStrophe(void) {
	if (!m_strophe) {
		return true;
	}
	HTp toleft = m_strophe->getPreviousField();
	if (!toleft) {
		return true;
	}
	int track = m_strophe->getTrack();
	int ltrack = toleft->getTrack();
	return track != ltrack;
}


bool HumdrumToken::isPrimaryStrophe(void) {
	return isFirstStrophe();
}



//////////////////////////////
//
// HumdrumToken::isStrophe -- Return true if the token has the given strophe
//   label.
//

bool HumdrumToken::isStrophe(const string& label) {
	if (!m_strophe) {
		return false;
	}
	if (label.empty()) {
		return *m_strophe == "*S/";
	}
	if (label[0] == '*') {
		return *m_strophe == label;
	}
	return m_strophe->substr(3) == label;
}



//////////////////////////////
//
// HumdrumToken::getStropheLabel -- Return the strophe label after *S/ in the
//    strophe token.  Returns the empty string when not in a strophe.
//

string HumdrumToken::getStropheLabel(void) {
	if (!m_strophe) {
		return "";
	}
	if (*m_strophe == "*S/") {
		return "";
	}
	return m_strophe->substr(3);
}





///////////////////////////////////////////////////////////////////////////
//
// MuseEventSet class functions --
//


//////////////////////////////
//
// MuseEventSet::MuseEventSet --
//

MuseEventSet::MuseEventSet (void) {
	events.reserve(20);
	clear();
}


MuseEventSet::MuseEventSet(HumNum atime) {
	setTime(atime);
	events.reserve(20);
}

MuseEventSet::MuseEventSet(const MuseEventSet& aSet) {
	absbeat = aSet.absbeat;
	events.resize(aSet.events.size());
	for (int i=0; i<(int)aSet.events.size(); i++) {
		events[i] = aSet.events[i];
	}
}



//////////////////////////////
//
// MuseEventSet::operator= --
//

MuseEventSet MuseEventSet::operator=(MuseEventSet& anevent) {
	if (&anevent == this) {
		return *this;
	}
	absbeat = anevent.absbeat;
	events.resize(anevent.events.size());
	for (int i=0; i<(int)events.size(); i++) {
		events[i] = anevent.events[i];
	}
	return *this;
}



//////////////////////////////
//
// MuseEventSet::clear --
//

void MuseEventSet::clear(void) {
	events.clear();
	absbeat.setValue(0,1);
}



//////////////////////////////
//
// MuseEventSet::setTime --
//

void MuseEventSet::setTime(HumNum abstime) {
	absbeat = abstime;
}



//////////////////////////////
//
// MuseEventSet::getTime --
//

HumNum MuseEventSet::getTime(void) {
	return absbeat;
}



//////////////////////////////
//
// MuseEventSet::appendRecord -- still have to sort after insertion...
//   also add a removeEvent function so deleted elements can be removed
//   gracefully.
//

void MuseEventSet::appendRecord(MuseRecord* arecord) {
	events.push_back(arecord);
}



//////////////////////////////
//
// MuseEventSet::operator[] --
//

MuseRecord& MuseEventSet::operator[](int eindex) {
	return *(events[eindex]);
}



//////////////////////////////
//
// MuseEventSet::getEventCount --
//

int MuseEventSet::getEventCount(void) {
	return (int)events.size();
}



///////////////////////////////////////////////////////////////////////////
//
// MuseData class functions --
//


//////////////////////////////
//
// MuseData::MuseData --
//

MuseData::MuseData(void) {
	m_data.reserve(100000);
}

MuseData::MuseData(MuseData& input) {
	m_data.resize(input.m_data.size());
	MuseRecord* temprec;
	int i;
	for (i=0; i<(int)m_data.size(); i++) {
		temprec  = new MuseRecord;
		*temprec = *(input.m_data[i]);
		m_data[i]  = temprec;
		m_data[i]->setLineIndex(i);
		m_data[i]->setOwner(this);
	}
	m_sequence.resize(input.m_sequence.size());
	for (i=0; i<(int)input.m_sequence.size(); i++) {
	  m_sequence[i] = new MuseEventSet;
	  *(m_sequence[i]) = *(input.m_sequence[i]);
	}

	m_name = input.m_name;
}



//////////////////////////////
//
// MuseData::~MuseData --
//

MuseData::~MuseData() {
	clear();
}



//////////////////////////////
//
// MuseData::operator= --
//

MuseData& MuseData::operator=(MuseData& input) {
	if (this == &input) {
		return *this;
	}
	m_data.resize(input.m_data.size());
	MuseRecord* temprec;
	int i;
	for (i=0; i<(int)m_data.size(); i++) {
		temprec = new MuseRecord;
		*temprec = *(input.m_data[i]);
		m_data[i] = temprec;
		m_data[i]->setLineIndex(i);
		m_data[i]->setOwner(this);
	}
	// do something with m_sequence...
	m_name = input.m_name;
	return *this;
}



//////////////////////////////
//
// MuseData::getLineCount -- return the number of lines in the MuseData file.
//

int MuseData::getLineCount(void) {
	return (int)m_data.size();
}



//////////////////////////////
//
// MuseData::append -- add a MuseRecord to end of file.
//

int MuseData::append(MuseRecord& arecord) {
	MuseRecord* temprec = new MuseRecord;
	*temprec = arecord;
	temprec->setOwner(this);
	m_data.push_back(temprec);
	m_data.back()->setLineIndex((int)m_data.size() - 1);
	return (int)m_data.size()-1;
}


int MuseData::append(MuseData& musedata) {
	int oldsize = (int)m_data.size();
	int newlinecount = musedata.getLineCount();
	if (newlinecount <= 0) {
		return -1;
	}

	m_data.resize((int)m_data.size()+newlinecount);
	for (int i=0; i<newlinecount; i++) {
		m_data[i+oldsize] = new MuseRecord;
		*(m_data[i+oldsize]) = musedata[i];
		m_data[i+oldsize]->setLineIndex(i+oldsize);
		m_data[i+oldsize]->setOwner(this);
	}
	return (int)m_data.size()-1;
}


int MuseData::append(string& charstring) {
	MuseRecord* temprec;
	temprec = new MuseRecord;
	temprec->setString(charstring);
	temprec->setType(E_muserec_unknown);
	temprec->setQStamp(0);
	m_data.push_back(temprec);
	temprec->setLineIndex((int)m_data.size() - 1);
	temprec->setOwner(this);
	return (int)m_data.size()-1;
}



//////////////////////////////
//
// MuseData::insert -- add a MuseRecord to middle of file.  Not the most
//   efficient, but not too bad as long as the file is not too long, the
//   insertion is close to the end of the file, and you don't use this
//   method to add a set of sequential lines (write a different function
//   for that).
//

void MuseData::insert(int lindex, MuseRecord& arecord) {
	MuseRecord* temprec;
	temprec = new MuseRecord;
	*temprec = arecord;
	temprec->setOwner(this);

	m_data.resize(m_data.size()+1);
	for (int i=(int)m_data.size()-1; i>lindex; i--) {
		m_data[i] = m_data[i-1];
		m_data[i]->setLineIndex(i);
	}
	m_data[lindex] = temprec;
	temprec->setLineIndex(lindex);
}



//////////////////////////////
//
// MuseData::clear --
//

void MuseData::clear(void) {
	for (int i=0; i<(int)m_data.size(); i++) {
		if (m_data[i] != NULL) {
			delete m_data[i];
			m_data[i] = NULL;
		}
	}
	for (int i=0; i<(int)m_sequence.size(); i++) {
		m_sequence[i]->clear();
		delete m_sequence[i];
		m_sequence[i] = NULL;
	}
	m_error.clear();
	m_data.clear();
	m_sequence.clear();
	m_name = "";
}



//////////////////////////////
//
// MuseData::operator[] --
//

MuseRecord& MuseData::operator[](int lindex) {
	return *(m_data[lindex]);
}



//////////////////////////////
//
// MuseData::getRecord --
//

MuseRecord& MuseData::getRecord(int lindex) {
	return *(m_data[lindex]);
}



//////////////////////////////
//
// MuseData::getRecordPointer --
//

MuseRecord* MuseData::getRecordPointer(int lindex) {
	return m_data[lindex];
}


//////////////////////////////
//
// MuseData::getRecord -- This version with two index inputs is
//     used to access a data line based on the event time index (time sorted
//     viewpoint) and the particular record index for that event time.
//

MuseRecord& MuseData::getRecord(int eindex, int erecord) {
	return *(m_data[getEvent(eindex)[erecord].getLineIndex()]);
}



//////////////////////////////
//
// MuseData::read -- read a MuseData file from a file or input stream.
//   0x0a      = unix
//   0x0d      = apple
//   0x0d 0x0a = dos
//

int MuseData::read(istream& input) {
	m_error.clear();
	string dataline;
	dataline.reserve(256);
	int character;
	char value;
	int  isnewline;
	char lastvalue = 0;

	while (!input.eof()) {
		character = input.get();
		if (input.eof()) {
			// end of file found without a newline termination on last line.
			if (dataline.size() > 0) {
				MuseData::append(dataline);
				dataline.clear();
				break;
			}
		}
		value = (char)character;
		if ((value == 0x0d) || (value == 0x0a)) {
			isnewline = 1;
		} else {
			isnewline = 0;
		}

		if (isnewline && (value == 0x0a) && (lastvalue == 0x0d)) {
			// ignore the second newline character in a dos-style newline.
			lastvalue = value;
			continue;
		} else {
			lastvalue = value;
		}

		if (isnewline) {
			MuseData::append(dataline);
			dataline.clear();
		} else {
			dataline.push_back(value);
		}
	}

	for (int i=0; i<(int)m_data.size(); i++) {
		m_data[i]->setLineIndex(i);
	}

	doAnalyses();
	if (hasError()) {
		cerr << m_error << endl;
		return 0;
	} else {
		return 1;
	}
}


int MuseData::readFile(const string& filename) {
	ifstream infile(filename);
	return MuseData::read(infile);
}

int MuseData::readString(const string& data) {
	stringstream ss;
	ss << data;
	return MuseData::read(ss);
}



//////////////////////////////
//
// MuseData::doAnalyses --  perform post-processing analysis of the data file
//    (in the correct order).
//

void MuseData::doAnalyses(void) {
	analyzeType();
	analyzeTpq();
	if (hasError()) { return; }
	assignHeaderBodyState();
   analyzeLayers();
	analyzeRhythm();
	if (hasError()) { return; }
	constructTimeSequence();
	if (hasError()) { return; }
	analyzePitch();
	if (hasError()) { return; }
	analyzeTies();
	if (hasError()) { return; }
	linkPrintSuggestions();
	linkMusicDirections();
}



//////////////////////////////
//
// MuseData::analyzeTpq -- Read $ records for Q: field values and store
//    the ticks-per-quarter note value on each line after that $ record.
//    This is used later to extract the logical duration of notes and rests.
//

void MuseData::analyzeTpq(void) {
	HumRegex hre;
	int ticks = 0;
	for (int i=0; i<getLineCount(); i++) {
		MuseRecord* mr = &getRecord(i);
		if (!mr->isAttributes()) {
			mr->setTpq(ticks);

			continue;
		}
		string line = getLine(i);
		if (hre.search(line, " Q:(\\d+)")) {
			ticks = hre.getMatchInt(1);
		}
		mr->setTpq(ticks);
	}
}



//////////////////////////////
//
// MuseData::analyzePitch -- calculate the pitch of all notes in terms
//    of their base40 value.
//

void MuseData::analyzePitch() {
	for (int i=0; i<(int)m_data.size(); i++) {
		m_data[i]->setMarkupPitch(m_data[i]->getBase40());
	}
}



//////////////////////////////
//
// MuseData::analyzeTies -- identify which notes are tied to each other.
//

void MuseData::analyzeTies(void) {
	for (int i=0; i<(int)m_sequence.size(); i++) {
		for (int j=0; j<m_sequence[i]->getEventCount(); j++) {
			if (!getEvent(i)[j].tieQ()) {
				continue;
			}
			processTie(i, j, -1);
		}
	}
}



//////////////////////////////
//
// MuseData::processTie -- follow a tied note to the last note
//   in the tied m_sequence, filling in the tie information along the way.
//   Hanging ties (particularly ties note at the ends of first repeats, etc.)
//   still need to be considered.
//

void MuseData::processTie(int eindex, int rindex, int lastindex) {
	int& i = eindex;
	int& j = rindex;


	// lineindex = index of line in original file for the current line.
	int lineindex = getEvent(i)[j].getLineIndex();


	if ((lastindex < 0) &&
		 (m_data[lineindex]->getLastTiedNoteLineIndex() >= 0)) {
		// If there previously tied note already marked in the data, then
		// this note has already been processed for ties, so exit function
		// without doing any further processing.
		return;
	}

	// store the location of the note tied to previously:
	m_data[lineindex]->setLastTiedNoteLineIndex(lastindex);

	// If the current note contains a tie marker, then there is
	// another tied note in the future, so go look for it.
	if (!m_data[lineindex]->tieQ()) {
		m_data[lineindex]->setNextTiedNoteLineIndex(-1);
		return;
	}

	// There is another note tied to this one in the future, so
	// first get the absolute time location of the future tied note
	HumNum abstime    = m_data[lineindex]->getQStamp();
	HumNum notedur    = m_data[lineindex]->getNoteDuration();
	HumNum searchtime = abstime + notedur;

	// Get the event index which occurs at the search time:
	int nexteindex = getNextEventIndex(eindex, abstime + notedur);

	if (nexteindex < 0) {
		// Couldn't find any data at that absolute time index, so give up:
		m_data[lineindex]->setNextTiedNoteLineIndex(-1);
		return;
	}

	// The pitch of the tied note should match this one; otherwise, it
	// would not be a tied note...
	int base40 = m_data[lineindex]->getPitch();

	// The tied note will preferrably be found in the same track as the
	// current note (but there could be a cross-track tie occurring, so
	// check for that if there is no same-track tie):
	int track = m_data[lineindex]->getTrack();

	int nextrindex = searchForPitch(nexteindex, base40, track);
	if (nextrindex < 0) {
		// Didn't find specified note at the given event index in the given
		// track, so search for the same pitch in any track at the event time:
		 nextrindex = searchForPitch(nexteindex, base40, -1);
	}

	if (nextrindex < 0) {
		// Failed to find a note at the target event time which could be
		// tied to the current note (no pitches at that time which match
		// the pitch of the current note).  This is a haning tie which is
		// either a data error, or is a tie to a note at an earlier time
		// in the music (such as at the beginning of a repeated section).
		// for now just ignore the hanging tie, but probably mark it in
		// some way in the future.
		m_data[lineindex]->setNextTiedNoteLineIndex(-1);
		return;
	}

	// now the specific note to which this one is tied to is known, so
	// go and process that note:

	int nextindex = getEvent(nexteindex)[nextrindex].getLineIndex();

	m_data[lineindex]->setNextTiedNoteLineIndex(nextindex);

	processTie(nexteindex, nextrindex, lineindex);
}



//////////////////////////////
//
// MuseData::searchForPitch -- search for a matching pitch in the given
//   track at the specified event index.  If the track is negative, then
//   find the first matching pitch in any track.
//
//   Will also have to separate by category, so that grace notes are
//   searched for separately from regular notes / chord notes, and
//   cue notes as well.
//

int MuseData::searchForPitch(int eventindex, int b40, int track) {
	int targettrack;
	int targetpitch;
	int targettype;

	for (int j=0; j<m_sequence[eventindex]->getEventCount(); j++) {
		targettype = getEvent(eventindex)[j].getType();
		if ((targettype != E_muserec_note_regular) &&
			 (targettype != E_muserec_note_chord) ) {
			// ignore non-note data (at least ones without durations):
			continue;
		}
		targettrack = getEvent(eventindex)[j].getTrack();
		if ((track >= 0) && (track != targettrack)) {
			continue;
		}
		targetpitch = getEvent(eventindex)[j].getPitch();
		if (targetpitch == b40) {
			return j;
		}
	}

	return -1;
}



//////////////////////////////
//
// MuseData::getNextEventIndex -- return the event index for the given
//   absolute time value.  The starting index is given first, and it
//   is assumed that the target absolute time occurs on or after the
//   starting index value.  Returns -1 if that absolute time is not
//   found in the data (or occurs before the start index.
//

int MuseData::getNextEventIndex(int startindex, HumNum target) {
	int output = -1;
	for (int i=startindex; i<(int)m_sequence.size(); i++) {
		if (m_sequence[i]->getTime() == target) {
			output = i;
			break;
		}
	}
	return output;
}


//////////////////////////////
//
// MuseData::last -- return the last record in the data.  Make sure
// that isEmpty() is not true before calling this function.
//

MuseRecord& MuseData::last(void) {
	return (*this)[getNumLines()-1];
}



//////////////////////////////
//
// MuseData::isEmpty -- return true if there are no MuseRecords in the
//    object; otherwise returns true;
//

int MuseData::isEmpty(void) {
	return m_data.empty();
}



//////////////////////////////
//
// MuseData::analyzeType --
//

void MuseData::analyzeType(void) {
	int commentQ = 0;
	int h = 0;
	MuseData& thing = *this;
	int foundattributes = 0;
	int foundend = 0;

	HumRegex hre;
	int groupmemberships = -1;
	for (int i=0; i<getLineCount(); i++) {
		string line = thing[i].getLine();
		if (hre.search(line, "^Group memberships:")) {
			groupmemberships = i;
			break;
		}
	}
	if (groupmemberships < 0) {
		cerr << "Error cannot parse MuseData content" << endl;
		return;
	}

	thing[groupmemberships].setType(E_muserec_header_11);

	h = 11;
	for (int i=groupmemberships-1; i>=0; i--) {
		if (thing[i].getLength() > 0) {
			if (thing[i][0] == '@') {
				thing[i].setType(E_muserec_comment_line);
				continue;
			}
			if (thing[i][0] == '&') {
				// start or end of multi-line comment;
				commentQ = !commentQ;
				if (!commentQ) {
					thing[i].setType(E_muserec_comment_toggle);
	       continue;
				}
			}
			if (commentQ) {
				thing[i].setType(E_muserec_comment_toggle);
				continue;
			}
		}
		h--;
		if      (h==1)  { thing[i].setType(E_muserec_header_1);  continue; }
		else if (h==2)  { thing[i].setType(E_muserec_header_2);  continue; }
		else if (h==3)  { thing[i].setType(E_muserec_header_3);  continue; }
		else if (h==4)  { thing[i].setType(E_muserec_header_4);  continue; }
		else if (h==5)  { thing[i].setType(E_muserec_header_5);  continue; }
		else if (h==6)  { thing[i].setType(E_muserec_header_6);  continue; }
		else if (h==7)  { thing[i].setType(E_muserec_header_7);  continue; }
		else if (h==8)  { thing[i].setType(E_muserec_header_8);  continue; }
		else if (h==9)  { thing[i].setType(E_muserec_header_9);  continue; }
		else if (h==10) { thing[i].setType(E_muserec_header_10); continue; }
	}

	commentQ = 0;
	h = 11;
	for (int i=groupmemberships+1; i<getNumLines(); i++) {

		if (thing[i].getLength() > 0) {
			if (thing[i][0] == '@') {
				thing[i].setType(E_muserec_comment_line);
				continue;
			}
			if (thing[i][0] == '&') {
				// start or end of multi-line comment;
				commentQ = !commentQ;
				if (!commentQ) {
					thing[i].setType(E_muserec_comment_toggle);
	       continue;
				}
			}
			if (commentQ) {
				thing[i].setType(E_muserec_comment_toggle);
				continue;
			}
		}
		h++;
		if      (h==1)  { thing[i].setType(E_muserec_header_1);  continue; }
		else if (h==2)  { thing[i].setType(E_muserec_header_2);  continue; }
		else if (h==3)  { thing[i].setType(E_muserec_header_3);  continue; }
		else if (h==4)  { thing[i].setType(E_muserec_header_4);  continue; }
		else if (h==5)  { thing[i].setType(E_muserec_header_5);  continue; }
		else if (h==6)  { thing[i].setType(E_muserec_header_6);  continue; }
		else if (h==7)  { thing[i].setType(E_muserec_header_7);  continue; }
		else if (h==8)  { thing[i].setType(E_muserec_header_8);  continue; }
		else if (h==9)  { thing[i].setType(E_muserec_header_9);  continue; }
		else if (h==10) { thing[i].setType(E_muserec_header_10); continue; }

		else if (h==11) { thing[i].setType(E_muserec_header_11); continue; }
		else if (h==12) { thing[i].setType(E_muserec_header_12); continue; }

		if (thing[i].getLength() == 0) {
			thing[i].setType(E_muserec_empty);
			continue;
		}

		if ((h > 12) && (thing[i][0] != '$') && (foundattributes == 0)) {
			thing[i].setType(E_muserec_header_12);
			continue;
		}

		if (foundend && thing[i][0] != '/') {
			thing[i].setType(E_muserec_endtext);
			continue;
		}

		switch (thing[i][0]) {
			case 'A': case 'B': case 'C': case 'D': case 'E': case 'F':
			case 'G': thing[i].setType(E_muserec_note_regular);       break;
			case ' ': thing[i].setType(E_muserec_note_chord);         break;
			case 'c': thing[i].setType(E_muserec_note_cue);           break;
			case 'g': thing[i].setType(E_muserec_note_grace);         break;
			case 'P': thing[i].setType(E_muserec_print_suggestion);   break;
			case 'S': thing[i].setType(E_muserec_sound_directives);   break;
			case '/': thing[i].setType(E_muserec_end);
			          foundend = 1;
			          break;
			case 'a': thing[i].setType(E_muserec_append);             break;
			case 'b': thing[i].setType(E_muserec_backspace);          break;
			case 'f': thing[i].setType(E_muserec_figured_harmony);    break;
			case 'i': thing[i].setType(E_muserec_rest_invisible);     break;
			case 'm': thing[i].setType(E_muserec_measure);            break;
			case 'r': thing[i].setType(E_muserec_rest);               break;
			case '*': thing[i].setType(E_muserec_musical_directions); break;
			case '$': thing[i].setType(E_muserec_musical_attributes);
			          foundattributes = 1;
			          break;
		}
	}
}



//////////////////////////////
//
// MuseData::analyzeRhythm -- calculate the start time in quarter notes
//   for each note/rest in the file.
//
//   Secondary chord notes may or may not have a duration listed.
//   If they do not, then the duration of the note is the same
//   as the primary note of the chord.
//
// char*            getTickDurationField         (char* output);
//

void MuseData::analyzeRhythm(void) {
	HumNum cumulative(0,1);
	HumNum linedur(0,1);
	int tpq = 1;
	HumRegex hre;
	HumNum figadj = 0;   // needed for figured harmony
	HumNum primarychordnoteduration(0,1);  // needed for chord notes

	for (int i=0; i<(int)m_data.size(); i++) {
		if (m_data[i]->isAttributes()) {
			string line = m_data[i]->getLine();
			if (hre.search(line, "Q:(\\d+)", "")) {
				tpq = hre.getMatchInt(1);
			}
		}

		if (m_data[i]->isChordNote()) {
			// insert an automatic back command for chord tones
			// also deal with cue-size note chords?
			m_data[i]->setQStamp(cumulative - primarychordnoteduration);

			// Check to see if the secondary chord note has a duration.
			// If so, then set the note duration to that value; otherwise,
			// set the note duration to the duration of the primary chord
			// note (first note before the current note which is not a chord
			// note).
			string buffer = m_data[i]->getTickDurationField();
			if (hre.search(buffer, "\\d", "")) {
				m_data[i]->setNoteDuration(m_data[i]->getNoteTickDuration(), tpq);
			} else {
				m_data[i]->setNoteDuration(primarychordnoteduration);
			}
			m_data[i]->setLineDuration(0);
		} else if (m_data[i]->isFiguredHarmony()) {
			// Tick values on figured harmony lines do not advance the
			// cumulative timestamp; instead they temporarily advance
			// the time placement of the next figure if it occurs
			// during the same note as the previous figure.
			m_data[i]->setQStamp(cumulative + figadj);
			HumNum tick = m_data[i]->getLineTickDuration();
			if (tick == 0) {
				figadj = 0;
			} else {
				HumNum dur = tick;
				dur /= tpq;
				figadj += dur;
			}
		} else {
			m_data[i]->setQStamp(cumulative);
			m_data[i]->setNoteDuration(m_data[i]->getNoteTickDuration(), tpq);
			m_data[i]->setLineDuration(m_data[i]->getNoteDuration());
			linedur.setValue(m_data[i]->getLineTickDuration(), tpq);
			cumulative += linedur;
		}

		switch (m_data[i]->getType()) {
			case E_muserec_note_regular:
			// should also worry about cue and grace note chords?
			primarychordnoteduration = linedur;
		}
	}

	// adjust Sound and Print records so that they occur at the same
	// absolute time as the note they affect.
	for (int i=1; i<(int)m_data.size(); i++) {
		switch (m_data[i]->getType()) {
			case E_muserec_print_suggestion:
			case E_muserec_sound_directives:
				m_data[i]->setQStamp(m_data[i-1]->getQStamp());
		}
	}

}



//////////////////////////////
//
// MuseData::getInitialTpq -- return the Q: field in the first $ record
//    at the top of the file.  If there is an updated Q: field, this
//    function will need to be improved since TPQ cannot change in MIDI files,
//    for example.
//

int MuseData::getInitialTpq(void) {
	int output = 0;
	if (m_data.empty()) {
		return output;
	}
	HumRegex hre;
	int i;
	if (m_data[0]->getType() == E_muserec_unknown) {
		// search for first line which starts with '$':
		for (i=0; i<(int)m_data.size(); i++) {
			if (m_data[i]->getLength() <= 0) {
				continue;
			}
			if ((*m_data[i])[0] == '$') {
				string line = m_data[i]->getLine();
				if (hre.search(line, "Q:(\\d+)", "")) {
					output = hre.getMatchInt(1);
				}
				break;
			}
		}
	} else {
		for (int i=0; i<(int)m_data.size(); i++) {
			if (m_data[i]->getType() == E_muserec_musical_attributes) {
				string line = m_data[i]->getLine();
				if (hre.search(line, "Q:(\\d+)", "")) {
					output = hre.getMatchInt(1);
				}
				break;
			}
		}
	}

	return output;
}



//////////////////////////////
//
// constructTimeSequence -- Make a list of the lines in the file
//    sorted by the absolute time at which they occur.
//

void MuseData::constructTimeSequence(void) {
	// * clear old event set
	// * allocate the size to match number of lines (* 2 probably).

	MuseData& thing = *this;
	for (int i=0; i<(int)m_data.size(); i++) {
		insertEventBackwards(thing[i].getQStamp(), &thing[i]);
		if (hasError()) {
			return;
		}
	}
}



///////////////////////////////
//
// MuseData::getEventCount -- returns the number of unique times
//    at which data line occur in the file.
//

int MuseData::getEventCount(void) {
	return (int)m_sequence.size();
}



///////////////////////////////
//
// MuseData::getEvent --
//

MuseEventSet& MuseData::getEvent(int eindex) {
	return *(m_sequence[eindex]);
}



///////////////////////////////////////////////////////////////////////////
//
// private functions
//


//////////////////////////////
//
// printSequenceTimes --
//

void printSequenceTimes(vector<MuseEventSet*>& m_sequence) {
	for (int i=0; i<(int)m_sequence.size(); i++) {
		cout << m_sequence[i]->getTime().getFloat() << " ";
	}
	cout << endl;
}



//////////////////////////////
//
// MuseData::insertEventBackwards -- insert an event into a time-sorted
//    organization of the file. Searches for the correct time location to
//    insert event starting at the end of the list (since MuseData files
//    are mostly sorted in time.
//
//

void MuseData::insertEventBackwards(HumNum atime, MuseRecord* arecord) {
	if (m_sequence.empty()) {
		MuseEventSet* anevent = new MuseEventSet;
		anevent->setTime(atime);
		anevent->appendRecord(arecord);
		m_sequence.push_back(anevent);
		return;
	}

	for (int i=(int)m_sequence.size()-1; i>=0; i--) {
		if (m_sequence[i]->getTime() == atime) {
			m_sequence[i]->appendRecord(arecord);
			return;
		} else if (m_sequence[i]->getTime() < atime) {
			// insert new event entry after the current one since it occurs later.
			MuseEventSet* anevent = new MuseEventSet;
			anevent->setTime(atime);
			anevent->appendRecord(arecord);
			if (i == (int)m_sequence.size()-1) {
				// just append the event at the end of the list
				m_sequence.push_back(anevent);
	    return;
			} else {
				// event has to be inserted before end of list, so move
				// later ones up in list.
				m_sequence.resize(m_sequence.size()+1);
				for (int j=(int)m_sequence.size()-1; j>i+1; j--) {
					m_sequence[j] = m_sequence[j-1];
				}
				// store the newly created event entry in m_sequence:
				m_sequence[i+1] = anevent;
				return;
			}
		}
	}
	stringstream ss;
	ss << "Funny error occurred at time " << atime;
	setError(ss.str());
}



//////////////////////////////
//
// MuseData::getTiedDuration -- these version acess the record lines
//    via the time-sorted event index.
//

HumNum MuseData::getTiedDuration(int eindex, int erecord) {
	return getTiedDuration(getLineIndex(eindex, erecord));
}



//////////////////////////////
//
// MuseData:getTiedDuration --
//

HumNum MuseData::getTiedDuration(int index) {
	HumNum output(0,1);

	// if the line is not a regular/chord note with duration, then
	// return the duration field.
	if ((getRecord(index).getType() != E_muserec_note_chord) &&
		 (getRecord(index).getType() != E_muserec_note_regular) ) {
		return output;
	}

	// if the note is tied to a previous note, then return a
	// duration of 0 (behavior may change in the future).
	if (getRecord(index).getLastTiedNoteLineIndex() >= 0) {
		return output;
	}

	// if the note is not tied to anything into the future, then
	// gives it actual duration
	if (getRecord(index).getNextTiedNoteLineIndex() < 0) {
		return getRecord(index).getNoteDuration();
	}

	// this is start of a group of tied notes.  Start figuring out
	// how long the duration of the tied group is.
	output = getRecord(index).getNoteDuration();
	int myindex = index;
	while (getRecord(myindex).getNextTiedNoteLineIndex() >= 0) {
		myindex = getRecord(myindex).getNextTiedNoteLineIndex();
		output += getRecord(myindex).getNoteDuration();
	}

	return output;
}



//////////////////////////////
//
// MuseData::getLineIndex -- return the line number of a particular
//    event/record index pair (the line index is the same as the index
//    into the list of lines).
//

int MuseData::getLineIndex(int eindex, int erecord) {
	return getRecord(eindex, erecord).getLineIndex();
}



//////////////////////////////
//
// MuseData::getLineDuration -- return the duration of an isolated line.
//

HumNum MuseData::getLineDuration(int eindex, int erecord) {
	return getRecord(eindex, erecord).getLineDuration();
}



//////////////////////////////
//
// MuseData::getNoteDuration -- return the duration of an isolated note.
//

HumNum MuseData::getNoteDuration(int eindex, int erecord) {
	return getRecord(eindex, erecord).getNoteDuration();
}



//////////////////////////////
//
// MuseData::getLastTiedNoteLineIndex -- return the line index of the
//     previous note to which this one is tied to.  Returns -1 if there
//     is no previous tied note.
//

int MuseData::getLastTiedNoteLineIndex(int eindex, int erecord) {
	return getRecord(eindex, erecord).getLastTiedNoteLineIndex();
}



//////////////////////////////
//
// MuseData::getNextTiedNoteLineIndex -- returns the line index of the
//    next note to which this one is tied to.  Returns -1 if there
//    is no previous tied note.
//

int MuseData::getNextTiedNoteLineIndex(int eindex, int erecord) {
	return getRecord(eindex, erecord).getNextTiedNoteLineIndex();
}



//////////////////////////////
//
// MuseData::getType -- return the record type of a particular event
//     record.
//

int MuseData::getType(int eindex, int erecord) {
	return getRecord(eindex, erecord).getType();
}



//////////////////////////////
//
// MuseData::getQStamp -- return the absolute beat time (quarter
//    note durations from the start of the music until the current
//    object.
//

HumNum MuseData::getQStamp(int lindex) {
	return m_data[lindex]->getQStamp();
}


HumNum MuseData::getAbsBeat(int lindex) {
	return m_data[lindex]->getQStamp();
}



//////////////////////////////
//
// MuseData::getLineTickDuration --
//

int MuseData::getLineTickDuration(int lindex) {
	return m_data[lindex]->getLineTickDuration();
}


//////////////////////////////
//
// MuseData::setFilename --
//

void MuseData::setFilename(const string& filename) {
	m_name = filename;
}



//////////////////////////////
//
// MuseData::getFilename --
//

string MuseData::getFilename(void) {
	return m_name;
}


/*

//////////////////////////////
//
// MuseData::getPartName --
//

string MuseData::getPartName(void) {
	string output;
	for (int i=0; i<getLineCount(); i++) {
		if (isPartName(i)) {
			output = getPartName(i);
			break;
		}
	}
	for (int i=(int)output.size() - 1; i>=0; i--) {
		if (isspace(output[i])) {
			output.resize((int)output.size() - 1);
		} else {
			break;
		}
	}
	return output;
}

*/


//////////////////////////////
//
// MuseData::getPartName -- return name of the part
//

string MuseData::getPartName(void) {
	int line = getPartNameIndex();
	if (line < 0) {
		return "";
	}
	return m_data[line]->getPartName();
}



//////////////////////////////
//
// MuseData::getPartNameIndex -- Search for the part name in the header.
//    search the entire file if it is missing (which it should not be.
//

int MuseData::getPartNameIndex(void) {
	int output = -1;
	for (int i=0; i<(int)m_data.size(); i++) {
		if (m_data[i]->isPartName()) {
			return i;
		}
	}
	return output;
}



//////////////////////////////
//
// MuseData::isMember -- returns true if the file belongs to the
//     given membership string.  Example memberships are "score",
//     "skore", "part", "sound".
//

int MuseData::isMember(const string& mstring) {
	for (int i=0; i<(int)m_data.size(); i++) {
		if (m_data[i]->getType() == E_muserec_group_memberships) {
			if (strstr(m_data[i]->getLine().c_str(), mstring.c_str()) != NULL) {
				return 1;
			} else {
				return 0;
			}
		}
		if (m_data[i]->getType() == E_muserec_musical_attributes) {
			break;
		}
	}
	return 0;
}



//////////////////////////////
//
// MuseData::getMembershipPartNumber -- returns the part number within
//     a given membership for the data.
// Example:
//     sound: part 1 of 4
//  in this case the value returned is 1.
//

int MuseData::getMembershipPartNumber(const string& mstring) {
	string searchstring = "^";
	searchstring += mstring;
	searchstring += ":";

	HumRegex hre;
	for (int i=0; i<(int)m_data.size(); i++) {
		if (m_data[i]->getType() == E_muserec_header_12) {
			string line = m_data[i]->getLine();
			if (hre.search(line, searchstring)) {
				if (hre.search(line, "part\\s*(\\d+)\\s*of\\s*(\\d+)")) {
					string partnum = hre.getMatch(1);
					return hre.getMatchInt(1);
				}
			}
		}
		if (m_data[i]->getType() == E_muserec_musical_attributes) {
			break;
		}
	}
	return 0;
}



////////////////////////////////
//
// MuseData::selectMembership --
//

void MuseData::selectMembership(const string& selectstring) {
	if (!isMember(selectstring)) {
		// not a member of the given membership, so can't select that
		// membership.
		return;
	}
	string buffer;
	buffer = "Group memberships: ";
	buffer += selectstring;

	for (int i=0; i<getNumLines(); i++) {
		if ((*this)[i].getType() == E_muserec_group_memberships) {
			(*this)[i].setLine(buffer);
		} else if ((*this)[i].getType() == E_muserec_header_12) {
			if (strncmp((*this)[i].getLine().c_str(), selectstring.c_str(),
					selectstring.size()) != 0) {
				(*this)[i].setType(E_muserec_deleted);
			}
		}
	}


}



///////////////////////////////
//
// MuseData::cleanLineEndings -- remove spaces at the end of lines
//

void MuseData::cleanLineEndings(void) {
	for (int i=0; i<this->getLineCount(); i++) {
		(*this)[i].cleanLineEnding();
	}
}



//////////////////////////////
//
// MuseData::getError --
//

string MuseData::getError(void) {
	return m_error;
}



//////////////////////////////
//
// MuseData::hasError --
//

bool MuseData::hasError(void) {
	return !m_error.empty();
}


//////////////////////////////
//
// MuseData::clearError --
//

void MuseData::clearError(void) {
	m_error.clear();
}



//////////////////////////////
//
// MuseData:::etError --
//

void MuseData::setError(const string& error) {
	m_error = error;
}



//////////////////////////////
//
// MuseData::getFileDuration --
//

HumNum MuseData::getFileDuration(void) {
	return getRecord(getLineCount()-1).getQStamp();
}



//////////////////////////////
//
// MuseData::getLine -- return the textual content of the given line index.
//

string MuseData::getLine(int index) {
	if (index < 0) {
		return "";
	}
	if (index >= getLineCount()) {
		return "";
	}
	string output = getRecord(index).getLine();
	return output;
}



//////////////////////////////
//
// MuseData::isPartName -- return true if partname line.
//

bool MuseData::isPartName(int index) {
	return getRecord(index).isPartName();
}



//////////////////////////////
//
// MuseData::isWorkInfo -- return true if a work info line.
//

bool MuseData::isWorkInfo(int index) {
	return getRecord(index).isWorkInfo();
}



//////////////////////////////
//
// MuseData::isWorkTitle -- return true if a work title line.
//

bool MuseData::isWorkTitle(int index) {
	return getRecord(index).isWorkTitle();
}



//////////////////////////////
//
// MuseData::isHeaderRecord -- return true if in the header.
//

bool MuseData::isHeaderRecord(int index) {
	return getRecord(index).isHeaderRecord();
}



//////////////////////////////
//
// MuseData::isBodyRecord -- return true if in the body.
//

bool MuseData::isBodyRecord(int index) {
	return getRecord(index).isBodyRecord();
}



//////////////////////////////
//
// MuseData::isCopyright -- return true if a work title line.
//

bool MuseData::isCopyright(int index) {
	return getRecord(index).isCopyright();
}



//////////////////////////////
//
// MuseData::isMovementTitle -- return true if a movement title line.
//

bool MuseData::isMovementTitle(int index) {
	return getRecord(index).isMovementTitle();
}



//////////////////////////////
//
// MuseData::isRegularNote -- return true if a regular note line.
//     This is either a single note, or the first note in a chord.
//

bool MuseData::isRegularNote(int index) {
	return getRecord(index).isRegularNote();
}



//////////////////////////////
//
// MuseData::isAnyNote -- return true if note line of any time.
//

bool MuseData::isAnyNote(int index) {
	return getRecord(index).isAnyNote();
}



//////////////////////////////
//
// MuseData::isEncoder -- return true if note line.
//

bool MuseData::isEncoder(int index) {
	return getRecord(index).isEncoder();
}



//////////////////////////////
//
// MuseData::isId -- return true if Id line.
//

bool MuseData::isId(int index) {
	return getRecord(index).isId();
}



//////////////////////////////
//
// MuseData::isSource -- return true if note line.
//

bool MuseData::isSource(int index) {
	return getRecord(index).isSource();
}



//////////////////////////////
//
// MuseData::getWorkInfo --
//

string MuseData::getWorkInfo(void) {
	for (int i=0; i<getLineCount(); i++) {
		if (isWorkInfo(i)) {
			return cleanString(getLine(i));
		} else if (isAnyNote(i)) {
			break;
		}
	}
	return "";
}



//////////////////////////////
//
// MuseData::getOpus --
//     WK#:1,1       MV#:1
//

string MuseData::getOpus(void) {
	string workinfo = getWorkInfo();
	HumRegex hre;
	if (hre.search(workinfo, "^\\s*WK\\s*#\\s*:\\s*(\\d+)")) {
		return hre.getMatch(1);
	}
	return "";
}



//////////////////////////////
//
// MuseData::getNumber -- Return number of work in opus.
//     WK#:1,1       MV#:1
//

string MuseData::getNumber(void) {
	string workinfo = getWorkInfo();
	HumRegex hre;
	if (hre.search(workinfo, "^\\s*WK\\s*#\\s*:\\s*(\\d+)\\s*[,/]\\s*(\\d+)")) {
		return hre.getMatch(2);
	}
	return "";
}



//////////////////////////////
//
// MuseData::getMovementNumber --
//     WK#:1,1       MV#:1
//

string MuseData::getMovementNumber(void) {
	string workinfo = getWorkInfo();
	HumRegex hre;
	if (hre.search(workinfo, "MV\\s*#\\s*:\\s*(\\d+)")) {
		return hre.getMatch(1);
	}
	return "";
}



//////////////////////////////
//
// MuseData::getWorkTitle --
//

string MuseData::getWorkTitle(void) {
	for (int i=0; i<getLineCount(); i++) {
		if (isWorkTitle(i)) {
			return cleanString(getLine(i));
		} else if (isAnyNote(i)) {
			break;
		}
	}
	return "";
}



//////////////////////////////
//
// MuseData::getCopyright --
//

string MuseData::getCopyright(void) {
	for (int i=0; i<getLineCount(); i++) {
		if (isCopyright(i)) {
			return cleanString(getLine(i));
		} else if (isAnyNote(i)) {
			break;
		}
	}
	return "";
}



//////////////////////////////
//
// MuseData::getMovementTitle --
//

string MuseData::getMovementTitle(void) {
	for (int i=0; i<getLineCount(); i++) {
		if (isMovementTitle(i)) {
			return cleanString(getLine(i));
		} else if (isAnyNote(i)) {
			break;
		}
	}
	return "";
}



//////////////////////////////
//
// MuseData::getSource --
//

string MuseData::getSource(void) {
	for (int i=0; i<getLineCount(); i++) {
		if (isSource(i)) {
			return cleanString(getLine(i));
		} else if (isAnyNote(i)) {
			break;
		}
	}
	return "";
}



//////////////////////////////
//
// MuseData::getEncoder --
//

string MuseData::getEncoder(void) {
	for (int i=0; i<getLineCount(); i++) {
		if (isEncoder(i)) {
			return cleanString(getLine(i));
		} else if (isAnyNote(i)) {
			break;
		}
	}
	return "";
}



//////////////////////////////
//
// MuseData::getId --
//

string MuseData::getId(void) {
	for (int i=0; i<getLineCount(); i++) {
		if (isId(i)) {
			return cleanString(getLine(i));
		} else if (isAnyNote(i)) {
			break;
		}
	}
	return "";
}



//////////////////////////////
//
// MuseData::getComposer --  The composer is not indicated in a MuseData file.
//    Infer it from the ID line if a file-location ID is present, since the
//    composers name is abbreviated in the directory name.
//

string MuseData::getComposer(void) {
	string id = getId();
	if (id.find("{cor/") != string::npos) {
		return "Corelli, Arcangelo";
	} else if (id.find("{beet/") != string::npos) {
		return "Beethoven, Ludwig van";
	}
	return "";
}



//////////////////////////////
//
// MuseData::getComposerDate --
//

string MuseData::getComposerDate(void) {
	string id = getId();
	if (id.find("{cor/") != string::npos) {
		return "1653/02/17-1713/01/08";
	} else if (id.find("{beet/") != string::npos) {
		return "1770/12/17-1827/03/26";
	}
	return "";
}



//////////////////////////////
//
// MuseData::getEncoderName --
//

string MuseData::getEncoderName(void) {
	string encoder = getEncoder();
	HumRegex hre;
	if (hre.search(encoder, "^\\s*(\\d+)/(\\d+)/(\\d+)\\s+(.*)\\s*$")) {
		return hre.getMatch(4);
	}
	return "";
}



//////////////////////////////
//
// MuseData::getEncoderDate --
//

string MuseData::getEncoderDate(void) {
	string encoder = getEncoder();
	HumRegex hre;
	if (hre.search(encoder, "^\\s*(\\d+)/(\\d+)/(\\d+)\\s+(.*)\\s*$")) {
		string month = hre.getMatch(1);
		string day   = hre.getMatch(2);
		string year  = hre.getMatch(3);
		if (year.size() == 2) {
			int value = stoi(year);
			if (value < 70) {
				value += 2000;
			} else {
				value += 1900;
			}
			year = to_string(value);
		}
		if (month.size() == 1) {
			month = "0" + month;
		}
		if (day.size() == 1) {
			day = "0" + day;
		}
		string value = year + "/" + month + "/" + day;
		return value;
	}
	return "";
}



//////////////////////////////
//
// MuseData::cleanString --
//

string MuseData::cleanString(const string& input) {
	string output1 = trimSpaces(input);
	string output = convertAccents(input);
	return output;
}



//////////////////////////////
//
// MuseData::trimSpaces --
//

string MuseData::trimSpaces(const string& input) {
	string output;
	int status = 0;
	for (int i=0; i<(int)input.size(); i++) {
		if (!status) {
			if (isspace(input[i])) {
				continue;
			}
			status = 1;
		}
		output += input[i];
	}
	for (int i=(int)output.size()-1; i>=0; i--) {
		if (isspace(output[i])) {
			output.resize((int)output.size() - 1);
		} else {
			break;
		}
	}
	return output;
}



//////////////////////////////
//
// MuseData::convertAccents -- Convert MuseData character encodings into
//     UTF-8.   Also need to convert upper bit ASCII to UTF-8.
//

string MuseData::convertAccents(const string& input) {
	string output;
	output.reserve(input.size());
	int isize = (int)input.size();
	for (int i=0; i<isize; i++) {
		if (input[i] == '\\') {
			if (i <= isize - 3) {
				// check for escaped characters
				// Newer form is [a-zA-Z]\d
				// Older form is \d[a-zA-Z]

				// 0 = ?


				// 1 = tilde accents
				if (((input[i+1] == 'n') && (input[i+2] == '1')) ||
				    ((input[i+2] == 'n') && (input[i+1] == '1'))) {
					output += "ñ";
					i += 2;
					continue;
				}
				if (((input[i+1] == 'N') && (input[i+2] == '1')) ||
				    ((input[i+2] == 'N') && (input[i+1] == '1'))) {
					output += "Ñ";
					i += 2;
					continue;
				}
				if (((input[i+1] == 'o') && (input[i+2] == '1')) ||
				    ((input[i+2] == 'o') && (input[i+1] == '1'))) {
					output += "õ";
					i += 2;
					continue;
				}
				if (((input[i+1] == 'O') && (input[i+2] == '1')) ||
				    ((input[i+2] == 'O') && (input[i+1] == '1'))) {
					output += "Õ";
					i += 2;
					continue;
				}


				// 2 = misc or cedilla/slash accents
				if (((input[i+1] == 'c') && (input[i+2] == '2')) ||
				    ((input[i+2] == 'c') && (input[i+1] == '2'))) {
					output += "ç";
					i += 2;
					continue;
				}
				if (((input[i+1] == 'C') && (input[i+2] == '2')) ||
				    ((input[i+2] == 'C') && (input[i+1] == '2'))) {
					output += "Ç";
					i += 2;
					continue;
				}
				if (((input[i+1] == 'o') && (input[i+2] == '2')) ||
				    ((input[i+2] == 'o') && (input[i+1] == '2'))) {
					output += "ø";
					i += 2;
					continue;
				}
				if (((input[i+1] == 'O') && (input[i+2] == '2')) ||
				    ((input[i+2] == 'O') && (input[i+1] == '2'))) {
					output += "Ø";
					i += 2;
					continue;
				}
				if (((input[i+1] == 's') && (input[i+2] == '2')) ||
				    ((input[i+2] == 's') && (input[i+1] == '2'))) {
					output += "ß";
					i += 2;
					continue;
				}


				// 3 = umlaut accent
				// Lower case:
				if (((input[i+1] == 'a') && (input[i+2] == '3')) ||
				    ((input[i+2] == 'a') && (input[i+1] == '3'))) {
					output += "ä";
					i += 2;
					continue;
				}
				if (((input[i+1] == 'e') && (input[i+2] == '3')) ||
				    ((input[i+2] == 'e') && (input[i+1] == '3'))) {
					output += "ë";
					i += 2;
					continue;
				}
				if (((input[i+1] == 'i') && (input[i+2] == '3')) ||
				    ((input[i+2] == 'i') && (input[i+1] == '3'))) {
					output += "ï";
					i += 2;
					continue;
				}
				if (((input[i+1] == 'o') && (input[i+2] == '3')) ||
				    ((input[i+2] == 'o') && (input[i+1] == '3'))) {
					output += "ö";
					i += 2;
					continue;
				}
				if (((input[i+1] == 'u') && (input[i+2] == '3')) ||
				    ((input[i+2] == 'u') && (input[i+1] == '3'))) {
					output += "ü";
					i += 2;
					continue;
				}
				if (((input[i+1] == 'y') && (input[i+2] == '3')) ||
				    ((input[i+2] == 'y') && (input[i+1] == '3'))) {
					output += "ÿ";
					i += 2;
					continue;
				}
				// Upper case:
				if (((input[i+1] == 'A') && (input[i+2] == '3')) ||
				    ((input[i+2] == 'A') && (input[i+1] == '3'))) {
					output += "Ä";
					i += 2;
					continue;
				}
				if (((input[i+1] == 'E') && (input[i+2] == '3')) ||
				    ((input[i+2] == 'E') && (input[i+1] == '3'))) {
					output += "Ë";
					i += 2;
					continue;
				}
				if (((input[i+1] == 'I') && (input[i+2] == '3')) ||
				    ((input[i+2] == 'I') && (input[i+1] == '3'))) {
					output += "Ï";
					i += 2;
					continue;
				}
				if (((input[i+1] == 'O') && (input[i+2] == '3')) ||
				    ((input[i+2] == 'O') && (input[i+1] == '3'))) {
					output += "Ö";
					i += 2;
					continue;
				}
				if (((input[i+1] == 'U') && (input[i+2] == '3')) ||
				    ((input[i+2] == 'U') && (input[i+1] == '3'))) {
					output += "Ü";
					i += 2;
					continue;
				}
				if (((input[i+1] == 'Y') && (input[i+2] == '3')) ||
				    ((input[i+2] == 'Y') && (input[i+1] == '3'))) {
					output += "Ÿ";
					i += 2;
					continue;
				}


				// 4 = misc or ring accents
				if (((input[i+1] == 'a') && (input[i+2] == '4')) ||
				    ((input[i+2] == 'a') && (input[i+1] == '4'))) {
					output += "å";
					i += 2;
					continue;
				}
				if (((input[i+1] == 'A') && (input[i+2] == '4')) ||
				    ((input[i+2] == 'A') && (input[i+1] == '4'))) {
					output += "Å";
					i += 2;
					continue;
				}


				// 5 = misc or hacheck accents
				if (((input[i+1] == 'r') && (input[i+2] == '5')) ||
				    ((input[i+2] == 'r') && (input[i+1] == '5'))) {
					output += "ř";
					i += 2;
					continue;
				}
				if (((input[i+1] == 'R') && (input[i+2] == '5')) ||
				    ((input[i+2] == 'R') && (input[i+1] == '5'))) {
					output += "Ř";
					i += 2;
					continue;
				}
				if (((input[i+1] == 's') && (input[i+2] == '5')) ||
				    ((input[i+2] == 's') && (input[i+1] == '5'))) {
					output += "š";
					i += 2;
					continue;
				}
				if (((input[i+1] == 'S') && (input[i+2] == '5')) ||
				    ((input[i+2] == 'S') && (input[i+1] == '5'))) {
					output += "Š";
					i += 2;
					continue;
				}


				// 6 = ?


				// 7 = acute accent:
				// Lower case:
				if (((input[i+1] == 'a') && (input[i+2] == '7')) ||
				    ((input[i+2] == 'a') && (input[i+1] == '7'))) {
					output += "á";
					i += 2;
					continue;
				}
				if (((input[i+1] == 'e') && (input[i+2] == '7')) ||
				    ((input[i+2] == 'e') && (input[i+1] == '7'))) {
					output += "é";
					i += 2;
					continue;
				}
				if (((input[i+1] == 'i') && (input[i+2] == '7')) ||
				    ((input[i+2] == 'i') && (input[i+1] == '7'))) {
					output += "í";
					i += 2;
					continue;
				}
				if (((input[i+1] == 'o') && (input[i+2] == '7')) ||
				    ((input[i+2] == 'o') && (input[i+1] == '7'))) {
					output += "ó";
					i += 2;
					continue;
				}
				if (((input[i+1] == 'u') && (input[i+2] == '7')) ||
				    ((input[i+2] == 'u') && (input[i+1] == '7'))) {
					output += "ú";
					i += 2;
					continue;
				}
				if (((input[i+1] == 'y') && (input[i+2] == '7')) ||
				    ((input[i+2] == 'y') && (input[i+1] == '7'))) {
					output += "ý";
					i += 2;
					continue;
				}
				// Upper case:
				if (((input[i+1] == 'A') && (input[i+2] == '7')) ||
				    ((input[i+2] == 'A') && (input[i+1] == '7'))) {
					output += "Á";
					i += 2;
					continue;
				}
				if (((input[i+1] == 'E') && (input[i+2] == '7')) ||
				    ((input[i+2] == 'E') && (input[i+1] == '7'))) {
					output += "É";
					i += 2;
					continue;
				}
				if (((input[i+1] == 'I') && (input[i+2] == '7')) ||
				    ((input[i+2] == 'I') && (input[i+1] == '7'))) {
					output += "Í";
					i += 2;
					continue;
				}
				if (((input[i+1] == 'O') && (input[i+2] == '7')) ||
				    ((input[i+2] == 'O') && (input[i+1] == '7'))) {
					output += "Ó";
					i += 2;
					continue;
				}
				if (((input[i+1] == 'U') && (input[i+2] == '7')) ||
				    ((input[i+2] == 'U') && (input[i+1] == '7'))) {
					output += "Ú";
					i += 2;
					continue;
				}
				if (((input[i+1] == 'Y') && (input[i+2] == '7')) ||
				    ((input[i+2] == 'Y') && (input[i+1] == '7'))) {
					output += "Ý";
					i += 2;
					continue;
				}


				// 8 = grave accent:
				// Lower case:
				if (((input[i+1] == 'a') && (input[i+2] == '8')) ||
				    ((input[i+2] == 'a') && (input[i+1] == '8'))) {
					output += "à";
					i += 2;
					continue;
				}
				if (((input[i+1] == 'e') && (input[i+2] == '8')) ||
				    ((input[i+2] == 'e') && (input[i+1] == '8'))) {
					output += "è";
					i += 2;
					continue;
				}
				if (((input[i+1] == 'i') && (input[i+2] == '8')) ||
				    ((input[i+2] == 'i') && (input[i+1] == '8'))) {
					output += "ì";
					i += 2;
					continue;
				}
				if (((input[i+1] == 'o') && (input[i+2] == '8')) ||
				    ((input[i+2] == 'o') && (input[i+1] == '8'))) {
					output += "ò";
					i += 2;
					continue;
				}
				if (((input[i+1] == 'u') && (input[i+2] == '8')) ||
				    ((input[i+2] == 'u') && (input[i+1] == '8'))) {
					output += "ù";
					i += 2;
					continue;
				}
				// Upper case:
				if (((input[i+1] == 'A') && (input[i+2] == '8')) ||
				    ((input[i+2] == 'A') && (input[i+1] == '8'))) {
					output += "À";
					i += 2;
					continue;
				}
				if (((input[i+1] == 'E') && (input[i+2] == '8')) ||
				    ((input[i+2] == 'E') && (input[i+1] == '8'))) {
					output += "È";
					i += 2;
					continue;
				}
				if (((input[i+1] == 'I') && (input[i+2] == '8')) ||
				    ((input[i+2] == 'I') && (input[i+1] == '8'))) {
					output += "Ì";
					i += 2;
					continue;
				}
				if (((input[i+1] == 'O') && (input[i+2] == '8')) ||
				    ((input[i+2] == 'O') && (input[i+1] == '8'))) {
					output += "Ò";
					i += 2;
					continue;
				}
				if (((input[i+1] == 'U') && (input[i+2] == '8')) ||
				    ((input[i+2] == 'U') && (input[i+1] == '8'))) {
					output += "Ù";
					i += 2;
					continue;
				}


				// 9 = circumflex accent:
				// Lower case:
				if (((input[i+1] == 'a') && (input[i+2] == '9')) ||
				    ((input[i+2] == 'a') && (input[i+1] == '9'))) {
					output += "â";
					i += 2;
					continue;
				}
				if (((input[i+1] == 'e') && (input[i+2] == '9')) ||
				    ((input[i+2] == 'e') && (input[i+1] == '9'))) {
					output += "ê";
					i += 2;
					continue;
				}
				if (((input[i+1] == 'i') && (input[i+2] == '9')) ||
				    ((input[i+2] == 'i') && (input[i+1] == '9'))) {
					output += "î";
					i += 2;
					continue;
				}
				if (((input[i+1] == 'o') && (input[i+2] == '9')) ||
				    ((input[i+2] == 'o') && (input[i+1] == '9'))) {
					output += "ô";
					i += 2;
					continue;
				}
				if (((input[i+1] == 'u') && (input[i+2] == '9')) ||
				    ((input[i+2] == 'u') && (input[i+1] == '9'))) {
					output += "û";
					i += 2;
					continue;
				}
				// Upper case:
				if (((input[i+1] == 'A') && (input[i+2] == '9')) ||
				    ((input[i+2] == 'A') && (input[i+1] == '9'))) {
					output += "Â";
					i += 2;
					continue;
				}
				if (((input[i+1] == 'E') && (input[i+2] == '9')) ||
				    ((input[i+2] == 'E') && (input[i+1] == '9'))) {
					output += "Ê";
					i += 2;
					continue;
				}
				if (((input[i+1] == 'I') && (input[i+2] == '9')) ||
				    ((input[i+2] == 'I') && (input[i+1] == '9'))) {
					output += "Î";
					i += 2;
					continue;
				}
				if (((input[i+1] == 'O') && (input[i+2] == '9')) ||
				    ((input[i+2] == 'O') && (input[i+1] == '9'))) {
					output += "Ô";
					i += 2;
					continue;
				}
				if (((input[i+1] == 'U') && (input[i+2] == '9')) ||
				    ((input[i+2] == 'U') && (input[i+1] == '9'))) {
					output += "Û";
					i += 2;
					continue;
				}


			}
		}
		if (i <= isize - 3) {
			if (((unsigned char)input[i] == 0xef) && ((unsigned char)input[i+1] == 0xbf) && ((unsigned char)input[i+2] == 0xbd)) {
				// UTF-8 replacement character (could mean multiple things)
				// but assume it is ä:
				output += "ä";
				i += 2;
				continue;
			}
		}

		// Reference: https://wiki.ccarh.org/wiki/Dmuse:_Color_and_upper-ASCII_codes

		if ((input[i] >= 32) || (input[i] <= 126)) {
			// regular ASCII character
			output += input[i];
			continue;
		}

		switch ((unsigned char)input[i]) {

			case 0x00: output += "";  break;
			case 0x01: output += "♯"; break;
			case 0x02: output += "♭"; break;
			case 0x03: output += "♮"; break;
			case 0x04: output += "◆"; break;
			case 0x05: output += "⎛"; break;
			case 0x06: output += "⎝"; break;
			case 0x07: output += "●"; break;
			case 0x08: output += "⎞"; break;
			case 0x09: output += "┊"; break;
			case 0x0a: output += 0x0a; break;
			case 0x0b: output += "©"; break;
			case 0x0c: output += "⎠"; break;
			case 0x0d: output += 0x0d; break;
			case 0x0e: output += "→"; break;
			case 0x0f: output += "☼"; break;

			case 0x10: output += "▶"; break;
			case 0x11: output += "◀"; break;
			case 0x12: output += "√"; break;
			case 0x13: output += "▔"; break;
			case 0x14: output += "¶"; break;
			case 0x15: output += "§"; break;
			case 0x16: output += "⟋"; break;
			case 0x17: output += "⟍"; break;
			case 0x18: output += "↑"; break;
			case 0x19: output += "↓"; break;
			case 0x1a: output += "±"; break;
			case 0x1b: output += 0x1b; break; // esc
			case 0x1c: output += "←"; break;
			case 0x1d: output += "↔"; break;
			case 0x1e: output += "▲"; break;
			case 0x1f: output += "▼"; break;

			// 0x20 to 0x7e are regular ASCII characters handled above

			case 0x7f: output += "⌂"; break;

			case 0x80: output += "Ç"; break; case 0x81: output += "ü"; break;
			case 0x82: output += "é"; break; case 0x83: output += "â"; break;
			case 0x84: output += "ä"; break; case 0x85: output += "à"; break;
			case 0x86: output += "å"; break; case 0x87: output += "ç"; break;
			case 0x88: output += "ê"; break; case 0x89: output += "ë"; break;
			case 0x8a: output += "è"; break; case 0x8b: output += "ï"; break;
			case 0x8c: output += "î"; break; case 0x8d: output += "ì"; break;
			case 0x8e: output += "Ä"; break; case 0x8f: output += "Å"; break;

			case 0x90: output += "É"; break; case 0x91: output += "æ"; break;
			case 0x92: output += "Æ"; break; case 0x93: output += "ô"; break;
			case 0x94: output += "ö"; break; case 0x95: output += "ò"; break;
			case 0x96: output += "û"; break; case 0x97: output += "ù"; break;
			case 0x98: output += "ÿ"; break; case 0x99: output += "Ö"; break;
			case 0x9a: output += "Ü"; break; case 0x9b: output += "¢"; break;
			case 0x9c: output += "£"; break; case 0x9d: output += "¥"; break;
			case 0x9e: output += "Pt"; break; case 0x9f: output += "𝑓"; break;

			case 0xa0: output += "á"; break; case 0xa1: output += "í"; break;
			case 0xa2: output += "ó"; break; case 0xa3: output += "ú"; break;
			case 0xa4: output += "ñ"; break; case 0xa5: output += "Ñ"; break;
			case 0xa6: output += "Ř"; break; case 0xa7: output += "ř"; break;
			case 0xa8: output += "¿"; break; case 0xa9: output += "┌"; break;
			case 0xaa: output += "┐"; break; case 0xab: output += "½"; break;
			case 0xac: output += "¼"; break; case 0xad: output += "¡"; break;
			case 0xae: output += "«"; break; case 0xaf: output += "»"; break;

			case 0xb0: output += "▓"; break; case 0xb1: output += "▒"; break;
			case 0xb2: output += "░"; break; case 0xb3: output += "│"; break;
			case 0xb4: output += "┤"; break; case 0xb5: output += "╡"; break;
			case 0xb6: output += "╢"; break; case 0xb7: output += "╖"; break;
			case 0xb8: output += "╕"; break; case 0xb9: output += "╣"; break;
			case 0xba: output += "║"; break; case 0xbb: output += "╗"; break;
			case 0xbc: output += "╝"; break; case 0xbd: output += "╜"; break;
			case 0xbe: output += "╛"; break; case 0xbf: output += "┐"; break;

			case 0xc0: output += "└"; break; case 0xc1: output += "┴"; break;
			case 0xc2: output += "┬"; break; case 0xc3: output += "├"; break;
			case 0xc4: output += "─"; break; case 0xc5: output += "┼"; break;
			case 0xc6: output += "╞"; break; case 0xc7: output += "╟"; break;
			case 0xc8: output += "╚"; break; case 0xc9: output += "╔"; break;
			case 0xca: output += "╩"; break; case 0xcb: output += "╦"; break;
			case 0xcc: output += "╠"; break; case 0xcd: output += "═"; break;
			case 0xce: output += "╬"; break; case 0xcf: output += "╧"; break;

			case 0xd0: output += "╨"; break; case 0xd1: output += "╤"; break;
			case 0xd2: output += "╥"; break; case 0xd3: output += "╙"; break;
			case 0xd4: output += "╘"; break; case 0xd5: output += "╒"; break;
			case 0xd6: output += "╓"; break; case 0xd7: output += "╫"; break;
			case 0xd8: output += "╪"; break; case 0xd9: output += "┘"; break;
			case 0xda: output += "┌"; break; case 0xdb: output += "█"; break;
			case 0xdc: output += "▄"; break; case 0xdd: output += "▌"; break;
			case 0xde: output += "▐"; break; case 0xdf: output += "▀"; break;

			case 0xe0: output += "Â"; break; case 0xe1: output += "À"; break;
			case 0xe2: output += "Á"; break; case 0xe3: output += "Ê"; break;
			case 0xe4: output += "Ë"; break; case 0xe5: output += "È"; break;
			case 0xe6: output += "Î"; break; case 0xe7: output += "Ì"; break;
			case 0xe8: output += "Í"; break; case 0xe9: output += "Ï"; break;
			case 0xea: output += "Ô"; break; case 0xeb: output += "Ò"; break;
			case 0xec: output += "Ó"; break; case 0xed: output += "Û"; break;
			case 0xee: output += "Ù"; break; case 0xef: output += "Ú"; break;

			case 0xf0: output += "Ÿ"; break; case 0xf1: output += "ý"; break;
			case 0xf2: output += "Ý"; break; case 0xf3: output += "ø"; break;
			case 0xf4: output += "Ø"; break; case 0xf5: output += "õ"; break;
			case 0xf6: output += "Õ"; break; case 0xf7: output += "ß"; break;
			case 0xf8: output += "Š"; break; case 0xf9: output += "š"; break;
			case 0xfa: output += "·"; break; case 0xfb: output += "ϕ"; break;
			case 0xfc: output += "Φ"; break; case 0xfd: output += "°"; break;
			case 0xfe: output += "·"; break; case 0xff: output += "▁"; break;

			default:   output += input[i]; break;
		}
	}

	return output;
}



//////////////////////////////
//
// MuseData::analyzeLayers --  When there is a backup command in the
//   measure and no voice information, provide the voice information.
//   Primarily use the stem directions to make this determination.
//   Also, other features can be used:
//      beaming (do not split beamed notes across layers)
//      pitch (higher versus lower pitch).
//   Mostly this is only useful for two-voiced measures.
//   How to deal with voices on multiple staves will be more complex.
//

void MuseData::analyzeLayers(void) {
	int lcount = getLineCount();
	for (int i=0; i<lcount; i++) {
		i = analyzeLayersInMeasure(i);
	}
}



//////////////////////////////
//
// MuseData::analyzeLayersInMeasure -- returns the
//   index of the next barline.
//

int MuseData::analyzeLayersInMeasure(int startindex) {
	int i = startindex;
	int lcount = getLineCount();
	if (i >= lcount) {
		return lcount+1;
	}

	// Not necessarily a barline, but at least the first
	// record for the measure (may be missing at start
	// of music).

	while ((i < lcount) && isHeaderRecord(i)) {
		i++;
	}
	if ((i < lcount) && getRecord(i).isBarline()) {
		i++;
	}
	// Now should be at start of data for a measure.

	if (i >= lcount) {
		return lcount+1;
	}

	vector<vector<MuseRecord*>> segments(1);
	while (i < lcount) {
		MuseRecord *mr = &getRecord(i);
		if (mr->isBarline()) {
			break;
		}
		segments.back().push_back(mr);
		if (mr->isBackup()) {
			segments.resize(segments.size() + 1);
		}
		i++;
	}
	int position = i-1;

	if (segments.size() < 2) {
		// no backup in measure, so single voice/layer
		return position;
	}

	// Assign each backup segment to a successive track
	// if the layer does not have explicit track information.
	int track;

	for (int i=0; i<(int)segments.size(); i++) {
		for (int j=0; j<(int)segments[i].size(); j++) {
			MuseRecord* mr = segments[i][j];
			int trackfield = mr->getTrack();
			if (trackfield == 0) {
				track = i+1;
			} else {
				track = trackfield;
			}
			mr->setLayer(track);
		}
	}


	return position;
}



//////////////////////////////
//
// assignHeaderBodyState --
//

void MuseData::assignHeaderBodyState(void) {
	int state = 1;
	int foundend = 0;
	for (int i=0; i<(int)m_data.size(); i++) {
		if (m_data[i]->isAnyComment()) {
			// Comments inherit state if previous non-comment line
			m_data[i]->setHeaderState(state);
			continue;
		}
		if (state == 0) {
			// no longer in the header
			m_data[i]->setHeaderState(state);
			continue;
		}
		if ((!foundend) && m_data[i]->isGroup()) {
			foundend = 1;
			m_data[i]->setHeaderState(state);
			continue;
		}
		if (foundend && !m_data[i]->isGroup()) {
			state = 0;
			m_data[i]->setHeaderState(state);
			continue;
		}
		// still in header
		m_data[i]->setHeaderState(state);
	}
}


//////////////////////////////
//
// MuseData::linkPrintSuggestions -- Store print suggestions with
//    the record that they apply to.  A print suggestion starts
//    with the letter "P" and follows immediately after the
//    record to which they apply (unless another print suggestion
//    or a comment.
//

void MuseData::linkPrintSuggestions(void) {
	// don't go all of the way to 0: stop at header:
	vector<int> Plines;
	for (int i=getLineCount()-1; i>=0; i--) {
		if (!m_data[i]->isPrintSuggestion()) {
			continue;
		}
		Plines.clear();
		Plines.push_back(i);
		i--;
		while (i>=0 && (m_data[i]->isPrintSuggestion() || m_data[i]->isAnyComment())) {
			if (m_data[i]->isPrintSuggestion()) {
				cerr << "PRINT SUGGESTION: " << m_data[i] << endl;
				Plines.push_back(i);
			}
			i--;
		}
		if (i<0) {
			break;
		}
		// Store the print suggestions on the current line, which is at least
		// a note/rest or musical direction.
		for (int j=0; j<(int)Plines.size(); j++) {
			m_data[i]->addPrintSuggestion(Plines[j] - i);
		}
		Plines.clear();
	}
}



//////////////////////////////
//
// MuseData::linkMusicDirections -- Store music directions with
//    the record that they apply to.  A music direction starts
//    with "*" and precedes the record to which they apply (unless
//    a print suggestion or a comment intervenes.
//


void MuseData::linkMusicDirections(void) {
	vector<int> Dlines;
	for (int i=0; i<getLineCount(); i++) {
		if (!m_data[i]->isDirection()) {
			continue;
		}
		Dlines.clear();
		Dlines.push_back(i);
		i++;
		while (i<getLineCount() && !m_data[i]->isAnyNoteOrRest()) {
			if (m_data[i]->isMusicalDirection()) {
				Dlines.push_back(i);
			}
			i++;
		}
		if (i>=getLineCount()) {
			break;
		}
		// Store the print suggestions on the current line, which is hopefully
		// a note/rest or musical direction.
		for (int j=0; j<(int)Dlines.size(); j++) {
			m_data[i]->addMusicDirection(Dlines[j] - i);
		}
		Dlines.clear();
	}
}


//////////////////////////////
//
// MuseData::getMeasureNumber -- If index == 0, return the next barnumber
//     minus 1.  If on a measure record, return the number on that line.
//     If neither, then search backwards for the last measure line (or 0
//     index) and return the measure number for that barline (or 0 index).
//

string MuseData::getMeasureNumber(int index) {
	MuseData& md = *this;
	if ((index > 0) && !md[index].isMeasure()) {
		for (int i=index-1; i>= 0; i--) {
			if (md[i].isMeasure()) {
				index = i;
				break;
			}
		}
	}
	if ((index != 0) && !md[index].isMeasure()) {
		index = 0;
	}
	if (index == 0) {
		// search for the first measure, and return
		// that number.  If there are notes before the
		// first barline, return the next measure number
		// minus 1.
		bool dataQ = false;
		for (int i=0; i<md.getLineCount(); i++) {
			if (md[i].isAnyNoteOrRest()) {
				dataQ = true;
			}
			if (!md[i].isMeasure()) {
				continue;
			}
			if (!dataQ) {
				string number = md[i].getMeasureNumber();
				return number;
			} else {
				// first measure is not numbered, so return next
				// measure number minus 1.
				for (int j=index; j<md.getLineCount(); j++) {
					if (md[j].isMeasure()) {
						string number = md[j].getMeasureNumber();
						if (number.empty()) {
							// problem, so return empty string
							return "";
						}
						int value = stoi(number) - 1;
						return to_string(value);
					}
				}
			}
		}
	} else {
		// found a measure line, so extract the number:
		string number = md[index].getMeasureNumber();
		return number;
	}
	return "";
}



//////////////////////////////
//
// MuseData::measureHasData -- From current index to the next barline
// 	(or end of file), as at least one note or rest.
//

bool MuseData::measureHasData(int index) {
	MuseData& md = *this;
	if (md[index].isMeasure()) {
		index++;
	}
	for (int i=index; i<md.getLineCount(); i++) {
		if (md[i].isMeasure()) {
			return false;
		}
		if (md[i].isAnyNoteOrRest()) {
			return true;
		}
	}
	return false;
}



//////////////////////////////
//
// MuseData::getNextMeasureIndex -- Find the next measure line after
//    the given line.  Return line count of MuseData if no measure line
//    found before end of file.
//

int MuseData::getNextMeasureIndex(int index) {
	MuseData& md = *this;
	if (md[index].isMeasure()) {
		index++;
	}
	for (int i=index; i<md.getLineCount(); i++) {
		if (md[i].isMeasure()) {
			return i;
		}
	}
	return md.getLineCount();
}



///////////////////////////////////////////////////////////////////////////
//
// friendly functions
//


//////////////////////////////
//
// operator<< --
//

ostream& operator<<(ostream& out, MuseData& musedata) {
	for (int i=0; i<musedata.getLineCount(); i++) {
		if (musedata[i].getType() != E_muserec_deleted) {
			out << musedata[i].getLine() << (char)0x0d << (char)0x0a;
		}
	}
	return out;
}




///////////////////////////////////////////////////////////////////////////
//
// MuseDataSet class functions --
//


//////////////////////////////
//
// MuseDataSet::MuseDataSet --
//

MuseDataSet::MuseDataSet (void) {
	m_part.reserve(100);
}



//////////////////////////////
//
// MuseDataSet::clear -- Remove contents of object.
//

void MuseDataSet::clear(void) {
	int i;
	for (i=0; i<(int)m_part.size(); i++) {
		delete m_part[i];
	}

}



//////////////////////////////
//
// MuseDataSet::operator[] --
//

MuseData& MuseDataSet::operator[](int index) {
	return *m_part[index];
}



//////////////////////////////
//
// MuseDataSet::readPart -- read a single MuseData part, appending it
//      to the current list of parts.
//

int MuseDataSet::readPartFile(const string& filename) {
	MuseData* md = new MuseData;
	md->readFile(filename);
	md->setFilename(filename);
	return appendPart(md);
}

int MuseDataSet::readPartString(const string& data) {
	stringstream ss;
	ss << data;
	return readPart(ss);
}


int MuseDataSet::readPart(istream& input) {
	MuseData* md = new MuseData;
	md->read(input);
	return appendPart(md);
}



//////////////////////////////
//
// MuseDataSet::read -- read potentially Multiple parts from a single file.
//   First clear the contents of any previous part data.
//

int MuseDataSet::readFile(const string& filename) {
	MuseDataSet::clear();
	ifstream infile(filename);
	return MuseDataSet::read(infile);
}


int MuseDataSet::readString(const string& data) {
	stringstream ss;
	ss << data;
	return MuseDataSet::read(ss);
}


int MuseDataSet::readString(istream& input) {
	stringstream buffer;
	buffer << input.rdbuf();
	return readString(buffer.str());
}


int MuseDataSet::readString(stringstream& input) {
	return readString(input.str());
}


// Similar to readstring(istream&) but reading separate
// MuseDatafiles directly:
int MuseDataSet::read(istream& infile) {
	vector<string> datalines;
	datalines.reserve(100000);
	string thing;

	while (!infile.eof()) {
		getline(infile, thing);
		if (infile.eof() && (thing.length() == 0)) {
			// last line was not terminated by a newline character
			break;
		}
		datalines.push_back(thing);
	}

	vector<int> startindex;
	vector<int> stopindex;
	analyzePartSegments(startindex, stopindex, datalines);

	stringstream *sstream;
	MuseData* md;
	for (int i=0; i<(int)startindex.size(); i++) {
		sstream = new stringstream;
		for (int j=startindex[i]; j<=stopindex[i]; j++) {
			 (*sstream) << datalines[j] << '\n';
		}
		md = new MuseData;
		md->read(*sstream);
		appendPart(md);
		delete sstream;
	}
	return 1;
}



//////////////////////////////
//
// MuseDataSet::appendPart -- append a MuseData pointer to the end of the
//   parts list and return the index number of the new part.
//

int MuseDataSet::appendPart(MuseData* musedata) {
	int index = (int)m_part.size();
	m_part.resize(m_part.size()+1);
	m_part[index] = musedata;
	return index;
}



//////////////////////////////
//
// MuseData::analyzePartSegments -- Calculate the starting line index
//    and the ending line index for each part in the input.
//

void MuseDataSet::analyzePartSegments(vector<int>& startindex,
		vector<int>& stopindex, vector<string>& lines) {

	startindex.clear();
	stopindex.clear();
	startindex.reserve(1000);
	stopindex.reserve(1000);

	vector<int> types;
	// MuseData& thing = *this;

	types.resize(lines.size());
	std::fill(types.begin(), types.end(), E_muserec_unknown);

	// first identify lines which are multi-line comments so they will
	// not cause confusion in the next step
	int commentstate = 0;
	for (int i=0; i<(int)lines.size(); i++) {
		if (lines[i].c_str()[0] == '&') {
			types[i] = E_muserec_comment_toggle;
			commentstate = !commentstate;
			continue;
		}
		if (commentstate) {
			types[i] = E_muserec_comment_line;
		}
	}

	// search the data for "Group memberships:" lines which are required
	// to be in the header of each part.
	vector<int> groupmemberships;
	groupmemberships.reserve(1000);
	int len = strlen("Group memberships:");
	for (int i=0; i<(int)lines.size(); i++) {
		if (strncmp("Group memberships:", lines[i].c_str(), len) == 0) {
			if (types[i] != E_muserec_comment_line) {
				groupmemberships.push_back(i);
			}
		}
	}

	// search backwards from "Group memberships:" until the end of the
	// header, sucking any comments which occurs just before the start
	// of the header. (currently only multi-line comments, but also need
	// to add single-line comments)
	int value;
	int headerline;
	int found = 0;
	for (int ii=0; ii<(int)groupmemberships.size(); ii++) {
		int i = groupmemberships[ii];
		types[i] = E_muserec_group_memberships;
		found = 0;
		headerline = 11;
		for (int j=i-1; j>=0; j--) {
			if (j < 0) {
				break;
			}
			if (lines[j].compare(0, 4, "/eof") == 0) {
				// end of previous file
				found = 1;
				value = j + 1;
				startindex.push_back(value);
				break;
			}
			if ((types[j] == E_muserec_comment_line) ||
				 (types[j] == E_muserec_comment_toggle)) {
//				j--;
				continue;
			}
			if (j < 0) {
				break;
			}
			headerline--;

			if (headerline == 0) {
				while ((j>= 0) && (lines[j][0] == '@')) {
					j--;
				}
				value = j+1;
				//value = j+2;
				found = 1;
				startindex.push_back(value);
				break;
			}

			if ((j >= 0) && (headerline == 0)) {
				value = j+1;
				found = 1;
				startindex.push_back(value);
				break;
			}
			if (j<0) {
				value = 0;
				found = 1;
				startindex.push_back(value);
				continue;
			}
			switch (headerline) {
				case 11: types[j] = E_muserec_header_11; break;
				case 10: types[j] = E_muserec_header_10; break;
				case  9: types[j] = E_muserec_header_9; break;
				case  8: types[j] = E_muserec_header_8; break;
				case  7: types[j] = E_muserec_header_7; break;
				case  6: types[j] = E_muserec_header_6; break;
				case  5: types[j] = E_muserec_header_5; break;
				case  4: types[j] = E_muserec_header_4; break;
				case  3: types[j] = E_muserec_header_3; break;
				case  2: types[j] = E_muserec_header_2; break;
				case  1: types[j] = E_muserec_header_1; break;
			}
		}
		if (!found) {
			value = 0;
			startindex.push_back(value);
		}
	}

	// now calculate the stopindexes:
	stopindex.resize(startindex.size());
	stopindex[(int)stopindex.size()-1] = (int)lines.size()-1;
	for (int i=0; i<(int)startindex.size()-1; i++) {
		stopindex[i] = startindex[i+1]-1;
	}
}



//////////////////////////////
//
// MuseDataSet::getFileCount -- return the number of parts found
//      in the MuseDataSet
//

int MuseDataSet::getFileCount(void) {
	return (int)m_part.size();
}



//////////////////////////////
//
// MuseDataSet::deletePart -- remove a particular part from the data set.
//

void MuseDataSet::deletePart(int index) {
	if (index < 0 || index > (int)m_part.size()-1) {
		cerr << "Trying to delete a non-existent part" << endl;
		return;
	}

	delete m_part[index];
	int i;
	for (i=index+1; i<(int)m_part.size(); i++) {
		m_part[i-1] = m_part[i];
	}
	m_part.resize(m_part.size()-1);
}



//////////////////////////////
//
// MuseDataSet::cleanLineEndings -- remove spaces for ends of lines.
//

void MuseDataSet::cleanLineEndings(void) {
	for (int i=0; i<(int)m_part.size(); i++) {
		m_part[i]->cleanLineEndings();
	}
}



//////////////////////////////
//
// MuseDataSet::clearError --
//

void MuseDataSet::clearError(void) {
	m_error = "";
}



//////////////////////////////
//
// MuseDataSet::hasError --
//

bool MuseDataSet::hasError(void) {
	return !m_error.empty();
}



//////////////////////////////
//
// MuseDataSet::setError --
//

void MuseDataSet::setError(const string& error) {
	m_error = error;
}



//////////////////////////////
//
// MuseDataSet::getError --
//

string MuseDataSet::getError(void) {
	return m_error;
}



//////////////////////////////
//
// MuseDataSet::getGroupIndexList -- Return the index number of files that
//    belong to the given target group.  If the group files are in a random
//    order, they will be returned in that order.
//

vector<int> MuseDataSet::getGroupIndexList(const string& group) {
	vector<int> output;
	MuseDataSet& mds = *this;
	HumRegex hre;
	string query = "^" + group;
	query += ":\\s*part\\s*(\\d+)\\s*of\\s*(\\d+)";
	bool groupMembership;
	for (int i=0; i<mds.getFileCount(); i++) {
		groupMembership = false;
		for (int j=0; j<mds[i].getLineCount(); j++) {
			if (hre.search(mds[i][j].getLine(), "^Group\\s+memberships?\\s*:", "i")) {
				groupMembership = true;
			}
			if (!groupMembership) {
				continue;
			}
			if (hre.search(mds[i][j].getLine(), query)) {
				output.push_back(i);
				break;
			}
		}
	}
	return output;
}



//////////////////////////////
//
// MuseData::getMidiTempo -- return the MIDI tempo (initial) for the score.
//    The MIDI tempo is in a file that has the string "Midi assignment" and/or
//    Group memberships: midi at the start of the file.  And a tempo after
//    Group memberhsips in the form:
//         192 quarter notes per minute
//    Returns 0.0 if there is no tempo specified.
//

double MuseDataSet::getMidiTempo(void) {
	int foundi = -1;
	int foundj = -1;
	for (int i=this->getFileCount() - 1; i>=0; i--) {
		for (int j=0; j<(*this)[i].getLineCount(); j++) {
			string line = (*this)[i].getRecord(j).getLine();
			if (line.compare(0, 15, "Midi assignment") == 0) {
				foundi = i;
				foundj = j;
				break;
			}
		}
		if (foundi >= 0) {
			break;
		}
	}
	if (foundi < 0) {
		// no tempo found
		return 0.0;
	}

	// continue to find the MIDI tempo line
	HumRegex hre;
	for (int j=foundj+1; j<(*this)[foundi].getLineCount(); j++) {
		string line = (*this)[foundi].getRecord(j).getLine();
		if (hre.search(line, "(\\d+\\.?\\d*)\\s*quarter notes per minute")) {
			return hre.getMatchDouble(1);
		}
	}

	return 0.0;
}



///////////////////////////////////////////////////////////////////////////

//////////////////////////////
//
// operator<< -- print out all parts in sequential order
//

ostream& operator<<(ostream& out, MuseDataSet& musedataset) {
	for (int i=0; i<musedataset.getFileCount(); i++) {
		for (int j=0; j<musedataset[i].getNumLines(); j++) {
			out << musedataset[i][j] << '\n';
		}
	}
	return out;
}




#define E_unknown   (0x7fff)

//////////////////////////////
//
// MuseRecord::getAttributeMap --
//

void MuseRecord::getAttributeMap(map<string, string>& amap) {
	amap.clear();
	// Should be "3" on the next line, but "1" or "2" might catch poorly formatted data.
	string contents = getLine().substr(2);
	if (contents.empty()) {
		return;
	}
	int i = 0;
	string key;
	string value;
	int state = 0;  // 0 outside, 1 = in key, 2 = in value
	while (i < (int)contents.size()) {
		switch (state) {
			case 0: // outside of key or value
				if (!isspace(contents[i])) {
					if (contents[i] == ':') {
						// Strange: should not happen
						key.clear();
						state = 2;
					} else {
						state = 1;
						key += contents[i];
					}
				}
				break;
			case 1: // in key
				if (!isspace(contents[i])) {
					if (contents[i] == ':') {
						value.clear();
						state = 2;
					} else {
						// Add to key, such as "C2" for second staff clef.
						key += contents[i];
					}
				}
				break;
			case 2: // in value
				if (key == "D") {
					value += contents[i];
				} else if (isspace(contents[i])) {
					// store parameter and clear variables
					amap[key] = value;
					state = 0;
					key.clear();
					value.clear();
				} else {
					value += contents[i];
				}
				break;
		}
		i++;
	}

	if ((!key.empty()) && (!value.empty())) {
		amap[key] = value;
	}
}



//////////////////////////////
//
// MuseRecord::getAttributes --
//

string MuseRecord::getAttributes(void) {
	string output;
	switch (getType()) {
		case E_muserec_musical_attributes:
			break;
		default:
			cerr << "Error: cannot use getAttributes function on line: "
				  << getLine() << endl;
			return "";
	}

	int ending = 0;
	int tempcol;
	for (int column=4; column <= getLength(); column++) {
		if (getColumn(column) == ':') {
			tempcol = column - 1;
			while (tempcol > 0 && getColumn(tempcol) != ' ') {
				tempcol--;
			}
			tempcol++;
			while (tempcol <= column) {
				output += getColumn(tempcol);
				if (output.back() == 'D') {
					ending = 1;
				}
				tempcol++;
			}
		}
		if (ending) {
			break;
		}
	}
	return output;
}



//////////////////////////////
//
// MuseRecord::attributeQ --
//

int MuseRecord::attributeQ(const string& attribute) {
	switch (getType()) {
		case E_muserec_musical_attributes:
			break;
		default:
			cerr << "Error: cannot use getAttributes function on line: "
				  << getLine() << endl;
			return 0;
	}


	string attributelist = getAttributes();

	int output = 0;
	int attstrlength = (int)attributelist.size();
	int attlength = (int)attribute.size();

	for (int i=0; i<attstrlength-attlength+1; i++) {
		if (attributelist[i] == attribute[0]) {
			output = 1;
			for (int j=0; j<attlength; j++) {
				if (attributelist[i] != attribute[j]) {
					output = 0;
					break;
				}
			}
			if (output == 1) {
				break;
			}
		}
	}
	return output;
}



//////////////////////////////
//
// MuseRecord::getAttributeInt --
//

int MuseRecord::getAttributeInt(char attribute) {
	switch (getType()) {
		case E_muserec_musical_attributes:
			break;
		default:
			cerr << "Error: cannot use getAttributeInt function on line: "
				  << getLine() << endl;
			return 0;
	}

	int output = E_unknown;
	int ending = 0;
	// int index = 0;
	int tempcol;
	int column;
	for (column=4; column <= getLength(); column++) {
		if (getColumn(column) == ':') {
			tempcol = column - 1;
			while (tempcol > 0 && getColumn(tempcol) != ' ') {
				tempcol--;
			}
			tempcol++;
			while (tempcol <= column) {
				if (getColumn(tempcol) == attribute) {
					ending = 2;
				} else if (getColumn(tempcol) == 'D') {
					ending = 1;
				}
				tempcol++;
				// index++;
			}
		}
		if (ending) {
			break;
		}
	}

	if (ending == 0 || ending == 1) {
		return output;
	} else {
		string value = &getColumn(column+1);
		if (value.empty()) {
			output = std::stoi(value);
			return output;
		} else {
			return 0;
		}
	}
}



//////////////////////////////
//
// MuseRecord::getAttributeField -- returns true if found attribute
//

int MuseRecord::getAttributeField(string& value, const string& key) {
	switch (getType()) {
		case E_muserec_musical_attributes:
			break;
		default:
			cerr << "Error: cannot use getAttributeInt function on line: "
				  << getLine() << endl;
			return 0;
	}

	int returnValue = 0;
	int ending = 0;
	// int index = 0;
	int tempcol;
	int column;
	for (column=4; column <= getLength(); column++) {
		if (getColumn(column) == ':') {
			tempcol = column - 1;
			while (tempcol > 0 && getColumn(tempcol) != ' ') {
				tempcol--;
			}
			tempcol++;
			while (tempcol <= column) {
				if (getColumn(tempcol) == key[0]) {
					ending = 2;
				} else if (getColumn(tempcol) == 'D') {
					ending = 1;
				}
				tempcol++;
				// index++;
			}
		}
		if (ending) {
			break;
		}
	}

	value.clear();
	if (ending == 0 || ending == 1) {
		return returnValue;
	} else {
		returnValue = 1;
		column++;
		while (getColumn(column) != ' ') {
			value += getColumn(column++);
		}
		return returnValue;
	}
}




//////////////////////////////
//
// MuseRecord::addMusicDirection -- add a delta index for associated
//     print suggestion.
//

void MuseRecord::addMusicDirection(int deltaIndex) {
	m_musicalDirections.push_back(deltaIndex);
}



//////////////////////////////
//
// MuseRecord::getDirectionAsciiCharacters -- returns columns 25
//    and later, but with the return string removing any trailing spaces.
//

std::string MuseRecord::getDirectionAsciiCharacters(void) {
	if (!isDirection()) {
		return "";
	}
	string& mrs = m_recordString;
	if (mrs.size() < 25) {
		return "";
	}
	string output = mrs.substr(24);
	size_t endpos = output.find_last_not_of(" \t\r\n");
   return (endpos != std::string::npos) ? output.substr(0, endpos + 1) : "";
}



//////////////////////////////
//
// MuseRecord::hasMusicalDirection --
//

bool MuseRecord::hasMusicalDirection(void) {
	if (isDirection()) {
		return true;
	}
	if (!m_musicalDirections.empty()) {
		return true;
	}
	return false;
}



//////////////////////////////
//
// MuseRecord::getMusicalDuration -- return any associated
//     Musical Direction record for the current record.  If there
//     is no linked direction, then return NULL.  If the record is
//     itself a muscial direction, return the pointer to the record.
//     Default value for index is 0.
//

MuseRecord* MuseRecord::getMusicalDirection(int index) {
	if (m_musicalDirections.empty()) {
		return NULL;
	}
	if (index >= (int)m_musicalDirections.size()) {
		return NULL;
	}
	return getDirectionRecord(m_musicalDirections.at(index));
}



//////////////////////////////
//
// MuseRecord::getDirectionRecord -- return the given direction from the store
//    delta index for the musical direction line.  Default value index = 0.
//

MuseRecord* MuseRecord::getDirectionRecord(int deltaIndex) {
	int index = m_lineindex + deltaIndex;
	if (index < 0) {
		return NULL;
	}
	if (!m_owner) {
		return NULL;
	}
	int lineCount = m_owner->getLineCount();
	if (index >= lineCount) {
		return NULL;
	}
	return m_owner->getRecordPointer(index);
}



//////////////////////////////
//
// MuseRecord::getDirectionType -- columns 17 and 18 of
//    musical directions.  This function will remove space
//    chaters from the columns.
//
// Direction Types:
// =================
// A = segno sign
// E = dynamics hairpin start (qualifiers [BCDG])
// F = dynamics hairpin start
// G = dynamics letters (in columns 25+)
// H = dash line start (qualifiers [BCDG])
// J = dash line end (qualifiers [BCDG])
// P = piano pedal start
// Q = piano pedal end
// R = rehearsal number or letter
// U = shift notes up (usually by 8va)
// V = shift notes down (usually by 8va)
// W = stop octave shift
// X = tie terminator
//

string MuseRecord::getDirectionType(void) {
	if (!isDirection()) {
		return "";
	}
	string value = getColumns(17, 18);
	if (value[1] == ' ') {
		value.resize(1);
	}
	if (value[0] == ' ') {
		value.resize(0);
	}
	return value;
}



//////////////////////////////
//
// MuseRecord::isDynamic -- helper function for getDirectionType() == "G".
//

bool MuseRecord::isDynamic(void) {
	string dirtype = getDirectionType();
	if (dirtype.empty()) {
		return false;
	}
	if (dirtype.at(0) == 'G') {
		return true;
	} else {
		return false;
	}
}



//////////////////////////////
//
// MuseRecord::getDynamicText --
//

string MuseRecord::getDynamicText(void) {
	return getDirectionAsciiCharacters();
}




//////////////////////////////
//
// MuseRecord::getFigureCountField -- column 2.
//

string MuseRecord::getFigureCountField(void) {
	allowFigurationOnly("getFigureCountField");
	return extract(2, 2);
}



//////////////////////////////
//
// MuseRecord::getFigurationCountString --
//

string MuseRecord::getFigureCountString(void) {
	allowFigurationOnly("getFigureCount");
	string output = extract(2, 2);
	if (output[0] == ' ') {
		output = "";
	}
	return output;
}



//////////////////////////////
//
// MuseRecord::getFigurationCount --
//

int MuseRecord::getFigureCount(void) {
	allowFigurationOnly("getFigureCount");
	string temp = getFigureCountString();
	int output = (int)strtol(temp.c_str(), NULL, 36);
	return output;
}



//////////////////////////////
//
// getFigurePointerField -- columns 6-8.
//

string MuseRecord::getFigurePointerField(void) {
	allowFigurationOnly("getFigurePointerField");
	return extract(6, 8);
}



//////////////////////////////
//
// getFigurePointer -- columns 6-8 for figures, removing
//    spaces.
//

string MuseRecord::getFigurePointer(void) {
	return trimSpaces(getFigurePointerField());
}



//////////////////////////////
//
// MuseRecord::getFigureDuration -- return the duration
//    in ticks for figured bass (to give an offset to next
//    figure which happens before another note in the score).
//

int MuseRecord::getFigureDuration(void) {
	string value = getFigurePointer();
	int output = 0;
	if (value.empty()) {
		return output;
	} else {
		try {
			output = std::stoi(value);
		} catch (const std::invalid_argument& e) {
        cout << "Invalid integer: " << e.what() << ". Setting to 0." << endl;
        output = 0;
		}
	}
	return output;
}



//////////////////////////////
//
// figurePointerQ --
//

int MuseRecord::figurePointerQ(void) {
	allowFigurationOnly("figurePointerQ");
	int output = 0;
	for (int i=6; i<=8; i++) {
		if (getColumn(i) != ' ') {
			output = 1;
			break;
		}
	}
	return output;
}



//////////////////////////////
//
// MuseRecord::getFigureString --
//

string MuseRecord::getFigureString(void) {
	string output = getFigureFields();
	for (int i=(int)output.size()-1; i>= 0; i--) {
		if (isspace(output[i])) {
			output.resize((int)output.size() - 1);
		} else {
			break;
		}
	}
	return output;
}



//////////////////////////////
//
// MuseRecord::getFigureFields -- columns 17 -- 80
//

string MuseRecord::getFigureFields(void) {
	allowFigurationOnly("getFigureFields");
	return extract(17, 80);
}


//////////////////////////////
//
// MuseRecord::figureFieldsQ --
//

int MuseRecord::figureFieldsQ(void) {
	allowFigurationOnly("figureFieldsQ");
	int output = 0;
	if (getLength() < 17) {
		output = 0;
	} else {
		for (int i=17; i<=80; i++) {
			if (getColumn(i) != ' ') {
				output = 1;
				break;
			}
		}
	}
	return output;
}



//////////////////////////////
//
// getFigure --
//

string MuseRecord::getFigure(int index) {
	string output;
	allowFigurationOnly("getFigure");
	if (index >= getFigureCount()) {
		return output;
	}
	string temp = getFigureString();
	if (index == 0) {
		return temp;
	}
	HumRegex hre;
	vector<string> pieces;
	hre.split(pieces, temp, " +");
	if (index < (int)pieces.size()) {
	output = pieces[index];
	}
	return output;
}




//////////////////////////////
//
// MuseRecord::getPartName -- return the name line in the
//     MuseData part's header.  This is the 9th non-comment
//     line in a part file.
//

string MuseRecord::getPartName(void) {
	if (isPartName()) {
		HumRegex hre;
		string raw = this->getLine();
		hre.replaceDestructive(raw, "", "^\\s+");
		hre.replaceDestructive(raw, "", "\\s+$");
		return raw;
	} else {
		return "";
	}
}




//////////////////////////////
//
// MuseRecord::getKernBeamStyle --
//

string MuseRecord::getKernBeamStyle(void) {
	string output;
	string beams = getBeamField();
	for (int i=0; i<(int)beams.size(); i++) {
		switch (beams[i]) {
			case '[':                 // start beam
				output += "L";
				break;
			case '=':                 // continue beam
				// do nothing
				break;
			case ']':                 // end beam
				output += "J";
				break;
			case '/':                 // forward hook
				output += "K";
				break;
			case '\\':                 // backward hook
				output += "k";
				break;
			default:
				;  // do nothing
		}
	}
	return output;
}



//////////////////////////////
//
// MuseRecord::getKernNoteStyle --
//     default values: beams = 0, stems = 0
//

string MuseRecord::getKernNoteStyle(int beams, int stems) {
	string output;

	if (!isAnyNote()) {
		// not a note, so return nothing
		return "";
	}

	// place the rhythm
	stringstream tempdur;
	HumNum notetype = getGraphicNoteType();
	HumNum mod = getTimeModification();
	if (mod != 1) {
		notetype *= mod;
	}

	// logical duration of the note
	HumNum logicalduration = getTicks();
	logicalduration /= getTpq();
	string durrecip = Convert::durationToRecip(logicalduration);

	// graphical duration of the note
	string graphicrecip = getGraphicRecip();
	HumNum graphicdur = Convert::recipToDuration(graphicrecip);

	string displayrecip;

	if (graphicdur != logicalduration) {
		// switch to the logical duration and store the graphic
		// duration.  The logical duration will be used on the
		// main kern token, and the graphic duration will be stored
		// as a layout parameter, such as !LO:N:vis=4. to display
		// the note as a dotted quarter regardless of the logical
		// duration.

		// Current test file has encoding bug related to triplets, so
		// disable graphic notation dealing with tuplets for now.

		// for now just looking to see if one has a dot and the other does not
		if ((durrecip.find(".") != string::npos) &&
				(graphicrecip.find(".") == string::npos)) {
			m_graphicrecip = graphicrecip;
			displayrecip = durrecip;
		} else if ((durrecip.find(".") == string::npos) &&
				(graphicrecip.find(".") != string::npos)) {
			m_graphicrecip = graphicrecip;
			displayrecip = durrecip;
		}
	}

	if (displayrecip.size() > 0) {
		output = displayrecip;
	} else {
		tempdur << notetype;
		output = tempdur.str();
		// add any dots of prolongation to the output string
		output += getStringProlongation();
	}

	// add the pitch to the output string
	string musepitch = getPitchString();
	string kernpitch = Convert::musePitchToKernPitch(musepitch);
	output += kernpitch;

	string logicalAccidental = getAccidentalString();
	string notatedAccidental = getNotatedAccidentalString();

	if (notatedAccidental.empty() && !logicalAccidental.empty()) {
		// Indicate that the logical accidental should not be
		// displayed (because of key signature or previous
		// note in the measure that alters the accidental
		// state of the current note).
		output += "y";
	} else if ((logicalAccidental == notatedAccidental) && !notatedAccidental.empty()) {
		// Indicate that the accidental should be displayed
		// and is not suppressed by the key signature or a
		// previous note in the measure.
		output += "X";
	}
	// There can be cases where the logical accidental
	// is natural but the notated accidetnal is sharp (but
	// the notated accidental means play a natural accidetnal).
	// Deal with this later.

	// if there is a notated natural sign, then add it now:
	string temp = getNotatedAccidentalField();
	if (temp == "n") {
		output += "n";
	}

	// check if a grace note
	if (getType() == 'g') {
		output += "Q";
	}

	// if stems is true, then show stem directions
	if (stems && stemDirectionQ()) {
		switch (getStemDirection()) {
			case 1:                         // 'u' = up
				output += "/";
				break;
			case -1:                        // 'd' = down
				output += "\\";
			default:
				; // nothing                 // ' ' = no stem (if stage 2)
		}
	}

	// if beams is true, then show any beams
	if (beams && beamQ()) {
		temp = getKernBeamStyle();
		output += temp;
	}

	if (isTied()) {
		string tiestarts;
		string tieends;
		int lasttie = getLastTiedNoteLineIndex();
		int nexttie = getNextTiedNoteLineIndex();
		int state = 0;
		if (lasttie >= 0) {
			state |= 2;
		}
		if (nexttie >= 0) {
			state |= 1;
		}
		switch (state) {
			case 1:
				tiestarts += "[";
				break;
			case 2:
				tieends += "]";
				break;
			case 3:
				tieends += "_";
				break;
		}
		if (state) {
			output = tiestarts + output + tieends;
		}
	}

	string slurstarts;
	string slurends;
	getSlurInfo(slurstarts, slurends);
	if ((!slurstarts.empty()) || (!slurends.empty())) {
		output = slurstarts + output + slurends;
	}

	return output;
}


//////////////////////////////
//
// MuseRecord::getKernNoteAccents --
//

string MuseRecord::getKernNoteAccents(void) {
	string output;
	int addnotecount = getAddCount();
	for (int i=0; i<addnotecount; i++) {
		string tempnote = getAddItem(i);
		switch (tempnote[0]) {
			case 'v':   output += "v";   break; // up-bow
			case 'n':   output += "u";   break; // down-bow
			case 'o':   output += "j";   break; // harmonic
			case 'O':   output += "I";   break; // open string (to generic)
			case 'A':   output += "^";   break; // accent up
			case 'V':   output += "^";   break; // accent down
			case '>':   output += "^";   break; // horizontal accent
			case '.':   output += "'";   break; // staccato
			case '_':   output += "~";   break; // tenuto
			case '=':   output += "~'";  break; // detached legato
			case 'i':   output += "s";   break; // spiccato
			case '\'':  output += ",";   break; // breath mark
			case 'F':   output += ";";   break; // fermata up
			case 'E':   output += ";";   break; // fermata down
			case 'S':   output += ":";   break; // staccato
			case 't':   output += "O";   break; // trill (to generic)
			case 'r':   output += "S";   break; // turn
			case 'k':   output += "O";   break; // delayed turn (to generic)
			case 'w':   output += "O";   break; // shake (to generic)
			case 'M':   output += "O";   break; // mordent (to generic)
			case 'j':   output += "H";   break; // glissando (slide)
		}
	}

	return output;
}



//////////////////////////////
//
// MuseRecord::getKernRestStyle --
//

string MuseRecord::getKernRestStyle(void) {

	string output;
	string rhythmstring;

	// place the rhythm
	stringstream tempdur;

	if (!isAnyRest()) {
		// not a rest, so return nothing
		return "";
	}

	// logical duration of the note
	HumNum logicalduration = getTicks();
	logicalduration /= getTpq();
	string durrecip = Convert::durationToRecip(logicalduration);

	/*
	int notetype;
	if (graphicNoteTypeQ()) {
		notetype = getGraphicNoteType();

		if (timeModificationLeftQ()) {
			notetype = notetype / 4 * getTimeModificationLeft();
		}
		if (timeModificationRightQ()) {
			notetype = notetype * getTimeModificationRight() / 2;
		}
		tempdur << notetype;
		output =  tempdur.str();

		// add any dots of prolongation to the output string
		output += getStringProlongation();
	} else {   // stage 1 data:
		HumNum dnotetype(getTickDuration(), quarter);
		rhythmstring = Convert::durationToRecip(dnotetype);
		output += rhythmstring;
	}
	*/

	output = durrecip;

	// add the pitch to the output string
	output += "r";

	if (isInvisibleRest()) {
		output += "yy";
	}

	return output;
}



//////////////////////////////
//
// MuseRecord::getKernMeasure -- Return the **kern measure token
//     for barline.
//

string MuseRecord::getKernMeasure(void) {
	if (!isBarline()) {
		return "";
	}
	string measureStyle = getMeasureType();
	string measureFlag  = getMeasureFlags();

	string output = "=";
	if ((measureStyle.find("mheavy") != string::npos) && measureFlag.empty()) {
		output += "=";
	}

	if ((output != "==") && measureNumberQ()) {
		output += getMeasureNumber();
	}

	if (measureStyle == "mheavy1") {
		output += "!";
	} else if (measureStyle == "mheavy2") {
		if (measureFlagEqual(":||:")) {
			output += ":|!|:";
		} else if (measureFlagEqual("|: :|")) {
			// Vivaldi op. 1, no. 1, mvmt. 1, m. 10: mheavy4          |: :|
			output += ":|!|:";
		}
	} else if (measureStyle == "mheavy3") {
		output += "!|";
	} else if (measureStyle == "mheavy4") {
		if (measureFlagEqual(":||:")) {
			output += ":!!:";
		} else if (measureFlagEqual(":||:")) {
			output += ":|!|:";
		} else if (measureFlagEqual("|: :|")) {
			output += ":|!|:";
		} else {
			output += "!!";
		}
	}
	return output;
}



//////////////////////////////
//
// MuseRecord::getKernNoteOtherNotations -- Extract note-level ornaments
//    and articulations.  See MuseRecord::getOtherNotation() for list
//    of "other notations".
//

string MuseRecord::getKernNoteOtherNotations(void) {
	string output;
	string notations = getOtherNotations();
	for (int i=0; i<(int)notations.size(); i++) {
		switch(notations[i]) {
			case 'F': // fermata above
				output += ";";
				break;
			case 'E': // fermata below
				output += ";<";
				break;
			case '.': // staccato
				output += "'";
				break;
			case ',': // breath mark
				output += ",";
				break;
			case '=': // tenuto-staccato
				output += "~'";
				break;
			case '>': // accent
				output += "^";
				break;
			case 'A': // heavy accent
				output += "^^";
				break;
			case 'M': // mordent
				output += "M";
				break;
			case 'r': // turn
				output += "S";
				break;
			case 't': // trill
				output += "T";
				break;
			case 'n': // down bow
				output += "u";
				break;
			case 'v': // up bow
				output += "v";
				break;
			case 'Z': // sfz
				output += "zz";
				break;
		}
	}
	return output;
}




//////////////////////////////
//
// MuseRecord::getMeasureNumberField -- Columns 9-12 contain the measure number.
//

string MuseRecord::getMeasureNumberField(void) {
	if (!isBarline()) {
		return "";
	}
	return extract(9, 12);
}



//////////////////////////////
//
// MuseRecord::getMeasureNumber -- Remove spaces from field.
//

string MuseRecord::getMeasureNumber(void) {
	return trimSpaces(getMeasureNumberField());
}



//////////////////////////////
//
// MuseRecord::getMeasureType -- Columns 1-7.
//

string MuseRecord::getMeasureType(void) {
	if (!isBarline()) {
		return "";
	}
	return extract(1, 7);
}



//////////////////////////////
//
// MuseRecord::measureNumberQ -- Returns true if barline
//     has a measure number.
//

bool MuseRecord::measureNumberQ(void) {
	return (getMeasureNumber() != "");
}



//////////////////////////////
//
// MuseRecord::getMeasureFlags --  Columns 17 to 80.  This is
//    the styling of the barline.
//

string MuseRecord::getMeasureFlags(void) {
	if (m_recordString.size() < 17) {
		return "";
	} else {
		return trimSpaces(m_recordString.substr(16));
	}
}



//////////////////////////////
//
// MuseRecord::measureFermataQ -- returns true if there is a
//	fermata above or below the measure
//

int MuseRecord::measureFermataQ(void) {
	int output = 0;
	for (int i=17; i<=80 && i<= getLength(); i++) {
		if (getColumn(i) == 'F' || getColumn(i) == 'E') {
			output = 1;
			break;
		}
	}
	return output;
}



//////////////////////////////
//
// MuseRecord::measureFlagEqual -- Returns true if there are non-space
//     characters in columns 17 through 80.   A more smarter way of
//     doing this is checking the allocated length of the record, and
//     do not search non-allocated columns for non-space characters...
//

bool MuseRecord::measureFlagEqual(const string& key) {
	return (getMeasureFlags() == key);
}



//////////////////////////////
//
// MuseRecord::addMeasureFlag -- add the following characters to the
//    Flag region of the measure flag area (columns 17-80).  But only
//    add the flag if it is not already present in the region.  If it is
//    not present, then append it after the last non-space character
//    in that region.  A space will be added between the last item
//    and the newly added parameter.
//

void MuseRecord::addMeasureFlag(const string& strang) {
	string flags = getColumns(17, 80);
	string flag = strang;

	HumRegex hre;
	hre.replaceDestructive(flag, "\\*", "\\*", "g");
	hre.replaceDestructive(flag, "\\|", "\\|", "g");
	if (hre.search(flags, flag)) {
		// flag was already found in flags, so don't do anything
		return;
	}
	hre.replaceDestructive(flags, "", "\\s+$");
	flags += " ";
	flags += strang;
	setColumns(flags, 17, 80);
}




//////////////////////////////
//
// MuseRecord::getOtherNotationsString -- return columns
//   32-42 (for notes and rests, but currently not checking if
//   the correct record type).
//

string MuseRecord::getOtherNotations(void) {
    if ((int)m_recordString.size() < 32) {
        return "";
    } else {
        size_t lengthToExtract = std::min(size_t(12), m_recordString.size() - 31);
        return m_recordString.substr(31, lengthToExtract);
    }
}



//////////////////////////////
//
// MuseRecord::hasFermata -- Return 0 if no fermata
//    E or F in columns 32-43 of a note or rest.  Returns +1 if fermata
//    above (E), or -1 if fermata below (F).  Check for &+ before
//    fermata for editorial.
//

int MuseRecord::hasFermata(void) {
	string notations = getOtherNotations();
	for (int i=0; i<(int)notations.size(); i++) {
		if (notations[i] == 'F') {
			return +1;
		}
		if (notations[i] == 'E') {
			return -1;
		}
	}
	return 0;
}




//////////////////////////////
//
// MuseRecord::getNoteField -- returns the string containing the pitch,
//	accidental and octave characters.
//

string MuseRecord::getNoteField(void) {
	switch (getType()) {
		case E_muserec_note_regular:
			return extract(1, 4);
			break;
		case E_muserec_note_chord:
		case E_muserec_note_cue:
		case E_muserec_note_grace:
			return extract(2, 5);
			break;
		default:
			cerr << "Error: cannot use getNoteField function on line: "
			     << getLine() << endl;
	}
	return "";
}



//////////////////////////////
//
// MuseRecord::getOctave -- returns the first numeric character
//	in the note field of a MuseData note record
//

int MuseRecord::getOctave(void) {
	string recordInfo = getNoteField();
	int index = 0;
	while ((index < (int)recordInfo.size()) && !std::isdigit(recordInfo[index])) {
		index++;
	}
	if (index >= (int)recordInfo.size()) {
		cerr << "Error: no octave specification in note field: " << recordInfo
			  << endl;
		return 0;
	}
	return recordInfo[index] - '0';
}


string MuseRecord::getOctaveString(void) {
	string recordInfo = getNoteField();
	int index = 0;
	while ((index < (int)recordInfo.size()) && !std::isdigit(recordInfo[index])) {
		index++;
	}
	if (index >= (int)recordInfo.size()) {
		cerr << "Error: no octave specification in note field: " << recordInfo
			  << endl;
		return "";
	}
	string output;
	output += recordInfo[index];
	return output;
}



//////////////////////////////
//
// MuseRecord::getPitch -- int version returns the base40 representation
//

int MuseRecord::getPitch(void) {
	string recordInfo = getNoteField();
	return Convert::museToBase40(recordInfo);
}


string MuseRecord::getPitchString(void) {
	string output = getNoteField();
	int len = (int)output.size();
	int index = len-1;
	while (index >= 0 && output[index] == ' ') {
		output.resize(index);
		index--;
	}
	return output;
}



//////////////////////////////
//
// MuseRecord::getPitchClass -- returns the pitch without the octave information
//

int MuseRecord::getPitchClass(void) {
	return getPitch() % 40;
}


string MuseRecord::getPitchClassString(void) {
	string output = getNoteField();
	int index = 0;
	while ((index < (int)output.size()) &&  !std::isdigit(output[index])) {
		index++;
	}
	output.resize(index);
	return output;
}



//////////////////////////////
//
// MuseRecord::getAccidental -- int version return -2 for double flat,
//	-1 for flat, 0 for natural, +1 for sharp, +2 for double sharp
//

int MuseRecord::getAccidental(void) {
	string recordInfo = getNoteField();
	int output = 0;
	int index = 0;
	while ((index < (int)recordInfo.size()) && (index < 16)) {
		if (recordInfo[index] == 'f') {
			output--;
		} else if (recordInfo[index] == '#') {
			output++;
		}
		index++;
	}
	return output;
}


string MuseRecord::getAccidentalString(void) {
	string output;
	int type = getAccidental();
	switch (type) {
		case -2: output = "ff"; break;
		case -1: output =  "f"; break;
		case  0: output =   ""; break;
		case  1: output =  "#"; break;
		case  2: output = "##"; break;
		default:
			output = getNoteField();
			cerr << "Error: unknown type of accidental: " << output << endl;
			return "";
	}
	return output;
}



//////////////////////////////
//
// MuseRecord::getBase40 -- return the base40 pitch value of the data
// line.  Middle C set to 40 * 4 + 2;  Returns -100 for non-pitched items.
// (might have to update for note_cur_chord and note_grace_chord which
// do not exist yet.
//

int MuseRecord::getBase40(void) {
	switch (getType()) {
		case E_muserec_note_regular:
		case E_muserec_note_chord:
		case E_muserec_note_cue:
		case E_muserec_note_grace:
			break;
		default:
			return -100;
	}
	return getPitch();
}



//////////////////////////////
//
// MuseRecord::setStemDown --
//

void MuseRecord::setStemDown(void) {
	getColumn(23) = 'd';
}



//////////////////////////////
//
// MuseRecord::setStemUp --
//

void MuseRecord::setStemUp(void) {
	getColumn(23) = 'u';
}



//////////////////////////////
//
// MuseRecord::setPitch -- input is a base40 value which gets converted
// to a diatonic pitch name.
//    Default value: chordnote = 0
//    Default value: gracenote = 0
//

void MuseRecord::setPitch(int base40, int chordnote, int gracenote) {
	string diatonic;
	switch (Convert::base40ToDiatonic(base40) % 7) {
		case 0:  diatonic = 'C'; break;
		case 1:  diatonic = 'D'; break;
		case 2:  diatonic = 'E'; break;
		case 3:  diatonic = 'F'; break;
		case 4:  diatonic = 'G'; break;
		case 5:  diatonic = 'A'; break;
		case 6:  diatonic = 'B'; break;
		default: diatonic = 'X';
	}

	string octave;
	octave  += char('0' + base40 / 40);

	string accidental;
	int acc = Convert::base40ToAccidental(base40);
	switch (acc) {
		case -2:   accidental = "ff"; break;
		case -1:   accidental = "f";  break;
		case +1:   accidental = "#";  break;
		case +2:   accidental = "##"; break;
	}
	string pitchname = diatonic + accidental + octave;

	if (chordnote) {
		if (gracenote) {
			setGraceChordPitch(pitchname);
		} else {
			setChordPitch(pitchname);
		}
	} else {
		setPitch(pitchname);
	}
}


void MuseRecord::setChordPitch(const string& pitchname) {
	getColumn(1) = ' ';
	setPitchAtIndex(1, pitchname);
}

void MuseRecord::setGracePitch(const string& pitchname) {
	getColumn(1) = 'g';
	setPitchAtIndex(1, pitchname);
}

void MuseRecord::setGraceChordPitch(const string& pitchname) {
	getColumn(1) = 'g';
	getColumn(2) = ' ';
	setPitchAtIndex(2, pitchname);
}

void MuseRecord::setCuePitch(const string& pitchname) {
	getColumn(1) = 'c';
	setPitchAtIndex(1, pitchname);
}


void MuseRecord::setPitch(const string& pitchname) {
	int start = 0;
	// If the record is already set to a grace note or a cue note,
	// then place pitch information starting at column 2 (index 1).
	if ((getColumn(1) == 'g') || (getColumn(1) == 'c')) {
		start = 1;
	}
	setPitchAtIndex(start, pitchname);
}


void MuseRecord::setPitchAtIndex(int index, const string& pitchname) {
	int len = (int)pitchname.size();
	if ((len > 4) && (pitchname != "irest")) {
		cerr << "Error in MuseRecord::setPitchAtIndex: " << pitchname << endl;
		return;
	}
	insertString(index+1, pitchname);

	// Clear any text fields not used by current pitch data.
	for (int i=4-len-1; i>=0; i--) {
		(*this)[index + len + i] = ' ';
	}
}



//////////////////////////////
//
// MuseRecord::getTickDurationField -- returns the string containing the
//      duration, and tie information.
//

string MuseRecord::getTickDurationField(void) {
	switch (getType()) {
		case E_muserec_figured_harmony:
		case E_muserec_note_regular:
		case E_muserec_note_chord:
		case E_muserec_rest:
		case E_muserec_backward:
		case E_muserec_forward:
			return extract(6, 9);
			break;
		// these record types do not have duration, per se:
		case E_muserec_note_cue:
		case E_muserec_note_grace:
		default:
			return "    ";
			// cerr << "Error: cannot use getTickDurationField function on line: "
			//      << getLine() << endl;
			// return "";
	}
	return "";
}



//////////////////////////////
//
// MuseRecord::getTickDurationString -- returns the string containing the duration,
//

string MuseRecord::getTickDurationString(void) {
	string output = getTickDurationField();
	int length = (int)output.size();
	int i = length - 1;
	while (i>0 && (output[i] == '-' || output[i] == ' ')) {
		output.resize(i);
		i--;
		length--;
	}

	int start = 0;
	while (output[start] == ' ') {
		start++;
	}

	if (start != 0) {
		for (i=0; i<length-start; i++) {
			output[i] = output[start+i];
		}
	}
	output.resize(length-start);

	return output;
}



//////////////////////////////
//
// MuseRecord::getTickDuration -- return the tick value found
//    in columns 6-8 in some data type, returning 0 if the record
//    type does not have a duration field.
//

int MuseRecord::getTickDuration(void) {
	string recordInfo = getTickDurationString();
	if (recordInfo.empty()) {
		return 0;
	}
	return std::stoi(recordInfo);
}



//////////////////////////////
//
// MuseRecord::getLineTickDuration -- returns the logical duration of the
//      data line.  Supresses the duration field of secondary chord notes.
//

int MuseRecord::getLineTickDuration(void) {
	if (getType() == E_muserec_note_chord) {
		return 0;
	}

	string recordInfo = getTickDurationString();
	if (recordInfo.empty()) {
		return 0;
	}
	int value = std::stoi(recordInfo);
	if (getType() == E_muserec_backspace) {
		return -value;
	}

	return value;
}



//////////////////////////////
//
// MuseRecord::getTicks -- similar to getLineTickDuration, but is non-zero
//    for secondary chord notes.
//

int MuseRecord::getTicks(void) {
	string recordInfo = getTickDurationString();
	if (recordInfo.empty()) {
		return 0;
	}
	int value = std::stoi(recordInfo);
	if (getType() == E_muserec_backspace) {
		return -value;
	}

	return value;
}


//////////////////////////////
//
// MuseRecord::getNoteTickDuration -- Similar to getLineTickDuration,
//    but do not suppress the duration of secondary chord-tones.
//

int MuseRecord::getNoteTickDuration(void) {
	string recordInfo = getTickDurationString();
	int value = 0;
	if (recordInfo.empty()) {
		return value;
	}
	value = std::stoi(recordInfo);
	if (getType() == E_muserec_backspace) {
		return -value;
	}
	return value;
}



//////////////////////////////
//
// MuseRecord::setDots --
//

void MuseRecord::setDots(int value) {
	switch (value) {
		case 0: getColumn(18) = ' ';   break;
		case 1: getColumn(18) = '.';   break;
		case 2: getColumn(18) = ':';   break;
		case 3: getColumn(18) = ';';   break;
		case 4: getColumn(18) = '!';   break;
		default: cerr << "Error in MuseRecord::setDots : " << value << endl;
	}
}



//////////////////////////////
//
// MuseRecord::getDotCount --
//

int MuseRecord::getDotCount(void) {
	char value = getColumn(18);
	switch (value) {
		case ' ': return 0;
		case '.': return 1;
		case ':': return 2;
		case ';': return 3;
		case '!': return 4;
	}
	return 0;
}



//////////////////////////////
//
// MuseRecord::setNoteheadShape -- Duration with augmentation dot component
//      removed.  Duration of 1 is quarter note.
//

void MuseRecord::setNoteheadShape(HumNum duration) {
	HumNum  note8th(1,2);
	HumNum  note16th(1,4);
	HumNum  note32nd(1,8);
	HumNum  note64th(1,16);
	HumNum  note128th(1,32);
	HumNum  note256th(1,64);

	if (duration > 16) {                 // maxima
		setNoteheadMaxima();
	} else if (duration > 8) {           // long
		setNoteheadLong();
	} else if (duration > 4) {           // breve
		if (m_roundBreve) {
			setNoteheadBreveRound();
		} else {
			setNoteheadBreve();
		}
	} else if (duration > 2) {           // whole note
		setNoteheadWhole();
	} else if (duration > 1) {           // half note
		setNoteheadHalf();
	} else if (duration > note8th) {     // quarter note
		setNoteheadQuarter();
	} else if (duration > note16th) {    // eighth note
		setNotehead8th();
	} else if (duration > note32nd) {    // 16th note
		setNotehead16th();
	} else if (duration > note64th) {    // 32nd note
		setNotehead32nd();
	} else if (duration > note128th) {   // 64th note
		setNotehead64th();
	} else if (duration > note256th) {   // 128th note
		setNotehead128th();
	} else if (duration == note256th) {  // 256th note
		// not allowing tuplets on the 256th note level.
		setNotehead256th();
	} else {
		cerr << "Error in duration: " << duration << endl;
		return;
	}
}



//////////////////////////////
//
// MuseRecord::setNoteheadShape -- Duration with augmentation dot component
//      removed.  Duration of 1 is quarter note.
//

void MuseRecord::setNoteheadShapeMensural(HumNum duration) {
	HumNum note8th(1, 2);
	HumNum note16th(1, 4);
	HumNum note32th(1, 8);
	HumNum note64th(1, 16);
	HumNum note128th(1, 32);
	HumNum note256th(1, 64);

	if (duration > 16) {                 // maxima
		setNoteheadMaxima();
	} else if (duration > 8) {           // long
		setNoteheadLong();
	} else if (duration > 4) {           // breve
		setNoteheadBreve();
	} else if (duration > 2) {           // whole note
		setNoteheadWholeMensural();
	} else if (duration > 1) {           // half note
		setNoteheadHalfMensural();
	} else if (duration > note8th) {     // quarter note
		setNoteheadQuarterMensural();
	} else if (duration > note16th) {    // eighth note
		setNotehead8thMensural();
	} else if (duration > note32th) {    // 16th note
		setNotehead16thMensural();
	} else if (duration > note64th) {    // 32nd note
		setNotehead32ndMensural();
	} else if (duration > note128th) {   // 64th note
		setNotehead64thMensural();
	} else if (duration > note256th) {   // 128th note
		setNotehead128thMensural();
	} else if (duration >= note256th) {  // 256th note
		// don't allow tuplets on 256th note level.
		setNotehead256thMensural();
	} else {
		cerr << "Error in duration: " << duration << endl;
		return;
	}
}

void MuseRecord::setNoteheadMaxima(void) {
	if ((*this)[0] == 'c' || ((*this)[0] == 'g')) {
		cerr << "Error: cue/grace notes cannot be maximas in setNoteheadLong"
			  << endl;
		return;
	} else {
		getColumn(17) = 'M';
	}
}

void MuseRecord::setNoteheadLong(void) {
	if ((*this)[0] == 'c' || ((*this)[0] == 'g')) {
		cerr << "Error: cue/grace notes cannot be longs in setNoteheadLong"
			  << endl;
		return;
	} else {
		getColumn(17) = 'L';
	}
}

void MuseRecord::setNoteheadBreve(void) {
	setNoteheadBreveSquare();
}

void MuseRecord::setNoteheadBreveSquare(void) {
	if ((*this)[0] == 'g') {        // grace note
		getColumn(8) = 'A';
	} else if ((*this)[0] == 'c') { // cue-sized note (with duration)
		getColumn(17) = 'A';
	} else {                        // normal note
		getColumn(17) = 'B';
	}
}

void MuseRecord::setNoteheadBreveRound(void) {
	if ((*this)[0] == 'g') {        // grace note
		getColumn(8) = 'A';
	} else if ((*this)[0] == 'c') { // cue-sized note (with duration)
		getColumn(17) = 'A';
	} else {                        // normal note
		getColumn(17) = 'b';
	}
}

void MuseRecord::setNoteheadBreveMensural(void) {
	setNoteheadBreveSquare();
}

void MuseRecord::setNoteheadWhole(void) {
	if ((*this)[0] == 'g') {        // grace note
		getColumn(8) = '9';
	} else if ((*this)[0] == 'c') { // cue-sized note (with duration)
		getColumn(17) = '9';
	} else {                        // normal note
		getColumn(17) = 'w';
	}
}

void MuseRecord::setNoteheadWholeMensural(void) {
	if ((*this)[0] == 'g') {        // grace note
		getColumn(8) = '9';
	} else if ((*this)[0] == 'c') { // cue-sized note (with duration)
		getColumn(17) = '9';
	} else {                        // normal note
		getColumn(17) = 'W';
	}
}

void MuseRecord::setNoteheadHalf(void) {
	if ((*this)[0] == 'g') {        // grace note
		getColumn(8) = '8';
	} else if ((*this)[0] == 'c') { // cue-sized note (with duration)
		getColumn(17) = '8';
	} else {                        // normal note
		getColumn(17) = 'h';
	}
}

void MuseRecord::setNoteheadHalfMensural(void) {
	if ((*this)[0] == 'g') {        // grace note
		getColumn(8) = '8';
	} else if ((*this)[0] == 'c') { // cue-sized note (with duration)
		getColumn(17) = '8';
	} else {                        // normal note
		getColumn(17) = 'H';
	}
}

void MuseRecord::setNoteheadQuarter(void) {
	if ((*this)[0] == 'g') {        // grace note
		getColumn(8) = '7';
	} else if ((*this)[0] == 'c') { // cue-sized note (with duration)
		getColumn(17) = '7';
	} else {                        // normal note
		getColumn(17) = 'q';
	}
}

void MuseRecord::setNoteheadQuarterMensural(void) {
	if ((*this)[0] == 'g') {        // grace note
		getColumn(8) = '7';
	} else if ((*this)[0] == 'c') { // cue-sized note (with duration)
		getColumn(17) = '7';
	} else {                        // normal note
		getColumn(17) = 'Q';
	}
}

void MuseRecord::setNotehead8th(void) {
	if ((*this)[0] == 'g') {        // grace note
		getColumn(8) = '6';
	} else if ((*this)[0] == 'c') { // cue-sized note (with duration)
		getColumn(17) = '6';
	} else {                        // normal note
		getColumn(17) = 'e';
	}
}

void MuseRecord::setNotehead8thMensural(void) {
	if ((*this)[0] == 'g') {        // grace note
		getColumn(8) = '6';
	} else if ((*this)[0] == 'c') { // cue-sized note (with duration)
		getColumn(17) = '6';
	} else {                        // normal note
		getColumn(17) = 'E';
	}
}

void MuseRecord::setNotehead16th(void) {
	if ((*this)[0] == 'g') {        // grace note
		getColumn(8) = '5';
	} else if ((*this)[0] == 'c') { // cue-sized note (with duration)
		getColumn(17) = '5';
	} else {                        // normal note
		getColumn(17) = 's';
	}
}

void MuseRecord::setNotehead16thMensural(void) {
	if ((*this)[0] == 'g') {        // grace note
		getColumn(8) = '5';
	} else if ((*this)[0] == 'c') { // cue-sized note (with duration)
		getColumn(17) = '5';
	} else {                        // normal note
		getColumn(17) = 'S';
	}
}

void MuseRecord::setNotehead32nd(void) {
	if ((*this)[0] == 'g') {        // grace note
		getColumn(8) = '4';
	} else if ((*this)[0] == 'c') { // cue-sized note (with duration)
		getColumn(17) = '4';
	} else {                        // normal note
		getColumn(17) = 't';
	}
}

void MuseRecord::setNotehead32ndMensural(void) {
	if ((*this)[0] == 'g') {        // grace note
		getColumn(8) = '4';
	} else if ((*this)[0] == 'c') { // cue-sized note (with duration)
		getColumn(17) = '4';
	} else {                        // normal note
		getColumn(17) = 'T';
	}
}

void MuseRecord::setNotehead64th(void) {
	if ((*this)[0] == 'g') {        // grace note
		getColumn(8) = '3';
	} else if ((*this)[0] == 'c') { // cue-sized note (with duration)
		getColumn(17) = '3';
	} else {                        // normal note
		getColumn(17) = 'x';
	}
}

void MuseRecord::setNotehead64thMensural(void) {
	if ((*this)[0] == 'g') {        // grace note
		getColumn(8) = '3';
	} else if ((*this)[0] == 'c') { // cue-sized note (with duration)
		getColumn(17) = '3';
	} else {                        // normal note
		getColumn(17) = 'X';
	}
}

void MuseRecord::setNotehead128th(void) {
	if ((*this)[0] == 'g') {        // grace note
		getColumn(8) = '2';
	} else if ((*this)[0] == 'c') { // cue-sized note (with duration)
		getColumn(17) = '2';
	} else {                        // normal note
		getColumn(17) = 'y';
	}
}

void MuseRecord::setNotehead128thMensural(void) {
	if ((*this)[0] == 'g') {        // grace note
		getColumn(8) = '2';
	} else if ((*this)[0] == 'c') { // cue-sized note (with duration)
		getColumn(17) = '2';
	} else {                        // normal note
		getColumn(17) = 'Y';
	}
}

void MuseRecord::setNotehead256th(void) {
	if ((*this)[0] == 'g') {        // grace note
		getColumn(8) = '1';
	} else if ((*this)[0] == 'c') { // cue-sized note (with duration)
		getColumn(17) = '1';
	} else {                        // normal note
		getColumn(17) = 'z';
	}
}

void MuseRecord::setNotehead256thMensural(void) {
	if ((*this)[0] == 'g') {        // grace note
		getColumn(8) = '1';
	} else if ((*this)[0] == 'c') { // cue-sized note (with duration)
		getColumn(17) = '1';
	} else {                        // normal note
		getColumn(17) = 'Z';
	}
}


/////////////////////////////
//
// MuseRecord::setBack --
//

void MuseRecord::setBack(int value) {
	insertString(1, "back");
	setTicks(value);
}



/////////////////////////////
//
// MuseRecord::setTicks -- return the numeric value in columns 6-9.
//

void MuseRecord::setTicks(int value) {
	if ((value < 0) || (value >= 1000)) {
		cerr << "@ Error: ticks out of range in MuseRecord::setTicks" << endl;
	}
	stringstream ss;
	ss << value;
	int len = (int)ss.str().size();
	insertString(5+3-len+1, ss.str());
}



//////////////////////////////
//
// MuseRecord::getTie --
//

string MuseRecord::getTieString(void) {
	string output;
	output += getColumn(9);
	if (output == " ") {
		output = "";
	}
	return output;
}


int MuseRecord::getTie(void) {
	return tieQ();
}


//////////////////////////////
//
// MuseRecord::getTie -- Set a tie marker in column 9.  Currently
// the function does not check the type of data, so will overr-write any
// data found in column 9 (such as if the record is not for a note).
//
// If the input parameter hidden is true, then the visual tie is not
// displayed, but the sounding tie is displayed.
//

int MuseRecord::setTie(int hidden) {
	getColumn(9) = '-';
	if (!hidden) {
		return addAdditionalNotation('-');
	} else {
		return -1;
	}
}



//////////////////////////////
//
// MuseRecord::addAdditionalNotation -- ties, slurs and tuplets.
//    Currently not handling editorial levels.
//

int MuseRecord::addAdditionalNotation(char symbol) {
	// search columns 32 to 43 for the specific symbol.
	// if it is found, then don't add.  If it is not found,
	// then do add.
	int i;
	int blank = -1;
	int nonempty = 0;  // true if a non-space character was found.

	for (i=43; i>=32; i--) {
		if (getColumn(i) == symbol) {
			return i;
		} else if (!nonempty && (getColumn(i) == ' ')) {
			blank = i;
		} else {
			nonempty = i;
		}
	}

	if (symbol == '-') {
		// give preferential treatment to placing only ties in
		// column 32
		if (getColumn(32) == ' ') {
			getColumn(32) = '-';
			return 32;
		}
	}

	if (blank < 0) {
		cerr << "Error in MuseRecord::addAdditionalNotation: "
			  << "no empty space for notation" << endl;
		return 0;
	}

	if ((blank <= 32) && (getColumn(33) == ' ')) {
		// avoid putting non-tie items in column 32.
		blank = 33;
	}

	getColumn(blank) = symbol;
	return blank;
}


// add a multi-character additional notation (such as a dynamic like mf):

int MuseRecord::addAdditionalNotation(const string& symbol) {
	int len = (int)symbol.size();
	// search columns 32 to 43 for the specific symbol.
	// if it is found, then don't add.  If it is not found,
	// then do add.
	int i, j;
	int blank = -1;
	int found = 0;
	int nonempty = 0;  // true if a non-space character was found.

	for (i=43-len; i>=32; i--) {
		found = 1;
		for (j=0; j<len; j++) {
			if (getColumn(i+j) != symbol[j]) {
				found = 0;
				break;
			}
		}
		if (found) {
			return i;
		} else if (!nonempty && (getColumn(i) == ' ')) {
// cout << "@COLUMN " << i << " is blank: " << getColumn(i) << endl;
			blank = i;
			// should check that there are enough blank lines to the right
			// as well...
		} else if (getColumn(i) != ' ') {
			nonempty = i;
		}
	}

	if (blank < 0) {
		cerr << "Error in MuseRecord::addAdditionalNotation2: "
			  << "no empty space for notation" << endl;
		return 0;
	}

// cout << "@ GOT HERE symbol = " << symbol << " and blank = " << blank << endl;
	if ((blank <= 32) && (getColumn(33) == ' ')) {
		// avoid putting non-tie items in column 32.
		blank = 33;
		// not worrying about overwriting something to the right
		// of column 33 since the empty spot was checked starting
		// on the right and moving towards the left.
	}
// cout << "@COLUMN 33 = " << getColumn(33) << endl;
// cout << "@ GOT HERE symbol = " << symbol << " and blank = " << blank << endl;

	for (j=0; j<len; j++) {
		getColumn(blank+j) = symbol[j];
	}
	return blank;
}



//////////////////////////////
//
// MuseRecord::tieQ -- returns true if the current line contains
//   a tie to a note in the future.  Does not check if there is a tie
//   to a note in the past.
//

int MuseRecord::tieQ(void) {
	int output = 0;
	switch (getType()) {
		case E_muserec_note_regular:
		case E_muserec_note_chord:
		case E_muserec_note_cue:
		case E_muserec_note_grace:
			if (getColumn(9) == '-') {
				output = 1;
			} else if (getColumn(9) == ' ') {
				output = 0;
			} else {
				output = -1;
			}
			break;
		default:
			return 0;
	}

	return output;
}


//////////////////////////////////////////////////////////////////////////
//
// graphical and intrepretive information for notes
//

//////////////////////////////
//
// MuseRecord::getFootnoteFlagField -- returns column 13 value
//

string MuseRecord::getFootnoteFlagField(void) {
	allowFigurationAndNotesOnly("getFootnoteField");
	return extract(13, 13);
}



//////////////////////////////
//
// MuseRecord::getFootnoteFlagString --
//

string MuseRecord::getFootnoteFlagString(void) {
	string output = getFootnoteFlagField();
	if (output[0] == ' ') {
		output = "";
	}
	return output;
}



//////////////////////////////
//
// MuseRecord::getFootnoteFlag --
//

int MuseRecord::getFootnoteFlag(void) {
	int output = 0;
	string recordInfo = getFootnoteFlagString();
	if (recordInfo[0] == ' ') {
		output = -1;
	} else {
		output = (int)strtol(recordInfo.c_str(), NULL, 36);
	}
	return output;
}



//////////////////////////////
//
// MuseRecord::footnoteFlagQ --
//

int MuseRecord::footnoteFlagQ(void) {
	int output = 0;
	string recordInfo = getFootnoteFlagField();
	if (recordInfo[0] == ' ') {
		output = 0;
	} else {
		output = 1;
	}
	return output;
}



//////////////////////////////
//
// MuseRecord::getLevelField -- return column 14
//

string MuseRecord::getLevelField(void) {
	allowFigurationAndNotesOnly("getLevelField");
	return extract(14, 14);
}



//////////////////////////////
//
// MuseRecord::getLevel --
//

string MuseRecord::getLevelString(void) {
	string output = getLevelField();
	if (output[0] == ' ') {
		output = "";
	}
	return output;
}


int MuseRecord::getLevel(void) {
	int output = 1;
	string recordInfo = getLevelField();
	if (recordInfo[0] == ' ') {
		output = 1;
	} else {
		output = (int)strtol(recordInfo.c_str(), NULL, 36);
	}
	return output;
}



//////////////////////////////
//
// MuseRecord::levelQ --
//

int MuseRecord::levelQ(void) {
	int output = 0;
	string recordInfo = getLevelField();
	if (recordInfo[0] == ' ') {
		output = 0;
	} else {
		output = 1;
	}
	return output;
}



//////////////////////////////
//
// MuseRecord::getTrackField -- return column 15
//

string MuseRecord::getTrackField(void) {
	if (!isAnyNoteOrRest()) {
		return extract(15, 15);
	} else {
		return " ";
	}
}



//////////////////////////////
//
// MuseRecord::getTrackString --
//

string MuseRecord::getTrackString(void) {
	string output = getTrackField();
	if (output[0] == ' ') {
		output = "";
	}
	return output;
}



//////////////////////////////
//
// MuseRecord::getTrack -- Return 0 if no track information (implicitly track 1,
//     or unlabelled higher track).
//

int MuseRecord::getTrack(void) {
	int output = 1;
	string recordInfo = getTrackField();
	if (recordInfo[0] == ' ') {
		output = 0;
	} else {
		output = (int)strtol(recordInfo.c_str(), NULL, 36);
	}
	return output;
}



//////////////////////////////
//
// MuseRecord::trackQ --
//

int MuseRecord::trackQ(void) {
	int output = 0;
	string recordInfo = getTrackField();
	if (recordInfo[0] == ' ') {
		output = 0;
	} else {
		output = 1;
	}

	return output;
}



//////////////////////////////
//
// MuseRecord::getGraphicNoteTypeField -- return column 17
//

string MuseRecord::getGraphicNoteTypeField(void) {
// allowNotesOnly("getGraphicNoteTypefield");
	if (getLength() < 17) {
		return " ";
	} else {
		return extract(17, 17);
	}
}



//////////////////////////////
//
// MuseRecord::getGraphicNoteType --
//

string MuseRecord::getGraphicNoteTypeString(void) {
	string output = getGraphicNoteTypeField();
	if (output[0] == ' ') {
		output = "";
	}
	return output;
}



//////////////////////////////
//
// MuseRecord::getGraphicRecip --
//

string MuseRecord::getGraphicRecip(void) {
	int notetype = getGraphicNoteType();
	string output;
	switch (notetype) {
		case -3: output = "0000"; break;  // double-maxima
		case -2: output = "000"; break;   // maxima
		case -1: output = "00"; break;    // long
		default:
			output = to_string(notetype);  // regular **recip number
	}
	int dotcount = getDotCount();
	for (int i=0; i<dotcount; i++) {
		output += '.';
	}
	return output;
}



//////////////////////////////
//
// MuseRecord::getGraphicNoteType --
//

int MuseRecord::getGraphicNoteType(void) {
	int output = 0;
	string recordInfo = getGraphicNoteTypeField();
	if (recordInfo[0] == ' ') {
		if (isInvisibleRest()) {
			// invisible rests do not have graphic note types
			// so make one up from the logical note type
			HumNum value = getTickDuration();
			value /= getTpq();
			if (value >= 32) {
				return -2;
			} else if (value >= 16) {
				return -1;
			} else if (value >= 8) {
				return 0;
			} else if (value >= 4) {
				return 1;
			} else if (value >= 2) {
				return 2;
			} else if (value >= 1) {
				return 4;
			} else if (value.getFloat() >= 0.5) {
				return 8;
			} else if (value.getFloat() >= 0.25) {
				return 16;
			} else if (value.getFloat() >= 0.125) {
				return 32;
			} else if (value.getFloat() >= 0.0625) {
				return 64;
			} else if (value.getFloat() >= 1.0/128) {
				return 128;
			} else if (value.getFloat() >= 1.0/256) {
				return 256;
			} else if (value.getFloat() >= 1.0/512) {
				return 512;
			} else {
				return 0;
			}
		} else {
			cerr << "Error: no graphic note type specified: " << getLine() << endl;
			return 0;
		}
	}

	switch (recordInfo[0]) {
		case 'M':                          // Maxima
			output = -2;           break;
		case 'L':   case 'B':              // Longa
			output = -1;           break;
		case 'b':   case 'A':              // Breve
			output = 0;            break;
		case 'w':   case '9':              // Whole
			output = 1;            break;
		case 'h':   case '8':              // Half
			output = 2;            break;
		case 'q':   case '7':              // Quarter
			output = 4;            break;
		case 'e':   case '6':              // Eighth
			output = 8;            break;
		case 's':   case '5':              // Sixteenth
			output = 16;           break;
		case 't':   case '4':              // 32nd note
			output = 32;           break;
		case 'x':   case '3':              // 64th note
			output = 64;           break;
		case 'y':   case '2':              // 128th note
			output = 128;          break;
		case 'z':   case '1':              // 256th note
			output = 256;          break;
		default:
			cerr << "Error: unknown graphical note type in column 17: "
				  << getLine() << endl;
	}

	return output;
}


//////////////////////////////
//
// MuseRecord::graphicNoteTypeQ --
//

int MuseRecord::graphicNoteTypeQ(void) {
	int output = 0;
	string recordInfo = getGraphicNoteTypeField();
	if (recordInfo[0] == ' ') {
		output = 0;
	} else {
		output = 1;
	}
	return output;
}



//////////////////////////////
//
// MuseRecord::graphicNoteTypeSize -- return 0 if cue note size,
//	otherwise, it will return 1 if regular size
//

int MuseRecord::getGraphicNoteTypeSize(void) {
	int output = 1;
	string recordInfo = getGraphicNoteTypeField();
	if (recordInfo[0] == ' ') {
		cerr << "Error: not graphic note specified in column 17: "
			  << getLine() << endl;
		return 0;
	}

	switch (recordInfo[0]) {
		case 'L': case 'b': case 'w': case 'h': case 'q': case 'e':
		case 's': case 't': case 'x': case 'y': case 'z':
			output = 1;
			break;
		case 'B': case 'A': case '9': case '8': case '7': case '6':
		case '5': case '4': case '3': case '2': case '1':
			output = 0;
			break;
		default:
			cerr << "Error: unknown graphical note type in column 17: "
				  << getLine() << endl;
			return 0;
	}

	return output;
}



//////////////////////////////
//
// MuseRecord::getProlongationField -- returns column 18
//

string MuseRecord::getProlongationField(void) {
//   allowNotesOnly("getProlongationField");   ---> rests also
	if (getLength() < 18) {
		return " ";
	} else {
		return extract(18, 18);
	}
}



//////////////////////////////
//
// MuseRecord::getProlongationString --
//

string MuseRecord::getProlongationString(void) {
	string output = getProlongationField();
	if (output[0] == ' ') {
		output = "";
	}
	return output;
}



//////////////////////////////
//
// MuseRecord::getProlongation --
//

int MuseRecord::getProlongation(void) {
	int output = 0;
	string recordInfo = getProlongationField();
	switch (recordInfo[0]) {
		case ' ':   output = 0;   break;
		case '.':   output = 1;   break;
		case ':':   output = 2;   break;
		default:
			cerr << "Error: unknon prologation character (column 18): "
				  << getLine() << endl;
			return 0;
	}
	return output;
}



//////////////////////////////
//
// MuseRecord::getStringProlongation --
//

string MuseRecord::getStringProlongation(void) {
	switch (getProlongation()) {
		case 0:   return "";     break;
		case 1:   return ".";    break;
		case 2:   return "..";   break;
		case 3:   return "...";  break;
		case 4:   return "...."; break;
		default:
			cerr << "Error: unknown number of prolongation dots (column 18): "
				  << getLine() << endl;
			return "";
	}
	return "";
}



//////////////////////////////
//
// MuseRecord::prolongationQ --
//

int MuseRecord::prolongationQ(void) {
	return getProlongation();
}


//////////////////////////////
//
// MuseRecord::getNotatedAccidentalField -- actual notated accidental is
//     stored in column 19.
//

string MuseRecord::getNotatedAccidentalField(void) {
	allowNotesOnly("getNotatedAccidentalField");
	if (getLength() < 19) {
		return " ";
	} else {
		string temp;
		temp += getColumn(19);
		return temp;
	}
}



//////////////////////////////
//
// MuseRecord::getNotatedAccidentalString --
//

string MuseRecord::getNotatedAccidentalString(void) {
	string output = getNotatedAccidentalField();
	if (output[0] == ' ') {
		output = "";
	}
	return output;
}



//////////////////////////////
//
// MuseRecord::getNotatedAccidental --
//

int MuseRecord::getNotatedAccidental(void) {
	int output = 0;
	string recordInfo = getNotatedAccidentalField();
	switch (recordInfo[0]) {
		case ' ':   output =  0;   break;
		case '#':   output =  1;   break;
		case 'n':   output =  0;   break;
		case 'f':   output = -1;   break;
		case 'x':   output =  2;   break;
		case 'X':   output =  2;   break;
		case '&':   output = -2;   break;
		case 'S':   output =  1;   break;
		case 'F':   output = -1;   break;
		default:
			cerr << "Error: unknown accidental: " << recordInfo[0] << endl;
			return 0;
	}
	return output;
}



//////////////////////////////
//
// MuseRecord::notatedAccidentalQ --
//

int MuseRecord::notatedAccidentalQ(void) {
	int output;
	string recordInfo = getNotatedAccidentalField();
	if (recordInfo[0] == ' ') {
		output = 0;
	} else {
		output = 1;
	}
	return output;
}



///////////////////////////////
//
// MuseRecord::getTimeModificationField -- return columns 20 -- 22.
//

string MuseRecord::getTimeModificationField(void) {
//   allowNotesOnly("getTimeModificationField");   ---> rests also
	if (getLength() < 20) {
		return  "   ";
	} else {
		return extract(20, 22);
	}
}



//////////////////////////////
//
// MuseRecord::getTimeModificationString --
//

string MuseRecord::getTimeModificationString(void) {
	string output = getTimeModificationField();
	HumRegex hre;
	if (hre.search(output, "[1-9A-Z]:[1-9A-Z]")) {
		return output;
	}
	return "";
}



//////////////////////////////
//
// MuseRecord::getTimeModification --
//

HumNum MuseRecord::getTimeModification(void) {
	string output = getTimeModificationField();
	HumRegex hre;
	if (hre.search(output, "([1-9A-Z]):([1-9A-Z])")) {
		string top = hre.getMatch(1);
		string bot = hre.getMatch(2);
		int topint = (int)strtol(top.c_str(), NULL, 36);
		int botint = (int)strtol(top.c_str(), NULL, 36);
		HumNum number(topint, botint);
		return number;
	} else {
		if (hre.search(output, "^([1-9A-Z])")) {
			string value = hre.getMatch(1);
			int top = (int)strtol(value.c_str(), NULL, 36);
			// Time modification can be "3  " for triplets.
			HumNum out(top, 2);
			return out;
		} else {
			return 1;
		}
	}
}



//////////////////////////////
//
// MuseRecord::getTimeModificationLeftField -- return column 20
//

string MuseRecord::getTimeModificationLeftField(void) {
	string output = getTimeModificationField();
	HumRegex hre;
	if (!hre.search(output, "^[1-9A-Z]:[1-9A-Z]$")) {
		return " ";
	}
	return output.substr(0, 1);
}



//////////////////////////////
//
// MuseRecord::getTimeModificationLeftString --
//

string MuseRecord::getTimeModificationLeftString(void) {
	string output = getTimeModificationField();
	HumRegex hre;
	if (!hre.search(output, "^[1-9A-Z]:[1-9A-Z]$")) {
		return "";
	}
	return output.substr(0, 1);
}



//////////////////////////////
//
// MuseRecord::getTimeModificationLeft --
//

int MuseRecord::getTimeModificationLeft(void) {
	int output = 0;
	string recordInfo = getTimeModificationLeftString();
	if (recordInfo.empty()) {
		return 1;
	} else {
		output = (int)strtol(recordInfo.c_str(), NULL, 36);
	}
	return output;
}



//////////////////////////////
//
// MuseRecord::getTimeModificationRightField -- return column 20
//

string MuseRecord::getTimeModificationRightField(void) {
	string output = getTimeModificationField();
	output = output[2];
	return output;
}



//////////////////////////////
//
// MuseRecord::getTimeModificationRight --
//

string MuseRecord::getTimeModificationRightString(void) {
	HumRegex hre;
	string output = getTimeModificationField();
	if (!hre.search(output, "^[1-9A-Z]:[1-9A-Z]$")) {
		return " ";
	}
	return output.substr(2, 1);
}



//////////////////////////////
//
// MuseRecord::getTimeModificationRight --
//

int MuseRecord::getTimeModificationRight(void) {
	string recordInfo = getTimeModificationRightString();
	HumRegex hre;
	if (recordInfo.empty()) {
		return 1;
	} else if (!hre.search(recordInfo, "^[1-9A-Z]$")) {
		return 1;
	} else {
		return (int)strtol(recordInfo.c_str(), NULL, 36);
	}
}



//////////////////////////////
//
// MuseRecord::timeModificationQ --
//

bool MuseRecord::timeModificationQ(void) {
	string recordInfo = getTimeModificationField();
	HumRegex hre;
	if (hre.search(recordInfo, "^[1-9A-Z]:[1-9A-Z]$")) {
		return true;
	} else {
		return false;
	}
}



//////////////////////////////
//
// MuseRecord::timeModificationLeftQ --
//

bool MuseRecord::timeModificationLeftQ(void) {
	string recordInfo = getTimeModificationField();
	HumRegex hre;
	string value;
	value.push_back(recordInfo.at(0));
	if (hre.search(value, "^[1-9A-Z]$")) {
		return true;
	} else {
		return false;
	}
}



//////////////////////////////
//
// MuseRecord::timeModificationRightQ --
//

bool MuseRecord::timeModificationRightQ(void) {
	string recordInfo = getTimeModificationField();
	HumRegex hre;
	string value;
	value.push_back(recordInfo.at(0));
	if (hre.search(value, "^[1-9A-Z]$")) {
		return true;
	} else {
		return false;
	}
}



//////////////////////////////
//
// MuseRecord::getStemDirectionField --
//

string MuseRecord::getStemDirectionField(void) {
	allowNotesOnly("getStemDirectionField");
	if (getLength() < 23) {
		return " ";
	} else {
		string temp;
		temp += getColumn(23);
		return temp;
	}
}



//////////////////////////////
//
// MuseRecord::getStemDirectionString --
//

string MuseRecord::getStemDirectionString(void) {
	string output = getStemDirectionField();
	if (output[0] == ' ') {
		output = "";
	}
	return output;
}



//////////////////////////////
//
// MuseRecord::getStemDirection --
//

int MuseRecord::getStemDirection(void) {
	int output = 0;
	string recordInfo = getStemDirectionField();
	switch (recordInfo[0]) {
		case 'u':   output = 1;   break;
		case 'd':   output = -1;  break;
		case ' ':   output = 0;   break;
		default:
			cerr << "Error: unknown stem direction: " << recordInfo[0] << endl;
			return 0;
	}
	return output;
}



//////////////////////////////
//
// MuseRecord::stemDirectionQ --
//

int MuseRecord::stemDirectionQ(void) {
	int output = 0;
	string recordInfo = getStemDirectionField();
	if (recordInfo[0] == ' ') {
		output = 0;
	} else {
		output = 1;
	}
	return output;
}



//////////////////////////////
//
// MuseRecord::getStaffField  -- returns column 24.
//

string MuseRecord::getStaffField(void) {
	allowNotesOnly("getStaffField");
	if (getLength() < 24) {
		return " ";
	} else {
		string temp;
		temp += getColumn(24);
		return temp;
	}
}



//////////////////////////////
//
// MuseRecord::getStaffString --
//

string MuseRecord::getStaffString(void) {
	string output = getStaffField();
	if (output[0] == ' ') {
		output = "";
	}
	return output;
}



//////////////////////////////
//
// MuseRecord::getStaff --
//

int MuseRecord::getStaff(void) {
	int output = 1;
	string recordInfo = getStaffField();
	if (recordInfo[0] == ' ') {
		output = 1;
	} else {
		output = (int)strtol(recordInfo.c_str(), NULL, 36);
	}
	return output;
}



//////////////////////////////
//
// MuseRecord::staffQ --
//

int MuseRecord::staffQ(void) {
	int output = 0;
	string recordInfo = getStaffField();
	if (recordInfo[0] == ' ') {
		output = 0;
	} else {
		output = 1;
	}
	return output;
}



//////////////////////////////
//
// MuseRecord::getBeamField --
//

string MuseRecord::getBeamField(void) {
	allowNotesOnly("getBeamField");
	if (getLength() < 26) {
		return "      ";
	} else {
		return extract(26, 31);
	}
}



//////////////////////////////
//
// MuseRecord::setBeamInfo --
//

void MuseRecord::setBeamInfo(string& strang) {
	setColumns(strang, 26, 31);
}



//////////////////////////////
//
// MuseRecord::beamQ --
//

int MuseRecord::beamQ(void) {
	int output = 0;
	allowNotesOnly("beamQ");
	if (getLength() < 26) {
		output = 0;
	} else {
		for (int i=26; i<=31; i++) {
			if (getColumn(i) != ' ') {
				output = 1;
				break;
			}
		}
	}
	return output;
}



//////////////////////////////
//
// MuseRecord::getBeam8 -- column 26
//

char MuseRecord::getBeam8(void) {
	allowNotesOnly("getBeam8");
	return getColumn(26);
}



//////////////////////////////
//
// MuseRecord::getBeam16 -- column 27
//

char MuseRecord::getBeam16(void) {
	allowNotesOnly("getBeam16");
	return getColumn(27);
}



//////////////////////////////
//
// MuseRecord::getBeam32 -- column 28
//

char MuseRecord::getBeam32(void) {
	allowNotesOnly("getBeam32");
	return getColumn(28);
}



//////////////////////////////
//
// MuseRecord::getBeam64 -- column 29
//

char MuseRecord::getBeam64(void) {
	allowNotesOnly("getBeam64");
	return getColumn(29);
}



//////////////////////////////
//
// MuseRecord::getBeam128 -- column 30
//

char MuseRecord::getBeam128(void) {
	allowNotesOnly("getBeam128");
	return getColumn(30);
}



//////////////////////////////
//
// MuseRecord::getBeam256 -- column 31
//

char MuseRecord::getBeam256(void) {
	allowNotesOnly("getBeam256");
	return getColumn(31);
}



//////////////////////////////
//
// MuseRecord::beam8Q --
//

int MuseRecord::beam8Q(void) {
	int output = 0;
	if (getBeam8() == ' ') {
		output = 0;
	} else {
		output = 1;
	}
	return output;
}



//////////////////////////////
//
// MuseRecord::beam16Q --
//

int MuseRecord::beam16Q(void) {
	int output = 0;
	if (getBeam16() == ' ') {
		output = 0;
	} else {
		output = 1;
	}
	return output;
}



//////////////////////////////
//
// MuseRecord::beam32Q --
//

int MuseRecord::beam32Q(void) {
	int output = 0;
	if (getBeam32() == ' ') {
		output = 0;
	} else {
		output = 1;
	}
	return output;
}



//////////////////////////////
//
// MuseRecord::beam64Q --
//

int MuseRecord::beam64Q(void) {
	int output = 0;
	if (getBeam64() == ' ') {
		output = 0;
	} else {
		output = 1;
	}
	return output;
}



//////////////////////////////
//
// MuseRecord::beam128Q --
//

int MuseRecord::beam128Q(void) {
	int output = 0;
	if (getBeam128() == ' ') {
		output = 0;
	} else {
		output = 1;
	}
	return output;
}



//////////////////////////////
//
// MuseRecord::beam256Q --
//

int MuseRecord::beam256Q(void) {
	int output = 0;
	if (getBeam256() == ' ') {
		output = 0;
	} else {
		output = 1;
	}
	return output;
}



//////////////////////////////
//
// MuseRecord::getAdditionalNotationsField -- returns the contents
// 	of columns 32-43.
//

string MuseRecord::getAdditionalNotationsField(void) {
	allowNotesOnly("getAdditionalNotationsField");
	return extract(32, 43);
}



//////////////////////////////
//
// MuseRecord::additionalNotationsQ --
//

int MuseRecord::additionalNotationsQ(void) {
	int output = 0;
	if (getLength() < 32) {
		output = 0;
	} else {
		for (int i=32; i<=43; i++) {
			if (getColumn(i) != ' ') {
				output = 1;
				break;
			}
		}
	}
	return output;
}



//////////////////////////////
//
// MuseRecord::getAddCount -- returns the number of items
//	in the additional notations field
//

int MuseRecord::getAddCount(void) {
	string addString = getAdditionalNotationsField();
	string addElement;    // element from the notation field

	int count = 0;
	int index = 0;
	while (getAddElementIndex(index, addElement, addString)) {
		count++;
	}

	return count;
}



//////////////////////////////
//
// MuseRecord::getAddItem -- returns the specified item
//	in the additional notations field
//

string MuseRecord::getAddItem(int elementIndex) {
	string output;
	int count = 0;
	int index = 0;
	string addString = getAdditionalNotationsField();

	while (count <= elementIndex) {
		getAddElementIndex(index, output, addString);
		count++;
	}

	return output;
}



//////////////////////////////
//
// MuseRecord::getAddItemLevel -- returns the specified item's
//	editorial level in the additional notations field
//

int MuseRecord::getAddItemLevel(int elementIndex) {
	int count = 0;
	int index = 0;
	string number;
	string addString = getAdditionalNotationsField();
	string elementString; // element field

	while (count < elementIndex) {
		getAddElementIndex(index, elementString, addString);
		count++;
	}

	int output = -1;
repeating:
	while (addString[index] != '&' && index >= 0) {
		index--;
	}
	if (addString[index] == '&' && !isalnum(addString[index+1])) {
		index--;
		goto repeating;
	} else if (addString[index] == '&') {
		number = addString[index+1];
		output = (int)strtol(number.c_str(), NULL, 36);
	}

	return output;
}



//////////////////////////////
//
// MuseRecord::getEditorialLevels -- returns a string containing the
//	edit levels given in the additional notation fields
//

string MuseRecord::getEditorialLevels(void) {
	string output;
	string addString = getAdditionalNotationsField();
	for (int index = 0; index < 12-1; index++) {
		if (addString[index] == '&' && isalnum(addString[index+1])) {
			output += addString[index+1];
		}
	}
	return output;
}



//////////////////////////////
//
// MuseRecord::addEditorialLevelQ -- returns true if there are any editorial
//	levels present in the additional notations fields
//

int MuseRecord::addEditorialLevelQ(void) {
	string addString = getAdditionalNotationsField();
	int output = 0;
	for (int i=0; i<12-1; i++) {   // minus one for width 2 (&0)
		if (addString[i] == '&' && isalnum(addString[i+1])) {
			output = 1;
		}
	}
	return output;
}



//////////////////////////////
//
// MuseRecord::findField -- returns true when it finds the first
//	instance of the key in the additional fields record.
//

int MuseRecord::findField(const string& key) {
	int len = (int)key.size();
	string notations = getAdditionalNotationsField();
	int output = 0;
	for (int i=0; i<12-len; i++) {
		if (notations[i] == key[0]) {
			output = 1;
			for (int j=0; j<len; j++) {
				if (notations[i] != key[j]) {
					output = 0;
					goto endofloop;
				}
			}
		}

		if (output == 1) {
			break;
		}
endofloop:
	;
	}

	return output;
}



//////////////////////////////
//
// MuseRecord::findField --
//

int MuseRecord::findField(char key, int mincol, int maxcol) {
	int start = mincol;
	int stop = getLength() - 1;

	if (start > stop) {
		return -1;
	}

	if (maxcol < stop) {
		stop = maxcol;
	}

	int i;
	for (i=start; i<=stop; i++) {
		if (m_recordString[i-1] == key) {
			return i;   // return the column which is offset from 1
		}
	}

	return -1;
}



//////////////////////////////
//
// MuseRecord::getSlurParameterRegion --
//

string MuseRecord::getSlurParameterRegion(void) {
	return getColumns(31, 43);
}



//////////////////////////////
//
// MuseRecord::getSlurStartColumn -- search column 32 to 43 for a slur
//    marker.  Returns the first one found from left to right.
//    returns -1 if a slur character was not found.
//

int MuseRecord::getSlurStartColumn(void) {
	int start = 31;
	int stop = getLength() - 1;
	if (stop >= 43) {
		stop = 42;
	}
	int i;
	for (i=start; i<=stop; i++) {
		switch (m_recordString[i]) {
			case '(':   // slur level 1
			case '[':   // slur level 2
			case '{':   // slur level 3
			case 'z':   // slur level 4
				return i+1;  // column is offset from 1
		}
	}

	return -1;
}



//////////////////////////////
//
// MuseRecord::getTextUnderlayField -- returns the contents
// 	of columns 44-80.
//

string MuseRecord::getTextUnderlayField(void) {
	allowNotesOnly("getTextUnderlayField");
	return extract(44, 80);
}



//////////////////////////////
//
// MuseRecord::textUnderlayQ --
//

int MuseRecord::textUnderlayQ(void) {
	int output = 0;
	if (getLength() < 44) {
		output = 0;
	} else {
		for (int i=44; i<=80; i++) {
			if (getColumn(i) != ' ') {
				output = 1;
				break;
			}
		}
	}
	return output;
}



//////////////////////////////
//
// MuseRecord::getVerseCount --
//

int MuseRecord::getVerseCount(void) {
	if (!textUnderlayQ()) {
		return 0;
	}

	int count = 1;
	for (int i=44; i<=getLength() && i <= 80; i++) {
		if (getColumn(i) == '|') {
			count++;
		}
	}

	return count;
}



//////////////////////////////
//
// MuseRecord::getVerse --
//

string MuseRecord::getVerse(int index) {
	string output;
	if (!textUnderlayQ()) {
		return output;
	}
	int verseCount = getVerseCount();
	if (index >= verseCount) {
		return output;
	}

	int tindex = 44;
	int c = 0;
	while (c < index && tindex < 80) {
		if (getColumn(tindex) == '|') {
			c++;
		}
		tindex++;
	}

	while (tindex <= 80 && getColumn(tindex) != '|') {
		output += getColumn(tindex++);
	}

	// remove trailing spaces
	int zindex = (int)output.size() - 1;
	while (output[zindex] == ' ') {
		zindex--;
	}
	zindex++;
	output.resize(zindex);

	// remove leading spaces
	int spacecount = 0;
	while (output[spacecount] == ' ') {
		spacecount++;
	}

	// problem here?
	for (int rr = 0; rr <= zindex-spacecount; rr++) {
		output[rr] = output[rr+spacecount];
	}

	return output;
}



//////////////////////////////
//
// MuseRecord::getVerseUtf8 --
//

string MuseRecord::getVerseUtf8(int index) {
	string tverse = getVerse(index);
	return MuseRecord::musedataToUtf8(tverse);
}



//////////////////////////////
//
// MuseRecord::getSlurInfo --
//
//   ( ) = regular slur
//   [ ] = second levels slur, convert to &( and &)
//   { } = third level slur, convert to &&( and &&)
//   Z   = fourth level slur (how to close?)
//

void MuseRecord::getSlurInfo(string& slurstarts, string& slurends) {
	slurstarts.clear();
	slurends.clear();

	string data = getSlurParameterRegion();
	for (int i=0; i<(int)data.size(); i++) {
		if (data[i] == '(') {
			slurstarts += '(';
		} else if (data[i] == ')') {
			slurends += ')';
		} else if (data[i] == '[') {
			slurstarts += "&{";
		} else if (data[i] == ']') {
			slurends += "&)";
		} else if (data[i] == '{') {
			slurstarts += "&&(";
		} else if (data[i] == '}') {
			slurends += "&&)";
		}
	}
}




//////////////////////////////
//
// MuseRecord::MuseRecord --
//

MuseRecord::MuseRecord(void) : MuseRecordBasic() { }
MuseRecord::MuseRecord(const string& aLine) : MuseRecordBasic(aLine) { }
MuseRecord::MuseRecord(MuseRecord& aRecord) : MuseRecordBasic(aRecord) { }



//////////////////////////////
//
// MuseRecord::~MuseRecord --
//

MuseRecord::~MuseRecord() {
	// do nothing
}



//////////////////////////////
//
// MuseRecord::operator= --
//

MuseRecord& MuseRecord::operator=(MuseRecord& aRecord) {
	// don't copy onto self
	if (&aRecord == this) {
		return *this;
	}

	setLine(aRecord.getLine());
	setType(aRecord.getType());
	m_lineindex = aRecord.m_lineindex;

	m_qstamp       = aRecord.m_qstamp;
	m_lineduration = aRecord.m_lineduration;
	m_noteduration = aRecord.m_noteduration;

	m_b40pitch     = aRecord.m_b40pitch;
	m_nexttiednote = aRecord.m_nexttiednote;
	m_lasttiednote = aRecord.m_lasttiednote;

	return *this;
}



///////////////////////////////////////////////////////////////////////////
//
// protected functions
//


//////////////////////////////
//
// MuseRecord::allowFigurationOnly --
//

void MuseRecord::allowFigurationOnly(const string& functionName) {
	switch (getType()) {
		case E_muserec_figured_harmony:
		  break;
		default:
			cerr << "Error: can only access " << functionName
				  << " on a figuration record.  Line is: " << getLine() << endl;
			return;
	}
}



//////////////////////////////
//
// MuseRecord::allowFigurationAndNotesOnly --
//

void MuseRecord::allowFigurationAndNotesOnly(const string& functionName) {
	switch (getType()) {
		case E_muserec_figured_harmony:
		case E_muserec_note_regular:
		case E_muserec_note_chord:
		case E_muserec_note_grace:
		case E_muserec_note_cue:
		  break;
		default:
			cerr << "Error: can only access " << functionName
				  << " on a figuration record.  Line is: " << getLine() << endl;
			return;
	}
}



//////////////////////////////
//
// MuseRecord::allowMeasuresOnly --
//

void MuseRecord::allowMeasuresOnly(const string& functionName) {
	switch (getType()) {
		case E_muserec_measure:
		  break;
		default:
			cerr << "Error: can only access " << functionName
				  << " on a measure record.  Line is: " << getLine() << endl;
			return;
	}
}


//////////////////////////////
//
// MuseRecord::allDirectionsOnly --
//

void MuseRecord::allowDirectionsOnly(const std::string& functionName) {
	switch (getType()) {
		case E_muserec_musical_directions:
			break;
		default:
			cerr << "Error: can only access " << functionName
				  << " on a musical direction record.  Line is: " << getLine() << endl;
			return;
	}
}



//////////////////////////////
//
// MuseRecord::allowNotesOnly --
//

void MuseRecord::allowNotesOnly(const string& functionName) {
	switch (getType()) {
		case E_muserec_note_regular:
		case E_muserec_note_chord:
		case E_muserec_note_grace:
		case E_muserec_note_cue:
		  break;
		default:
			cerr << "Error: can only access " << functionName
				  << " on a note record.  Line is: " << getLine() << endl;
			return;
	}
}



//////////////////////////////
//
// MuseRecord::allowNotesAndRestsOnly --
//

void MuseRecord::allowNotesAndRestsOnly(const string& functionName) {
	switch (getType()) {
		case E_muserec_note_regular:
		case E_muserec_note_chord:
		case E_muserec_note_grace:
		case E_muserec_note_cue:
		case E_muserec_rest:
		case E_muserec_rest_invisible:
		  break;
		default:
			cerr << "Error: can only access " << functionName
				  << " on a note or rest records.  Line is: " << getLine() << endl;
			return;
	}
}



//////////////////////////////
//
// MuseRecord::getAddElementIndex -- get the first element pointed
//	to by the specified index in the additional notations field.
//	returns 0 if no element was found, or 1 if an element was found.
//

int MuseRecord::getAddElementIndex(int& index, string& output, const string& input) {
	int finished = 0;
	int count = 0;
	output.clear();

	while (!finished) {
		switch (input[index]) {
			case '&':                     // editorial level marker
				// there is exactly one character following an editorial
				// marker.  neither the '&' nor the following character
				// is counted if the following character is in the set
				// [0..9, A..Z, a..z]
				index++;
				if (isalnum(input[index])) {
					index++;
				} else {
					// count '&' as an element
					count++;
					output += '&';
				}
				break;

			case 'p': case 'f':          // piano and forte
				// any sequence of 'p' and 'f' is considered one element
				count++;
				output += input[index++];
				while (input[index] == 'p' || input[index] == 'f') {
					output += input[index++];
				}
				break;

			case 'Z':                    // sfz, or Zp = sfp
				// elements starting with 'Z':
				//    Z      = sfz
				//    Zp     = sfp
				count++;
				output += input[index++];
				if (input[index] == 'p') {
					output += input[index++];
				}
				break;

			case 'm':                      // mezzo
				// a mezzo marking MUST be followed by a 'p' or an 'f'.
				count++;
				output += input[index++];
				if (input[index] == 'p' || input[index] == 'f') {
					output += input[index++];
				} else {
				  cout << "Error at \'m\' in notation field: " << input << endl;
				  return 0;
				}
				break;

			case 'S':                     // arpeggiation
				// elements starting with 'S':
				//   S     = arpeggiate (up)
				//   Sd    = arpeggiate down)
				count++;
				output += input[index++];
				if (input[index] == 'd') {
					output += input[index++];
				}
				break;

			case '1':                     // fingering
			case '2':                     // fingering
			case '3':                     // fingering
			case '4':                     // fingering
			case '5':                     // fingering
			// case ':':                  // finger substitution
				// keep track of finger substitutions
				count++;
				output += input[index++];
				if (input[index] == ':') {
					output += input[index++];
					output += input[index++];
				}
				break;

			//////////////////////////////
			// Ornaments
			//
			case 't':                     // trill (tr.)
			case 'r':                     // turn
			case 'k':                     // delayed turn
			case 'w':                     // shake
			case '~':                     // trill wavy line extension
			case 'c':                     // continued wavy line
			case 'M':                     // mordent
			case 'j':                     // slide (Schleifer)
			  // ornaments can be modified by accidentals:
			  //    s     = sharp
			  //    ss    = double sharp
			  //    f     = flat
			  //    ff    = double flat
			  //    h     = natural
			  //    u     = next accidental is under the ornament
			  // any combination of these characters following a
			  // ornament is considered one element.
			  //
			  count++;
			  index++;
			  while (input[index] == 's' || input[index] == 'f' ||
						input[index] == 'h' || input[index] == 'u') {
				  output += input[index++];
			  }
			  break;

			//////////////////////////////////////////////////////////////
			// The following chars are uniquely SINGLE letter items:    //
			//                                                          //
			//                                                          //
			case '-':                     // tie                        //
			case '(':                     // open  slur #1              //
			case ')':                     // close slur #1              //
			case '[':                     // open  slur #2              //
			case ']':                     // close slur #2              //
			case '{':                     // open  slur #3              //
			case '}':                     // close slur #3              //
			case 'z':                     // open  slur #4              //
			case 'x':                     // close slur #4              //
			case '*':                     // start written tuplet       //
			case '!':                     // end written tuplet         //
			case 'v':                     // up bow                     //
			case 'n':                     // down bow                   //
			case 'o':                     // harmonic                   //
			case 'O':                     // open string                //
			case 'Q':                     // thumb position             //
			case 'A':                     // accent (^)                 //
			case 'V':                     // accent (v)                 //
			case '>':                     // accent (>)                 //
			case '.':                     // staccatto                  //
			case '_':                     // tenuto                     //
			case '=':                     // detached tenuto            //
			case 'i':                     // spiccato                   //
			case '\'':                    // breath mark                //
			case 'F':                     // upright fermata            //
			case 'E':                     // inverted fermata           //
			case 'R':                     // rfz                        //
			case '^':                     // editorial accidental       //
			case '+':                     // cautionary accidental      //
				count++;                                                 //
				output += input[index++];                                //
				break;                                                   //
			//                                                          //
			//                                                          //
			//////////////////////////////////////////////////////////////
			case ' ':
				// ignore blank spaces and continue counting elements
				index++;
				break;
			default:
				cout << "Error: unknown additional notation: "
					  << input[index] << endl;
				return 0;
		}
		if (count != 0 || index >= 12) {
			finished = 1;
		}
	} // end of while (!finished) loop

	return count;
}



////////////////////
//
// MuseRecord::zerase -- removes specified number of characters from
// 	the beginning of the string.
//

void MuseRecord::zerase(string& inout, int num) {
	int len = (int)inout.size();
	if (num >= len) {
		inout = "";
	} else {
		for (int i=num; i<=len; i++) {
			inout[i-num] = inout[i];
		}
	}
	inout.resize(inout.size() - num);
}



/////////////////////////////////////////
//
// MuseRecord::getDirectionTypeString -- columns 17 and 18.
//    A = segno sign
//    B = right-justified text
//    C = center-justified text
//    D = left-justified text
//    E = dynamics hairpin start
//    F = dynamics hairpin end
//    G = letter dynamics (text given starting in column 25)
//    H = begin dashes (after words)
//    J = end dashes
//    P = pedal start
//    Q = pedal stop
//    R = rehearsal number or letter
//    U = octave up start
//    V = octave down start
//    W = octave stop
//

std::string MuseRecord::getDirectionTypeField(void) {
	allowDirectionsOnly("getDirectionType");
	return extract(17, 18);
}



//////////////////////////////
//
// MuseRecord::getDirectionTypeString -- Same as the field version, but
//    trailing spaces are removed (not leading ones, at least for now).
//

std::string MuseRecord::getDirectionTypeString(void) {
	string output = getDirectionTypeField();
	if (output.back() == ' ') {
		output.resize(output.size() - 1);
	}
	if (output.back() == ' ') {
		output.resize(output.size() - 1);
	}
	return output;
}



//////////////////////////////
//
// MuseRecord::isTextDirection -- Text is stored starting at column 25.
//    B = right justified
//    C = center justified
//    D = left justified
//

bool MuseRecord::isTextDirection(void) {
	string typefield = getDirectionTypeField();
	if (typefield.find('B') != string::npos) {
		return true;
	}
	if (typefield.find('C') != string::npos) {
		return true;
	}
	if (typefield.find('D') != string::npos) {
		return true;
	}
	return false;
}



//////////////////////////////
//
// MuseRecord::isHairpin --
//

bool MuseRecord::isHairpin(void) {
	string typefield = getDirectionTypeField();
	if (isHairpinStart()) {
		return true;
	}
	if (isHairpinStop()) {
		return true;
	}
	return false;
}



//////////////////////////////
//
// MuseRecord::isHairpinStart --
//

bool MuseRecord::isHairpinStart(void) {
	string typefield = getDirectionTypeField();
	if (typefield.find('E') != string::npos) {
		return true;
	}
	return false;
}



//////////////////////////////
//
// MuseRecord::isHairpinStop --
//

bool MuseRecord::isHairpinStop(void) {
	string typefield = getDirectionTypeField();
	if (typefield.find('F') != string::npos) {
		return true;
	}
	return false;
}



//////////////////////////////
//
// MuseRecord::isDashStart --
//

bool MuseRecord::isDashStart(void) {
	string typefield = getDirectionTypeField();
	if (typefield.find('H') != string::npos) {
		return true;
	}
	return false;
}



//////////////////////////////
//
// MuseRecord::isDashStop --
//

bool MuseRecord::isDashStop(void) {
	string typefield = getDirectionTypeField();
	if (typefield.find('J') != string::npos) {
		return true;
	}
	return false;
}



//////////////////////////////
//
// MuseRecord::isPedalStart --
//

bool MuseRecord::isPedalStart(void) {
	string typefield = getDirectionTypeField();
	if (typefield.find('P') != string::npos) {
		return true;
	}
	return false;
}



//////////////////////////////
//
// MuseRecord::isPedalEnd --
//

bool MuseRecord::isPedalEnd(void) {
	string typefield = getDirectionTypeField();
	if (typefield.find('Q') != string::npos) {
		return true;
	}
	return false;
}



//////////////////////////////
//
// MuseRecord::isRehearsal --
//

bool MuseRecord::isRehearsal(void) {
	string typefield = getDirectionTypeField();
	if (typefield.find('R') != string::npos) {
		return true;
	}
	return false;
}



//////////////////////////////
//
// MuseRecord::isOctiveUpStart --
//

bool MuseRecord::isOctaveUpStart(void) {
	string typefield = getDirectionTypeField();
	if (typefield.find('U') != string::npos) {
		return true;
	}
	return false;
}



//////////////////////////////
//
// MuseRecord::isOctaveDownStart --
//

bool MuseRecord::isOctaveDownStart(void) {
	string typefield = getDirectionTypeField();
	if (typefield.find('V') != string::npos) {
		return true;
	}
	return false;
}



//////////////////////////////
//
// MuseRecord::isOctaveStop --
//

bool MuseRecord::isOctaveStop(void) {
	string typefield = getDirectionTypeField();
	if (typefield.find('W') != string::npos) {
		return true;
	}
	return false;
}



//////////////////////////////
//
// MuseRecord::getDirectionText -- Return the text starting in column 25.
//

std::string MuseRecord::getDirectionText(void) {
	int length = (int)m_recordString.size();
	if (length < 25) {
		// no text
		return "";
	}
	return trimSpaces(m_recordString.substr(24));
}



//////////////////////////////
//
// MuseRecord::hasPrintSuggestions --
//

bool MuseRecord::hasPrintSuggestions(void) {
	MuseData* md = getOwner();
	if (md == NULL) {
		return false;
	}
	if (m_lineindex < 0) {
		return false;
	}
	if (m_lineindex >= md->getLineCount() - 1) {
		return false;
	}
	if (md->getRecord(m_lineindex).isPrintSuggestion()) {
		return true;
	} else {
		return false;
	}
}



//////////////////////////////
//
// MuseRecord::getPrintSuggestions -- Return any print suggestions
//     for the given column number
//

void MuseRecord::getPrintSuggestions(vector<string>& suggestions, int column) {
	suggestions.clear();

	MuseData* md = getOwner();
	if (md == NULL) {
		return;
	}
	if (m_lineindex < 0) {
		return;
	}
	if (m_lineindex >= md->getLineCount() - 1) {
		return;
	}
	if (!md->getRecord(m_lineindex+1).isPrintSuggestion()) {
		return;
	}

	string pline = md->getLine(m_lineindex+1);
	HumRegex hre;
	vector<string> entries;
	hre.split(entries, pline, "\\s+");
	for (int i=0; i<(int)entries.size(); i++) {
		if (entries[i][0] != 'C') {
			continue;
		}
		if (hre.search(entries[i], "C(\\d+):([^\\s]+)")) {
			int value = hre.getMatchInt(1);
			if (value == column) {
				suggestions.push_back(hre.getMatch(2));
			}
		}
	}
}



//////////////////////////////
//
// MuseRecord::getAllPrintSuggestions -- Return all print suggestions.
//

void MuseRecord::getAllPrintSuggestions(vector<string>& suggestions) {
	suggestions.clear();

	MuseData* md = getOwner();
	if (md == NULL) {
		return;
	}
	if (m_lineindex < 0) {
		return;
	}
	if (m_lineindex >= md->getLineCount() - 1) {
		return;
	}
	if (!md->getRecord(m_lineindex+1).isPrintSuggestion()) {
		return;
	}

	string pline = md->getLine(m_lineindex+1);
	HumRegex hre;
	vector<string> entries;
	hre.split(entries, pline, " ");
	for (int i=0; i<(int)entries.size(); i++) {
		if (entries[i][0] != 'C') {
			continue;
		}
		if (hre.search(entries[i], "C(\\d+):([^\\s]+)")) {
			suggestions.push_back(entries[i]);
		}
	}
}





//////////////////////////////
//
// MuseRecordBasic::isPartName --
//

bool MuseRecordBasic::isPartName(void) {
	return m_type == E_muserec_header_part_name;
}



//////////////////////////////
//
// MuseRecordBasic::isAttributes --
//

bool MuseRecordBasic::isAttributes(void) {
	return m_type == E_muserec_musical_attributes;
}



//////////////////////////////
//
// MuseRecordBasic::isSource --
//

bool MuseRecordBasic::isSource(void) {
	return m_type == E_muserec_source;
}



//////////////////////////////
//
// MuseRecordBasic::isEncoder --
//

bool MuseRecordBasic::isEncoder(void) {
	return m_type == E_muserec_encoder;
}



//////////////////////////////
//
// MuseRecordBasic::isId --
//

bool MuseRecordBasic::isId(void) {
	return m_type == E_muserec_id;
}



//////////////////////////////
//
// MuseRecordBasic::isBarline --
//

bool MuseRecordBasic::isBarline(void) {
	return m_type == E_muserec_measure;
}



//////////////////////////////
//
// MuseRecordBasic::isBackup --
//

bool MuseRecordBasic::isBackup(void) {
	return m_type == E_muserec_back;
}



//////////////////////////////
//
// MuseRecordBasic::isAnyComment --
//

bool MuseRecordBasic::isAnyComment(void) {
	return isLineComment() || isBlockComment();
}



//////////////////////////////
//
// MuseRecordBasic::isLineComment --
//

bool MuseRecordBasic::isLineComment(void) {
	return m_type == E_muserec_comment_line;
}



//////////////////////////////
//
// MuseRecordBasic::isBlockComment --
//

bool MuseRecordBasic::isBlockComment(void) {
	return m_type == E_muserec_comment_toggle;
}



//////////////////////////////
//
// MuseRecordBasic::isChordNote -- Is a regular note that is a seoncdary
//    note in a chord (not the first note in the chord).
//

bool MuseRecordBasic::isChordNote(void) {
	return m_type == E_muserec_note_chord;
}



//////////////////////////////
//
// MuseRecordBasic::isDirection -- Is a musical direction (text)
//     instruction.
//

bool MuseRecordBasic::isDirection(void) {
	return m_type == E_muserec_musical_directions;
}



//////////////////////////////
//
// MuseRecordBasic::isMusicalDirection -- Is a musical direction (text)
//     instruction.
//

bool MuseRecordBasic::isMusicalDirection(void) {
	return isDirection();
}



//////////////////////////////
//
// MuseRecordBasic::isGraceNote -- A grace note, either a single note or
//     the first note in a gracenote chord.
//

bool MuseRecordBasic::isGraceNote(void) {
	return m_type == E_muserec_note_grace;
}



//////////////////////////////
//
// MuseRecordBasic::isCueNote --
//

bool MuseRecordBasic::isCueNote(void) {
	return m_type == E_muserec_note_cue;
}



//////////////////////////////
//
// MuseRecordBasic::isChordNote --
//

bool MuseRecordBasic::isChordGraceNote(void) {
	return m_type == E_muserec_note_grace_chord;
}



//////////////////////////////
//
// MuseRecordBasic::isFiguredHarmony --
//

bool MuseRecordBasic::isFiguredHarmony(void) {
	return m_type == E_muserec_figured_harmony;
}



//////////////////////////////
//
// MuseRecordBasic::isPrintSuggestion --
//

bool MuseRecordBasic::isPrintSuggestion(void) {
	switch (m_type) {
		case E_muserec_print_suggestion:
			return true;
	}
	return false;
}



//////////////////////////////
//
// MuseRecordBasic::isRegularNote --
//

bool MuseRecordBasic::isRegularNote(void) {
	switch (m_type) {
		case E_muserec_note_regular:
			return true;
	}
	return false;
}


//////////////////////////////
//
// MuseRecordBasic::isAnyNote --
//

bool MuseRecordBasic::isAnyNote(void) {
	switch (m_type) {
		case E_muserec_note_regular:
		case E_muserec_note_chord:
		case E_muserec_note_cue:
		case E_muserec_note_grace:
		case E_muserec_note_grace_chord:
			return true;
	}
	return false;
}



//////////////////////////////
//
// MuseRecordBasic::isAnyNoteOrRest --
//

bool MuseRecordBasic::isAnyNoteOrRest(void) {
	switch (m_type) {
		case E_muserec_note_regular:
		case E_muserec_note_chord:
		case E_muserec_note_cue:
		case E_muserec_note_grace:
		case E_muserec_note_grace_chord:
		case E_muserec_rest_invisible:
		case E_muserec_rest:
			return true;
	}
	return false;
}



//////////////////////////////
//
// MuseRecordBasic::isInvisibleRest --
//

bool MuseRecordBasic::isInvisibleRest(void) {
	switch (m_type) {
		case E_muserec_rest_invisible:
			return true;
	}
	return false;
}



//////////////////////////////
//
// MuseRecordBasic::isRegularRest --
//

bool MuseRecordBasic::isRegularRest(void) {
	switch (m_type) {
		case E_muserec_rest:
			return true;
	}
	return false;
}



//////////////////////////////
//
// MuseRecordBasic::isAnyRest -- Also cue-sized rests?
//

bool MuseRecordBasic::isAnyRest(void) {
	switch (m_type) {
		case E_muserec_rest_invisible:
		case E_muserec_rest:
			return true;
	}
	return false;
}



//////////////////////////////
//
// MuseRecordBasic::isCopyright --
//

bool MuseRecordBasic::isCopyright(void) {
	switch (m_type) {
		case E_muserec_copyright:
			return true;
	}
	return false;
}



//////////////////////////////
//
// MuseRecordBasic::isWorkInfo --
//

bool MuseRecordBasic::isWorkInfo(void) {
	switch (m_type) {
		case E_muserec_work_info:
			return true;
	}
	return false;
}



//////////////////////////////
//
// MuseRecordBasic::isWorkTitle --
//

bool MuseRecordBasic::isWorkTitle(void) {
	switch (m_type) {
		case E_muserec_work_title:
			return true;
	}
	return false;
}



//////////////////////////////
//
// MuseRecordBasic::isMovementTitle --
//

bool MuseRecordBasic::isMovementTitle(void) {
	switch (m_type) {
		case E_muserec_movement_title:
			return true;
	}
	return false;
}



//////////////////////////////
//
// MuseRecordBasic::isGroup --
//

bool MuseRecordBasic::isGroup(void) {
	switch (m_type) {
		case E_muserec_group:
			return true;
	}
	return false;
}



//////////////////////////////
//
// MuseRecordBasic::isGroupMembership --
//

bool MuseRecordBasic::isGroupMembership(void) {
	switch (m_type) {
		case E_muserec_group_memberships:
			return true;
	}
	return false;
}



//////////////////////////////
//
// MuseRecordBasic::isHeaderRecord -- True if a header, or a comment
//   occurring before the first non-header record.
//

bool MuseRecordBasic::isHeaderRecord(void) {
	return m_header > 0;
}



//////////////////////////////
//
// MuseRecordBasic::isBodyRecord -- True if not a header record.
//

bool MuseRecordBasic::isBodyRecord(void) {
	return m_header == 0;
}




//////////////////////////////
//
// MuseRecord::addPrintSuggestion -- add a delta index for associated
//     print suggestion.
//

void MuseRecord::addPrintSuggestion(int deltaIndex) {
	m_printSuggestions.push_back(deltaIndex);
}






//////////////////////////////
//
// MuseRecordBasic::MuseRecordBasic --
//

MuseRecordBasic::MuseRecordBasic(void) {
	m_recordString.reserve(81);
	setType(E_muserec_unknown);
	m_owner        = NULL;
	m_lineindex    =   -1;
	m_qstamp      =    0;
	m_lineduration =    0;
	m_noteduration =    0;
	m_b40pitch     = -100;
	m_nexttiednote =   -1;
	m_lasttiednote =   -1;
	m_roundBreve   =    0;
	m_header       =   -1;
	m_layer        =    0;
}


// default value: index = -1;
MuseRecordBasic::MuseRecordBasic(const string& aLine, int index) {
	m_recordString.reserve(81);
	setLine(aLine);
	setType(E_muserec_unknown);
	m_lineindex = index;
	m_owner        = NULL;
	m_qstamp      =    0;
	m_lineduration =    0;
	m_noteduration =    0;
	m_b40pitch     = -100;
	m_nexttiednote =   -1;
	m_lasttiednote =   -1;
	m_roundBreve   =    0;
	m_header       =   -1;
	m_layer        =    0;
}


MuseRecordBasic::MuseRecordBasic(MuseRecordBasic& aRecord) {
	*this = aRecord;
}



//////////////////////////////
//
// MuseRecordBasic::~MuseRecordBasic --
//

MuseRecordBasic::~MuseRecordBasic() {
	m_recordString.resize(0);
	m_owner        = NULL;
	m_lineindex    =   -1;
	m_qstamp      =    0;
	m_lineduration =    0;
	m_noteduration =    0;
	m_b40pitch     = -100;
	m_nexttiednote =   -1;
	m_lasttiednote =   -1;
	m_roundBreve   =    0;
	m_layer        =    0;
}



//////////////////////////////
//
// MuseRecordBasic::clear -- remove content of record.
//

void MuseRecordBasic::clear(void) {
	m_recordString.clear();
	m_owner        = NULL;
	m_qstamp      =    0;
	m_lineindex    =   -1;
	m_lineduration =    0;
	m_noteduration =    0;
	m_b40pitch     = -100;
	m_nexttiednote =   -1;
	m_lasttiednote =   -1;
	m_roundBreve   =    0;
	m_header       =   -1;
	m_layer        =    0;
}



/////////////////////////////
//
// MuseRecordBasic::isEmpty -- returns true if only spaces on line, ignoring
//     non-printable characters (which may no longer be necessary).
//

int MuseRecordBasic::isEmpty(void) {
	for (int i=0; i<(int)m_recordString.size(); i++) {
		if (!std::isprint(m_recordString[i])) {
			continue;
		}
		if (!std::isspace(m_recordString[i])) {
			return 0;
		}
	}
	return 1;
}



//////////////////////////////
//
// MuseRecordBasic::extract -- extracts the character columns from the
//	storage string.  Appends a null character to the end of the
//	copied string.
//

string MuseRecordBasic::extract(int start, int end) {
	string output;
	int count = end - start + 1;
	for (int i=0; i<count; i++) {
		if (i+start <= getLength()) {
			output += getColumn(i+start);
		} else {
			output += ' ';
		}
	}
	return output;
}



//////////////////////////////
//
// MuseRecordBasic::getColumn -- same as operator[] but with an
//	offset of 1 rather than 0.
//

char& MuseRecordBasic::getColumn(int columnNumber) {
	int realindex = columnNumber - 1;
	int length = (int)m_recordString.size();
	// originally the limit for data columns was 80:
	// if (realindex < 0 || realindex >= 80) {
	// the new limit is somewhere above 900, but limit to 1024
	if (realindex < 0 || realindex >= 1024) {
		cerr << "Error trying to access column: " << columnNumber  << endl;
		cerr << "CURRENT DATA: ===============================" << endl;
		cerr << (*this);
		static char x = ' ';
		return x;
	} else if (realindex >= (int)m_recordString.size()) {
		m_recordString.resize(realindex+1);
		for (int i=length; i<=realindex; i++) {
			m_recordString[i] = ' ';
		}
	}
	return m_recordString[realindex];
}



//////////////////////////////
//
// MuseRecordBasic::getColumns --
//

string MuseRecordBasic::getColumns(int startcol, int endcol) {
	string output;
	int charcount = endcol - startcol + 1;
	if (charcount <= 0) {
		return output;
	}
	for (int i=startcol; i<=endcol; i++) {
		output += getColumn(i);
	}
	return output;
}



//////////////////////////////
//
// MuseRecordBasic::setColumns --
//

void MuseRecordBasic::setColumns(string& data, int startcol, int endcol) {
	if (startcol > endcol) {
		int temp = startcol;
		startcol = endcol;
		endcol = temp;
	}

	int dsize = (int)data.size();
	getColumn(endcol) = ' '; // allocate space if not already done
	int i;
	int ii;
	for (i=startcol; i<=endcol; i++) {
		ii = i - startcol;
		if (ii < dsize) {
			getColumn(i) = data[ii];
		} else {
			break;
		}
	}

}



//////////////////////////////
//
// MuseRecordBasic::getLength -- returns the size of the
//	character string being stored.  A number between
//	0 and 80.
//

int MuseRecordBasic::getLength(void) const {
	return (int)m_recordString.size();
}



//////////////////////////////
//
// MuseRecordBasic::getLine -- returns a pointer to data record
//

string MuseRecordBasic::getLine(void) {
	return m_recordString;
}



//////////////////////////////
//
// MuseRecordBasic::getType -- returns the type of the record.
//

int MuseRecordBasic::getType(void) const {
	return m_type;
}



//////////////////////////////
//
// MuseRecordBasic::operator=
//

MuseRecordBasic& MuseRecordBasic::operator=(MuseRecordBasic& aRecord) {
	// don't copy onto self
	if (&aRecord == this) {
		return *this;
	}

	setLine(aRecord.getLine());
	setType(aRecord.getType());
	m_lineindex = aRecord.m_lineindex;

	m_qstamp = aRecord.m_qstamp;
	m_lineduration = aRecord.m_lineduration;
	m_noteduration = aRecord.m_noteduration;

	m_b40pitch     = aRecord.m_b40pitch;
	m_nexttiednote = aRecord.m_nexttiednote;
	m_lasttiednote = aRecord.m_lasttiednote;

	return *this;
}


MuseRecordBasic& MuseRecordBasic::operator=(MuseRecordBasic* aRecord) {
	*this = *aRecord;
	return *this;
}


MuseRecordBasic& MuseRecordBasic::operator=(const string& aLine) {
	setLine(aLine);
	setType(aLine[0]);

	m_lineindex    =   -1;
	m_qstamp      =    0;
	m_lineduration =    0;
	m_noteduration =    0;
	m_b40pitch     = -100;
	m_nexttiednote =   -1;
	m_lasttiednote =   -1;

	return *this;
}



//////////////////////////////
//
// MuseRecordBasic::operator[] -- character array offset from 0.
//

char& MuseRecordBasic::operator[](int index) {
	return getColumn(index+1);
}



//////////////////////////////
//
// MuseRecordBasic::setLine -- sets the record to a (new) string
//

void MuseRecordBasic::setLine(const string& aLine) {
	m_recordString = aLine;
	// Line lengths should not exceed 80 characters according
	// to MuseData standard, so maybe have a warning or error if exceeded.
}



//////////////////////////////
//
// MuseRecordBasic::setType -- sets the type of the record
//

void MuseRecordBasic::setType(int aType) {
	m_type = aType;
}



//////////////////////////////
//
// MuseRecordBasic::setTypeGraceNote -- put a "g" in the first column.
//    shift pitch information over if it exists?  Maybe later.
//    Currently will destroy any pitch information.
//

void MuseRecordBasic::setTypeGraceNote(void) {
	setType(E_muserec_note_grace);
	(*this)[0] = 'g';
}



//////////////////////////////
//
// MuseRecordBasic::setTypeGraceChordNote -- put a "g" in the first column,
//    and a space in the second column.  Shift pitch information over if
//    it exists?  Maybe later.  Currently will destroy any pitch information.
//

void MuseRecordBasic::setTypeGraceChordNote(void) {
	setType(E_muserec_note_grace_chord);
	(*this)[0] = 'g';
	(*this)[1] = ' ';
}



//////////////////////////////
//
// MuseRecordBasic::shrink -- removes trailing spaces in a MuseData record
//

void MuseRecordBasic::shrink(void) {
	int i = (int)m_recordString.size() - 1;
	while (i >= 0 && m_recordString[i] == ' ') {
		m_recordString.resize((int)m_recordString.size()-1);
		i--;
	}
}



//////////////////////////////
//
// MuseRecordBasic::insertString --
//

void MuseRecordBasic::insertString(int column, const string& strang) {
	int len = (int)strang.size();
	if (len == 0) {
		return;
	}
	int index = column - 1;
	// make sure that record has text data up to the end of sring in
	// final location by preallocating the end location of string:
	(*this)[index+len-1] = ' ';
	int i;
	for (i=0; i<len; i++) {
		(*this)[i+index] = strang[i];
	}
}



//////////////////////////////
//
// MuseRecordBasic::insertStringRight -- Insert string right-justified
//    starting at given index.
//

void MuseRecordBasic::insertStringRight(int column, const string& strang) {
	int len = (int)strang.size();
	int index = column - 1;
	// make sure that record has text data up to the end of sring in
	// final location by preallocating the end location of string:
	(*this)[index] = ' ';
	int i;
	int ii;
	for (i=0; i<len; i++) {
		ii = index - i;
		if (ii < 0) {
			break;
		}
		(*this)[ii] = strang[len-i-1];
	}
}



//////////////////////////////
//
// MuseRecordBasic::appendString -- add a string to the end of the current
//     data in the record.
//

void MuseRecordBasic::appendString(const string& astring) {
	insertString(getLength()+1, astring);
}



//////////////////////////////
//
// MuseRecordBasic::appendInteger -- Insert an integer after the last character
//     in the current line.
//

void MuseRecordBasic::appendInteger(int value) {
	string buffer = to_string(value);
	insertString(getLength()+1, buffer);
}



//////////////////////////////
//
// MuseRecordBasic::appendRational -- Insert a rational after the last character
//     in the current line.
//

void MuseRecordBasic::appendRational(HumNum& value) {
	stringstream tout;
	value.printTwoPart(tout);
	tout << ends;
	insertString(getLength()+1, tout.str());
}



//////////////////////////////
//
// MuseRecordBasic::append -- append multiple objects in sequence
// from left to right onto the record.  The format contains
// characters with two possibilities at the moment:
//    "i": integer value
//    "s": string value
//

void MuseRecordBasic::append(const char* format, ...) {
	va_list valist;
	int     i;

	va_start(valist, format);

	union Format_t {
		int   i;
		char *s;
		int  *r;  // array of two integers for rational number
	} FormatData;

	HumNum rn;

	int len = (int)strlen(format);
	for (i=0; i<len; i++) {
		switch (format[i]) {   // Type to expect.
			case 'i':
				FormatData.i = va_arg(valist, int);
				appendInteger(FormatData.i);
				break;

			case 's':
				FormatData.s = va_arg(valist, char *);
				if (strlen(FormatData.s) > 0) {
					appendString(FormatData.s);
				}
				break;

			case 'r':
				 FormatData.r = va_arg(valist, int *);
				 rn.setValue(FormatData.r[0], FormatData.r[1]);
				 appendRational(rn);
				break;

			default:
				// don't put any character other than "i", "r" or "s"
				// in the format string
				break;
		}
	}

	va_end(valist);
}



//////////////////////////////
//
// MuseRecordBasic::setString --
//

void MuseRecordBasic::setString(string& astring) {
	m_recordString = astring;
}



//////////////////////////////
//
// MuseRecordBasic::setAbsBeat --
//


void MuseRecordBasic::setAbsBeat(HumNum value) {
	m_qstamp = value;
}


void MuseRecordBasic::setQStamp(HumNum value) {
	m_qstamp = value;
}


// default value botval = 1
void MuseRecordBasic::setAbsBeat(int topval, int botval) {
	m_qstamp.setValue(topval, botval);
}


void MuseRecordBasic::setQStamp(int topval, int botval) {
	m_qstamp.setValue(topval, botval);
}



//////////////////////////////
//
// MuseRecordBasic::getAbsBeat -- Quarter notes from the
//     start of the music.
//

HumNum MuseRecordBasic::getAbsBeat(void) {
	return m_qstamp;
}

HumNum MuseRecordBasic::getQStamp(void) {
	return m_qstamp;
}



//////////////////////////////
//
// MuseRecordBasic::setLineDuration -- set the duration of the line
//     in terms of quarter notes.
//

void MuseRecordBasic::setLineDuration(HumNum value) {
	m_lineduration = value;
}


// default value botval = 1
void MuseRecordBasic::setLineDuration(int topval, int botval) {
	m_lineduration.setValue(topval, botval);
}



//////////////////////////////
//
// MuseRecordBasic::getLineDuration -- set the duration of the line
//     in terms of quarter notes.
//

HumNum MuseRecordBasic::getLineDuration(void) {
	return m_lineduration;
}



//////////////////////////////
//
// MuseRecordBasic::setNoteDuration -- set the duration of the note
//     in terms of quarter notes.  If the line does not represent a note,
//     then the note duration should probably be 0...
//

void MuseRecordBasic::setNoteDuration(HumNum value) {
	m_noteduration = value;
}


// default value botval = 1
void MuseRecordBasic::setNoteDuration(int topval, int botval) {
	m_noteduration.setValue(topval, botval);
}



//////////////////////////////
//
// MuseRecordBasic::getNoteDuration -- get the duration of the note
//     in terms of quarter notes.  If the line does not represent a note,
//     then the note duration should probably be 0...
//

HumNum MuseRecordBasic::getNoteDuration(void) {
	return m_noteduration;
}



//////////////////////////////
//
// MuseRecordBasic::setLineIndex --
//

void MuseRecordBasic::setLineIndex(int index) {
	m_lineindex = index;
}



//////////////////////////////
//
// MuseRecordBasic::isTied -- True if the note is tied to a note
//    before or after it.
//  0 = no ties
//  1 = tied to previous note
//  2 = tied to future note
//  3 = tied to both previous and future note
//

int MuseRecordBasic::isTied(void) {
	int output = 0;
	if (getLastTiedNoteLineIndex() >= 0) {
		output += 1;
	}

	if (getNextTiedNoteLineIndex() >= 0) {
		output += 2;
	}

	return output;
}



//////////////////////////////
//
// MuseRecordBasic::getLastTiedNoteLineIndex --
//


int MuseRecordBasic::getLastTiedNoteLineIndex(void) {
	return m_lasttiednote;
}



//////////////////////////////
//
// MuseRecordBasic::getNextTiedNoteLineIndex --
//


int MuseRecordBasic::getNextTiedNoteLineIndex(void) {
	return m_nexttiednote;
}



//////////////////////////////
//
// MuseRecordBasic::hasTieGroupStart -- Return true if note is
//    at the start of a tied group.
//

int MuseRecordBasic::hasTieGroupStart(void) {
	if (getLastTiedNoteLineIndex() > 0) {
		// Note is in the middle of a tie group.
		return 0;
	}
	if (getNextTiedNoteLineIndex() > 0) {
		return 1;
	} else {
		// no tie on note.
		return 0;
	}
}



//////////////////////////////
//
// MuseRecordBasic::isNoteAttack -- Return true if note is
//    at the start of a tied group or is a regular note.
//

int MuseRecordBasic::isNoteAttack(void) {
	if (getLastTiedNoteLineIndex() > 0) {
		// Note is in the middle of a tie group.
		return 0;
	}
	if (getNextTiedNoteLineIndex() > 0) {
		return 1;
	} else {
		// no tie on note.
		return 1;
	}
}



//////////////////////////////
//
// MuseRecordBasic::setLastTiedNoteLineIndex --
//


void MuseRecordBasic::setLastTiedNoteLineIndex(int index) {
	m_lasttiednote = index;
}



//////////////////////////////
//
// MuseRecordBasic::setNextTiedNoteLineIndex --
//


void MuseRecordBasic::setNextTiedNoteLineIndex(int index) {
	m_nexttiednote = index;
}



//////////////////////////////
//
// MuseRecordBasic::setRoundedBreve -- set double whole notes rounded flag.
//

void MuseRecordBasic::setRoundedBreve(void) {
	m_roundBreve = 1;
}



//////////////////////////////
//
// MuseRecordBasic::setMarkupPitch -- set the base-40 pitch information
//   in the markup area.  Does not change the original pitch in
//   the text line of the data.
//

void MuseRecordBasic::setMarkupPitch(int aPitch) {
	m_b40pitch = aPitch;
}



//////////////////////////////
//
// MuseRecordBasic::getMarkupPitch -- get the base-40 pitch information
//   in the markup area.  Does not look at the original pitch in
//   the text line of the data. A negative value is a rest (or invalid).
//

int MuseRecordBasic::getMarkupPitch(void) {
	return m_b40pitch;
}



//////////////////////////////
//
// MuseRecordBasic::cleanLineEnding -- remove spaces at the end of the
//    line;
//

void MuseRecordBasic::cleanLineEnding(void) {
	int i = (int)m_recordString.size() - 1;
	// Don't remove first space on line.
	while ((i > 0) && (m_recordString[i] == ' ')) {
		m_recordString.resize((int)m_recordString.size() - 1);
		i = (int)m_recordString.size() - 1;
	}
}



//////////////////////////////
//
// MuseRecordBasic::trimSpaces --
//

string MuseRecordBasic::trimSpaces(std::string input) {
	string output;
	int status = 0;
	for (int i=0; i<(int)input.size(); i++) {
		if (!status) {
			if (isspace(input[i])) {
				continue;
			}
			status = 1;
		}
		output += input[i];
	}
	for (int i=(int)output.size()-1; i>=0; i--) {
		if (isspace(output[i])) {
			output.resize((int)output.size() - 1);
		} else {
			break;
		}
	}
	return output;
}



//////////////////////////////
//
// MuseRecordBasic::setHeaderState -- 1 = in header, 0 = in body, -1 = undefined.
//    Access with isHeaderRecord() and isBodyRecord().
//

void MuseRecordBasic::setHeaderState(int state) {
	if (state > 0) {
		m_header = 1;
	} else if (state < 0) {
		m_header = -1;
	} else {
		m_header = 0;
	}
}



//////////////////////////////
//
// MuseRecordBasic::setLayer -- Set the layer for the record.
//    This information is taken from the track parameter
//    of records, but may be inferred from its position in
//    relation to backup commands.  Zero means implicit layer 1.
//

void MuseRecordBasic::setLayer(int layer) {
	if (layer < 0) {
		m_layer = 0;
	} else {
		m_layer = layer;
	}
}



//////////////////////////////
//
// MuseRecordBasic::getLayer -- Get the layer for the record.
//    This information is taken from the track parameter
//    of records, but may be inferred from its position in
//    relation to backup commands.  Zero means implicit layer 1.
//

int MuseRecordBasic::getLayer(void) {
	return m_layer;
}



//////////////////////////////
//
// MuseRecordBasic:hasTpq --
//

bool MuseRecordBasic::hasTpq(void) {
	if (m_tpq) {
		return true;
	} else {
		return false;
	}
}



//////////////////////////////
//
// MuseRecordBasic:getTpq --
//

int MuseRecordBasic::getTpq(void) {
	return m_tpq;
}



//////////////////////////////
//
// MuseRecordBasic:setTpq --
//

void MuseRecordBasic::setTpq(int value) {
	if (value <= 0) {
		m_tpq = 0;
	} else {
		m_tpq = value;
	}
}



//////////////////////////////
//
// MuseRecordBasic:setVoice --
//

void MuseRecordBasic::setVoice(GridVoice* voice) {
	m_voice = voice;
}



//////////////////////////////
//
// MuseRecordBasic:getVoice --
//

GridVoice* MuseRecordBasic::getVoice(void) {
	return m_voice;
}



//////////////////////////////
//
// MuseRecordBasic:LayoutVis -- Return the graphical display of the
//    rhythm for the note/rest if it was different than the logical version.
//    This is temporary storage for inserting a layout command into the
//    MuseData-to-Humdrum converter (tool-musedata2hum.cpp).
//

std::string MuseRecordBasic::getLayoutVis(void) {
	return m_graphicrecip;
}



//////////////////////////////
//
// MuseRecordBasic::musedataToUtf8 --
//

string MuseRecordBasic::musedataToUtf8(string& input) {
	string output;
	int isize = (int)input.size();
	for (int i=0; i<isize; i++) {
		if (input[i] != '\\') {
			output += input[i];
			continue;
		}
		if (i+2 >= isize) {
			output += input[i];
			continue;
		}
		string st = input.substr(i+1, 2);

		// graves
		if (st == "A8") { output += (char)0xc3; output += (char)0x80; i+=2; continue; }
		if (st == "E8") { output += (char)0xc3; output += (char)0x88; i+=2; continue; }
		if (st == "I8") { output += (char)0xc3; output += (char)0x8c; i+=2; continue; }
		if (st == "O8") { output += (char)0xc3; output += (char)0x92; i+=2; continue; }
		if (st == "U8") { output += (char)0xc3; output += (char)0x99; i+=2; continue; }
		if (st == "a8") { output += (char)0xc3; output += (char)0xa0; i+=2; continue; }
		if (st == "e8") { output += (char)0xc3; output += (char)0xa8; i+=2; continue; }
		if (st == "i8") { output += (char)0xc3; output += (char)0xac; i+=2; continue; }
		if (st == "o8") { output += (char)0xc3; output += (char)0xb2; i+=2; continue; }
		if (st == "u8") { output += (char)0xc3; output += (char)0xb9; i+=2; continue; }

		// acutes
		if (st == "A7") { output += (char)0xc3; output += (char)0x81; i+=2; continue; }
		if (st == "E7") { output += (char)0xc3; output += (char)0x89; i+=2; continue; }
		if (st == "I7") { output += (char)0xc3; output += (char)0x8d; i+=2; continue; }
		if (st == "O7") { output += (char)0xc3; output += (char)0x93; i+=2; continue; }
		if (st == "U7") { output += (char)0xc3; output += (char)0x9a; i+=2; continue; }
		if (st == "a7") { output += (char)0xc3; output += (char)0xa1; i+=2; continue; }
		if (st == "e7") { output += (char)0xc3; output += (char)0xa9; i+=2; continue; }
		if (st == "i7") { output += (char)0xc3; output += (char)0xad; i+=2; continue; }
		if (st == "o7") { output += (char)0xc3; output += (char)0xb3; i+=2; continue; }
		if (st == "u7") { output += (char)0xc3; output += (char)0xba; i+=2; continue; }

		// umlauts
		if (st == "A3") { output += (char)0xc3; output += (char)0x84; i+=2; continue; }
		if (st == "E3") { output += (char)0xc3; output += (char)0x8b; i+=2; continue; }
		if (st == "I3") { output += (char)0xc3; output += (char)0x8f; i+=2; continue; }
		if (st == "O3") { output += (char)0xc3; output += (char)0x96; i+=2; continue; }
		if (st == "U3") { output += (char)0xc3; output += (char)0x9c; i+=2; continue; }
		if (st == "a3") { output += (char)0xc3; output += (char)0xa4; i+=2; continue; }
		if (st == "e3") { output += (char)0xc3; output += (char)0xab; i+=2; continue; }
		if (st == "i3") { output += (char)0xc3; output += (char)0xaf; i+=2; continue; }
		if (st == "o3") { output += (char)0xc3; output += (char)0xb6; i+=2; continue; }
		if (st == "u3") { output += (char)0xc3; output += (char)0xbc; i+=2; continue; }

		// other
		if (st == "s2") { output += (char)0xc3; output += (char)0x9f; i+=2; continue; }  // eszett

		// Older Musedata files reverse the number and letters:

		// graves
		if (st == "8A") { output += (char)0xc3; output += (char)0x80; i+=2; continue; }
		if (st == "8E") { output += (char)0xc3; output += (char)0x88; i+=2; continue; }
		if (st == "8I") { output += (char)0xc3; output += (char)0x8c; i+=2; continue; }
		if (st == "8O") { output += (char)0xc3; output += (char)0x92; i+=2; continue; }
		if (st == "8U") { output += (char)0xc3; output += (char)0x99; i+=2; continue; }
		if (st == "8a") { output += (char)0xc3; output += (char)0xa0; i+=2; continue; }
		if (st == "8e") { output += (char)0xc3; output += (char)0xa8; i+=2; continue; }
		if (st == "8i") { output += (char)0xc3; output += (char)0xac; i+=2; continue; }
		if (st == "8o") { output += (char)0xc3; output += (char)0xb2; i+=2; continue; }
		if (st == "8u") { output += (char)0xc3; output += (char)0xb9; i+=2; continue; }

		// acutes
		if (st == "7A") { output += (char)0xc3; output += (char)0x81; i+=2; continue; }
		if (st == "7E") { output += (char)0xc3; output += (char)0x89; i+=2; continue; }
		if (st == "7I") { output += (char)0xc3; output += (char)0x8d; i+=2; continue; }
		if (st == "7O") { output += (char)0xc3; output += (char)0x93; i+=2; continue; }
		if (st == "7U") { output += (char)0xc3; output += (char)0x9a; i+=2; continue; }
		if (st == "7a") { output += (char)0xc3; output += (char)0xa1; i+=2; continue; }
		if (st == "7e") { output += (char)0xc3; output += (char)0xa9; i+=2; continue; }
		if (st == "7i") { output += (char)0xc3; output += (char)0xad; i+=2; continue; }
		if (st == "7o") { output += (char)0xc3; output += (char)0xb3; i+=2; continue; }
		if (st == "7u") { output += (char)0xc3; output += (char)0xba; i+=2; continue; }

		// umlauts
		if (st == "3A") { output += (char)0xc3; output += (char)0x84; i+=2; continue; }
		if (st == "3E") { output += (char)0xc3; output += (char)0x8b; i+=2; continue; }
		if (st == "3I") { output += (char)0xc3; output += (char)0x8f; i+=2; continue; }
		if (st == "3O") { output += (char)0xc3; output += (char)0x96; i+=2; continue; }
		if (st == "3U") { output += (char)0xc3; output += (char)0x9c; i+=2; continue; }
		if (st == "3a") { output += (char)0xc3; output += (char)0xa4; i+=2; continue; }
		if (st == "3e") { output += (char)0xc3; output += (char)0xab; i+=2; continue; }
		if (st == "3i") { output += (char)0xc3; output += (char)0xaf; i+=2; continue; }
		if (st == "3o") { output += (char)0xc3; output += (char)0xb6; i+=2; continue; }
		if (st == "3u") { output += (char)0xc3; output += (char)0xbc; i+=2; continue; }

		// other
		if (st == "2s") { output += (char)0xc3; output += (char)0x9f; i+=2; continue; }  // eszett

	}

	return output;
}



//////////////////////////////
//
// MuseRecordBasic::setOwner --
//

void MuseRecordBasic::setOwner(MuseData* owner) {
	m_owner = owner;
}



//////////////////////////////
//
// MuseRecordBasic::getOwner --
//

MuseData* MuseRecordBasic::getOwner(void) {
	return m_owner;
}



///////////////////////////////////////////////////////////////////////////


//////////////////////////////
//
// operator<<
//

ostream& operator<<(ostream& out, MuseRecordBasic& aRecord) {
	aRecord.shrink();  // have to shrink automatically because
						    // muse2ps program chokes on line 9 of header
						    // if it has more than one space on a blank line.
	out << aRecord.getLine();
	return out;
}

ostream& operator<<(ostream& out, MuseRecordBasic* aRecord) {
	out << *aRecord;
	return out;
}



class MxmlMeasure;
class MxmlPart;

int MxmlEvent::m_counter = 0;

////////////////////////////////////////////////////////////////////////////


//////////////////////////////
//
// MxmlEvent::MxmlEvent -- Constructor.
//

MxmlEvent::MxmlEvent(MxmlMeasure* measure) {
	clear();
	m_owner = measure;
	m_sequence = m_counter++;
	m_stems = false;
}



//////////////////////////////
//
// MxmlEvent::~MxmlEvent -- Destructor.
//

MxmlEvent::~MxmlEvent() {
	clear();
}



//////////////////////////////
//
// MxmlEvent::clear -- Clear any previous contents of the object.
//

void MxmlEvent::clear(void) {
	m_starttime = m_duration = 0;
	m_modification = 1;
	m_eventtype = mevent_unknown;
	m_owner = NULL;
	m_linked = false;
	m_voice = -1;
	m_staff = 0;
	m_invisible = false;
	m_voiceindex = -1;
	m_sequence = -1;
	for (int i=0; i<(int)m_links.size(); i++) {
		delete m_links[i];
		m_links[i] = NULL;
	}
	m_links.resize(0);
}



///////////////////////////////
//
// MxmlEvent::enableStems --
//

void MxmlEvent::enableStems(void) {
	m_stems = true;
}



///////////////////////////////
//
// MxmlEvent::makeDummyRest --
//   default values:
//     staffindex = 0;
//     voiceindex = 0;
//

void MxmlEvent::makeDummyRest(MxmlMeasure* owner, HumNum starttime,
		HumNum duration, int staffindex, int voiceindex) {
	m_starttime = starttime;
	m_duration = duration;
	m_eventtype = mevent_forward;  // not a real rest (will be invisible)
	// m_node remains null
	// m_links remains empty
	m_linked = false;
	m_sequence = -m_counter;
	m_counter++;
	m_voice = 1;  // don't know what the original voice number is
	m_voiceindex = voiceindex;
	m_staff = staffindex + 1;
	m_maxstaff = m_staff;  // how is this used/set?
	//	m_hnode remains null
}



//////////////////////////////
//
// MxmlEvent::setStartTime -- Set the starting timestamp of the event
//    in terms of quater notes since the start of the music.
//

void MxmlEvent::setStartTime(HumNum value) {
	m_starttime = value;
}



//////////////////////////////
//
// MxmlEvent::setDuration -- Set the duration of the event in terms
//   of quarter note durations.
//

void MxmlEvent::setDuration(HumNum value) {
	m_duration = value;
}



//////////////////////////////
//
// MxmlEvent::getModification -- Get the tuplet scaling of the note's duration.
//

HumNum MxmlEvent::getModification(void) const {
	return m_modification;
}



//////////////////////////////
//
// MxmlEvent::setModification -- Set the tuplet scaling of the note's duration.
//

void MxmlEvent::setModification(HumNum value) {
	m_modification = value;
}



//////////////////////////////
//
// MxmlEvent::getStartTime -- Return the start time of the event in terms
//      of quarter notes since the start of the music.
//

HumNum MxmlEvent::getStartTime(void) const {
	return m_starttime;
}



//////////////////////////////
//
// MxmlEvent::getDuration -- Return the duration of the event in terms
//      of quarter note durations.
//

HumNum MxmlEvent::getDuration(void) const {
	return m_duration;
}



//////////////////////////////
//
// MxmlEvent::setOwner -- Indicate which measure the event belongs to.
//

void MxmlEvent::setOwner(MxmlMeasure* measure) {
	m_owner = measure;
}



//////////////////////////////
//
// MxmlEvent::getOwner -- Return the measure object that contains this
//     event.  If there is no owner, then returns NULL.
//

MxmlMeasure* MxmlEvent::getOwner(void) const {
	return m_owner;
}



//////////////////////////////
//
// MxmlEvent::reportVerseCountToOwner --
//

void MxmlEvent::reportVerseCountToOwner(int count) {
	if (!m_owner) {
		return;
	}
	m_owner->reportVerseCountToOwner(count);
}


void MxmlEvent::reportVerseCountToOwner(int staffindex, int count) {
	if (!m_owner) {
		return;
	}
	m_owner->reportVerseCountToOwner(staffindex, count);
}



//////////////////////////////
//
// MxmlEvent::reportDynamicToOwner -- inform the owner that there is a dynamic
//    that needs a spine to store it in.
//

void MxmlEvent::reportDynamicToOwner(void) {
	m_owner->reportDynamicToOwner();
}



//////////////////////////////
//
// MxmlEvent::reportFiguredBassToOwner -- inform the owner that there is a dynamic
//    that needs a spine to store it in.
//

void MxmlEvent::reportFiguredBassToOwner(void) {
	m_owner->reportFiguredBassToOwner();
}



//////////////////////////////
//
// MxmlEvent::reportCaesuraToOwner -- inform the owner that there is a caesura
//    that needs an RDF marker.
// default value: letter = "Z"
//

void MxmlEvent::reportCaesuraToOwner(const string& letter) const {
	m_owner->reportCaesuraToOwner(letter);
}



//////////////////////////////
//
// MxmlEvent::reportOrnamentToOwner --
//

void MxmlEvent::reportOrnamentToOwner(void) const {
	m_owner->reportOrnamentToOwner();
}



//////////////////////////////
//
// MxmlEvent::reportHarmonyCountToOwner --
//

void MxmlEvent::reportHarmonyCountToOwner(int count) {
	if (!m_owner) {
		return;
	}
	m_owner->reportHarmonyCountToOwner(count);
}



//////////////////////////////
//
// MxmlEvent::reportMeasureStyleToOwner --
//

void MxmlEvent::reportMeasureStyleToOwner (MeasureStyle style) {
	if (!m_owner) {
		return;
	}
	m_owner->receiveMeasureStyleFromChild(style);
}



//////////////////////////////
//
// MxmlEvent::reportEditorialAccidentalToOwner --
//

void MxmlEvent::reportEditorialAccidentalToOwner(void) {
	if (!m_owner) {
		return;
	}
	m_owner->receiveEditorialAccidentalFromChild();
}



//////////////////////////////
//
// MxmlEvent::getPartNumber --
//

int MxmlEvent::getPartNumber(void) const {
	if (!m_owner) {
		return 0;
	}
	return m_owner->getPartNumber();
}



//////////////////////////////
//
// MxmlEvent::getPartIndex --
//

int MxmlEvent::getPartIndex(void) const {
	if (!m_owner) {
		return 0;
	}
	return m_owner->getPartIndex();
}



//////////////////////////////
//
// MxmlEvent::getName --
//

const char* MxmlEvent::getName(void) const {
	return m_node.name();
}



//////////////////////////////
//
// MxmlEvent::setQTicks -- Set the number of ticks per quarter note.
//     Returns the number of times that the ticks has been set.
//     Returns 0 if the tick count is invalid.
//

int MxmlEvent::setQTicks(long value) {
	if (value <= 0) {
		return 0;
	}
	if (m_owner) {
		return m_owner->setQTicks(value);
	} else {
		return 0;
	}
}



//////////////////////////////
//
// MxmlEvent::getQTicks -- Get the number of ticks per quarter note.
//

long MxmlEvent::getQTicks(void) const {
	if (m_owner) {
		return m_owner->getQTicks();
	} else {
		return 0;
	}
}



//////////////////////////////
//
// MxmlEvent::getIntValue -- Convenience function for an XPath query,
//    where the child text of the element should be interpreted as
//    an integer.
//

long MxmlEvent::getIntValue(const char* query) const {
	const char* val = m_node.select_node(query).node().child_value();
	if (strcmp(val, "") == 0) {
		return 0;
	} else {
		return atoi(val);
	}
}



//////////////////////////////
//
// Mxmlvent::setDurationByTicks -- Given a <duration> element tick
//    count, set the duration by dividing by the current quarter-note
//    duration tick count (from a prevailing attribute setting for
//    <divisions>).
//

void MxmlEvent::setDurationByTicks(long value, xml_node el) {
	long ticks = getQTicks();
	if (ticks == 0) {
		setDuration(0);
		return;
	}

	if (isGrace()) {
		setDuration(0);
		return;
	}

	HumNum val = (int)value;
	val /= (int)ticks;

	HumNum modification;
	if (el) {
		HumNum checkval = getEmbeddedDuration(modification, el);
		if ((checkval == 0) && isRest()) {
			// This is a whole rest.
			// val = val
		} else if (checkval != val) {
			// cerr << "WARNING: True duration " << checkval << " does not match";
			// cerr << " tick duration (buggy data: " << val << ")" << endl;
			double difference = fabs(checkval.getFloat() - val.getFloat());
			if (difference < 0.1) {
				// only correct if the duration is small, since some programs
				// will mark rests such as half notes as whole notes (since they
				// are displayed as centered whole notes)
				val = checkval;
			}
		}
	}
	setDuration(val);
	setModification(modification);
}



//////////////////////////////
//
// MxmlEvent::hasChild -- True if the given XPath query resulting
//      element has a child node.
//

bool MxmlEvent::hasChild(const char* query) const {
	xpath_node result = m_node.select_node(query);
	return !result.node().empty();
}



//////////////////////////////
//
// MxmlEvent::attachToLast --
//

void MxmlEvent::attachToLastEvent(void) {
	if (!m_owner) {
		return;
	}
	m_owner->attachLastEventToPrevious();
}



//////////////////////////////
//
// MxmlEvent::link --  This function is used to link secondary
//   elements to a primary one.  Currently only used for chord notes.
//   The first note of a chord will be stored in event lists, and
//   secondary notes will be suppressed from the list and instead
//   accessed through the m_links structure.
//

void MxmlEvent::link(MxmlEvent* event) {
	m_links.push_back(event);
	event->setLinked();
}



//////////////////////////////
//
// MxmlEvent::setLinked -- Indicate that a note is a secondary
//     chord note.
//

void MxmlEvent::setLinked(void) {
	m_linked = true;
}



//////////////////////////////
//
// MxmlEvent::isLinked -- Returns true if the note is a secondary
//     chord note.
//

bool MxmlEvent::isLinked(void) const {
	return m_linked;
}



//////////////////////////////
//
// MxmlEvent::isRest --
//

bool MxmlEvent::isRest(void) {
	if (!m_node) {
		return false;
	}
	xml_node child = m_node.first_child();
	while (child) {
		if (nodeType(child, "rest")) {
			return true;
		}
		child = child.next_sibling();
	}
	return false;
}



//////////////////////////////
//
// MxmlEvent::isChord -- Returns true if the event is the primary note
//    in a chord.
//

bool MxmlEvent::isChord(void) const {
	if ((m_links.size() > 0) && nodeType(m_node, "note")) {
		return true;
	} else {
		return false;
	}
}



//////////////////////////////
//
// MxmlEvent::isGrace -- Returns true if the event is a grace note.
//

bool MxmlEvent::isGrace(void) {
	xml_node child = this->getNode();
	if (!nodeType(child, "note")) {
		return false;
	}
	child = child.first_child();
	while (child) {
		if (nodeType(child, "grace")) {
			return true;
		} else if (nodeType(child, "pitch")) {
			// grace element has to come before pitch
			return false;
		}
		child = child.next_sibling();
	}
	return false;
}



//////////////////////////////
//
// MxmlEvent::hasGraceSlash -- Returns true if the note is a grace note
//    with a slash.
//

bool MxmlEvent::hasGraceSlash(void) {
	xml_node child = this->getNode();
	if (!nodeType(child, "note")) {
		return false;
	}
	child = child.first_child();
	while (child) {
		if (nodeType(child, "grace")) {
			string slash = child.attribute("slash").value();
			if (slash == "yes") {
				return true;
			} else {
				return false;
			}
		} else if (nodeType(child, "pitch")) {
			// grace element has to come before pitch
			return false;
		}
		child = child.next_sibling();
	}
	return false;
}





//////////////////////////////
//
// MxmlEvent::hasSlurStart -- Returns 0 if no slur; otherwise, return the
//  number of slurs attached.  Currently ignoring slur@number.
//
//   number: used to keep track of overlapping slurs. (currently ignored).
//
//   direction: 0=unspecified, 1=positive curvature, -1=negative curvature.
//
//  <note>
//     <notations>
//         <slur type="start" orientation="under" number="1">
//         <slur type="start" orientation="over" number="2">
//
//  And also:
//
//  <note>
//     <notations>
//          <slur number="1" placement="above" type="start"/>
//          <slur number="s" placement="below" type="start"/>
//

int MxmlEvent::hasSlurStart(vector<int>& directions) {
	directions.clear();
	int output = 0;
	xml_node child = this->getNode();
	if (!nodeType(child, "note")) {
		return output;
	}
	child = child.first_child();
	while (child) {
		if (nodeType(child, "notations")) {
			xml_node grandchild = child.first_child();
			while (grandchild) {
				if (nodeType(grandchild, "slur")) {
					xml_attribute slurtype = grandchild.attribute("type");
					if (slurtype) {
						if (strcmp(slurtype.value(), "start") == 0) {
							output++;
						} else {
							grandchild = grandchild.next_sibling();
							continue;
						}
					}
					// for now ignore the slur numbers
					//string number = grandchild.attribute("number").value();
					//if (!number.empty()) {
					//	int num = stoi(number);
					//	if (num != 0) {
					//		xxx = num;
					//	}
					//}
					xml_attribute orientation = grandchild.attribute("orientation");
					int dir = 0;
					if (orientation) {
						if (strcmp(orientation.value(), "over") == 0) {
							dir = 1;
						} else if (strcmp(orientation.value(), "under") == 0) {
							dir = -1;
						}
					}
					xml_attribute placement = grandchild.attribute("placement");
					if (placement) {
						if (strcmp(placement.value(), "above") == 0) {
							dir = 1;
						} else if (strcmp(placement.value(), "below") == 0) {
							dir = -1;
						}
					}

					directions.push_back(dir);
				}
				grandchild = grandchild.next_sibling();
			}
		}
		child = child.next_sibling();
	}
	return output;
}



//////////////////////////////
//
// MxmlEvent::hasSlurStop -- Handles multiple stop on the same note now.
//
//  <note>
//     <notations>
//         <slur type="start" orientation="under" number="1">
//

int MxmlEvent::hasSlurStop(void) {
	int output = 0;
	xml_node child = this->getNode();
	if (!nodeType(child, "note")) {
		// maybe allow for other items such as rests?
		return output;
	}
	child = child.first_child();
	while (child) {
		if (nodeType(child, "notations")) {
			xml_node grandchild = child.first_child();
			while (grandchild) {
				if (nodeType(grandchild, "slur")) {
					xml_attribute slurtype = grandchild.attribute("type");
					if (slurtype) {
						if (strcmp(slurtype.value(), "stop") == 0) {
							output++;
							//string number = grandchild.attribute("number").value();
							//if (!number.empty()) {
							//	int num = stoi(number);
							//	if (num != 0) {
							//		return num;
							//	} else {
							//		return 1;
							//	}
							//}
						}
					}
				}
				grandchild = grandchild.next_sibling();
			}
		}
		child = child.next_sibling();
	}
	return output;
}



//////////////////////////////
//
// MxmlEvent::isFloating -- For a harmony or basso continuo item
//     which is not attached to a note onset.
//

bool MxmlEvent::isFloating(void) {
	xml_node empty = xml_node(NULL);
	if (m_node == empty && (m_hnode != empty)) {
		return true;
	} else {
		return false;
	}
}



//////////////////////////////
//
// MxmlEvent::getLinkedNotes --
//

vector<MxmlEvent*> MxmlEvent::getLinkedNotes(void) {
	return m_links;
}



//////////////////////////////
//
// MxmlEvent::print -- Useful for debugging.
//

ostream& MxmlEvent::print(ostream& out) {
	out << getStartTime() << "\t" << getDuration() << "\t" << m_node.name();
	if (isChord()) {
		out << "\tCHORD";
	}
	out << endl;
	return out;
}



//////////////////////////////
//
// MxmlEvent::getSequenceNumber -- Return the sequence number of the
//   event in the input data file.  Useful for sorting items which
//   occur at the same time.
//

int MxmlEvent::getSequenceNumber(void) const {
	return m_sequence;
}



//////////////////////////////
//
// MxmlEvent::getVoiceNumber -- Return the voice number of the event.
//

int MxmlEvent::getVoiceNumber(void) const {
	if (m_voice) {
		return m_voice;
	} else {
		return 1;
	}
}



//////////////////////////////
//
// MxmlEvent::setVoiceIndex --
//

void MxmlEvent::setVoiceIndex(int index) {
	m_voiceindex = index;
}



//////////////////////////////
//
// MxmlEvent::getVoiceIndex -- Return the voice number of the event.
//    But mod 4 which presumably sets the voice number on a staff.
//    This is not always true: "PrintMusic 2010 for Windows" may
//    use voice 2 for staff 2. In this case the voice index should
//    be calculated by %2 rather than %4.
//    default value: maxvoice = 4.
//
//    This function will replace with a query to MxmlPart
//    as to what the voice on a staff should be.
//

int MxmlEvent::getVoiceIndex(int maxvoice) const {
	if (m_voiceindex >= 0) {
		return m_voiceindex;
	}

	if (m_owner) {
		int voiceindex = m_owner->getVoiceIndex(m_voice);
		if (voiceindex >= 0) {
			return voiceindex;
		}
	}

	// the following case handles notes/rests which do not contain
	// a voice number.  Assume that this item should be placed
	// in the first voice.
	if (m_voiceindex < 0) {
		if (nodeType(m_node, "note")) {
			return 0;
		}
	}


	// don't know what the voice mapping is, so make one up:
	if (maxvoice < 1) {
		maxvoice = 4;
	}
	if (m_voice) {
		return (m_voice - 1) % maxvoice;
	} else {
		return 0;
	}
}



//////////////////////////////
//
// MxmlEvent::forceInvisible --
//

void MxmlEvent::forceInvisible(void) {
	m_invisible = true;
}



//////////////////////////////
//
// MxmlEvent::isInvisible --
//

bool MxmlEvent::isInvisible(void) {
	return m_invisible;
}



//////////////////////////////
//
// MxmlEvent::getStaffIndex --
//

int MxmlEvent::getStaffIndex(void) const {
	if (m_staff > 0) {
		vector<pair<int, int>> mapping = getOwner()->getOwner()->getVoiceMapping();
		if (getVoiceNumber() < (int)mapping.size()) {
			const auto& [mappingStaffIndex, mappingVoiceIndex] = mapping[getVoiceNumber()];
			if (m_staff - 1 != mappingStaffIndex) {
				return mappingStaffIndex;
			}
		}
		return m_staff - 1;
	}
	if (m_owner) {
		int staffindex = m_owner->getStaffIndex(m_voice);
		if (staffindex >= 0) {
			return staffindex;
		}
	}

	// don't know what the modified staff is, so give the original staff index:
	if (!m_staff) {
		return 0;
	} else {
		return m_staff - 1;
	}
}



//////////////////////////////
//
// MxmlEvent::getCrossStaffOffset --
//

int MxmlEvent::getCrossStaffOffset(void) const {
	if (m_staff > 0) {
		vector<pair<int, int>> mapping = getOwner()->getOwner()->getVoiceMapping();
		if (getVoiceNumber() < (int)mapping.size()) {
			const auto& [mappingStaffIndex, mappingVoiceIndex] = mapping[getVoiceNumber()];
			return m_staff - 1 - mappingStaffIndex;
		}
	}
	return 0;
}



//////////////////////////////
//
// MxmlEvent::setVoiceNumber --
//

void MxmlEvent::setVoiceNumber(int value) {
	m_voice = (short)value;
}



//////////////////////////////
//
// MxmlEvent::setStaffNumber --
//

void MxmlEvent::setStaffNumber(int value) {
	m_staff = (short)value;
}



//////////////////////////////
//
// MxmlEvent::getStaffNumber --
//

int MxmlEvent::getStaffNumber(void) const {
	if (!m_staff) {
		return 1;
	} else {
		return m_staff;
	}
}



//////////////////////////////
//
// MxmlEvent::getType --
//

measure_event_type MxmlEvent::getType(void) const {
	return m_eventtype;
}



//////////////////////////////
//
// MxmlEvent::parseEvent --
//

bool MxmlEvent::parseEvent(xpath_node el, HumNum starttime) {
	return parseEvent(el.node(), xml_node(NULL), starttime);
}


bool MxmlEvent::parseEvent(xml_node el, xml_node nextel, HumNum starttime) {
	m_node = el;

	bool floatingharmony = false;
	if (nodeType(m_node, "attributes")) {
		m_eventtype = mevent_attributes;
	} else if (nodeType(m_node, "backup")) {
		m_eventtype = mevent_backup;
	} else if (nodeType(m_node, "barline")) {
		m_eventtype = mevent_barline;
		setBarlineStyle(m_node);
	} else if (nodeType(m_node, "bookmark")) {
		m_eventtype = mevent_bookmark;
	} else if (nodeType(m_node, "direction")) {
		m_eventtype = mevent_direction;
	} else if (nodeType(m_node, "figured-bass")) {
		m_eventtype = mevent_figured_bass;
	} else if (nodeType(m_node, "forward")) {
		m_eventtype = mevent_forward;
		m_staff = -1; // set default staff if not supplied
		m_voice = -1; // set default staff if not supplied
	} else if (nodeType(m_node, "grouping")) {
		m_eventtype = mevent_grouping;
	} else if (nodeType(m_node, "harmony")) {
		m_eventtype = mevent_harmony;
		if (!nodeType(nextel, "note")) {
			// harmony is not attached to a note
			floatingharmony = true;
			m_staff = -1;
			m_voice = -1;
		}
	} else if (nodeType(m_node, "link")) {
		m_eventtype = mevent_link;
	} else if (nodeType(m_node, "note")) {
		m_eventtype = mevent_note;
		m_staff = 1; // set default staff if not supplied
		m_voice = -1; // set default staff if not supplied
	} else if (nodeType(m_node, "print")) {
		m_eventtype = mevent_print;
	} else if (nodeType(m_node, "sound")) {
		m_eventtype = mevent_sound;
	} else {
		m_eventtype = mevent_unknown;
	}

	int tempstaff    = 1;
	int tempvoice    = -1;
	int tempduration = 0;
	for (auto el = m_node.first_child(); el; el = el.next_sibling()) {
		if (nodeType(el, "staff")) {
			tempstaff = atoi(el.child_value());
		} else if (nodeType(el, "voice")) {
			tempvoice = atoi(el.child_value());
		} else if (nodeType(el, "duration")) {
			tempduration = atoi(el.child_value());
			// Duration must be set to 0 for figured bass.  But maybe need
			// duration to create line extensions.  Probably other elements
			// which are not notes should also have their durations set
			// to zero.
			if (nodeType(m_node, "figured-bass")) {
				tempduration = 0;
			}
		}
	}

	bool emptyvoice = false;
	if (!floatingharmony) {
		if (tempvoice < 0) {
			emptyvoice = true;
			if (nodeType(el, "note")) {
				this->setVoiceIndex(0);
			}
		}
	}

	if (m_eventtype == mevent_forward) {
		xml_node pel = el.previous_sibling();
		if (nodeType(pel, "harmony")) {
			// This is a spacer forward which is not in any voice/layer,
			// so invalidate is staff/voice to prevent it from being
			// converted to a rest.
			m_voice = -1;
			tempvoice = -1;
			m_staff = -1;
			tempstaff = -1;
		} else {
			// xml_node nel = el.next_sibling();
			// Need to check if the forward element should be interpreted
			// as an invisible rests.  Check to see if the previous and next
			// element are notes.  If so, then check their voice numbers and
			// if equal, then this forward element should be an invisible rest.
			// But this is true only if there is no other event happening
			// at the current position in the other voice(s) on the staff (or
			// perhaps include other staves on the system and/or part.

			// So this case might need to be addressed at a later stage when
			// the score is assembled, such as when adding null tokens, and a
			// null spot is located in the score.
		}
	}

	if (tempvoice >= 0) {
		m_voice = (short)tempvoice;
	}
	if (tempstaff > 0) {
		m_staff = (short)tempstaff;
	}
	if (!emptyvoice) {
   	reportStaffNumberToOwner(m_staff, m_voice);
	} else {
		// no voice child element, or not a note or rest.
	}
	HumNum timesigdur;
	HumNum difference;
	HumNum dur;
	MxmlMeasure* measure = getOwner();
	HumNum mst;
	if (measure) {
		mst = measure->getStartTime();
	}

	setStartTime(starttime);

	switch (m_eventtype) {
		case mevent_note:
			setDuration(0);
			if (hasChild("./chord")) {
				setDuration(0);
				attachToLastEvent();
			} else {
				setDurationByTicks(tempduration, el);
			}
			break;

		case mevent_forward:
			if (tempduration == 1) {
				// handle errors in SharpEye:
				long ticks = getQTicks();
				if ((double)tempduration / (double)ticks < 0.0001) {
					tempduration = 0;
					m_eventtype = mevent_unknown;
				}
			} else if (tempduration < 4) {
				// Warn about possible other errors:
				double fraction = (double)tempduration / getQTicks();
				if (fraction < 0.01) {
					cerr << "WARNING: FORWARD WITH A SMALL VALUE " << tempduration << endl;
				}
			}
			setDurationByTicks(tempduration);
			break;

		case mevent_backup:
			setDurationByTicks(-tempduration);
			dur = getDuration();
			difference = starttime - mst + dur;
			if (difference < 0) {
				// cerr << "Warning: backup before start of measure " << endl;
				setDuration(dur - difference);
			}
			break;

		case mevent_attributes:
			setQTicks(getIntValue("./divisions"));
			timesigdur = getTimeSigDur();
			if (timesigdur > 0) {
				reportTimeSigDurToOwner(timesigdur);
			}
			break;

		case mevent_figured_bass:
		case mevent_harmony:
		case mevent_barline:
		case mevent_bookmark:
		case mevent_grouping:
		case mevent_link:
		case mevent_direction:
		case mevent_print:
		case mevent_sound:
		case mevent_unknown:
			setDuration(tempduration);
			break;
		case mevent_float:
			// assigned later for floating harmony
			break;
	}

	if (floatingharmony) {
		m_hnode = el;
		m_eventtype = mevent_float;
		m_duration = 0;
		m_node = xml_node(NULL);
		m_voice = 1;
		m_voiceindex = 0;
	} else {
		// if the previous sibling was a <harmony>, then store
		// for later parsing.  May have to check even further back
		// until another note or barline was found.
		xml_node lastsib = el.previous_sibling();
		if (!lastsib) {
			return true;
		}
		if (nodeType(lastsib, "harmony")) {
			m_hnode = lastsib;
		}
	}

	return true;
}



//////////////////////////////
//
// MxmlEvent::getTimeSigDur -- extract the time signature duration
//     from an attributes element.  If there is no time signature
//     in the attributes list, then return 0.
//                <time>
//                    <beats>4</beats>
//                    <beat-type>4</beat-type>
//                </time>
//     Output duration is in units of quarter notes.
//

HumNum MxmlEvent::getTimeSigDur(void) {
	if (!nodeType(m_node, "attributes")) {
		return 0;
	}
	int beats = 0;
	int beattype = 4;
	xml_node child = m_node.first_child();
	while (child) {
		if (!nodeType(child, "time")) {
			child = child.next_sibling();
			continue;
		}
		xml_node grandchild = child.first_child();
		while (grandchild) {
			if (nodeType(grandchild, "beats")) {
				beats = atoi(grandchild.child_value());
			} else if (nodeType(grandchild, "beat-type")) {
				beattype = atoi(grandchild.child_value());
			}
			grandchild = grandchild.next_sibling();
		}
		break;
	}
	HumNum output = beats;
	output /= beattype;
	output *= 4; // convert to quarter note duration
	return output;
}



//////////////////////////////
//
// MxmlEvent::setBarlineStyle --
// "==" -> Final
//    <barline location="right">
//       <bar-style>light-heavy</bar-style>
//    </barline>
//
// ":|!" -> RepeatBackward
//    <barline location="right">
//       <bar-style>light-heavy</bar-style>
//       <repeat direction="backward"/>
//    </barline>
//
//  "!|:" -> RepeatForward
//    <barline location="left">
//        <repeat direction="forward"/>
//    </barline>
//

void MxmlEvent::setBarlineStyle(xml_node node) {
	xml_node child = node.first_child();
	int repeat = 0;
	string barstyle;
	while (child) {
		if (nodeType(child, "bar-style")) {
			barstyle = child.child_value();
		} else if (nodeType(child, "repeat")) {
			if (strcmp(child.attribute("direction").value(), "backward") == 0) {
				repeat = -1;
			} else if (strcmp(child.attribute("direction").value(),
					"forward") == 0) {
				repeat = +1;
			}
		}
		child = child.next_sibling();
	}

	if ((repeat == 0) && (barstyle == "light-light")) {
		reportMeasureStyleToOwner(MeasureStyle::Double);
	} else if ((repeat == 0) && (barstyle == "light-heavy")) {
		reportMeasureStyleToOwner(MeasureStyle::Final);
	} else if ((repeat == -1) && (barstyle == "light-heavy")) {
		reportMeasureStyleToOwner(MeasureStyle::RepeatBackward);
	} else if (repeat == +1) {
		reportMeasureStyleToOwner(MeasureStyle::RepeatForward);
	}
}



//////////////////////////////
//
// MxmlEvent::getRecip -- return **recip value for note/rest.
//   Units are whole notes.
//

string MxmlEvent::getRecip(void) const {
	HumNum dur = m_duration;
	dur /= 4;  // convert to whole-note units;
	int n = getDotCount();
	if (n > 0) {
		dur = dur * (1 << n) / ((1 << (n+1)) - 1);
	} else if (n < 0) {
		// calculate a dot count and adjust duration as needed
		if (dur.getNumerator() == 1) {
			// do nothing since it won't need dots
		} else {
			// otherwise check to three augmentation dots
			HumNum onedotdur = dur * (1 << 1) / ((1 << 2) - 1);
			if (onedotdur.getNumerator() == 1) {
				dur = onedotdur;
				n = 1;
			} else {
				HumNum twodotdur = dur * (1 << 2) / ((1 << 3) - 1);
				if (twodotdur.getNumerator() == 1) {
					dur = twodotdur;
					n = 2;
				} else {
					HumNum threedotdur = dur * (1 << 3) / ((1 << 4) - 1);
					if (threedotdur.getNumerator() == 1) {
						dur = threedotdur;
						n = 3;
					}
				}
			}
		}
	}
	stringstream ss;
	ss << dur.getDenominator();
	if (dur.getNumerator() != 1) {
		ss << "%" << dur.getNumerator();
	}
	for (int i=0; i<n; i++) {
		ss << ".";
	}
	return ss.str();
}



//////////////////////////////
//
// MxmlEvent::getKernPitch -- return **kern pitch of note/rest.
//

string MxmlEvent::getKernPitch(void) {
	bool rest = false;

	if (!m_node) {
		// this is for an interpreted whole-measure rest.  Needed
		// for multi-measure rests as generated by Sibelius.
		return "r";
	}

	xml_node child = m_node.first_child();

	string step;
	int alter  = 0;
	int octave = 4;
	bool explicitQ    = false;
	bool naturalQ     = false;
	bool editorialQ   = false;
	bool unpitchedQ   = false;
	// bool sharpQ       = false;
	// bool flatQ        = false;
	// bool doubleflatQ  = false;
	// bool doublesharpQ = false;

	if (nodeType(m_node, "forward")) {
		rest = true;
		forceInvisible();
	} else {
		while (child) {
			if (nodeType(child, "rest")) {
				rest = true;
				break;
			}

			if (nodeType(child, "pitch")) {
				xml_node grandchild = child.first_child();
				while (grandchild) {
					if (nodeType(grandchild, "step")) {
						step = grandchild.child_value();
					} else if (nodeType(grandchild, "alter")) {
						alter = atoi(grandchild.child_value());
					} else if (nodeType(grandchild, "octave")) {
						octave = atoi(grandchild.child_value());
					}
					grandchild = grandchild.next_sibling();
				}
			} else if (nodeType(child, "unpitched")) {
				unpitchedQ = true;
				xml_node grandchild = child.first_child();
				while (grandchild) {
					if (nodeType(grandchild, "display-step")) {
						step = grandchild.child_value();
					} else if (nodeType(grandchild, "alter")) {
						alter = atoi(grandchild.child_value());
					} else if (nodeType(grandchild, "display-octave")) {
						octave = atoi(grandchild.child_value());
					}
					grandchild = grandchild.next_sibling();
				}
			} else if (nodeType(child, "accidental")) {
				if (strcmp(child.child_value(), "natural") == 0) {
					naturalQ = true;
					explicitQ = true;
				} else if (strcmp(child.child_value(), "sharp") == 0) {
					// sharpQ = true;
					explicitQ = true;
				} else if (strcmp(child.child_value(), "flat") == 0) {
					// flatQ = true;
					explicitQ = true;
				} else if (strcmp(child.child_value(), "double-flat") == 0) {
					// doubleflatQ = true;
					explicitQ = true;
				} else if (strcmp(child.child_value(), "double-sharp") == 0) {
					// doublesharpQ = true;
					explicitQ = true;
				}
				string paren = child.attribute("parentheses").value();
				if (paren == "yes") {
					editorialQ = 1;
					reportEditorialAccidentalToOwner();
				}
				// Sibelius method of adding parenthese to notes:
    			//    <accidental cautionary="yes">natural</accidental>
				string caution = child.attribute("cautionary").value();
				if (caution == "yes") {
					editorialQ = 1;
					reportEditorialAccidentalToOwner();
				}
			}
			child = child.next_sibling();
		}
	}

	if (rest) {
		return "r";
	}

	int count = 1;
	char pc = 'X';
	if (step.size() > 0) {
		pc = step[0];
	}
	if (octave > 3) {
		pc = tolower(pc);
		count = octave - 3;
	} else {
		pc = toupper(pc);
		count = 4 - octave;
	}
	string output;
	if (unpitchedQ) {
		output += "R";
	}
	for (int i=0; i<count; i++) {
		output += pc;
	}

	if (alter > 0) {  // sharps
		for (int i=0; i<alter; i++) {
			output += '#';
		}
	} else if (alter < 0) { // flats
		for (int i=0; i>alter; i--) {
			output += '-';
		}
	}
	if (naturalQ) {
		output += 'n';
	} else if (explicitQ) {
		output += 'X';
	}

	if (editorialQ) {
		output += "i";
	}

	return output;
}



//////////////////////////////
//
// MxmlEvent::getPrefixNoteInfo --
//

string MxmlEvent::getPrefixNoteInfo(void) const {
	int tiestart = 0;
	int tiestop  = 0;
	// bool rest    = false;

	xml_node child = m_node.first_child();

	while (child) {
		if (nodeType(child, "rest")) {
			// rest = true;
		} else if (nodeType(child, "tie")) {
			xml_attribute tietype = child.attribute("type");
			if (tietype) {
				if (strcmp(tietype.value(), "start") == 0) {
					tiestart = 1;
				} else if (strcmp(tietype.value(), "stop") == 0) {
					tiestop = 1;
				}
			}
		}
		child = child.next_sibling();
	}

	stringstream ss;

	if (tiestart && !tiestop) {
		ss << "[";
	}

	return ss.str();
}



//////////////////////////////
//
// MxmlEvent::getPostfixNoteInfo --
//

string MxmlEvent::getPostfixNoteInfo(bool primarynote, const string& recip) const {
	int beamstarts   = 0;
	int beamends     = 0;
	//int beamconts    = 0;
	int hookbacks    = 0;
	int hookforwards = 0;
	int stem         = 0;
	int tiestart     = 0;
	int tiestop      = 0;

	bool unpitchedQ  = false;
	bool stemsQ      = m_stems;

	// bool rest = false;
	xml_node child = m_node.first_child();
	xml_node notations;

	while (child) {
		if (nodeType(child, "rest")) {
			// rest = true;
		} else if (strcmp(child.name(), "beam") == 0) {
			const char* beaminfo = child.child_value();
			if (strcmp(beaminfo, "begin") == 0) {
				beamstarts++;
			} else if (strcmp(beaminfo, "end") == 0) {
				beamends++;
			} else if (strcmp(beaminfo, "continue") == 0) {
				// beamconts++;
			} else if (strcmp(beaminfo, "forward hook") == 0) {
				hookforwards++;
			} else if (strcmp(beaminfo, "backward hook") == 0) {
				hookbacks++;
			}
		} else if (nodeType(child, "unpitched")) {
			unpitchedQ = true;
		} else if (nodeType(child, "stem")) {
			if (unpitchedQ || stemsQ || (getVoiceIndex() >= 2) || (getDuration() == 0)) {
				const char* stemdir = child.child_value();
				if (strcmp(stemdir, "up") == 0) {
					stem = 1;
				} else if (strcmp(stemdir, "down") == 0) {
					stem = -1;
				}
			}
		} else if (nodeType(child, "notations")) {
			notations = child;
		} else if (nodeType(child, "tie")) {
			xml_attribute tietype = child.attribute("type");
			if (tietype) {
				if (strcmp(tietype.value(), "start") == 0) {
					tiestart = 1;
				} else if (strcmp(tietype.value(), "stop") == 0) {
					tiestop = 1;
				}
			}
		}
		child = child.next_sibling();
	}

	stringstream ss;
	addNotations(ss, notations, beamstarts, recip);

	if (primarynote) {
		// only add these signifiers if this is the first
		// note in a chord.  This is mostly important for
		// beam descriptions, as there can be only one beam
		// for each chord in a **kern token.  stems are not
		// given since they are not needed for secondary
		// chord notes (but nothing bad will happen if they
		// are included on secondary notes.
		switch (stem) {
			case  1:	ss << '/'; break;
			case -1:	ss << '\\'; break;
		}
		int i;
		for (i=0; i<beamends; i++)     { ss << "J"; }
		for (i=0; i<hookbacks; i++)    { ss << "k"; }
		for (i=0; i<hookforwards; i++) { ss << "K"; }
		for (i=0; i<beamstarts; i++)   { ss << "L"; }
	}

	if (tiestart && tiestop) {
		ss << "_";
	} else if (tiestop) {
		ss << "]";
	}

	if (getCrossStaffOffset() > 0) {
		ss << "<";
	} else if (getCrossStaffOffset() < 0) {
		ss << ">";
	}

	return ss.str();
}



//////////////////////////////
//
// MxmlEvent::addNotations --
// see: http://www.music-cog.ohio-state.edu/Humdrum/representations/kern.html
//
// Others to add:
//   Turn
//   Inverted turn (Wagnerian turn)
//   TrillTurn (TR or tR).
//

void MxmlEvent::addNotations(stringstream& ss, xml_node notations,
		int beamstarts, const string& recip) const {
	if (!notations) {
		return;
	}

	xml_node child = notations.first_child();
	xml_node grandchild;

	bool staccato       = false;
	bool staccatissimo  = false;
	bool accent         = false;
	bool tenuto         = false;
	bool strongaccent   = false;
	bool fermata        = false;
	bool trill          = false;
	int  tremolo        = 0;
	bool fingered       = false;
	bool umordent       = false;
	bool lmordent       = false;
	bool upbow          = false;
	bool downbow        = false;
	bool harmonic       = false;
	bool breath         = false;
	bool caesura        = false;
	bool arpeggio       = false;

	while (child) {
		if (strcmp(child.name(), "articulations") == 0) {
			grandchild = child.first_child();
			while (grandchild) {
				if (strcmp(grandchild.name(), "staccato") == 0) {
					staccato = true;
				} else if (strcmp(grandchild.name(), "staccatissimo") == 0) {
					staccatissimo = true;
				} else if (strcmp(grandchild.name(), "spiccato") == 0) {
					staccatissimo = true;
				} else if (strcmp(grandchild.name(), "accent") == 0) {
					accent = true;
				} else if (strcmp(grandchild.name(), "tenuto") == 0) {
					tenuto = true;
				} else if (strcmp(grandchild.name(), "breath-mark") == 0) {
					breath = true;
				} else if (strcmp(grandchild.name(), "caesura") == 0) {
					caesura = true;
				} else if (strcmp(grandchild.name(), "strong-accent") == 0) {
					strongaccent = true;
				} else if (strcmp(grandchild.name(), "detached-legato") == 0) {
					tenuto = true;
					staccato = true;
				}
				grandchild = grandchild.next_sibling();
			}
		} else if (strcmp(child.name(), "technical") == 0) {
			// usermanuals.musicxml.com/MusicXML/Content/CT-MusicXML-technical.htm
			grandchild = child.first_child();
			while (grandchild) {
				if (strcmp(grandchild.name(), "up-bow") == 0) {
					upbow = true;
				} else if (strcmp(grandchild.name(), "down-bow") == 0) {
					downbow = true;
				} else if (strcmp(grandchild.name(), "harmonic") == 0) {
					// check of not an artificial harmonic
					xml_node artificial = grandchild.select_node("artificial").node();
					if (!artificial) {
						// natural harmonic
						harmonic = true;
					}
				}
				grandchild = grandchild.next_sibling();
			}
		} else if (strcmp(child.name(), "ornaments") == 0) {
			grandchild = child.first_child();
			while (grandchild) {

				if (strcmp(grandchild.name(), "trill-mark") == 0) {
					trill = true;
				}

            //  <ornaments>
            //     <tremolo type="single">2</tremolo>
            //  </ornaments>
				if (strcmp(grandchild.name(), "tremolo") == 0) {
					string ttype = grandchild.attribute("type").value();
					if (ttype == "start") {
						fingered = true;
					} else {
						fingered = false;
					}
					if (ttype != "stop") {
						string tstring = grandchild.child_value();
						tremolo = 1 << (stoi(tstring) + 2);
					}
				}

				// umordent
          	// <ornaments>
          	//   <inverted-mordent default-x="-4" default-y="-65" placement="below"/>
          	// </ornaments>
				if (strcmp(grandchild.name(), "inverted-mordent") == 0) {
					umordent = true;
				}
				if (strcmp(grandchild.name(), "mordent") == 0) {
					lmordent = true;
				}

				grandchild = grandchild.next_sibling();
			}
		} else if (strcmp(child.name(), "fermata") == 0) {
			fermata = true;
		} else if (strcmp(child.name(), "arpeggiate") == 0) {
			arpeggio = true;
		}

		child = child.next_sibling();
	}

	if (staccato)     { ss << "'";  }
	if (staccatissimo){ ss << "`";  }
	if (tenuto)       { ss << "~";  }
	if (accent)       { ss << "^";  }
	if (strongaccent) { ss << "^^"; }  // might be something else
	if (harmonic)     { ss << "o";  }
	if (trill) {
		ss << "t";
		// figure out whole-tone trills later via trillspell tool:
		reportOrnamentToOwner();
	}

	if (fermata)      { ss << ";";  }
	if (upbow)        { ss << "v";  }
	if (downbow)      { ss << "u";  }
	if (umordent) {
		ss << "m";
		// figure out whole-tone mordents later via trillspell tool:
		reportOrnamentToOwner();
	}
	if (lmordent) {
		ss << "w";
		// figure out whole-tone mordents later
		reportOrnamentToOwner();
	}
	if (breath)       { ss << ",";  }
	if (caesura)      {
		ss << "Z";
		reportCaesuraToOwner();
	}
	if (arpeggio)     { ss << ":";  }

	if (tremolo >= 8) {
		HumNum tvalue = tremolo;
		if (fingered) {
			if (beamstarts) {
				tvalue *= (1 << beamstarts);
			}
			tvalue *= m_modification;
			if (tvalue.isInteger()) {
				ss << "@@" << tvalue << "@@";
			} else {
				ss << "@@" << tvalue.getNumerator() << "%" << tvalue.getDenominator() << "@@";
			}
		} else {
			HumNum duration = Convert::recipToDurationNoDots(recip);
			if ((duration > 0) && (duration < 1)) {
				double dval = -log2(duration.getFloat());
				int twopow = int(dval);
				tvalue *= (1 << twopow);
			}
			tvalue *= m_modification;
			if (tvalue.isInteger()) {
				ss << "@" << tvalue << "@";
			} else {
				ss << "@" << tvalue.getNumerator() << "%" << tvalue.getDenominator() << "@";
			}
		}
	}
}



//////////////////////////////
//
// MxmlEvent::getNode --
//

xml_node MxmlEvent::getNode(void) {
	return m_node;
}



//////////////////////////////
//
// MxmlEvent::getElementName --
//

string MxmlEvent::getElementName(void) {
	if (m_node) {
		string name = m_node.name();
		return name;
	} else {
		return "NULL";
	}
}



//////////////////////////////
//
// MxmlEvent::getHNode -- Return <harmony> element.
//

xml_node MxmlEvent::getHNode(void) {
	return m_hnode;
}



///////////////////////////////////////////////////////////////////////////
//
// private functions --
//

//////////////////////////////
//
// MxmlEvent::reportStaffNumberToOwner --
//

void MxmlEvent::reportStaffNumberToOwner(int staffnum, int voicenum) {
	if (m_owner != NULL) {
		m_owner->receiveStaffNumberFromChild(staffnum, voicenum);
	}
}



//////////////////////////////
//
// MxmlEvent::reportTimeSigDurToOwner --
//

void MxmlEvent::reportTimeSigDurToOwner(HumNum duration) {
	if (m_owner != NULL) {
		m_owner->receiveTimeSigDurFromChild(duration);
	}
}



//////////////////////////////
//
//  MxmlEvent::getDotCount -- return the number of augmentation dots
//     which are children of the given event element.  Returns -1
//     if the dot count should be calculated for a duration (such as whole
//     measure rests).
//

int MxmlEvent::getDotCount(void) const {
	xml_node child = m_node.first_child();
	int output = 0;
	bool foundType = false;
	while (child) {
		if (nodeType(child, "type")) {
			foundType = true;
		}
		if (output && !nodeType(child, "dot")) {
			return output;
		}
		if (strcmp(child.name(), "dot") == 0) {
			output++;
		}
		child = child.next_sibling();
	}
	if (foundType) {
		return output;
	} else {
		return -1;
	}
}



//////////////////////////////
//
//  MxmlEvent::getRestPitch -- return the vertical position of a rest
//     as a kern pitch.
//   Example:
//    <note>
//       <rest>
//          <display-step>G</display-step>
//          <display-octave>4</display-octave>
//       </rest>
//       <duration>2</duration>
//       <voice>1</voice>
//       <type>quarter</type>
//    </note>
//

string MxmlEvent::getRestPitch(void) const {
	xpath_node rest = m_node.select_node("./rest");
	if (rest.node().empty()) {
		// not a rest, so no pitch information.
		return "";
	}
	xpath_node step = rest.node().select_node("./display-step");
	if (step.node().empty()) {
		// no vertical positioning information
	}
	string steptext = step.node().child_value();
	if (steptext.empty()) {
		return "";
	}
	xpath_node octave = rest.node().select_node("./display-octave");
	if (octave.node().empty()) {
		// not enough vertical positioning information
	}
	string octavetext = octave.node().child_value();
	if (octavetext.empty()) {
		return "";
	}

	int octaveval = stoi(octavetext);
	int count = 1;
	char pc = steptext[0];
	if (octaveval > 3) {
		pc = tolower(pc);
		count = octaveval - 3;
	} else {
		pc = toupper(pc);
		count = 4 - octaveval;
	}
	string output;
	for (int i=0; i<count; i++) {
		output += pc;
	}

	return output;
}



///////////////////////////////////////////////////////////////////////////
//
// static functions --
//

//////////////////////////////
//
// MxmlEvent::getEmbeddedDuration -- Given a <note>, return the
//   expeceded duration of the note, not from the <duration>, but
//   from a combination of <type> <dot>s and <time-modification>.
//   This value should match <duration>, but Sibelius has a buggy
//   <divisions> value so there can be round-off errors in the
//   duration of notes in MusicXML output from Sibelius.
//

HumNum MxmlEvent::getEmbeddedDuration(HumNum& modification, xml_node el) {
	if (!el) {
		return 0;
	}
	xml_node child = el.first_child();
   int dots          = 0;      // count of <dot /> elements
   HumNum type       = 0;      // powoftwo note type (as duration)
   bool tuplet       = false;  // is a tuplet
   int actualnotes   = 1;      // numerator of tuplet factor
   int normalnotes   = 1;      // denominator of tuplet factor
   HumNum normaltype = 0;      // poweroftwo duration of tuplet
   int tupdots       = 0;      // dots of "normal type" duration
	HumNum tfactor    = 1;
	modification      = 1;

	while (child) {
		if (strcmp(child.name(), "dot") == 0) {
			dots++;
		} else if (strcmp(child.name(), "type") == 0) {
			type = getQuarterDurationFromType(child.child_value());
		} else if (strcmp(child.name(), "time-modification") == 0) {
			xml_node grandchild = child.first_child();
			normaltype = type;
			tuplet = true;
			while (grandchild) {
				if (strcmp(grandchild.name(), "actual-notes") == 0) {
					actualnotes = atoi(grandchild.child_value());
				} else if (strcmp(grandchild.name(), "normal-notes") == 0) {
					normalnotes = atoi(grandchild.child_value());
				}
				grandchild = grandchild.next_sibling();
			}
         // no duration information after <time-modification> so exit
			// outer loop now.
			break;
		} else if (strcmp(child.name(), "normal-dot") == 0) {
			tupdots++;
		}
		child = child.next_sibling();
	}

	HumNum duration = type;
	if (dots) {
		HumNum newdur = duration;
		for (int i=0; i<dots; i++) {
			newdur += duration / (1 << (i+1));
		}
		duration = newdur;
	}
	if (tuplet) {
		modification.setValue(actualnotes, normalnotes);
		duration /= modification;
      if (normaltype != type) {
			cerr << "Warning: cannot handle this tuplet type yet" << endl;
		}
      if (tupdots != 0) {
			cerr << "Warning: cannot handle this tuplet dots yet" << endl;
		}
	}

	return duration;
}



////////////////////////////////////////
//
// MxmlEvent::getQuarterDurationFromType --
//

HumNum MxmlEvent::getQuarterDurationFromType(const char* type) {
	if      (strcmp(type, "quarter") == 0) { return 1;              }
	else if (strcmp(type, "eighth") == 0)  { return HumNum(1, 2);   }
	else if (strcmp(type, "half") == 0)    { return 2;              }
	else if (strcmp(type, "16th") == 0)    { return HumNum(1, 4);   }
	else if (strcmp(type, "whole") == 0)   { return 4;              }
	else if (strcmp(type, "32nd") == 0)    { return HumNum(1, 8);   }
	else if (strcmp(type, "64th") == 0)    { return HumNum(1, 16);  }
	else if (strcmp(type, "128th") == 0)   { return HumNum(1, 32);  }
	else if (strcmp(type, "256th") == 0)   { return HumNum(1, 64);  }
	else if (strcmp(type, "512th") == 0)   { return HumNum(1, 128); }
	else if (strcmp(type, "1024th") == 0)  { return HumNum(1, 256); }
	else if (strcmp(type, "breve") == 0)   { return 8;              }
	else if (strcmp(type, "long") == 0)    { return 16;             }
	else if (strcmp(type, "maxima") == 0)  { return 32;             }
	else {
		cerr << "Error: Unknown note type: " << type << endl;
		return 0;
	}
}


//////////////////////////////
//
// MxmlEvent::nodeType -- return true if node type matches string.
//

bool MxmlEvent::nodeType(xml_node node, const char* testname) {
	if (strcmp(node.name(), testname) == 0) {
		return true;
	} else {
		return false;
	}
}



//////////////////////////////
//
// MxmlEvent::setTexts --
//

void MxmlEvent::setTexts(vector<pair<int, xml_node>>& nodes) {
	m_text = nodes;
}



//////////////////////////////
//
// MxmlEvent::getTexts --
//

vector<pair<int, xml_node>>&  MxmlEvent::getTexts(void) {
	return m_text;
}



//////////////////////////////
//
// MxmlEvent::setTempos --
//

void MxmlEvent::setTempos(vector<pair<int, xml_node>>& nodes) {
	m_tempo = nodes;
}



//////////////////////////////
//
// MxmlEvent::getTempos --
//

vector<pair<int, xml_node>>&  MxmlEvent::getTempos(void) {
	return m_tempo;
}



//////////////////////////////
//
// MxmlEvent::setDynamics --
//

void MxmlEvent::setDynamics(xml_node node) {
	m_dynamics.push_back(node);
}



//////////////////////////////
//
// MxmlEvent::setBracket --
//

void MxmlEvent::setBracket(xml_node node) {
	m_brackets.push_back(node);
}



//////////////////////////////
//
// MxmlEvent::setHairpinEnding --
//

void MxmlEvent::setHairpinEnding(xml_node node) {
	m_hairpin_ending = node;
}



//////////////////////////////
//
// MxmlEvent::addFiguredBass --
//

void MxmlEvent::addFiguredBass(xml_node node) {
	m_figured_bass.push_back(node);
}



//////////////////////////////
//
// MxmlEvent::getDynamics --
//

vector<xml_node> MxmlEvent::getDynamics(void) {
	return m_dynamics;
}



//////////////////////////////
//
// MxmlEvent::getBrackets --
//

vector<xml_node> MxmlEvent::getBrackets(void) {
	return m_brackets;
}



//////////////////////////////
//
// MxmlEvent::getHairpinEnding --
//

xml_node MxmlEvent::getHairpinEnding(void) {
	return m_hairpin_ending;
}



//////////////////////////////
//
// MxmlEvent::getFiguredBassCount --
//

int MxmlEvent::getFiguredBassCount(void) {
	return (int)m_figured_bass.size();
}



//////////////////////////////
//
// MxmlEvent::getFiguredBass --
//

xml_node MxmlEvent::getFiguredBass(int index) {
	return m_figured_bass.at(index);
}



//////////////////////////////
//
// MxmlEvent::operator<< --
//

ostream& operator<<(ostream& output, xml_node element) {
	element.print(output);
	return output;
}




class MxmlPart;


//////////////////////////////
//
// MxmlMeasure::MxmlMeasure --
//

MxmlMeasure::MxmlMeasure(MxmlPart* part) {
	clear();
	setOwner(part);
}



//////////////////////////////
//
// MxmlMeasure::~MxmlMeasure --
//

MxmlMeasure::~MxmlMeasure() {
	clear();
}



//////////////////////////////
//
// MxmlMeasure::clear --
//

void MxmlMeasure::clear(void) {
	m_starttime = m_duration = 0;
	for (int i=0; i<(int)m_events.size(); i++) {
		delete m_events[i];
		m_events[i] = NULL;
	}
	m_events.clear();
	m_owner = NULL;
	m_timesigdur = -1;
	m_previous = m_following = NULL;
	m_style = MeasureStyle::Plain;
}



//////////////////////////////
//
// MxmlMeasure::enableStems --
//

void MxmlMeasure::enableStems(void) {
	m_stems = true;
}



//////////////////////////////
//
// MxmlMeasure::parseMeasure -- Reads XML data for one part's measure.
//

bool MxmlMeasure::parseMeasure(xpath_node mel) {
	return parseMeasure(mel.node());
}


bool MxmlMeasure::parseMeasure(xml_node mel) {
	bool output = true;
	vector<vector<int> > staffVoiceCounts;
	setStartTimeOfMeasure();

	HumNum starttime = getStartTime();
	HumNum st   = starttime;
	HumNum maxst = starttime;

	xml_node nextel;
	for (auto el = mel.first_child(); el; el = el.next_sibling()) {
		MxmlEvent* event = new MxmlEvent(this);
		if (m_stems) {
			event->enableStems();
		}
		m_events.push_back(event);
		nextel = el.next_sibling();
		output &= event->parseEvent(el, nextel, starttime);
		starttime += event->getDuration();
		if (starttime > maxst) {
			maxst = starttime;
		}
	}
	setDuration(maxst - st);

	// Should no longer be needed:
	// calculateDuration();

   bool needdummy = false;

   MxmlMeasure* pmeasure = getPreviousMeasure();
   if (getTimeSigDur() <= 0) {
      if (pmeasure) {
         setTimeSigDur(pmeasure->getTimeSigDur());
      }
   }

   if (getDuration() == 0) {
      if (pmeasure) {
         setDuration(pmeasure->getTimeSigDur());
      } else {
         setTimeSigDur(getTimeSigDur());
      }
      needdummy = true;
   }

	// Maybe check for overfull measures around here

   if (needdummy || getEventCount() == 0) {
      // if the duration of the measure is zero, then set the duration
      // of the measure to the duration of the time signature
      // This is needed for certain cases of multi-measure rests, where no
      // full-measure rest is given in the measure (Sibelius does this).
      setDuration(getTimeSigDur());
		addDummyRest();
   }

   // Neeed to check for empty voice/layers occuring lower in the
   // voice index list than layers which contain notes.  For example
   // if voice/layer 2 contains notes, but voice/layer 1 does not, then
   // a dummy full-measure rest should fill voice/layer 1.  The voice
   // layer 1 should be filled with the duration of the measure according
   // to the other voice/layers in the measure.  This is done later
   // after a voice analysis has been done in
   // musicxml2hum_interface::insertMeasure(), specifically:
	// musicxml2hum_interface::checkForDummyRests().

	sortEvents();

	return output;
}



//////////////////////////////
//
// MxmlMeasure::forceLastInvisible --
//

void MxmlMeasure::forceLastInvisible(void) {
   if (!m_events.empty()) {
      m_events.back()->forceInvisible();
   }
}



//////////////////////////////
//
// MxmlMeasure::getEventList --
//

vector<MxmlEvent*>& MxmlMeasure::getEventList(void) {
   return m_events;
}



//////////////////////////////
//
// MxmlMeasure::addDummyRest --
//

void MxmlMeasure::addDummyRest(void) {
   HumNum measuredur = getTimeSigDur();
   HumNum starttime = getStartTime();
   MxmlEvent* event = new MxmlEvent(this);
   m_events.push_back(event);
   MxmlMeasure* measure = this;
   event->makeDummyRest(measure, starttime, measuredur);
}


void MxmlMeasure::addDummyRest(HumNum starttime, HumNum duration,
		int staffindex, int voiceindex) {
	MxmlEvent* event = new MxmlEvent(this);
	m_events.push_back(event);
   MxmlMeasure* measure = this;
   event->makeDummyRest(measure, starttime, duration, staffindex, voiceindex);
}



//////////////////////////////
//
// MxmlMeasure::setStartTimeOfMeasure --
//

void MxmlMeasure::setStartTimeOfMeasure(void) {
	if (!m_owner) {
		setStartTimeOfMeasure(0);
		return;
	}
	MxmlMeasure* previous = m_owner->getPreviousMeasure(this);
	if (!previous) {
		setStartTimeOfMeasure(0);
		return;
	}
	setStartTimeOfMeasure(previous->getStartTime() + previous->getDuration());
}


void MxmlMeasure::setStartTimeOfMeasure(HumNum value) {
	m_starttime = value;
}



//////////////////////////////
//
// MxmlMeasure::calculateDuration --
//

void MxmlMeasure::calculateDuration(void) {
	HumNum maxdur   = 0;
	HumNum sum      = 0;
	for (int i=0; i<(int)m_events.size(); i++) {
		m_events[i]->setStartTime(sum + getStartTime());
		sum += m_events[i]->getDuration();
		if (maxdur < sum) {
			maxdur = sum;
		}
	}
	setDuration(maxdur);
}



//////////////////////////////
//
// MxmlMeasure::setDuration --
//

void MxmlMeasure::setDuration(HumNum value) {
	m_duration = value;
}



//////////////////////////////
//
// MxmlMeasure::getStartTime --
//

HumNum MxmlMeasure::getStartTime(void) const {
	return m_starttime;
}



//////////////////////////////
//
// MxmlMeasure::getDuration --
//

HumNum MxmlMeasure::getDuration(void) const {
	return m_duration;
}



//////////////////////////////
//
// MxmlMeasure::setOwner --
//

void MxmlMeasure::setOwner(MxmlPart* part) {
	m_owner = part;
}



//////////////////////////////
//
// MxmlMeasure::setOwner --
//

MxmlPart* MxmlMeasure::getOwner(void) const {
	return m_owner;
}



//////////////////////////////
//
// MxmlMeasure::reportVerseCountToOwner --
//

void MxmlMeasure::reportVerseCountToOwner(int count) {
	if (!m_owner) {
		return;
	}
	m_owner->receiveVerseCount(count);
}


void MxmlMeasure::reportVerseCountToOwner(int staffindex, int count) {
	if (!m_owner) {
		return;
	}
	m_owner->receiveVerseCount(staffindex, count);
}



//////////////////////////////
//
// MxmlMeasure::reportHarmonyCountToOwner --
//

void MxmlMeasure::reportHarmonyCountToOwner(int count) {
	if (!m_owner) {
		return;
	}
	m_owner->receiveHarmonyCount(count);
}



//////////////////////////////
//
// MxmlMeasure::reportDynamicToOwner --
//

void MxmlMeasure::reportDynamicToOwner(void) {
	m_owner->receiveDynamic();
}



//////////////////////////////
//
// MxmlMeasure::reportFiguredBassToOwner --
//

void MxmlMeasure::reportFiguredBassToOwner(void) {
	m_owner->receiveFiguredBass();
}



//////////////////////////////
//
// MxmlMeasure::reportCaesuraToOwner --
//

void MxmlMeasure::reportCaesuraToOwner(const string& letter) {
	m_owner->receiveCaesura(letter);
}



//////////////////////////////
//
// MxmlMeasure::reportOrnamentToOwner --
//

void MxmlMeasure::reportOrnamentToOwner(void) {
	m_owner->receiveOrnament();
}



//////////////////////////////
//
// MxmlMeasure::reportEditorialAccidentalToOwner --
//

void MxmlMeasure::reportEditorialAccidentalToOwner(void) {
	if (!m_owner) {
		return;
	}
	m_owner->receiveEditorialAccidental();
}



//////////////////////////////
//
// MxmlMeasure::receiveEditorialAccidentalFromChild --
//

void  MxmlMeasure::receiveEditorialAccidentalFromChild(void) {
	if (m_owner != NULL) {
		m_owner->receiveEditorialAccidental();
	}
}



//////////////////////////////
//
// MxmlMeasure::getPartNumber --
//

int MxmlMeasure::getPartNumber(void) const {
	if (!m_owner) {
		return 0;
	}
	return m_owner->getPartNumber();
}



//////////////////////////////
//
// MxmlMeasure::getPartIndex --
//

int MxmlMeasure::getPartIndex(void) const {
	if (!m_owner) {
		return -1;
	}
	return m_owner->getPartIndex();
}



//////////////////////////////
//
// MxmlMeasure::setQTicks -- Set the number of ticks per quarter note.
//     Returns the number of times that the ticks has been set.
//

int MxmlMeasure::setQTicks(long value) {
	if (m_owner) {
		return m_owner->setQTicks(value);
	} else {
		return 0;
	}
}



//////////////////////////////
//
// MxmlMeasure::getQTicks -- Get the number of ticks per quarter note.
//

long MxmlMeasure::getQTicks(void) const {
	if (m_owner) {
		return m_owner->getQTicks();
	} else {
		return 0;
	}
}



//////////////////////////////
//
// MxmlMeasure::attachLastEventToPrevious --
//

void MxmlMeasure::attachLastEventToPrevious(void) {
 	if (m_events.size() < 2) {
 		return;
 	}
	MxmlEvent* event = m_events.back();
	m_events.resize(m_events.size() - 1);
	m_events.back()->link(event);
}



//////////////////////////////
//
// MxmlMeasure::getEventCount --
//

int MxmlMeasure::getEventCount(void) const {
	return (int)m_events.size();
}



//////////////////////////////
//
// MxmlMeasure::getSortedEvents --
//

vector<SimultaneousEvents>* MxmlMeasure::getSortedEvents(void) {
	return &m_sortedevents;
}



//////////////////////////////
//
// MxmlMeasure::getEvent --
//

MxmlEvent* MxmlMeasure::getEvent(int index) const {
	if (index < 0) {
		return NULL;
	}
	if (index >= (int)m_events.size()) {
		return NULL;
	}
	return m_events[index];
}



//////////////////////////////
//
// MxmlMeasure::setPreviousMeasure --
//

void MxmlMeasure::setPreviousMeasure(MxmlMeasure* event) {
	m_previous = event;
}



//////////////////////////////
//
// MxmlMeasure::setNextMeasure --
//

void MxmlMeasure::setNextMeasure(MxmlMeasure* event) {
	m_following = event;
}



//////////////////////////////
//
// MxmlMeasure::getPreviousMeasure --
//

MxmlMeasure* MxmlMeasure::getPreviousMeasure(void) const {
	return m_previous;
}



//////////////////////////////
//
// MxmlMeasure::getNextMeasure --
//

MxmlMeasure* MxmlMeasure::getNextMeasure(void) const {
	return m_following;
}



//////////////////////////////
//
// MxmlMeasure::getVoiceIndex --
//

int MxmlMeasure::getVoiceIndex(int voicenum) {
   if (m_owner) {
      return m_owner->getVoiceIndex(voicenum);
   } else {
      return -1;
   }
}



//////////////////////////////
//
// MxmlMeasure::getStaffIndex --
//

int MxmlMeasure::getStaffIndex(int voicenum) {
   if (m_owner) {
      return m_owner->getStaffIndex(voicenum);
   } else {
      return -1;
   }
}



///////////////////////////////////////////////////////////////////////////
//
// private functions --
//

//////////////////////////////
//
// MxmlMeasure::sortEvents -- Sorts events for the measure into
//   time order.  They are split into zero-duration evnets and
//   non-zero events.  mevent_floating type are placed into the
//   non-zero events eventhough they have zero duration (this is
//   for harmony not attached to a note attack, and will be
//   eventually including basso continuo figuration having the
//   same situation).
//

void MxmlMeasure::sortEvents(void) {
	int i;
	set<HumNum> times;

	for (i=0; i<(int)m_events.size(); i++) {
		times.insert(m_events[i]->getStartTime());
	}

	m_sortedevents.resize(times.size());
	int counter = 0;

	for (HumNum val : times) {
		m_sortedevents[counter++].starttime = val;
	}

	// setup sorted access:
	map<HumNum, SimultaneousEvents*> mapping;
	for (i=0; i<(int)m_sortedevents.size(); i++) {
		mapping[m_sortedevents[i].starttime] = &m_sortedevents[i];
	}

	HumNum duration;
	HumNum starttime;
	for (i=0; i<(int)m_events.size(); i++) {

		// skip storing certain types of events:
		switch (m_events[i]->getType()) {
			case mevent_backup:
				continue;
			case mevent_forward:
            if (m_events[i]->getDuration() == this->getDuration()) {
                 // forward elements are encoded as whole-measure rests
                 // if they fill the duration of a measure
            } else if (m_events[i]->getVoiceIndex() < 0) {
               // Skip forward elements which are not invisible rests
               continue;
            }
            break;
			default:
				break;
		}

		starttime = m_events[i]->getStartTime();
		duration  = m_events[i]->getDuration();
		if (m_events[i]->isFloating()) {
			mapping[starttime]->nonzerodur.push_back(m_events[i]);
		} else if (duration == 0) {
			mapping[starttime]->zerodur.push_back(m_events[i]);
		} else {
			mapping[starttime]->nonzerodur.push_back(m_events[i]);
		}
	}

	/* debugging information:

	int j;
	vector<SimultaneousEvents>& se = m_sortedevents;

	cout << "QTIME SORTED EVENTS:" << endl;
	for (i=0; i<(int)se.size(); i++) {
		if (se[i].zerodur.size() > 0) {
			cout << se[i].starttime << "z\t";
			for (j=0; j<(int)se[i].zerodur.size(); j++) {
				cout << " " << se[i].zerodur[j]->getName();
				cout << "(";
				cout << se[i].zerodur[j]->getPartNumber();
				cout << ",";
				cout << se[i].zerodur[j]->getStaffNumber();
				cout << ",";
				cout << se[i].zerodur[j]->getVoiceNumber();
				cout << ")";
			}
			cout << endl;
		}
		if (se[i].nonzerodur.size() > 0) {
			cout << se[i].starttime << "\t";
			for (j=0; j<(int)se[i].nonzerodur.size(); j++) {
				cout << " " << se[i].nonzerodur[j]->getName();
				cout << "(";
				cout << se[i].nonzerodur[j]->getPartNumber();
				cout << ",";
				cout << se[i].nonzerodur[j]->getStaffNumber();
				cout << ",";
				cout << se[i].nonzerodur[j]->getVoiceNumber();
				cout << ")";
			}
			cout << endl;
		}
	}
	*/

}



//////////////////////////////
//
// MxmlMeasure::receiveStaffNumberFromChild -- Receive a staff number
//    placement for a note or rest and pass it along to the part class
//    so that it can keep track of the maximum staff number used in
//    the part.
//

void MxmlMeasure::receiveStaffNumberFromChild(int staffnum, int voicenum) {
	reportStaffNumberToOwner(staffnum, voicenum);
}



//////////////////////////////
//
// MxmlMeasure::receiveTimeSigDurFromChild --
//

void MxmlMeasure::receiveTimeSigDurFromChild(HumNum duration) {
   setTimeSigDur(duration);
}



//////////////////////////////
//
// MxmlMeasure::setTimeSigDur --
//

void MxmlMeasure::setTimeSigDur(HumNum duration) {
   m_timesigdur = duration;
}



//////////////////////////////
//
// MxmlMeasure::getTimeSigDur --
//

HumNum MxmlMeasure::getTimeSigDur(void) {
   return m_timesigdur;
}



//////////////////////////////
//
// MxmlMeasure::reportStaffNumberToOwner -- Send a staff number
//    placement for a note or rest and pass it along to the part class
//    so that it can keep track of the maximum staff number used in
//    the part.
//

void MxmlMeasure::reportStaffNumberToOwner(int staffnum, int voicenum) {
	if (m_owner != NULL) {
		m_owner->receiveStaffNumberFromChild(staffnum, voicenum);
	}
}



//////////////////////////////
//
// MxmlMeasure::receiveMeasureStyleFromChild --
//

void  MxmlMeasure::receiveMeasureStyleFromChild(MeasureStyle style) {
	if (style == MeasureStyle::RepeatForward) {
		MxmlMeasure* previous = getPreviousMeasure();
		if (previous) {
			previous->setStyle(style);
		}
	} else {
		setStyle(style);
	}
}



//////////////////////////////
//
// MxmlMeasure::getStyle --
//

MeasureStyle MxmlMeasure::getStyle(void) {
	return m_style;
}



//////////////////////////////
//
// MxmlMeasure::getBarStyle --
//

MeasureStyle MxmlMeasure::getBarStyle(void) {
	return getStyle();
}



//////////////////////////////
//
// MxmlMeasure::setStyle --
//

void MxmlMeasure::setStyle(MeasureStyle style) {
	if (m_style == MeasureStyle::Plain) {
		m_style = style;
	} else if ((m_style == MeasureStyle::RepeatBackward) &&
			(style == MeasureStyle::RepeatForward)) {
		m_style = MeasureStyle::RepeatBoth;
	} else if ((m_style == MeasureStyle::RepeatForward) &&
			(style == MeasureStyle::RepeatBackward)) {
		m_style = MeasureStyle::RepeatBoth;
	} else {
		// some sort of problem to deal with later
		m_style = style;
	}
}



//////////////////////////////
//
// MxmlMeasure::setBarStyle --
//

void MxmlMeasure::setBarStyle(MeasureStyle style) {
	m_style = style;
}



class MxmlMeasure;
class MxmlPart;


//////////////////////////////
//
// MxmlPart::MxmlPart -- Constructor.
//

MxmlPart::MxmlPart(void) {
	clear();
}



//////////////////////////////
//
// MxmlPart::~MxmlPart -- Deconstructor.
//

MxmlPart::~MxmlPart(void) {
	clear();
}



//////////////////////////////
//
// MxmlPart::clear -- Clear all internal variables of object.
//

void MxmlPart::clear(void) {
	for (int i=0; i<(int)m_measures.size(); i++) {
		delete m_measures[i];
		m_measures[i] = NULL;
	}
	m_measures.clear();
	m_partnum = 0;
	m_maxstaff = 0;
	m_verseCount.resize(0);
	m_harmonyCount = 0;
	m_editorialAccidental = false;
}



//////////////////////////////
//
// MxmlPart::enableStems --
//


void MxmlPart::enableStems(void) {
	m_stems = true;
}


//////////////////////////////
//
// MxmlPart::getQTicks -- Return the current divisions element value,
//    which are the number of integer ticks representing a quarter-note
//    duration.
//

long MxmlPart::getQTicks(void) const {
	if (m_qtick.size() > 0) {
		return m_qtick.back();
	} else {
		return 0;
	}
}



//////////////////////////////
//
// MxmlPart::setQTicks -- Set the current attribute/divisions value,
//     which is the number of integer ticks representing a quarter-note
//     duration.
//

int MxmlPart::setQTicks(long value) {
	if (value < 0) {
		return (int)m_qtick.size();
	}
	if (m_qtick.size() > 0) {
		if (m_qtick.back() == value) {
			return (int)m_qtick.size();
		}
	}
	m_qtick.push_back(value);
	return (int)m_qtick.size();
}



//////////////////////////////
//
// MxmlPart::addMeasure -- Append a new measure to the list of measure element.
//

bool MxmlPart::addMeasure(xpath_node mel) {
	return addMeasure(mel.node());
}


bool MxmlPart::addMeasure(xml_node mel) {
	MxmlMeasure* meas = new MxmlMeasure(this);
	if (m_stems) {
		meas->enableStems();
	}
	if (m_measures.size() > 0) {
		meas->setPreviousMeasure(m_measures.back());
		m_measures.back()->setNextMeasure(meas);
	}
	m_measures.push_back(meas);
	bool status = meas->parseMeasure(mel);

	return status;
}



//////////////////////////////
//
// MxmlPart::getMeasureCount -- Return the number of stored measures.
//

int MxmlPart::getMeasureCount(void) const {
	return (int)m_measures.size();
}



//////////////////////////////
//
// MxmlPart::getMeasure -- Get the measure number at the given index.
//

MxmlMeasure* MxmlPart::getMeasure(int index) const {
	if (index < 0) {
		return NULL;
	}
	int stupidwarningsuppression = (int)m_measures.size();
	if ((index - stupidwarningsuppression) >= 0) {
		return NULL;
	}
	return m_measures[index];
}



//////////////////////////////
//
// MxmlPart::getPreviousMeasure -- Given a measure, return the
//    previous measure occuring before it.
//

MxmlMeasure* MxmlPart::getPreviousMeasure(MxmlMeasure* measure) const {
	if (!measure) {
		return NULL;
	}
	if (measure == *m_measures.begin()) {
		return NULL;
	}
	if (m_measures.size() == 0) {
		return NULL;
	}

	return measure->getPreviousMeasure();
}



//////////////////////////////
//
// MxmlPart::getDuration --  Return the duration of the part in units
//     of quarter notes.  This is a sum of the duration of all measures in
//     the part.
//

HumNum MxmlPart::getDuration(void) const {
	if (m_measures.size() == 0) {
		return 0;
	}
	return m_measures.back()->getStartTime() + m_measures.back()->getDuration();
}



//////////////////////////////
//
// MxmlPart::setPartNumber -- Set the part number for the part.  Typically
//   starts at "1" for the top part in a system.
//

void MxmlPart::setPartNumber(int number) {
	m_partnum = number;
}



//////////////////////////////
//
// MxmlPart::getPartNumber -- Return the part number for the part.  Typically
//     starts at "1" for the top part in a system.
//

int MxmlPart::getPartNumber(void) const {
	return m_partnum;
}



//////////////////////////////
//
// MxmlPart::getPartIndex -- Return the part number for the part.  Typically
//     starts at "0" for the top part in a system.
//

int MxmlPart::getPartIndex(void) const {
	return m_partnum - 1;
}



//////////////////////////////
//
// MxmlPart::getStaffCount -- Return the number of staves which the part
//   contains, such as 2 for piano parts.
//

int MxmlPart::getStaffCount(void) const {
	if (!m_maxstaff) {
		return 1;
	} else {
		return m_maxstaff;
	}
}



//////////////////////////////
//
// MxmlPart::getHarmonyCount -- Return the number of verses in the part.
//

int MxmlPart::getHarmonyCount(void) const {
	return m_harmonyCount;
}



//////////////////////////////
//
// MxmlPart::hasEditorialAccidental -- Return true if part contains an editorial
//    accidental (represented as parentheses around the accidental in MusicXML.
//

bool MxmlPart::hasEditorialAccidental(void) const {
	return m_editorialAccidental;
}



//////////////////////////////
//
// MxmlPart::hasDynamics --
//

bool MxmlPart::hasDynamics(void) const {
	return m_has_dynamics;
}



//////////////////////////////
//
// MxmlPart::hasFiguredBass --
//

bool MxmlPart::hasFiguredBass(void) const {
	return m_has_figured_bass;
}



//////////////////////////////
//
// MxmlPart::getVerseCount -- Return the number of verses in the part.
//

int MxmlPart::getVerseCount(void) const {
	if (m_verseCount.size() == 0) {
		return 0;
	} else {
		return m_verseCount[0];
	}
}


int MxmlPart::getVerseCount(int staffindex) const {
	int staffnum = staffindex + 1;
	if (staffnum < (int)m_verseCount.size()) {
		return m_verseCount[staffnum];
	} else {
		return 0;
	}
}



//////////////////////////////
//
// MxmlPart::getCaesura -- Returns the RDF marker for a caesura in **kern
//    data (or an empty string if there is no marker defined).
//

string MxmlPart::getCaesura(void) const {
	return m_caesura;
}



//////////////////////////////
//
// MxmlPart::receiveHarmonyCount --
//

void MxmlPart::receiveHarmonyCount(int count) {
	m_harmonyCount = count;
}



//////////////////////////////
//
// MxmlPart::receiveDynamic --
//

void MxmlPart::receiveDynamic(void) {
	m_has_dynamics = true;
}



//////////////////////////////
//
// MxmlPart::receiveFiguredBass --
//

void MxmlPart::receiveFiguredBass(void) {
	m_has_figured_bass = true;
}



//////////////////////////////
//
// MxmlPart::receiveCaesura --
//

void MxmlPart::receiveCaesura(const string& letter) {
	m_caesura = letter;
}



//////////////////////////////
//
// MxmlPart::receiveOrnament --
//

void MxmlPart::receiveOrnament(void) {
	m_hasOrnaments = true;
}



//////////////////////////////
//
// MxmlPart::hasOrnaments --
//

bool MxmlPart::hasOrnaments(void) const {
	return m_hasOrnaments;
}



//////////////////////////////
//
// MxmlPart::receiveEditorialAccidental --
//

void MxmlPart::receiveEditorialAccidental(void) {
	m_editorialAccidental = true;
}



//////////////////////////////
//
// MxmlPart::receiveVerseCount --
//

void MxmlPart::receiveVerseCount(int count) {
if (count > 0)  {
}
	receiveVerseCount(0, count);
}


void MxmlPart::receiveVerseCount(int staffindex, int count) {
	int staffnum = staffindex + 1;
	if (staffnum < 0) {
		return;
	}
	if (staffnum < (int)m_verseCount.size()) {
		if (count > m_verseCount[staffnum]) {
			m_verseCount[staffnum] = count;
		}
	} else {
		int oldsize = (int)m_verseCount.size();
		int newsize = staffnum + 1;
		m_verseCount.resize(newsize);
		for (int i=oldsize; i<newsize; i++) {
			m_verseCount[i] = 0;
		}
		m_verseCount[staffnum] = count;
	}
}



///////////////////////////////////////////////////////////////////////////
//
// private fuctions --
//

//////////////////////////////
//
// MxmlMeasure::receiveStaffNumberFromChild -- Receive a staff number
//    placement for a note or rest and pass it along to the part class
//    so that it can keep track of the maximum staff number used in
//    the part.
//

void MxmlPart::receiveStaffNumberFromChild(int staffnum, int voicenum) {
	if (m_maxstaff < staffnum) {
		m_maxstaff = staffnum;
	}
	trackStaffVoices(staffnum, voicenum);
}



//////////////////////////////
//
// MxmlPart::trackStaffVoices -- Keep track of which staff voices
//     occur on.  This will be used later to assign voices to
//     spines, and to make notes in the voice which are not on
//     the home staff (cross-staff beaming, etc).
//

void MxmlPart::trackStaffVoices(int staffnum, int voicenum) {
	vector<vector<int> >& sv = m_staffvoicehist;
	if (staffnum < 0) {
		return;
	}
	if (voicenum < 0) {
		return;
	}
	if (staffnum >= (int)sv.size()) {
		sv.resize(staffnum+1);
	}
	if (voicenum >= (int)sv[staffnum].size()) {
		int oldsize = (int)sv[staffnum].size();
		int newsize = voicenum + 1;
		sv[staffnum].resize(newsize);
		for (int i=oldsize; i<newsize; i++) {
			sv[staffnum][i] = 0;
		}
	}
	sv[staffnum][voicenum]++;
}


//////////////////////////////
//
// MxmlPart::prepareVoiceMapping -- Takes the histogram of staff/voice
//    pairings and create a list of new voice indexes for each
//    staff.  In Finale & Sibelius, four voices are hardwired to each
//    staff: staff1 {1, 2, 3, 4}, staff2 {5, 6, 7, 8}.  But some
//    software will not use this, instead: staff1 {1}, staff2 {2}.
//    The m_voicemapping variable will re-index voice numbers independently
//    for each staff:
//       staff1 {1, 2, 3, 4}, staff2 {5, 6, 7, 8}
//       staff1 {0, 1, 2, 3}, staff2 {0, 1, 2, 3}
//    and:
//       staff1 {1}, staff2 {2}
//       staff1 {0}, staff2 {0}
//    strange cases such as this should also work:
//       staff1 {1, 3, 5, 7}, staff2 {2, 4, 6, 8}
//       staff1 {0, 1, 2, 3}, staff2 {0, 1, 2, 3}
//    A voice is assigned to the most common staff on which its note/rests
//    occur.
//
//    voicenum in MusicXML is mapped to a (staffindex, voiceindex) pair
//       vector<pair<int, int> > m_voicemapping;
//
// Example mapping process:
// First, start with a histogram of staff/voice numbers in MusicXML file:
// 	STAFF 0:	55
// 	STAFF 1:	0	98
// 	STAFF 2:	39	0	41
// In this case staff1 has a single voice numbered "1" (with 98 counts)
// And staff2 has a single voice, numbered "2".  The final mapping
// in m_voicemapping is:
// 	0 (voice number 1) => staffindex 0, voiceindex 0
// 	1 (voice number 2) => staffindex 1, voiceindex 0
// staff0 and voice0 assignments are ignored, since there are not
// for notes (usually measures which are on staff0/voice0, and
// non-notes such as harmony which will be attached to a staff with
// but voice0, but ignored at least for now.
//

void MxmlPart::prepareVoiceMapping(void) {
	vector<vector<int> >& sv = m_staffvoicehist;
	int staffcount = (int)sv.size() - 1;
	if (staffcount < 1) {
		return;
	}
	int i, j;
	int maxvoicenum = 0;
	// a staff without any voices will probably cause problems,
	// so maybe check for such a case. 0th position in sv is
	// not used, so maxvoicenum is an index for sv.

	for (i=1; i<(int)sv.size(); i++) {
		if ((int)sv[i].size() - 1 > maxvoicenum) {
			maxvoicenum = (int)sv[i].size() - 1;
		}
	}

	// reindex voice numbers to voice indexes on staves:
	// m_voicemapping[homevoicenum] => {homestaffindex, newvoiceindex}
	pair<int, int> empty;
	empty.first = -1;
	empty.second = -1;
	int homestaffnum;
	int homevoicenum;
	int newvoiceindex;
	int count;
	int maxcount;

	// for each voice number in the MusicXML data, assign
	// a voiceindex for it on each staff.
	for (j=1; j<=maxvoicenum; j++) {
		maxcount = -1;
		homestaffnum = -1;
		homevoicenum = -1;
		for (i=1; i<(int)sv.size(); i++) {
			if (j >= (int)sv[i].size()) {
				continue;
			}
			count = sv[i][j];
			if ((count > 0) && (maxcount < count)) {
				maxcount = count;
				homestaffnum = i;
				homevoicenum = j;
			}
		}
		if (homestaffnum < 1) {
			continue;
		}
		if (homevoicenum < 1) {
			continue;
		}

		// find highest newvoiceindex for the current staff
		newvoiceindex = -1;
		for (int n=1; n<(int)m_voicemapping.size(); n++) {
			if (m_voicemapping[n].first == homestaffnum - 1) {
				newvoiceindex++;
			}
		}
		// assign to next highest newvoiceindex for staff:
		newvoiceindex++;

		// add the new mapping for homevoicenum to (staffindex, newvoiceindex)
		if (homevoicenum >= (int)m_voicemapping.size()) {
			int oldsize = (int)m_voicemapping.size();
			int newsize = homevoicenum + 1;
			m_voicemapping.resize(newsize);
			for (int m=oldsize; m<newsize; m++) {
				m_voicemapping[m] = empty;
			}
			m_voicemapping[homevoicenum].first = homestaffnum - 1;
			m_voicemapping[homevoicenum].second = newvoiceindex;
		} else {
			m_voicemapping[homevoicenum].first = homestaffnum - 1;
			m_voicemapping[homevoicenum].second = newvoiceindex;
		}
	}
}



//////////////////////////////
//
// MxmlPart::getVoiceIndex -- Convert a MusicXML voice number to
//    a voice index on a particular staff.
//

int MxmlPart::getVoiceIndex(int voicenum) {
	if (voicenum < 1) {
		return -1;
	}
	if (voicenum >= (int)m_voicemapping.size()) {
		return -1;
	}
	return m_voicemapping[voicenum].second;
}



//////////////////////////////
//
// MxmlPart::getStaffIndex -- Convert a MusicXML voice number to
//    a voice index on a particular staff.
//

int MxmlPart::getStaffIndex(int voicenum) {
	if (voicenum < 1) {
		return -1;
	}
	if (voicenum >= (int)m_voicemapping.size()) {
		return -1;
	}
	return m_voicemapping[voicenum].first;
}



//////////////////////////////
//
// MxmlPart::printStaffVoiceInfo --
//

void MxmlPart::printStaffVoiceInfo(void) {
	vector<vector<int> >& sv = m_staffvoicehist;
	int i, j;
	cout << "\n!!STAFF-VOICE MAPPING:\n";
	for (i=0; i<(int)sv.size(); i++) {
		cout << "!!\tSTAFF " << i << ":";
		for (j=0; j<(int)sv[i].size(); j++) {
			cout << "\t" << sv[i][j];
		}
		cout << endl;
	}
	cout << "!!REMAPPING:\n";
	for (i=1; i<(int)m_voicemapping.size(); i++) {
		cout << "!!\tvoicenum " << i << ":\t(";
		cout << m_voicemapping[i].first << ", ";
		cout << m_voicemapping[i].second << ")\n";
	}
	cout << endl;
}



//////////////////////////
//
// MxmlPart::parsePartInfo -- find the part name and part abbreviation
//    if there are any.
//
// Example:
//     <score-part id="P1">
//      <part-name>Alto</part-name>
//      <part-name-display>
//       <display-text>Alto</display-text>
//      </part-name-display>
//      <part-abbreviation> </part-abbreviation>
//      <part-abbreviation-display>
//       <display-text> </display-text>
//      </part-abbreviation-display>
//      <score-instrument id="P1-I1">
//       <instrument-name> </instrument-name>
//      </score-instrument>
//     </score-part>
//

void MxmlPart::parsePartInfo(xml_node partinfo) {
// ggg cerr << "PART INFO ID " << partinfo.attribute("id").value() << endl;
	xml_node partnamenode = partinfo.select_node("./part-name").node();
	if (partnamenode) {
// ggg cerr << "PART NAME " << partnamenode.child_value() << endl;
		m_partname = cleanSpaces(partnamenode.child_value());
	}
	xml_node abbrnode = partinfo.select_node("./part-abbreviation").node();
	if (abbrnode) {
		m_partabbr = cleanSpaces(abbrnode.child_value());
	}
}



//////////////////////////////
//
// MxmlPart::getPartName --
//

string MxmlPart::getPartName(void) const {
	return m_partname;
}



//////////////////////////////
//
// MxmlPart::getPartAbbr --
//

string MxmlPart::getPartAbbr(void) const {
	return m_partabbr;
}



//////////////////////////////
//
// MxmlPart::cleanSpaces -- remove leading/trailing spaces in string.
//

string MxmlPart::cleanSpaces(const string& input) {
	string output;
	int content = false;
	for (int i=0; i<(int)input.size(); i++) {
		if ((!content) && isspace(input[i]))  {
			continue;
		}
		content = true;
		if (isspace(input[i]) && isspace(input[i-1])) {
			continue;
		}
		if (isspace(input[i])) {
			output += ' ';
		} else {
			output += input[i];
		}
	}
	if (isspace(output.back())) {
		output.resize(output.size() - 1);
	}

	return output;
}



//////////////////////////////
//
// NoteCell::NoteCell -- Constructor.
//

NoteCell::NoteCell(NoteGrid* owner, HTp token) {
	clear();
	m_owner = owner;
	m_token = token;
	calculateNumericPitches();
}



//////////////////////////////
//
// NoteCell::clear -- Clear the contents of the object.
//

void NoteCell::clear(void) {
	m_owner = NULL;
	m_token = NULL;
	m_b7  = 0;
	m_b12 = 0;
	m_b40 = 0;
	m_accidental = 0;
	m_nextAttackIndex = -1;
	m_prevAttackIndex = -1;
	m_currAttackIndex = -1;
	m_timeslice = -1;
	m_voice = -1;
}



//////////////////////////////
//
// NoteCell::calculateNumericPitches -- Fills in b7, b12, and b40 variables.
//    0 = rest, negative values for sustained notes.
//

void NoteCell::calculateNumericPitches(void) {
	if (!m_token) {
		m_b40        = NAN;
		m_b12        = NAN;
		m_b7         = NAN;
		m_accidental = NAN;
		return;
	}

	bool sustain = m_token->isNull() || m_token->isSecondaryTiedNote();
	if (m_token->isRest()) {
		m_b40 = NAN;
	} else {
		HTp resolve = m_token->resolveNull();
		if (resolve->isRest()) {
			m_b40 = NAN;
		} else if (resolve->isNull()) {
			m_b40 = NAN;
		} else {
			m_b40 = Convert::kernToBase40(resolve);
			m_b40 = (sustain ? -m_b40 : m_b40);
		}
	}

	// convert to base-7 (diatonic pitch numbers)
	if (m_b40 > 0) {
		m_b7         = Convert::base40ToDiatonic((int)m_b40);
		m_b12        = Convert::base40ToMidiNoteNumber((int)m_b40);
		m_accidental = Convert::base40ToAccidental((int)m_b40);
	} else if (m_b40 < 0) {
		m_b7         = -Convert::base40ToDiatonic(-(int)m_b40);
		m_b12        = -Convert::base40ToMidiNoteNumber(-(int)m_b40);
		m_accidental = -Convert::base40ToAccidental(-(int)m_b40);
	} else {
		m_b7         = NAN;
		m_b12        = NAN;
		m_accidental = NAN;
	}
}



//////////////////////////////
//
// NoteCell::getSgnKernPitch -- Return the **kern representation of the pitch.
//   Parentheses are placed around the pitch name if the NoteCell is a
//   sustain.
//

string NoteCell::getSgnKernPitch(void) {
	if (isRest()) {
		return "r";
	}
	string pitch;
	pitch = Convert::base40ToKern((int)getAbsBase40Pitch());
	if (isSustained()) {
		pitch.insert(0, "(");
		pitch += ")";
	}
	return pitch;
}



//////////////////////////////
//
// NoteCell::getAbsKernPitch -- Return the **kern representation of the pitch.
//

string NoteCell::getAbsKernPitch(void) {
	if (isRest()) {
		return "r";
	}
	return Convert::base40ToKern((int)getAbsBase40Pitch());
}



//////////////////////////////
//
// NoteCell::isSustained --
//

bool NoteCell::isSustained(void) {
	if (m_b40 < 0) {
		return true;
	} else if (m_b40 > 0) {
		return false;
	}
	// calculate if rest is a "sustain" or an "attack"
	if (m_currAttackIndex == m_timeslice) {
		return false;
	} else {
		return true;
	}
}



//////////////////////////////
//
// NoteCell::operator- -- Calculate the diatonic interval between
//   two notes.  Maybe layter allow subtraction operator to deal
//   with base-12 and base-40 represnetation.
//

double NoteCell::operator-(NoteCell& B) {
	NoteCell& A = *this;
	return A.getAbsDiatonicPitch() - B.getAbsDiatonicPitch();
}


double NoteCell::operator-(int B) {
	NoteCell& A = *this;
	return A.getAbsDiatonicPitch() - B;
}



//////////////////////////////
//
// NoteCell::getLineIndex -- Returns the line index of the note in
//    the original file.
//

int NoteCell::getLineIndex(void) {
	if (!m_token) {
		return -1;
	}
	return m_token->getLineIndex();
}



//////////////////////////////
//
// NoteCell::getFieldIndex -- Returns the field index of the note in
//    the original file.
//

int NoteCell::getFieldIndex(void) {
	if (!m_token) {
		return -1;
	}
	return m_token->getFieldIndex();
}



//////////////////////////////
//
// NoteCell:printNoteInfo --
//

ostream& NoteCell::printNoteInfo(ostream& out) {
	out << getSliceIndex()       << "\t";
	out << getSgnKernPitch()     << "\t";
	out << getPrevAttackIndex()  << "\t";
	out << getCurrAttackIndex()  << "\t";
	out << getNextAttackIndex()  << "\t";
	out << getSgnDiatonicPitch() << "\t";
	out << getSgnMidiPitch()     << "\t";
	out << getSgnBase40Pitch();
	out << endl;
	return out;
}



//////////////////////////////
//
// NoteCell::getDiatonicIntervalFromPreviousAttack --
//

double NoteCell::getDiatonicIntervalFromPreviousAttack(void) {
	int previ = getPrevAttackIndex();
	if (previ < 0) {
		return NAN;
	}
	if (!m_owner) {
		return NAN;
	}
	return getAbsDiatonicPitch()
			- m_owner->cell(m_voice,previ)->getAbsDiatonicPitch();
}



//////////////////////////////
//
// NoteCell::getDiatonicIntervalFromNextAttack --
//

double NoteCell::getDiatonicIntervalToNextAttack(void) {
	int nexti = getNextAttackIndex();
	if (nexti < 0) {
		return NAN;
	}
	if (!m_owner) {
		return NAN;
	}
	return m_owner->cell(m_voice,nexti)->getAbsDiatonicPitch()
			- getAbsDiatonicPitch();
}



//////////////////////////////
//
// NoteCell::isRest --
//

bool NoteCell::isRest(void) {
	// bug in GCC requires :: prefix to resolve two different isnan() defs.
	return Convert::isNaN(m_b40);
}



//////////////////////////////
//
// NoteCell::getMetricLevel --
//

double NoteCell::getMetricLevel(void) {
	if (!m_owner) {
		return NAN;
	}
	return m_owner->getMetricLevel(getLineIndex());
}



//////////////////////////////
//
// NoteCell::getDurationFromStart --
//

HumNum NoteCell::getDurationFromStart(void) {
	if (m_token) {
		return m_token->getDurationFromStart();
	} else {
		return -1;
	}
}


//////////////////////////////
//
// NoteCell::getDuration -- Return the duration to the next note attack
//     in the grid in the same voice.
//

HumNum NoteCell::getDuration(void) {
	if (!m_owner) {
		return Convert::recipToDuration(m_token);
	}
	return m_owner->getNoteDuration(getVoiceIndex(), getSliceIndex());
}



//////////////////////////////
//
// NoteCell::setMeter --
//

void NoteCell::setMeter(int topval, HumNum botval) {
	m_metertop = topval;
	m_meterbot = botval;
}



//////////////////////////////
//
// NoteCell::getMeterTop --
//

int NoteCell::getMeterTop(void) {
	return m_metertop;
}



//////////////////////////////
//
// NoteCell::getMeterBottom --
//

HumNum NoteCell::getMeterBottom(void) {
	return m_meterbot;
}



//////////////////////////////
//
// NoteCell::getSgnDiatonicPitchClass --
//

double NoteCell::getSgnDiatonicPitchClass(void) {
	if (Convert::isNaN(m_b7)) {
		return GRIDREST;
	} else if (m_b7 < 0) {
		return -(double)(((int)-m_b7) % 7);
	} else {
		return (double)(((int)m_b7) % 7);
	}
}



//////////////////////////////
//
// NoteCell::getAbsDiatonicPitchClass --
//

double NoteCell::getAbsDiatonicPitchClass(void) {
	if (Convert::isNaN(m_b7)) {
		return GRIDREST;
	} else {
		return (double)(((int)fabs(m_b7)) % 7);
	}
}



//////////////////////////////
//
// NoteCell::getSgnBase40PitchClass --
//

double NoteCell::getSgnBase40PitchClass(void) {
	if (Convert::isNaN(m_b40)) {
		return GRIDREST;
	} else if (m_b40 < 0) {
		return -(double)(((int)-m_b40) % 40);
	} else {
		return (double)(((int)m_b40) % 40);
	}
}



//////////////////////////////
//
// NoteCell::getAbsBase40PitchClass --
//

double NoteCell::getAbsBase40PitchClass(void) {
	if (Convert::isNaN(m_b40)) {
		return GRIDREST;
	} else {
		return (double)(((int)fabs(m_b40)) % 40);
	}
}


//////////////////////////////
//
// NoteCell::isAttack --
//

bool NoteCell::isAttack(void) {
	if (Convert::isNaN(m_b40)) {
		return false;
	}
	return m_b40>0? true:false;
}



//////////////////////////////
//
// NoteGrid::NoteGrid -- Constructor.
//

NoteGrid::NoteGrid(HumdrumFile& infile) {
	m_infile = NULL;
	load(infile);
}



//////////////////////////////
//
// NoteGrid::~NoteGrid -- Deconstructor.
//

NoteGrid::~NoteGrid() {
	clear();
}



//////////////////////////////
//
// NoteGrid::clear -- Deallocate storage and make the object empty.
//

void NoteGrid::clear(void) {
	m_infile = NULL;
	m_kernspines.clear();

	vector<vector<NoteCell* > >& grid = m_grid;
	for (int i=0; i<(int)grid.size(); i++) {
		for (int j=0; j<(int)grid[i].size(); j++) {
			if (grid[i][j]) {
				grid[i][j]->clear();
				delete grid[i][j];
				grid[i][j] = NULL;
			}
		}
		grid[i].clear();
	}
	grid.clear();
}



//////////////////////////////
//
// NoteGrid::getVoiceCount -- Return the number of voices/parts in the grid.
//

int NoteGrid::getVoiceCount(void) {
	return (int)m_grid.size();
}



//////////////////////////////
//
// NoteGrid::getSliceCount -- Return the number of time slices in the grid.
//     the grid.
//

int NoteGrid::getSliceCount(void) {
	if (m_grid.size() == 0) {
		return 0;
	} else {
		return (int)m_grid[0].size();
	}
}



//////////////////////////////
//
// NoteGrid::load -- Generate a two-dimensional list of notes
//     in a score.  Each row has at least one note attack, or an
//     empty data line in the Humdrum file will be skipped.
//

bool NoteGrid::load(HumdrumFile& infile) {
	// remove any previous contents:
	clear();

	m_infile = &infile;

	m_kernspines = infile.getKernSpineStartList();
	vector<HTp>& kernspines = m_kernspines;

	vector<int> metertops(infile.getMaxTrack() + 1, 0);
	vector<HumNum> meterbots(infile.getMaxTrack() + 1, 0);

	if (kernspines.size() == 0) {
		cerr << "Warning: no **kern spines in file" << endl;
		return false;
	}

	vector<vector<NoteCell* > >& grid = m_grid;
	grid.resize(kernspines.size());
	for (int i=0; i<(int)grid.size(); i++) {
		grid[i].reserve(infile.getLineCount());
	}

	//int attack = 0;
	int track, lasttrack;
	vector<HTp> current;
	HumRegex hre;
	for (int i=0; i<infile.getLineCount(); i++) {
		if (infile[i].isInterpretation()) {
			for (int j=0; j<infile[i].getFieldCount(); j++) {
				if (!infile[i].token(j)->isKern()) {
					continue;
				}
				track = infile.token(i, j)->getTrack();
				if (hre.search(*infile.token(i, j), "\\*M(\\d+)/(\\d+)%(\\d+)")) {
					metertops[track] = hre.getMatchInt(1);
					meterbots[track] = hre.getMatchInt(2);
					meterbots[track] /= hre.getMatchInt(3);
				} else if (hre.search(*infile.token(i, j), "\\*M(\\d+)/(\\d+)")) {
					metertops[track] = hre.getMatchInt(1);
					meterbots[track] = hre.getMatchInt(2);
				} else {
					continue;
				}

			}
		}
		if (!infile[i].isData()) {
			continue;
		}
		track = 0;
		//attack = 0;
		current.clear();
		for (int j=0; j<infile[i].getFieldCount(); j++) {
			lasttrack = track;
			track = infile.token(i, j)->getTrack();
			if (!infile[i].token(j)->isDataType("**kern")) {
				continue;
			}
			if (track == lasttrack) {
				// secondary voice: ignore
				continue;
			}
			current.push_back(infile.token(i, j));
			if (!(current.back()->isRest()
					|| current.back()->isSecondaryTiedNote())) {
				//attack++;
			}
		}
		if (current.size() != kernspines.size()) {
			cerr << "Error: Unequal vector sizes " << current.size()
			     << " compared to " << kernspines.size() << endl;
			return false;
		}
		for (int j=0; j<(int)current.size(); j++) {
			NoteCell* cell = new NoteCell(this, current[j]);
			track = current[j]->getTrack();
			cell->setVoiceIndex(j);
			cell->setSliceIndex((int)grid[j].size());
			cell->setMeter(metertops[track], meterbots[track]);
			grid[j].push_back(cell);
		}
	}

	buildAttackIndexes();

	return true;
}



//////////////////////////////
//
// NoteGrid::cell -- Return the given cell in the grid.
//

NoteCell* NoteGrid::cell(int voiceindex, int sliceindex) {
	return m_grid.at(voiceindex).at(sliceindex);
}




//////////////////////////////
//
// NoteGrid::printDiatonicGrid --
//

void NoteGrid::printDiatonicGrid(ostream& output) {
	for (int j=0; j<getSliceCount(); j++) {
		for (int i=0; i<(int)getVoiceCount(); i++) {
			output << cell(i, j)->getSgnDiatonicPitch();
			if (i < getVoiceCount() - 1) {
				output << "\t";
			}
		}
		output << endl;
	}
}



//////////////////////////////
//
// NoteGrid::printMidiGrid --
//

void NoteGrid::printMidiGrid(ostream& output) {
	for (int j=0; j<getSliceCount(); j++) {
		for (int i=0; i<(int)getVoiceCount(); i++) {
			output << cell(i, j)->getSgnMidiPitch();
			if (i < getVoiceCount() - 1) {
				output << "\t";
			}
		}
		output << endl;
	}
}



//////////////////////////////
//
// NoteGrid::printBase40Grid --
//

void NoteGrid::printBase40Grid(ostream& output) {
	for (int j=0; j<getSliceCount(); j++) {
		for (int i=0; i<(int)getVoiceCount(); i++) {
			output << cell(i, j)->getSgnBase40Pitch();
			if (i < getVoiceCount() - 1) {
				output << "\t";
			}
		}
		output << endl;
	}
}



//////////////////////////////
//
// NoteGrid::printRawGrid --
//

void NoteGrid::printRawGrid(ostream& output) {
	for (int j=0; j<getSliceCount(); j++) {
		for (int i=0; i<(int)getVoiceCount(); i++) {
			output << cell(i, j)->getToken();
			if (i < getVoiceCount() - 1) {
				output << "\t";
			}
		}
		output << endl;
	}
}



//////////////////////////////
//
// NoteGrid::printKernGrid --
//

void NoteGrid::printKernGrid(ostream& output) {
	for (int j=0; j<getSliceCount(); j++) {
		for (int i=0; i<(int)getVoiceCount(); i++) {
			output << cell(i, j)->getSgnKernPitch();
			if (i < getVoiceCount() - 1) {
				output << "\t";
			}
		}
		output << endl;
	}
}



//////////////////////////////
//
// NoteGrid::buildAttackIndexes -- create forward and backward
//     note attack indexes for each cell.
//

void NoteGrid::buildAttackIndexes(void) {
	for (int i=0; i<(int)m_grid.size(); i++) {
		buildAttackIndex(i);
	}
}



//////////////////////////////
//
// NoteGrid::buildAttackIndex -- create forward and backward
//     note attack indexes for each cell in a single voice.
//

void NoteGrid::buildAttackIndex(int vindex) {
	vector<NoteCell*>& part = m_grid[vindex];

	// Set the slice index for the attack of the current note.  This
	// will be the same as the current slice if the NoteCell is an attack.
	// Otherwise if the note is a sustain, thie index will be set
	// to the slice of the attack correspinding to this NoteCell.
	// For rests, the first rest in a continuous sequence of rests
	// will be marked as the "attack" of the rest.
	NoteCell* currentcell = NULL;
	for (int i=0; i<(int)part.size(); i++) {
		if (i == 0) {
			part[0]->setCurrAttackIndex(0);
			continue;
		}
		if (part[i]->isRest()) {
			// This is a rest, so check for a rest sustain or start
			// of a rest sequence.
			if (part[i-1]->isRest()) {
				// rest "sustain"
				if (currentcell && !part[i]->getToken()->isNull()) {
					currentcell->m_tiedtokens.push_back(part[i]->getToken());
				}
				part[i]->setCurrAttackIndex(part[i-1]->getCurrAttackIndex());
			} else {
				// rest "attack";
				part[i]->setCurrAttackIndex(i);
			}
		} else if (part[i]->isAttack()) {
			part[i]->setCurrAttackIndex(i);
			currentcell = part[i];
		} else {
			// This is a sustain, so get the attack index of the
			// note from the previous slice index.
			part[i]->setCurrAttackIndex(part[i-1]->getCurrAttackIndex());
			if (currentcell && !part[i]->getToken()->isNull()) {
				currentcell->m_tiedtokens.push_back(part[i]->getToken());
			}
		}
	}

	// start with note attacks marked in the previous and next note slots:
	for (int i=0; i<(int)part.size(); i++) {
		if (part[i]->isAttack()) {
			part[i]->setNextAttackIndex(i);
			part[i]->setPrevAttackIndex(i);
		} else if (part[i]->isRest()) {
			if (part[i]->getCurrAttackIndex() == i) {
				part[i]->setNextAttackIndex(i);
				part[i]->setPrevAttackIndex(i);
			}
		}
	}

	// Go back and adjust the next note attack index:
	int value = -1;
	int temp  = -1;
	for (int i=(int)part.size()-1; i>=0; i--) {
		if (!part[i]->isSustained()) {
			temp = part[i]->getNextAttackIndex();
			part[i]->setNextAttackIndex(value);
			value = temp;
		} else {
			part[i]->setNextAttackIndex(value);
		}
	}

	// Go back and adjust the previous note attack index:
	value = -1;
	temp  = -1;
	for (int i=0; i<(int)part.size(); i++) {
		if (!part[i]->isSustained()) {
			temp = part[i]->getPrevAttackIndex();
			part[i]->setPrevAttackIndex(value);
			value = temp;
		} else {
			if (i != 0) {
				part[i]->setPrevAttackIndex(part[i-1]->getPrevAttackIndex());
			}
		}
	}

}



//////////////////////////////
//
// NoteGrid::getAbsDiatonicPitch -- Return the diatonic pitch number for
//     the given cell.
//

double NoteGrid::getAbsDiatonicPitch(int vindex, int sindex) {
	return m_grid.at(vindex).at(sindex)->getAbsDiatonicPitch();
}



//////////////////////////////
//
// NoteGrid::getSgnDiatonicPitch -- Return the diatonic pitch number for
//     the given cell.
//

double NoteGrid::getSgnDiatonicPitch(int vindex, int sindex) {
	return m_grid.at(vindex).at(sindex)->getSgnDiatonicPitch();
}



//////////////////////////////
//
// NoteGrid::getAbsMidiPitch -- Return the MIDI pitch number for
//     the given cell.
//

double NoteGrid::getAbsMidiPitch(int vindex, int sindex) {
	return m_grid.at(vindex).at(sindex)->getAbsMidiPitch();
}



//////////////////////////////
//
// NoteGrid::getSgnMidiPitch -- Return the MIDI pitch number for
//     the given cell.
//

double NoteGrid::getSgnMidiPitch(int vindex, int sindex) {
	return m_grid.at(vindex).at(sindex)->getSgnMidiPitch();
}



//////////////////////////////
//
// NoteGrid::getAbsBase40Pitch -- Return the base-40 pitch number for
//     the given cell.
//

double NoteGrid::getAbsBase40Pitch(int vindex, int sindex) {
	return m_grid.at(vindex).at(sindex)->getAbsBase40Pitch();
}



//////////////////////////////
//
// NoteGrid::getSgnBase40Pitch -- Return the base-40 pitch number for
//     the given cell.
//

double NoteGrid::getSgnBase40Pitch(int vindex, int sindex) {
	return m_grid.at(vindex).at(sindex)->getSgnBase40Pitch();
}



//////////////////////////////
//
// NoteGrid::getAbsKernPitch -- Return the **kern pitch name for
//     the given cell.  Sustained notes are enclosed in parentheses.
//

string NoteGrid::getAbsKernPitch(int vindex, int sindex) {
	return m_grid.at(vindex).at(sindex)->getAbsKernPitch();
}



//////////////////////////////
//
// NoteGrid::getSgnKernPitch -- Return the **kern pitch name for
//     the given cell.  Sustained notes are enclosed in parentheses.
//

string NoteGrid::getSgnKernPitch(int vindex, int sindex) {
	return m_grid.at(vindex).at(sindex)->getSgnKernPitch();
}



//////////////////////////////
//
// NoteGrid::getToken -- Return the HumdrumToken pointer for
//     the given cell.
//

HTp NoteGrid::getToken(int vindex, int sindex) {
	return m_grid.at(vindex).at(sindex)->getToken();
}



//////////////////////////////
//
// NoteGrid::getPrevAttackDiatonic -- Return the Diatonic note number
//     of the previous attacked note for the given cell.  Return 0 if
//     the previous note attack is a rest or there is no previous note
//     in the voice.
//

int NoteGrid::getPrevAttackDiatonic(int vindex, int sindex) {
	NoteCell*& cell = m_grid.at(vindex).at(sindex);
	int index = cell->getPrevAttackIndex();
	if (index < 0) {
		return 0;
	} else {
		return (int)this->cell(vindex, index)->getAbsDiatonicPitch();
	}
}



//////////////////////////////
//
// NoteGrid::getNextAttackDiatonic -- Return the Diatonic note number
//     of the next attacked note for the given cell.  Return 0 if
//     the next note attack is a rest or there is no next note
//     in the voice.
//

int NoteGrid::getNextAttackDiatonic(int vindex, int sindex) {
	NoteCell*& cell = m_grid.at(vindex).at(sindex);
	int index = cell->getNextAttackIndex();
	if (index < 0) {
		return 0;
	} else {
		return (int)(this->cell(vindex, index)->getAbsDiatonicPitch());
	}
}



//////////////////////////////
//
// NoteGrid::getLineIndex -- return the line index in the original
//    Humdrum data for the given slice index.
//

int NoteGrid::getLineIndex(int sindex) {
	if (m_grid.size() == 0) {
		return -1;
	}
	return m_grid.at(0).at(sindex)->getToken()->getLineIndex();
}



//////////////////////////////
//
// NoteGrid::getFieldIndex -- return the field index in the original
//    Humdrum data for the given slice index.
//

int NoteGrid::getFieldIndex(int sindex) {
	if (m_grid.size() == 0) {
		return -1;
	}
	return m_grid.at(0).at(sindex)->getToken()->getFieldIndex();
}



//////////////////////////////
//
// NoteGrid::getNoteAndRestAttacks -- Return the note attacks,
//    and the first rest slice ("rest attack") for a particular voice.
//

void NoteGrid::getNoteAndRestAttacks(vector<NoteCell*>& attacks,
		int vindex) {
	attacks.resize(0);
	int max = getSliceCount();
	if (max == 0) {
		return;
	}
	attacks.reserve(max);
	NoteCell* note = cell(vindex, 0);
	attacks.push_back(note);
	while (attacks.back()->getNextAttackIndex() > 0) {
		note = cell(vindex, attacks.back()->getNextAttackIndex());
		if (note == attacks.back()) {
			cerr << "Strange duplicate: ";
			note->printNoteInfo(cerr);
			break;
		}
		attacks.push_back(note);
	}
}



//////////////////////////////
//
// NoteGrid::getMetricLevel --
//

double NoteGrid::getMetricLevel(int sindex) {
	if (!m_infile) {
		return NAN;
	}
	if ((getSliceCount() == 0) || (getVoiceCount() == 0)) {
		return NAN;
	}
	if (m_metriclevels.empty()) {
		int track = 0;
		if ((getVoiceCount() > 0) && (getSliceCount() > 0)) {
			track = cell(0, 0)->getToken()->getTrack();
		}
		m_infile->getMetricLevels(m_metriclevels, track, NAN);
	}
	return m_metriclevels[sindex];
}



//////////////////////////////
//
// NoteGrid::getNoteDuration --
//

HumNum NoteGrid::getNoteDuration(int vindex, int sindex) {
	NoteCell* curnote = cell(vindex, sindex);
	int attacki = curnote->getCurrAttackIndex();
	int nexti   = curnote->getNextAttackIndex();
	HumNum starttime = 0;
	if (attacki >= 0) {
		starttime = cell(vindex, attacki)->getDurationFromStart();
	}
	HumNum endtime = m_infile->getScoreDuration();;
	if (nexti >= 0) {
		endtime = cell(vindex, nexti)->getDurationFromStart();
	}
	return endtime - starttime;
}



//////////////////////////////
//
// NoteGrid::printGridInfo -- for debugging.
//

void NoteGrid::printGridInfo(ostream& out) {
	for (int i=0; i<getVoiceCount(); i++) {
		printVoiceInfo(out, i);
		out << endl;
	}

}



//////////////////////////////
//
// NoteGrid::printVoiceInfo -- for debugging.
//

void NoteGrid::printVoiceInfo(ostream& out, int vindex) {
	out << "============================================================";
	out << endl;
	out << "i\tnote\tprevi\tcurri\tnexti\tb7\tmidi\tb40\n";
	for (int i=0; i<getSliceCount(); i++) {
		this->cell(vindex, i)->printNoteInfo(out);
	}
}




///////////////////////////////////////////////////////////////////////////
//
// Option_register class function definitions.
//


//////////////////////////////
//
// Option_register::Option_register -- Constructor.
//

Option_register::Option_register(void) {
	m_modifiedQ = 0;
	setType('s');
}


Option_register::Option_register(const string& aDefinition, char aType,
		const string& aDefaultOption) {
	m_modifiedQ = 0;
	setType(aType);
	setDefinition(aDefinition);
	setDefault(aDefaultOption);
}


Option_register::Option_register(const string& aDefinition, char aType,
		const string& aDefaultOption, const string& aModifiedOption) {
	m_modifiedQ = 0;
	setType(aType);
	setDefinition(aDefinition);
	setDefault(aDefaultOption);
	setModified(aModifiedOption);
}


Option_register::Option_register(const Option_register& reg) {
	m_definition = reg.m_definition;
	m_description = reg.m_description;
	m_defaultOption = reg.m_defaultOption;
	m_modifiedOption = reg.m_modifiedOption;
	m_modifiedQ = reg.m_modifiedQ;
	m_type = reg.m_type;
}



//////////////////////////////
//
// Option_register::operator= --
//

Option_register& Option_register::operator=(const Option_register& reg) {
	if (this == &reg) {
		return *this;
	}
	m_definition = reg.m_definition;
	m_description = reg.m_description;
	m_defaultOption = reg.m_defaultOption;
	m_modifiedOption = reg.m_modifiedOption;
	m_modifiedQ = reg.m_modifiedQ;
	m_type = reg.m_type;
	return *this;
}



//////////////////////////////
//
// Option_register::~Option_register -- Destructor.
//

Option_register::~Option_register() {
	// do nothing
}



//////////////////////////////
//
// Option_register::clearModified -- Clear any changes in the option value.
//

void Option_register::clearModified(void) {
	m_modifiedOption.clear();
	m_modifiedQ = 0;
}



//////////////////////////////
//
// Option_register::getDefinition -- Returns the initial definition.
//	string used to define this entry.
//

string Option_register::getDefinition(void) {
	return m_definition;
}



//////////////////////////////
//
// Option_register::getDescription -- Return the textual description
//      of the entry.
//

string Option_register::getDescription(void) {
	return m_description;
}



//////////////////////////////
//
// Option_register::getDefault --  Return the default value string.
//

string Option_register::getDefault(void) {
	return m_defaultOption;
}



//////////////////////////////
//
// Option_register::getModified -- Return the modified option string.
//

string Option_register::getModified(void) {
	return m_modifiedOption;
}



//////////////////////////////
//
// Option_register::isModified -- Return true if option has been
//    set on the command-line.
//

bool Option_register::isModified(void) {
	return m_modifiedQ;
}



//////////////////////////////
//
// Option_register::getType -- Return the data type of the option.
//

char Option_register::getType(void) {
	return m_type;
}



//////////////////////////////
//
// Option_register::getOption -- return the modified option
//  	or the default option if no modified option.
//

string Option_register::getOption(void) {
	if (isModified()) {
		return getModified();
	} else {
		return getDefault();
	}
}



//////////////////////////////
//
// Option_register::reset -- clear contents of register entry.
//

void Option_register::reset(void) {
	m_definition.clear();
	m_description.clear();
	m_defaultOption.clear();
	m_modifiedOption.clear();
	m_modifiedQ = false;
	m_type = 's';
}



//////////////////////////////
//
// Option_register::setDefault -- Set the default value.
//

void Option_register::setDefault(const string& aString) {
	m_defaultOption = aString;
}



//////////////////////////////
//
// Option_register::setDefinition -- Set the option definition.
//

void Option_register::setDefinition(const string& aString) {
	m_definition = aString;
}



//////////////////////////////
//
// Option_register::setDescription -- Set the textual description.
//

void Option_register::setDescription(const string& aString) {
	m_description = aString;
}



//////////////////////////////
//
// Option_register::setModified -- Set the modified value.
//

void Option_register::setModified(const string& aString) {
	m_modifiedOption = aString;
	m_modifiedQ = 1;
}



//////////////////////////////
//
// Option_register::setType -- Set the option type.
//

void Option_register::setType(char aType) {
	m_type = aType;
}



//////////////////////////////
//
// Option_register::print -- Print the state of the option registery entry.
//     Useul for debugging.
//

ostream& Option_register::print(ostream& out) {
	out << "definition:\t"     << m_definition     << endl;
	out << "description:\t"    << m_description    << endl;
	out << "defaultOption:\t"  << m_defaultOption  << endl;
	out << "modifiedOption:\t" << m_modifiedOption << endl;
	out << "modifiedQ:\t\t"    << m_modifiedQ      << endl;
	out << "type:\t\t"         << m_type           << endl;
	return out;
}



///////////////////////////////////////////////////////////////////////////
//
// Options class function definitions.
//

//////////////////////////////
//
// Options::Options -- Constructor.
//

Options::Options(void) {
	// do nothing
}


Options::Options(int argc, char** argv) {
	setOptions(argc, argv);
}


Options::Options(const Options& options) {
	m_argv = options.m_argv;
	m_arguments = options.m_arguments;
	m_optionFlag = options.m_optionFlag;
	m_optionList = options.m_optionList;
	m_options_error_checkQ = options.m_options_error_checkQ;
	m_processedQ = options.m_processedQ;
	m_suppressQ = options.m_suppressQ;
	m_optionsArgQ = options.m_optionsArgQ;
	for (int i=0; i<(int)options.m_optionRegister.size(); i++) {
		Option_register* orr = new Option_register(*options.m_optionRegister[i]);
		m_optionRegister.push_back(orr);
	}

}



//////////////////////////////
//
// Options::~Options -- Destructor.
//

Options::~Options() {
	reset();
}



//////////////////////////////
//
// Options::operator= --
//

Options& Options::operator=(const Options& options) {
	if (this == &options) {
		return *this;
	}
	m_argv = options.m_argv;
	m_arguments = options.m_arguments;
	m_optionFlag = options.m_optionFlag;
	m_optionList = options.m_optionList;
	m_options_error_checkQ = options.m_options_error_checkQ;
	m_processedQ = options.m_processedQ;
	m_suppressQ = options.m_suppressQ;
	m_optionsArgQ = options.m_optionsArgQ;

	for (int i=0; i<(int)m_optionRegister.size(); i++) {
		delete m_optionRegister[i];
		m_optionRegister[i] = NULL;
	}
	m_optionRegister.clear();

	for (int i=0; i<(int)options.m_optionRegister.size(); i++) {
		Option_register* orr = new Option_register(*options.m_optionRegister[i]);
		m_optionRegister.push_back(orr);
	}

	m_error.str("");
	return *this;
}



//////////////////////////////
//
// Options::argc -- returns the argument count as input from main().
//

int Options::argc(void) const {
	return (int)m_argv.size();
}



//////////////////////////////
//
// Options::argv -- returns the arguments strings as input from main().
//

const vector<string>& Options::argv(void) const {
	return m_argv;
}



//////////////////////////////
//
// Options::define -- store an option definition in the registry.  Option
//     definitions have this sructure:
//        option-name|alias-name1|alias-name2=option-type:option-default
// option-name :: name of the option (one or more character, not including
//      spaces or equal signs.
// alias-name  :: equivalent name(s) of the option.
// option-type :: single charater indicating the option data type.
// option-default :: default value for option if no given on the command-line.
//

int Options::define(const string& aDefinition) {
	Option_register* definitionEntry = NULL;

	// Error if definition string doesn't contain an equals sign
	auto location = aDefinition.find("=");
	if (location == string::npos) {
		m_error << "Error: no \"=\" in option definition: " << aDefinition << endl;
		return -1;
	}

	string aliases = aDefinition.substr(0, location);
	string rest    = aDefinition.substr(location+1);
	string otype   = rest;
	string ovalue  = "";

	location = rest.find(":");
	if (location != string::npos) {
		otype  = rest.substr(0, location);
		ovalue = rest.substr(location+1);
	}

	// Remove anyspaces in the option type field
	otype.erase(remove_if(otype.begin(), otype.end(), ::isspace), otype.end());

	// Option types are only a single charater (b, i, d, c or s)
	if (otype.size() != 1) {
		m_error << "Error: option type is invalid: " << otype
			  << " in option definition: " << aDefinition << endl;
		return -1;
	}

	// Check to make sure that the type is known
	if (otype[0] != OPTION_STRING_TYPE  &&
		 otype[0] != OPTION_INT_TYPE     &&
		 otype[0] != OPTION_FLOAT_TYPE   &&
		 otype[0] != OPTION_DOUBLE_TYPE  &&
		 otype[0] != OPTION_BOOLEAN_TYPE &&
		 otype[0] != OPTION_CHAR_TYPE ) {
		m_error << "Error: unknown option type \'" << otype[0]
			  << "\' in defintion: " << aDefinition << endl;
		return -1;
	}

	// Set up space for a option entry in the registry
	definitionEntry = new Option_register(aDefinition, otype[0], ovalue);

	int definitionIndex = (int)m_optionRegister.size();

	// Store option aliases
	string optionName;
	aliases += '|';
	for (int i=0; i<(int)aliases.size(); i++) {
		if (::isspace(aliases[i])) {
			continue;
		} else if (aliases[i] == '|') {
			if (isDefined(optionName)) {
				m_error << "Option \"" << optionName << "\" from definition:" << endl;
				m_error << "\t" << aDefinition << endl;
				m_error << "is already defined in: " << endl;
				m_error << "\t" << getDefinition(optionName) << endl;
				return -1;
			}
			if (optionName.size() > 0) {
				m_optionList[optionName] = definitionIndex;
			}
			optionName.clear();
		} else {
			optionName += aliases[i];
		}
	}

	// Store definition in registry and return its indexed location.
	// This location will be used to link option aliases to the main
	// command name.
	m_optionRegister.push_back(definitionEntry);
	return definitionIndex;
}


int Options::define(const string& aDefinition, const string& aDescription) {
	int index = define(aDefinition);
	m_optionRegister[index]->setDescription(aDescription);
	return index;
}



//////////////////////////////
//
// Options::isDefined -- Return true if option is present in registry.
//

int Options::isDefined(const string& name) {
	if (m_optionList.find(name) == m_optionList.end()) {
		return 0;
	} else {
		return 1;
	}
}



//////////////////////////////
//
// Options::getArg -- returns the specified argument.
//	argurment 0 is the command name.
//

string Options::getArg(int index) {
	if (index == 0) {
		if (m_argv.empty()) {
			return "";
		} else {
			return m_argv[0];
		}
	}
	if (index < 1 || index > (int)m_arguments.size()) {
		m_error << "Error: argument " << index << " does not exist." << endl;
		return "";
	}
	return m_arguments[index - 1];
}

// Alias:

string Options::getArgument(int index) {
	return getArg(index);
}



//////////////////////////////
//
// Options::getArgCount --  number of arguments on command line.
//	does not count the options or the command name.
//

int Options::getArgCount(void) {
	return (int)m_arguments.size();
}

// Alias:

int Options::getArgumentCount(void) {
	return getArgCount();
}



//////////////////////////////
//
// Options::getArgList -- return a string vector of the arguments
//     after the options have been parsed out of it.  This list
//     excludes the command name (uses Options::getCommand() for that).
//

vector<string>& Options::getArgList(vector<string>& output) {
	output = m_arguments;
	return output;
}

// Alias:

vector<string>& Options::getArgumentList(vector<string>& output) {
	return getArgList(output);
}



//////////////////////////////
//
// Options::getBoolean --  returns true if the option was
//	used on the command line.
//

bool Options::getBoolean(const string& optionName) {
	int index = getRegIndex(optionName);
	if (index < 0) {
		return 0;
	}
	return m_optionRegister[index]->isModified();
}



//////////////////////////////
//
// Options::getCommand -- returns argv[0] (the first string
//     in the original argv list.
//

string Options::getCommand(void) {
	if (m_argv.empty()) {
		return "";
	} else {
		return m_argv[0];
	}
}



//////////////////////////////
//
// Options::getCommandLine -- returns a string which contains the
//     command-line call to the program, including any appended
//     options.  This command only works after .process() is run.
//

string Options::getCommandLine(void) {
	string output;
	for (int i=0; i<(int)m_argv.size(); i++) {
		// check for how " and ' are dealt with in m_arguments...
		output += m_argv[i];
		if ((int)output.size() < (int)m_argv.size() - 1) {
			output += ' ';
		}
	}
	return output;
}



//////////////////////////////
//
// Options::getDefinition -- returns the definition for the specified
//      option name.  Returns empty string if there is no entry for
//      the option name.  spaces count in the input option name.
//

string Options::getDefinition(const string& optionName) {
	auto it = m_optionList.find(optionName);
	if (it == m_optionList.end()) {
		return "";
	} else {
		return m_optionRegister[it->second]->getDefinition();
	}
}



//////////////////////////////
//
// Options::getDouble -- returns the double float associated
//	with the given option.  Returns 0 if there is no
//	number associated with the option.
//

double Options::getDouble(const string& optionName) {
	return strtod(getString(optionName).c_str(), (char**)NULL);
}



//////////////////////////////
//
// Options::getChar -- Return the first character in the option string;
//      If the length is zero, then return '\0'.
//

char Options::getChar(const string& optionName) {
	return getString(optionName).c_str()[0];
}



//////////////////////////////
//
// Options::getFloat -- Return the floating point number
//	associated with the given option.
//

float Options::getFloat(const string& optionName) {
	return (float)getDouble(optionName);
}



//////////////////////////////
//
// Options::getInt -- Return the integer argument.  Can handle
//	hexadecimal, decimal, and octal written in standard
//	C syntax.
//

int Options::getInt(const string& optionName) {
	return (int)strtol(getString(optionName).c_str(), (char**)NULL, 0);
}

int Options::getInteger(const string& optionName) {
	return getInt(optionName);
}



//////////////////////////////
//
// Options::getString -- Return the option argument string.
//

string Options::getString(const string& optionName) {
	int index = getRegIndex(optionName);
	if (index < 0) {
		return "UNKNOWN OPTION";
	} else {
		return m_optionRegister[index]->getOption();
	}
}



//////////////////////////////
//
// Options::optionsArg -- Return true if --options is present
//    on the command line, otherwise returns false.
//

int Options::optionsArg(void) {
	return m_optionsArgQ;
}



//////////////////////////////
//
// Options::print -- Print a list of the defined options.
//

ostream& Options::print(ostream& out) {
	vector<string> declarations;
	vector<string> descriptions;
	int maxlen = 0;
	for (int i=0; i<(int)m_optionRegister.size(); i++) {
		declarations.push_back(m_optionRegister[i]->getDefinition());
		if (maxlen < (int)declarations.back().size()) {
			maxlen = (int)declarations.back().size();
		}
		descriptions.push_back(m_optionRegister[i]->getDescription());
	}
	int separation = 3;

	for (int i=0; i<(int)declarations.size(); i++) {
		out << declarations[i];
		for (int j=(int)declarations[i].size(); j < maxlen + separation; j++) {
			out << ' ';
		}
		out << descriptions[i] << endl;
	}
	return out;
}



//////////////////////////////
//
// Options::printEmscripten -- Print a list of the defined options
//    when compiled with Emscripten (for use in https://verovio.humdrum.org
//    with JavaScript compiled code for a web browser using Humdrum data.
//

ostream& Options::printEmscripten(ostream& out) {
	vector<string> declarations;
	vector<string> descriptions;
	out << "!!@@BEGIN: PREHTML" << endl;
	out << "!!<table>" << endl;
	out << "!!   <tr>" << endl;
	out << "!!      <th>Option</th><th>Type</th><th>Default</th><th>Description</th>" << endl;
	out << "!!   </tr>" << endl;
	HumRegex hre;
	for (int i=0; i<(int)m_optionRegister.size(); i++) {
		out << "!!   <tr>" << endl;
		string definition = m_optionRegister[i]->getDefinition();
		string description = m_optionRegister[i]->getDescription();
		string option = "";
		string optionType = "";
		string defaultValue = "";
		string prefix = "";
		if (hre.search(definition, "^([^|]+).*=([a-z]):?(.*)$")) {
			option = hre.getMatch(1);
			if (option.length() == 0) {
				prefix = "";
			} else if (option.length() == 1) {
				prefix = "-";
			} else if (option.length() > 1) {
				prefix = "--";
			}
			optionType = hre.getMatch(2);
			defaultValue = hre.getMatch(3);

			if (optionType == "b")      { optionType = "boolean"; }
			else if (optionType == "s") { optionType = "string";  }
			else if (optionType == "i") { optionType = "integer"; }
			else if (optionType == "d") { optionType = "double";  }

			hre.replaceDestructive(option,       "&lt;", "<", "g");
			hre.replaceDestructive(option,       "&gt;", ">", "g");
			hre.replaceDestructive(optionType,   "&lt;", "<", "g");
			hre.replaceDestructive(optionType,   "&gt;", ">", "g");
			hre.replaceDestructive(defaultValue, "&lt;", "<", "g");
			hre.replaceDestructive(defaultValue, "&gt;", ">", "g");
			hre.replaceDestructive(description,  "&lt;", "<", "g");
			hre.replaceDestructive(description,  "&gt;", ">", "g");

			out << "!!     <td>" << prefix << option << "</td>";
			out << "<td>" << optionType << " </td>";
			out << "<td>" << optionType << " </td>";
			out << "<td>" << defaultValue << " </td>";
			out << "<td>" << description << " </td>";
			out << endl;
		}
		out << "!!   </tr>" << endl;
	}
	out << "!!</table>" << endl;
	out << "!!@@END: PREHTML" << endl;
	return out;
}



//////////////////////////////
//
// Options::reset -- Clear all defined options.
//

void Options::reset(void) {
	m_argv.clear();
	m_arguments.clear();

	for (int i=0; i<(int)m_optionRegister.size(); i++) {
		delete m_optionRegister[i];
		m_optionRegister[i] = NULL;
	}
	m_optionRegister.clear();
}



//////////////////////////////
//
// Options::getFlag -- Set the character which is usually set to a dash.
//

char Options::getFlag(void) {
	return m_optionFlag;
}



//////////////////////////////
//
// Options::setFlag -- Set the character used to indicate an
//	option.  For unix this is usually '-', in MS-DOS,
//	this is usually '/';  But the syntax of the Options
//	class is for Unix-style options.
//

void Options::setFlag(char aFlag) {
	m_optionFlag = aFlag;
}



//////////////////////////////
//
// Options::setModified --
//

void Options::setModified(const string& optionName, const string& aString) {
	int index = getRegIndex(optionName);
	if (index < 0) {
		return;
	}

	m_optionRegister[getRegIndex(optionName)]->setModified(aString);
}



//////////////////////////////
//
// Options::setOptions --  Store the input list of options.
//

void Options::setOptions(int argc, char** argv) {
	m_processedQ = 0;
	m_argv.resize(argc);
	for (int i=0; i<argc; i++) {
		m_argv[i] = argv[i];
	}
}


void Options::setOptions(const vector<string>& argv) {
	m_processedQ = 0;
	m_argv = argv;
}


void Options::setOptions(const string& args) {
	m_processedQ = 0;
   m_argv = tokenizeCommandLine(args);
}



//////////////////////////////
//
// Options::appendOptions -- Add argc and argv data to the current
//      list residing inside the Options class variable.
//

void Options::appendOptions(int argc, char** argv) {
	m_processedQ = 0;
	for (int i=0; i<argc; i++) {
		m_argv.push_back(argv[i]);
	}
}


void Options::appendOptions(vector<string>& argv) {
	m_processedQ = 0;
	for (int i=0; i<(int)argv.size(); i++) {
		m_argv.push_back(argv[i]);
	}
}



//////////////////////////////
//
// Options::appendOptions -- parse the string like command-line arguments.
//   Either double or single quotes can be used to encapsulate
//   a command-line token.  If double quotes are used to encapsulate,
//   then you will not have to back quote single quotes inside the
//   token string, but you will have to backslash double quotes:
//      "-T \"\"" but "-T ''"
//   Likewise for single quotes in reverse with double quotes:
//      '-T \'\'' is equal to: '-T ""'
//

void Options::appendOptions(string& args) {
	vector<string> arglist = tokenizeCommandLine(args);
	appendOptions(arglist);
}



//////////////////////////////
//
// Options::tokenizeCommandLine -- Parse a string for individual
//    command-line strings.
//

vector<string> Options::tokenizeCommandLine(const string& arguments) {
	char ch;
	int doublequote = 0;
	int singlequote = 0;

	vector<string> tokens;
	vector<string> tempargv;
	string tempvalue;

	tokens.reserve(100);
	tempargv.reserve(100);
	tempvalue.reserve(1000);

	for (int i=0; i<(int)arguments.size(); i++) {
		if (!singlequote && (arguments[i] == '"')) {
			if ((i>0) && (arguments[i-1] != '\\')) {
				doublequote = !doublequote;
				if (doublequote == 0) {
					// finished a doublequoted section of data, so store
					// even if it is the empty string
					ch = '\0';
					tempvalue += (ch);
					tokens.push_back(tempvalue);
					tempvalue.clear();
					continue;
				} else {
					// don't store the leading ":
					continue;
				}
			}
		} else if (!doublequote && (arguments[i] == '\'')) {
			if ((i>0) && (arguments[i-1] != '\\')) {
				singlequote = !singlequote;
				if (singlequote == 0) {
					// finished a singlequote section of data, so store
					// even if it is the empty string
					ch = '\0';
					tempvalue += ch;
					tokens.push_back(tempvalue);
					tempvalue.clear();
					continue;
				} else {
					// don't store the leading ":
					continue;
				}
			}
		}

		if ((!doublequote && !singlequote) && std::isspace(arguments[i])) {
			if (tempvalue.size() > 0) {
				// tempvalue += ch;
				tokens.push_back(tempvalue);
				tempvalue.clear();
			}
		} else {
			ch = arguments[i];
			tempvalue += ch;
		}
	}
	if (tempvalue.size() > 0) {
		tokens.push_back(tempvalue);
		tempvalue.clear();
	}

	return tokens;
}



//////////////////////////////
//
// Options:getType -- Return the type of the option.
//

char Options::getType(const string& optionName) {
	int index = getRegIndex(optionName);
	if (index < 0) {
		return -1;
	} else {
		return m_optionRegister[getRegIndex(optionName)]->getType();
	}
}



//////////////////////////////
//
// Options::process -- Same as xverify.
//   	default values: error_check = 1, suppress = 0;
//

bool Options::process(int argc, char** argv, int error_check, int suppress) {
	setOptions(argc, argv);
	xverify(error_check, suppress);
	return !hasParseError();
}


bool Options::process(const vector<string>& argv, int error_check, int suppress) {
	setOptions(argv);
	xverify(error_check, suppress);
	return !hasParseError();
}


bool Options::process(const string& argv, int error_check, int suppress) {
	setOptions(argv);
	xverify(error_check, suppress);
	return !hasParseError();
}


bool Options::process(int error_check, int suppress) {
	xverify(error_check, suppress);
	return !hasParseError();
}



//////////////////////////////
//
// Options::xverify --
//	default value: error_check = 1, suppress = 0;
//

void Options::xverify(int argc, char** argv, int error_check, int suppress) {
	setOptions(argc, argv);
	xverify(error_check, suppress);
}


void Options::xverify(int error_check, int suppress) {
	m_options_error_checkQ = error_check ? true : false;
	m_suppressQ = suppress ? true : false;


	// if calling xverify again, must remove previous argument list.
	if (m_arguments.size() != 0) {
		m_arguments.clear();
	}

	int position   = 0;
	int running    = 0;
	bool optionend = false;
	int i          = 1;
	int oldi;
	int terminate = 15000; // for malformed options (missing arguments)
	int tcount = 0;

	while ((i < (int)m_argv.size()) && !optionend) {
		tcount++;
		if (tcount > terminate) {
			m_error << "Error: missing option argument" << endl;
			m_error << "ARGV count: " << m_argv.size()  << endl;
			m_error << "terminate: "  << terminate      << endl;
			m_error << "tcount: "     << tcount         << endl;
			break;
		}
		if (isOption(m_argv[i], i)) {
			oldi = i;
			i = storeOption(i, position, running);
			if (i != oldi) {
				running = 0;
				position = 0;
			}
		} else {
			if (m_argv[i].size() == 2 && m_argv[i][0] == getFlag() &&
				m_argv[i][2] == getFlag() ) {
					optionend = 1;
				i++;
				break;
			} else {                          // this is an argument
				m_arguments.push_back(m_argv[i]);
				i++;
			}
		}
		if (hasParseError()) {
			break;
		}
	}
}





///////////////////////////////////////////////////////////////////////////
//
// private functions
//


//////////////////////////////
//
// Options::getRegIndex -- returns the index of the option associated
//	with this name.
//

int Options::getRegIndex(const string& optionName) {
	if (m_suppressQ && (optionName == "options")) {
			return -1;
	}

	if (optionName == "options") {
		#ifndef __EMSCRIPTEN__
		print(cout);
		exit(0);
		#endif
		return -1;
	}

	auto it = m_optionList.find(optionName);
	if (it == m_optionList.end()) {
		if (m_options_error_checkQ) {
			m_error << "Error: unknown option \"" << optionName << "\"." << endl;
			#ifndef __EMSCRIPTEN__
				cerr << "Error: unknown option \"" << optionName << "\"." << endl;
			#endif
			print(cerr);
			#ifndef __EMSCRIPTEN__
				exit(1);
			#endif
			return -1;
		} else {
			return -1;
		}
	} else {
		return it->second;
	}
}



//////////////////////////////
//
// Options::isOption --  returns true if the string is an option.
//	"--" is not an option, also '-' is not an option.
//	aString is assumed to not be NULL.
//

bool Options::isOption(const string& aString, int& argp) {
	if (aString[0] == getFlag()) {
		if (aString[1] == '\0') {
			argp++;
			return false;
		} else if (aString[1] == getFlag()) {
			if (aString[2] == '\0') {
				argp++;
				return false;
			} else {
				return true;
			}
		} else {
			return true;
		}
	} else {
		return false;
	}
}



//////////////////////////////
//
// Options::storeOption --
//

#define OPTION_FORM_SHORT     0
#define OPTION_FORM_LONG      1
#define OPTION_FORM_CONTINUE  2

int Options::storeOption(int index, int& position, int& running) {
	int optionForm;
	char tempname[1024];
	char optionType = '\0';

	if (running) {
		optionForm = OPTION_FORM_CONTINUE;
	} else if (m_argv[index][1] == getFlag()) {
		optionForm = OPTION_FORM_LONG;
	} else {
		optionForm = OPTION_FORM_SHORT;
	}

	switch (optionForm) {
		case OPTION_FORM_CONTINUE:
			position++;
			tempname[0] = m_argv[index][position];
			tempname[1] = '\0';
			optionType = getType(tempname);
			if (optionType != OPTION_BOOLEAN_TYPE) {
				running = 0;
				position++;
			}
			break;
		case OPTION_FORM_SHORT:
			position = 1;
			tempname[0] = m_argv[index][position];
			tempname[1] = '\0';
			optionType = getType(tempname);
			if (optionType != OPTION_BOOLEAN_TYPE) {
				position++;
			}
			break;
		case OPTION_FORM_LONG:
			position = 2;
			while (m_argv[index][position] != '=' &&
					m_argv[index][position] != '\0') {
				tempname[position-2] = m_argv[index][position];
				position++;
			}
			tempname[position-2] = '\0';
			optionType = getType(tempname);
			if ((unsigned char)optionType == 0xff) {         // suppressed --options option
				m_optionsArgQ = 1;
				break;
			}
			if (m_argv[index][position] == '=') {
				if (optionType == OPTION_BOOLEAN_TYPE) {
					m_error << "Error: boolean variable cannot have any options: "
						  << tempname << endl;
					return -1;
				}
				position++;
			}
			break;
	}

	if ((unsigned char)optionType == 0xff) {              // suppressed --options option
		m_optionsArgQ = 1;
		index++;
		position = 0;
		return index;
	}

	if (m_argv[index][position] == '\0' &&
			optionType != OPTION_BOOLEAN_TYPE) {
		index++;
		position = 0;
	}

	if (optionForm != OPTION_FORM_LONG && optionType == OPTION_BOOLEAN_TYPE &&
			m_argv[index][position+1] != '\0') {
		running = 1;
	} else if (optionType == OPTION_BOOLEAN_TYPE &&
			m_argv[index][position+1] == '\0') {
		running = 0;
	}

	if (index >= (int)m_argv.size()) {
		m_error << "Error: last option requires a parameter" << endl;
		return -1;
	}
	setModified(tempname, &m_argv[index][position]);

	if (!running) {
		index++;
	}
	return index;
}



//////////////////////////////
//
// Options::printOptionList --
//

ostream& Options::printOptionList(ostream& out) {
	for (auto it = m_optionList.begin(); it != m_optionList.end(); it++) {
		out << it->first << "\t" << it->second << endl;
	}
	return out;
}



//////////////////////////////
//
// Options::printOptionBooleanState --
//

ostream& Options::printOptionListBooleanState(ostream& out) {
	for (auto it = m_optionList.begin(); it != m_optionList.end(); it++) {
		out << it->first << "\t"
			 << m_optionRegister[it->second]->isModified() << endl;
	}
	return out;
}



//////////////////////////////
//
// Options::printRegister --
//

ostream& Options::printRegister(ostream& out) {
	for (auto it = m_optionRegister.begin(); it != m_optionRegister.end(); it++) {
		(*it)->print(out);
	}
	return out;
}



/////////////////////////////
//
// Options::hasParseError -- Returns true if there was an error parsing
//     the arguments.
//

bool Options::hasParseError(void) {
	return !m_error.str().empty();
}



//////////////////////////////
//
// Options::getParseError --
//

string Options::getParseError(void) {
	return m_error.str();
}


ostream& Options::getParseError(ostream& out) {
	out << m_error.str();
	return m_error;
}




//////////////////////////////
//
// PixelColor::PixelColor --
//

PixelColor::PixelColor(void) {
	// do nothing
}


PixelColor::PixelColor(const PixelColor& color) {
	Red   = color.Red;
	Green = color.Green;
	Blue  = color.Blue;
}

PixelColor::PixelColor(const string& color) {
	setColor(color);
}

PixelColor::PixelColor(int red, int green, int blue) {
	Red   = (unsigned int)limit(red, 0, 255);
	Green = (unsigned int)limit(green, 0, 255);
	Blue  = (unsigned int)limit(blue, 0, 255);
}

PixelColor::PixelColor(float red, float green, float blue) {
	Red   = (unsigned int)floatToChar(red);
	Green = (unsigned int)floatToChar(green);
	Blue  = (unsigned int)floatToChar(blue);
}

PixelColor::PixelColor(double red, double green, double blue) {
	Red   = (unsigned int)limit(floatToChar((float)red), 0, 255);
	Green = (unsigned int)limit(floatToChar((float)green), 0, 255);
	Blue  = (unsigned int)limit(floatToChar((float)blue), 0, 255);
}



//////////////////////////////
//
// PixelColor::~PixelColor --
//

PixelColor::~PixelColor() {
	// do nothing
}



//////////////////////////////
//
// PixelColor::invert -- negate the color.
//

void PixelColor::invert(void) {
	Red   = ~Red;
	Green = ~Green;
	Blue  = ~Blue;
}



//////////////////////////////
//
// PixelColor::getRed --
//

int PixelColor::getRed(void) {
	return Red;
}



//////////////////////////////
//
// PixelColor::getGreen --
//

int PixelColor::getGreen(void) {
	return Green;
}



//////////////////////////////
//
// PixelColor::getBlue --
//

int PixelColor::getBlue(void) {
	return Blue;
}



//////////////////////////////
//
// PixelColor::setRed --
//

void PixelColor::setRed(int value) {
	Red = (unsigned char)limit(value, 0, 255);
}



//////////////////////////////
//
// PixelColor::setGreen --
//

void PixelColor::setGreen(int value) {
	Green = (unsigned char)limit(value, 0, 255);
}



//////////////////////////////
//
// PixelColor::setBlue --
//

void PixelColor::setBlue(int value) {
	Blue = (unsigned char)limit(value, 0, 255);
}



//////////////////////////////
//
// PixelColor::getRedF --
//

float PixelColor::getRedF(void) {
	return charToFloat(Red);
}


//////////////////////////////
//
// PixelColor::getGreenF --
//

float PixelColor::getGreenF(void) {
	return charToFloat(Green);
}


//////////////////////////////
//
// PixelColor::getBlueF --
//

float PixelColor::getBlueF(void) {
	return charToFloat(Blue);
}


//////////////////////////////
//
// PixelColor::setRedF --
//

void PixelColor::setRedF(float value) {
	Red = (unsigned int)floatToChar(value);
}


//////////////////////////////
//
// PixelColor::setGreenF --
//

void PixelColor::setGreenF(float value) {
	Green = (unsigned int)floatToChar(value);
}


//////////////////////////////
//
// PixelColor::setBlueF --
//

void PixelColor::setBlueF(float value) {
	Blue = (unsigned int)floatToChar(value);
}


//////////////////////////////
//
// PixelColor::setColor --
//

void PixelColor::setColor(PixelColor& color) {
	Red   = color.Red;
	Green = color.Green;
	Blue  = color.Blue;
}



//////////////////////////////
//
// PixelColor::setColor --
//

PixelColor& PixelColor::setColor(int red, int green, int blue) {
	Red   = (unsigned int)limit(red, 0, 255);
	Green = (unsigned int)limit(green, 0, 255);
	Blue  = (unsigned int)limit(blue, 0, 255);
	return *this;
}



//////////////////////////////
//
// PixelColor::setColor -- set the contents to the specified value.
//

PixelColor& PixelColor::setColor(const string& colorstring) {
	PixelColor color;
	color = getColor(colorstring);
	Red   = color.Red;
	Green = color.Green;
	Blue  = color.Blue;

	return *this;
}


//////////////////////////////
//
// PixelColor::makeGrey --
//

PixelColor& PixelColor::makeGrey(void) {
	unsigned char average = limit((int)(((int)Red+(int)Green+(int)Blue)/3.0+0.5),0,255);
	Red = Green = Blue = average;
	return *this;
}


//////////////////////////////
//
// PixelColor::setGrayNormalized --  input in the range from 0.0 to 1.0.
//

PixelColor& PixelColor::setGrayNormalized(double value) {
	int graylevel = int(value * 256.0);
	if (graylevel >= 256) {
		graylevel = 255;
	}
	if (graylevel < 0) {
		graylevel = 0;
	}
	Red = Green = Blue = graylevel;
	return *this;
}

PixelColor& PixelColor::setGreyNormalized(double value) {
	return setGrayNormalized(value);
}


//////////////////////////////
//
// PixelColor::makeGray --
//

PixelColor& PixelColor::makeGray(void) {
	return makeGrey();
}


//////////////////////////////
//
// PixelColor::operator> --
//

int PixelColor::operator>(int number) {
	if (Red   <= number) return 0;
	if (Green <= number) return 0;
	if (Blue  <= number) return 0;
	return 1;
}



//////////////////////////////
//
// PixelColor::operator< --
//

int PixelColor::operator<(int number) {
	if (Red   >= number) return 0;
	if (Green >= number) return 0;
	if (Blue  >= number) return 0;
	return 1;
}



//////////////////////////////
//
// PixelColor::operator== --
//

int PixelColor::operator==(PixelColor& color) {
	if (Red != color.Red) {
		return 0;
	}
	if (Green != color.Green) {
		return 0;
	}
	if (Blue != color.Blue) {
		return 0;
	}
	return 1;
}



//////////////////////////////
//
// PixelColor::operator!= --
//

int PixelColor::operator!=(PixelColor& color) {
	if ((Red == color.Red) && (Green == color.Green) && (Blue == color.Blue)) {
		return 0;
	} else {
		return 1;
	}
}



//////////////////////////////
//
// PixelColor::operator*= --
//

PixelColor& PixelColor::operator*=(double number) {
	Red = (unsigned char)limit(floatToChar(charToFloat(Red)*number),     0, 255);
	Green = (unsigned char)limit(floatToChar(charToFloat(Green)*number), 0, 255);
	Blue = (unsigned char)limit(floatToChar(charToFloat(Blue)*number),   0, 255);
	return *this;
}



//////////////////////////////
//
// PixelColor::operator= --
//

PixelColor& PixelColor::operator=(PixelColor color) {
	if (this == &color) {
		return *this;
	}
	Red   = color.Red;
	Green = color.Green;
	Blue  = color.Blue;
	return *this;
}


PixelColor& PixelColor::operator=(int value) {
	Red   = (unsigned char)limit(value, 0, 255);
	Green = Red;
	Blue  = Red;
	return *this;
}



//////////////////////////////
//
// PixelColor::operator+ --
//

PixelColor PixelColor::operator+(PixelColor& color) {
	PixelColor output;
	output.Red   = (unsigned char)limit((int)Red   + color.Red,   0, 255);
	output.Green = (unsigned char)limit((int)Green + color.Green, 0, 255);
	output.Blue  = (unsigned char)limit((int)Blue  + color.Blue,  0, 255);
	return output;
}



//////////////////////////////
//
// PixelColor::operator+= --
//

PixelColor& PixelColor::operator+=(int number) {
	setRed(getRed()     + number);
	setGreen(getGreen() + number);
	setBlue(getBlue()   + number);
	return *this;
}

//////////////////////////////
//
// PixelColor::operator- --
//

PixelColor PixelColor::operator-(PixelColor& color) {
	PixelColor output;
	output.Red   = (unsigned char)limit((int)Red   - color.Red,   0, 255);
	output.Green = (unsigned char)limit((int)Green - color.Green, 0, 255);
	output.Blue  = (unsigned char)limit((int)Blue  - color.Blue,  0, 255);
	return output;
}



//////////////////////////////
//
// PixelColor::operator* --
//

PixelColor PixelColor::operator*(PixelColor& color) {
	PixelColor output;
	output.Red   = (unsigned char)limit(floatToChar(charToFloat(Red)*charToFloat(color.Red)), 0, 255);
	output.Green = (unsigned char)limit(floatToChar(charToFloat(Green)*charToFloat(color.Green)), 0, 255);
	output.Blue  = (unsigned char)limit(floatToChar(charToFloat(Blue)*charToFloat(color.Blue)), 0, 255);
	return output;
}


PixelColor PixelColor::operator*(double number) {
	PixelColor output;
	output.Red   = (unsigned char)limit(floatToChar(charToFloat(Red)*number),   0, 255);
	output.Green = (unsigned char)limit(floatToChar(charToFloat(Green)*number), 0, 255);
	output.Blue  = (unsigned char)limit(floatToChar(charToFloat(Blue)*number),  0, 255);
	return output;
}


PixelColor PixelColor::operator*(int number) {
	PixelColor output;
	output.Red   = (unsigned char)limit(floatToChar(charToFloat(Red)*number),   0, 255);
	output.Green = (unsigned char)limit(floatToChar(charToFloat(Green)*number), 0, 255);
	output.Blue  = (unsigned char)limit(floatToChar(charToFloat(Blue)*number),  0, 255);
	return output;
}



//////////////////////////////
//
// PixelColor::operator/ --
//

PixelColor PixelColor::operator/(double number) {
	PixelColor output;
	output.Red   = (unsigned char)limit(floatToChar(charToFloat(Red)/number),   0, 255);
	output.Green = (unsigned char)limit(floatToChar(charToFloat(Green)/number), 0, 255);
	output.Blue  = (unsigned char)limit(floatToChar(charToFloat(Blue)/number),  0, 255);
	return output;
}

PixelColor PixelColor::operator/(int number) {
	PixelColor output;
	output.Red   = (unsigned char)limit(floatToChar(charToFloat(Red)/(double)number),   0, 255);
	output.Green = (unsigned char)limit(floatToChar(charToFloat(Green)/(double)number), 0, 255);
	output.Blue  = (unsigned char)limit(floatToChar(charToFloat(Blue)/(double)number),  0, 255);
	return output;
}



//////////////////////////////
//
// PixelColor::getColor -- Switch named colors to SVG list.
//

PixelColor PixelColor::getColor(const string& colorstring) {
	PixelColor output;
	int start = 0;
	int hasdigit  = 0;
	int length = (int)colorstring.size();
	if (length > 128) {
		cout << "ERROR: color string too long: " << colorstring << endl;
		exit(1);
	}
	if (length == 7) {
		if (colorstring[0] == '#') {
			hasdigit = 1;
			start = 1;
		}
	} else if (length == 6) {
		int allxdigit = 1;
		start = 0;
		for (int i=start; i<length; i++) {
			allxdigit = allxdigit && isxdigit(colorstring[i]);
		}
		if (allxdigit) {
			hasdigit = 1;
		} else {
			hasdigit = 0;
		}
	}

	// check for decimal strings with spaces around numbers: "255 255 255"
	if ((colorstring.find(' ') != string::npos) ||
		 (colorstring.find('\t') != string::npos)) {
		char buffer[256] = {0};
		strcpy(buffer, colorstring.c_str());
		char* ptr = strtok(buffer, " \t\n:;");
		int tred   = -1;
		int tgreen = -1;
		int tblue  = -1;
		if (ptr != NULL) {
			sscanf(ptr, "%d", &tred);
			ptr = strtok(NULL, " \t\n;:");
		}
		if (ptr != NULL) {
			sscanf(ptr, "%d", &tgreen);
			ptr = strtok(NULL, " \t\n;:");
		}
		if (ptr != NULL) {
			sscanf(ptr, "%d", &tblue);
			ptr = strtok(NULL, " \t\n;:");
		}
		if (tred > 0 && tgreen > 0 && tblue > 0) {
			output.setColor(tred, tgreen, tblue);
			return output;
		}
	}

	if (hasdigit) {
		string piece1 = colorstring.substr(start,   2);
		string piece2 = colorstring.substr(start+2, 2);
		string piece3 = colorstring.substr(start+4, 2);

		int rval = (int)strtol(piece1.c_str(), NULL, 16);
		int gval = (int)strtol(piece2.c_str(), NULL, 16);
		int bval = (int)strtol(piece3.c_str(), NULL, 16);

		output.setColor(rval, gval, bval);
		return output;
	}

	// color string
	output.setColor(0,0,0);
	const string& cs = colorstring;

	char fc = '\0';
	if (!cs.empty()) {
		fc = cs[0];
	}

	switch (fc) {
	case 'a':
	if (cs == "aliceblue"           ) { output.setColor("#f0f8ff");  return output; }
	if (cs == "antiquewhite"        ) { output.setColor("#faebd7");  return output; }
	if (cs == "aqua"                ) { output.setColor("#00ffff");  return output; }
	if (cs == "aquamarine"          ) { output.setColor("#7fffd4");  return output; }
	if (cs == "azure"               ) { output.setColor("#f0ffff");  return output; }
	break; case 'b':
	if (cs == "beige"               ) { output.setColor("#f5f5dc");  return output; }
	if (cs == "bisque"              ) { output.setColor("#ffe4c4");  return output; }
	if (cs == "black"               ) { output.setColor("#000000");  return output; }
	if (cs == "blanchediamond"      ) { output.setColor("#ffebcd");  return output; }
	if (cs == "blue"                ) { output.setColor("#0000ff");  return output; }
	if (cs == "blueviolet"          ) { output.setColor("#8a2be2");  return output; }
	if (cs == "brown"               ) { output.setColor("#a52a2a");  return output; }
	if (cs == "burlywood"           ) { output.setColor("#ffe4c4");  return output; }
	break; case 'c':
	if (cs == "cadetblue"           ) { output.setColor("#5f9ea0");  return output; }
	if (cs == "chartreuse"          ) { output.setColor("#7fff00");  return output; }
	if (cs == "coral"               ) { output.setColor("#ff7f50");  return output; }
	if (cs == "cornflowerblue"      ) { output.setColor("#6495ed");  return output; }
	if (cs == "cornsilk"            ) { output.setColor("#fff8dc");  return output; }
	if (cs == "crimson"             ) { output.setColor("#dc143c");  return output; }
	if (cs == "cyan"                ) { output.setColor("#00ffff");  return output; }
	break; case 'd':
	if (cs == "darkblue"            ) { output.setColor("#00008b");  return output; }
	if (cs == "darkcyan"            ) { output.setColor("#008b8b");  return output; }
	if (cs == "darkgoldenrod"       ) { output.setColor("#b8860b");  return output; }
	if (cs == "darkgray"            ) { output.setColor("#a9a9a9");  return output; }
	if (cs == "darkgrey"            ) { output.setColor("#a9a9a9");  return output; }
	if (cs == "darkgreen"           ) { output.setColor("#006400");  return output; }
	if (cs == "darkkhaki"           ) { output.setColor("#bdb76b");  return output; }
	if (cs == "darkmagenta"         ) { output.setColor("#8b008b");  return output; }
	if (cs == "darkolivegreen"      ) { output.setColor("#556b2f");  return output; }
	if (cs == "darkorange"          ) { output.setColor("#ff8c00");  return output; }
	if (cs == "darkorchid"          ) { output.setColor("#9932cc");  return output; }
	if (cs == "darkred"             ) { output.setColor("#8b0000");  return output; }
	if (cs == "darksalmon"          ) { output.setColor("#e9967a");  return output; }
	if (cs == "darkseagreen"        ) { output.setColor("#8dbc8f");  return output; }
	if (cs == "darkslateblue"       ) { output.setColor("#483d8b");  return output; }
	if (cs == "darkslategray"       ) { output.setColor("#2e4e4e");  return output; }
	if (cs == "darkslategrey"       ) { output.setColor("#2e4e4e");  return output; }
	if (cs == "darkturquoise"       ) { output.setColor("#00ded1");  return output; }
	if (cs == "darkviolet"          ) { output.setColor("#9400d3");  return output; }
	if (cs == "deeppink"            ) { output.setColor("#ff1493");  return output; }
	if (cs == "deepskyblue"         ) { output.setColor("#00bfff");  return output; }
	if (cs == "dimgray"             ) { output.setColor("#696969");  return output; }
	if (cs == "dimgrey"             ) { output.setColor("#696969");  return output; }
	if (cs == "dodgerblue"          ) { output.setColor("#1e90ff");  return output; }
	break; case 'f':
	if (cs == "firebrick"           ) { output.setColor("#b22222");  return output; }
	if (cs == "floralwhite"         ) { output.setColor("#fffaf0");  return output; }
	if (cs == "forestgreen"         ) { output.setColor("#228b22");  return output; }
	if (cs == "fuchsia"             ) { output.setColor("#ff00ff");  return output; }
	break; case 'g':
	if (cs == "gainsboro"           ) { output.setColor("#dcdcdc");  return output; }
	if (cs == "ghostwhite"          ) { output.setColor("#f8f8ff");  return output; }
	if (cs == "gold"                ) { output.setColor("#ffd700");  return output; }
	if (cs == "goldenrod"           ) { output.setColor("#daa520");  return output; }
	if (cs == "gray"                ) { output.setColor("#808080");  return output; }
	if (cs == "grey"                ) { output.setColor("#808080");  return output; }
	if (cs == "green"               ) { output.setColor("#008000");  return output; }
	if (cs == "greenyellow"         ) { output.setColor("#adff2f");  return output; }
	break; case 'h':
	if (cs == "honeydew"            ) { output.setColor("#f0fff0");  return output; }
	if (cs == "hotpink"             ) { output.setColor("#ff69b4");  return output; }
	if (cs == "indianred"           ) { output.setColor("#cd5c5c");  return output; }
	break; case 'i':
	if (cs == "indigo"              ) { output.setColor("#4b0082");  return output; }
	if (cs == "ivory"               ) { output.setColor("#fffff0");  return output; }
	break; case 'k':
	if (cs == "khaki"               ) { output.setColor("#f0e68c");  return output; }
	break; case 'l':
	if (cs == "lavenderblush"       ) { output.setColor("#fff0f5");  return output; }
	if (cs == "lavender"            ) { output.setColor("#e6e6fa");  return output; }
	if (cs == "lawngreen"           ) { output.setColor("#7cfc00");  return output; }
	if (cs == "lemonchiffon"        ) { output.setColor("#fffacd");  return output; }
	if (cs == "lightblue"           ) { output.setColor("#add8e6");  return output; }
	if (cs == "lightorange"         ) { output.setColor("#ff9c00");  return output; }
	if (cs == "lightcoral"          ) { output.setColor("#f08080");  return output; }
	if (cs == "lightcyan"           ) { output.setColor("#e0ffff");  return output; }
	if (cs == "lightgoldenrodyellow") { output.setColor("#fafad2");  return output; }
	if (cs == "lightgreen"          ) { output.setColor("#90ee90");  return output; }
	if (cs == "lightgrey"           ) { output.setColor("#d3d3d3");  return output; }
	if (cs == "lightpink"           ) { output.setColor("#ffb6c1");  return output; }
	if (cs == "lightsalmon"         ) { output.setColor("#ffa07a");  return output; }
	if (cs == "lightseagreen"       ) { output.setColor("#20b2aa");  return output; }
	if (cs == "lightskyblue"        ) { output.setColor("#87cefa");  return output; }
	if (cs == "lightslategray"      ) { output.setColor("#778899");  return output; }
	if (cs == "lightslategrey"      ) { output.setColor("#778899");  return output; }
	if (cs == "lightsteelblue"      ) { output.setColor("#b0c4de");  return output; }
	if (cs == "lightyellow"         ) { output.setColor("#ffffe0");  return output; }
	if (cs == "lime"                ) { output.setColor("#00ff00");  return output; }
	if (cs == "limegreen"           ) { output.setColor("#32cd32");  return output; }
	if (cs == "linen"               ) { output.setColor("#faf0e6");  return output; }
	break; case 'm':
	if (cs == "magenta"             ) { output.setColor("#ff00ff");  return output; }
	if (cs == "maroon"              ) { output.setColor("#800000");  return output; }
	if (cs == "maroon"              ) { output.setColor("#800000");  return output; }
	if (cs == "mediumaquamarine"    ) { output.setColor("#66cdaa");  return output; }
	if (cs == "mediumblue"          ) { output.setColor("#0000cd");  return output; }
	if (cs == "mediumorchid"        ) { output.setColor("#ba55d3");  return output; }
	if (cs == "mediumpurple"        ) { output.setColor("#9370db");  return output; }
	if (cs == "mediumseagreen"      ) { output.setColor("#3cb371");  return output; }
	if (cs == "mediumslateblue"     ) { output.setColor("#7b68ee");  return output; }
	if (cs == "mediumspringgreen"   ) { output.setColor("#00fa9a");  return output; }
	if (cs == "mediumturquoise"     ) { output.setColor("#48d1cc");  return output; }
	if (cs == "mediumvioletred"     ) { output.setColor("#c71585");  return output; }
	if (cs == "midnightblue"        ) { output.setColor("#191970");  return output; }
	if (cs == "mintcream"           ) { output.setColor("#f5fffa");  return output; }
	if (cs == "mistyrose"           ) { output.setColor("#ffe4e1");  return output; }
	if (cs == "moccasin"            ) { output.setColor("#ffe4b5");  return output; }
	break; case 'n':
	if (cs == "navajowhite"         ) { output.setColor("#ffdead");  return output; }
	if (cs == "navy"                ) { output.setColor("#000080");  return output; }
	if (cs == "navy"                ) { output.setColor("#000080");  return output; }
	break; case 'o':
	if (cs == "oldlace"             ) { output.setColor("#fdf5e6");  return output; }
	if (cs == "olive"               ) { output.setColor("#6b8e23");  return output; }
	if (cs == "olivedrab"           ) { output.setColor("#6b8e23");  return output; }
	if (cs == "orange"              ) { output.setColor("#ff4500");  return output; }
	if (cs == "orangered"           ) { output.setColor("#ff4500");  return output; }
	if (cs == "orchid"              ) { output.setColor("#da70d6");  return output; }
	break; case 'p':
	if (cs == "palegoldenrod"       ) { output.setColor("#eee8aa");  return output; }
	if (cs == "palegreen"           ) { output.setColor("#98fb98");  return output; }
	if (cs == "paleturquoise"       ) { output.setColor("#afeeee");  return output; }
	if (cs == "palevioletred"       ) { output.setColor("#db7093");  return output; }
	if (cs == "papayawhip"          ) { output.setColor("#ffefd5");  return output; }
	if (cs == "peachpuff"           ) { output.setColor("#ffdab9");  return output; }
	if (cs == "peru"                ) { output.setColor("#cd853f");  return output; }
	if (cs == "pink"                ) { output.setColor("#ffc8cb");  return output; }
	if (cs == "plum"                ) { output.setColor("#dda0dd");  return output; }
	if (cs == "powderblue"          ) { output.setColor("#b0e0e6");  return output; }
	if (cs == "purple"              ) { output.setColor("#800080");  return output; }
	if (cs == "purple"              ) { output.setColor("#800080");  return output; }
	break; case 'q':
	if (cs == "quartz"              ) { output.setColor("#c9c9f3");  return output; }
	break; case 'r':
	if (cs == "red"                 ) { output.setColor("#ff0000");  return output; }
	if (cs == "rosybrown"           ) { output.setColor("#bc8f8f");  return output; }
	if (cs == "royalblue"           ) { output.setColor("#4169e1");  return output; }
	break; case 's':
	if (cs == "saddlebrown"         ) { output.setColor("#8b4513");  return output; }
	if (cs == "salmon"              ) { output.setColor("#fa8072");  return output; }
	if (cs == "sandybrown"          ) { output.setColor("#f4a460");  return output; }
	if (cs == "seagreen"            ) { output.setColor("#2e8b57");  return output; }
	if (cs == "seashell"            ) { output.setColor("#fff5ee");  return output; }
	if (cs == "sienna"              ) { output.setColor("#a0522d");  return output; }
	if (cs == "silver"              ) { output.setColor("#c0c0c0");  return output; }
	if (cs == "silver"              ) { output.setColor("#c0c0c0");  return output; }
	if (cs == "skyblue"             ) { output.setColor("#87ceeb");  return output; }
	if (cs == "slateblue"           ) { output.setColor("#6a5acd");  return output; }
	if (cs == "snow"                ) { output.setColor("#fffafa");  return output; }
	if (cs == "steelblue"           ) { output.setColor("#4682b4");  return output; }
	break; case 't':
	if (cs == "tan"                 ) { output.setColor("#d2b48c");  return output; }
	if (cs == "teal"                ) { output.setColor("#008080");  return output; }
	if (cs == "thistle"             ) { output.setColor("#d8bfd8");  return output; }
	if (cs == "tomato"              ) { output.setColor("#ff6347");  return output; }
	if (cs == "turquoise"           ) { output.setColor("#40e0d0");  return output; }
	break; case 'v':
	if (cs == "violet"              ) { output.setColor("#ee82ee");  return output; }
	break; case 'w':
	if (cs == "wheat"               ) { output.setColor("#f5deb3");  return output; }
	if (cs == "white"               ) { output.setColor("#ffffff");  return output; }
	if (cs == "white"               ) { output.setColor("#ffffff");  return output; }
	if (cs == "whitesmoke"          ) { output.setColor("#f5f5f5");  return output; }
	break; case 'y':
	if (cs == "yellow"              ) { output.setColor("#ffff00");  return output; }
	if (cs == "yellowgreen"         ) { output.setColor("#9acd32");  return output; }
	}

	// References:
	//            http://netdancer.com/rgbblk.htm
	//            http://www.htmlhelp.com/cgi-bin/color.cgi?rgb=FFFFFF
	//            http://www.brobstsystems.com/colors1.htm

	return output;
}



//////////////////////////////
//
// PixelColor::writePpm6 -- write the pixel in PPM 6 format.
//

void PixelColor::writePpm6(ostream& out) {
	out << (unsigned char)getRed() << (unsigned char)getGreen() << (unsigned char)getBlue();
}

void PixelColor::writePpm3(ostream& out) {
	out << (int)getRed()   << " "
		 << (int)getGreen() << " "
		 << (int)getBlue()  << " ";
}



//////////////////////////////
//
// PixelColor::setHue --
//

PixelColor& PixelColor::setHue(float value) {
	double fraction = value - (int)value;
	if (fraction < 0) {
		fraction = fraction + 1.0;
	}

	if (fraction < 1.0/6.0) {
		Red   = 255;
		Green = (unsigned char)limit(floatToChar(6.0 * fraction), 0, 255);
		Blue  = 0;
	} else if (fraction < 2.0/6.0) {
		Red   = (unsigned char)limit(255 - floatToChar(6.0 * (fraction - 1.0/6.0)), 0,255);
		Green = 255;
		Blue  = 0;
	} else if (fraction < 3.0/6.0) {
		Red   = 0;
		Green = 255;
		Blue  = (unsigned char)limit(floatToChar(6.0 * (fraction - 2.0/6.0)), 0,255);
	} else if (fraction < 4.0/6.0) {
		Red   = 0;
		Blue  = 255;
		Green = (unsigned char)limit(255 - floatToChar(6.0 * (fraction - 3.0/6.0)), 0,255);
	} else if (fraction < 5.0/6.0) {
		Red   = (unsigned char)limit(floatToChar(6.0 * (fraction - 4.0/6.0)), 0,255);
		Green = 0;
		Blue  = 255;
	} else if (fraction <= 6.0/6.0) {
		Red   = 255;
		Green = 0;
		Blue  = (unsigned char)limit(255 - floatToChar(6.0 * (fraction - 5.0/6.0)), 0,255);
	} else {
		Red   = 0;
		Green = 0;
		Blue  = 0;
	}

	return *this;
}



//////////////////////////////
//
// PixelColor::setTriHue -- Red, Green, Blue with a little overlap
//

PixelColor& PixelColor::setTriHue(float value) {
	double fraction = value - (int)value;
	if (fraction < 0) {
		fraction = fraction + 1.0;
	}
	if (fraction < 1.0/3.0) {
		Green = (unsigned char)limit(floatToChar(3.0 * fraction), 0, 255);
		Red   = (unsigned char)limit(255 - Green, 0, 255);
		Blue  = 0;
	} else if (fraction < 2.0/3.0) {
		setBlue(floatToChar(3.0 * (fraction - 1.0/3.0)));
		setGreen(255 - getBlue());
		setRed(0);
	} else {
		setRed(floatToChar(3.0 * (fraction - 2.0/3.0)));
		setBlue(255 - Red);
		setGreen(0);
	}

	return *this;
}




//////////////////////////////////////////////////////////////////////////
//
// private functions:
//


//////////////////////////////
//
// PixelColor::charToFloat --
//

float PixelColor::charToFloat(int value) {
	return value / 255.0;
}


//////////////////////////////
//
// PixelColor::floatToChar --
//

int PixelColor::floatToChar(float value) {
	return limit((int)(value * 255.0 + 0.5), 0, 255);
}


//////////////////////////////
//
// limit --
//

int PixelColor::limit(int value, int min, int max) {
	if (value < min) {
		value = min;
	} else if (value > max) {
		value = max;
	}
	return value;
}



//////////////////////////////
//
// PixelColor:mix -- mix two colors together.
//

PixelColor PixelColor::mix(PixelColor& color1, PixelColor& color2) {

	PixelColor p1 = color1.getHsi();
	PixelColor p2 = color2.getHsi();

	PixelColor output;
	unsigned int r = ((unsigned int)color1.Red + (unsigned int)color2.Red)/2;
	unsigned int g = ((unsigned int)color1.Green + (unsigned int)color2.Green)/2;
	unsigned int b = ((unsigned int)color1.Blue + (unsigned int)color2.Blue)/2;

	output.setRed(r);
	output.setGreen(g);
	output.setBlue(b);

	return output;
}



//////////////////////////////
//
// PixelColor::rgb2hsi -- convert from RGB color space to HSI color space.
//     You have to keep track of color space used by pixel since RGB/HSI
//     state is not stored in pixel.
//

PixelColor& PixelColor::rgb2hsi(void) {

	// Convert RGB into range from 0 to 255.0:
	double R = Red   / 255.0;
	double G = Green / 255.0;
	double B = Blue  / 255.0;

	// HSI will be in the range from 0.0 to 1.0;
	double H = 0.0; // will be stored in Red parameter
	double S = 0.0; // will be stored in Green parameter
	double I = 0.0; // will be stored in Blue parameter

	double min = R;
	if (G < min) min = G;
	if (B < min) min = B;

	I = (R+G+B)/3.0;
	S = 1 - min/I;
	if (S == 0.0) {
		H = 0.0;
	} else {
		H = ((R-G)+(R-B))/2.0;
		H = H/sqrt((R-G)*(R-G) + (R-B)*(G-B));
		H = acos(H);
		if (B > G) {
			H = 2*M_PI - H;
		}
		H = H/(2*M_PI);
	}

	// Adjust output range from 0 to 255:
	int h = (int)(H  * 255.0 + 0.5);
	if (h < 0)   { h = 0; }
	if (h > 255) { h = 255; }

	int s = (int)(S  * 255.0 + 0.5);
	if (s < 0)   { s = 0; }
	if (s > 255) { s = 255; }

	int i = (int)(I  * 255.0 + 0.5);
	if (i < 0)   { i = 0; }
	if (i > 255) { i = 255; }

	Red   = h;
	Green = s;
	Blue  = i;

	return *this;
}



//////////////////////////////
//
// PixelColor::hsi2rgb -- convert from HSI color space to RGB color space.
//

PixelColor& PixelColor::hsi2rgb(void) {

	// Scale input HSI into the range from 0.0 to 1.0:
	double H = Red   / 255.0;
	double S = Green / 255.0;
	double I = Blue  / 255.0;

	double R = 0.0;
	double G = 0.0;
	double B = 0.0;

	if (H < 1.0/3.0) {
		B = (1-S)/3;
		R = (1+S*cos(2*M_PI*H)/cos(M_PI/3-2*M_PI*H))/3.0;
		G = 1 - (B + R);
	} else if (H < 2.0/3.0) {
		H = H - 1.0/3.0;
		R = (1-S)/3;
		G = (1+S*cos(2*M_PI*H)/cos(M_PI/3-2*M_PI*H))/3.0;
		B = 1 - (R+G);
	} else {
		H = H - 2.0/3.0;
		G = (1-S)/3;
		B = (1+S*cos(2*M_PI*H)/cos(M_PI/3-2*M_PI*H))/3.0;
		R = 1 - (G+B);
	}

	// Adjust output range from 0 to 255:
	int r = (int)(I * R * 3.0 * 255.0 + 0.5);
	if (r < 0)   { r = 0; }
	if (r > 255) { r = 255; }

	int g = (int)(I * G * 3.0 * 255.0 + 0.5);
	if (g < 0)   { g = 0; }
	if (g > 255) { g = 255; }

	int b = (int)(I * B * 3.0 * 255.0 + 0.5);
	if (b < 0)   { b = 0; }
	if (b > 255) { b = 255; }

	Red   = r;
	Green = g;
	Blue  = b;

	return *this;
}



//////////////////////////////
//
// PixelColor::getHsi -- convert from RGB color space to HSI color space.
//     You have to keep track of color space used by pixel since RGB/HSI
//     state is not stored in pixel.
//

PixelColor PixelColor::getHsi(void) {
	PixelColor tempColor = *this;
	tempColor.rgb2hsi();
	return tempColor;
}



//////////////////////////////
//
// PixelColor::getRgb -- convert from HSI color space to RGB color space.
//     You have to keep track of color space used by pixel since RGB/HSI
//     state is not stored in pixel.
//

PixelColor PixelColor::getRgb(void) {
	PixelColor tempColor = *this;
	tempColor.hsi2rgb();
	return tempColor;
}


//////////////////////////////
//
// PixelColor::getHexColor --
//

string PixelColor::getHexColor(void) {
	string output = "#";
	unsigned char redA   = (Red   & 0xF0) >> 4;
	unsigned char redB   = (Red   & 0x0F);
	unsigned char greenA = (Green & 0xF0) >> 4;
	unsigned char greenB = (Green & 0x0F);
	unsigned char blueA  = (Blue  & 0xF0) >> 4;
	unsigned char blueB  = (Blue  & 0x0F);

	if (redA < 10) {
		output += '0' + redA;
	} else {
		output += 'A' + redA - 10;
	}
	if (redB < 10) {
		output += '0' + redB;
	} else {
		output += 'A' + redB - 10;
	}

	if (greenA < 10) {
		output += '0' + greenA;
	} else {
		output += 'A' + greenA - 10;
	}
	if (greenB < 10) {
		output += '0' + greenB;
	} else {
		output += 'A' + greenB - 10;
	}

	if (blueA < 10) {
		output += '0' + blueA;
	} else {
		output += 'A' + blueA - 10;
	}
	if (blueB < 10) {
		output += '0' + blueB;
	} else {
		output += 'A' + blueB - 10;
	}

	return output;
}


///////////////////////////////////////////////////////////////////////////
//
// other functions
//


//////////////////////////////
//
// operator<< --
//
// for use with P3 ASCII pnm images: print red green blue triplet.
//

ostream& operator<<(ostream& out, PixelColor apixel) {
	out << apixel.getRed() << ' ';
	out << apixel.getGreen() << ' ';
	out << apixel.getBlue();
	return out;
}




/////////////////////////////////
//
// Tool_1520ify::Tool_1520ify -- Set the recognized options for the tool.
//

Tool_1520ify::Tool_1520ify(void) {
	define("R|no-reference-records=b",                "do not add reference records");
	define("r|only-add-reference-records=b",          "only add reference records");

	define("B|do-not-delete-breaks=b",                "do not delete system/page break markers");
	define("b|only-delete-breaks=b",                  "only delete breaks");

	define("A|do-not-fix-instrument-abbreviations=b", "do not fix instrument abbreviations");
	define("a|only-fix-instrument-abbreviations=b",   "only fix instrument abbreviations");

	define("E|do-not-fix-editorial-accidentals=b",    "do not fix instrument abbreviations");
	define("e|only-fix-editorial-accidentals=b",      "only fix editorial accidentals");

	define("T|do-not-add-terminal-longs=b",           "do not add terminal long markers");
	define("t|only-add-terminal-longs=b",             "only add terminal longs");

	define("N|do-not-remove-empty-transpositions=b",  "do not remove empty transposition instructions");
	define ("n|only-remove-empty-transpositions=b",   "only remove empty transpositions");
}



/////////////////////////////////
//
// Tool_1520ify::run -- Primary interfaces to the tool.
//

bool Tool_1520ify::run(HumdrumFileSet& infiles) {
	bool status = true;
	for (int i=0; i<infiles.getCount(); i++) {
		status &= run(infiles[i]);
	}
	return status;
}


bool Tool_1520ify::run(const string& indata, ostream& out) {
	HumdrumFile infile(indata);
	bool status = run(infile);
	if (hasAnyText()) {
		getAllText(out);
	} else {
		out << infile;
	}
	return status;
}


bool Tool_1520ify::run(HumdrumFile& infile, ostream& out) {
	bool status = run(infile);
	if (hasAnyText()) {
		getAllText(out);
	} else {
		out << infile;
	}
	return status;
}

//
// In-place processing of file:
//

bool Tool_1520ify::run(HumdrumFile& infile) {
	processFile(infile);

	// Re-load the text for each line from their tokens.
	infile.createLinesFromTokens();

	// Need to adjust the line numbers for tokens for later
	// processing.
	m_humdrum_text << infile;
	return true;
}



//////////////////////////////
//
// Tool_1520ify::processFile --
//

void Tool_1520ify::processFile(HumdrumFile& infile) {

	bool abbreviationsQ  = true;
	bool accidentalsQ    = true;
	bool referencesQ     = true;
	bool terminalsQ      = true;
	bool breaksQ         = true;
	bool transpositionsQ = true;

	if (getBoolean("no-reference-records")) { referencesQ = false; }
	if (getBoolean("only-add-reference-records")) {
		abbreviationsQ  = false;
		accidentalsQ    = false;
		referencesQ     = true;
		terminalsQ      = false;
		breaksQ         = false;
		transpositionsQ = false;
	}

	if (getBoolean("do-not-delete-breaks")) { breaksQ = false; }
	if (getBoolean("only-delete-breaks")) {
		abbreviationsQ  = false;
		accidentalsQ    = false;
		referencesQ     = false;
		terminalsQ      = false;
		breaksQ         = true;
		transpositionsQ = false;
	}

	if (getBoolean("do-not-fix-instrument-abbreviations")) { abbreviationsQ = false; }
	if (getBoolean("only-fix-instrument-abbreviations")) {
		abbreviationsQ  = true;
		accidentalsQ    = false;
		referencesQ     = false;
		terminalsQ      = false;
		breaksQ         = false;
		transpositionsQ = false;
	}

	if (getBoolean("do-not-fix-editorial-accidentals")) { accidentalsQ = false; }
	if (getBoolean("only-fix-editorial-accidentals")) {
		abbreviationsQ  = false;
		accidentalsQ    = true;
		referencesQ     = false;
		terminalsQ      = false;
		breaksQ         = false;
		transpositionsQ = false;
	}

	if (getBoolean("do-not-add-terminal-longs")) { terminalsQ = false; }
	if (getBoolean("only-add-terminal-longs")) {
		abbreviationsQ  = false;
		accidentalsQ    = false;
		referencesQ     = false;
		terminalsQ      = true;
		breaksQ         = false;
		transpositionsQ = false;
	}

	if (getBoolean("do-not-remove-empty-transpositions")) { transpositionsQ = false; }
	if (getBoolean("only-remove-empty-transpositions")) {
		abbreviationsQ  = false;
		accidentalsQ    = false;
		referencesQ     = false;
		terminalsQ      = false;
		breaksQ         = false;
		transpositionsQ = true;
	}

	if (abbreviationsQ)  { fixInstrumentAbbreviations(infile); }
	if (accidentalsQ)    { fixEditorialAccidentals(infile); }
	if (referencesQ)     { addBibliographicRecords(infile); }
	if (terminalsQ)      { addTerminalLongs(infile); }
	if (breaksQ)         { deleteBreaks(infile); }
	if (transpositionsQ) { deleteDummyTranspositions(infile); }

	adjustSystemDecoration(infile);

	// Input lyrics may contain "=" signs which are to be converted into
	// spaces in **text data, and into elisions when displaying with verovio.
	Tool_shed shed;
	vector<string> argv;
	argv.push_back("shed");
	argv.push_back("-x");     // only apply to **text spines
	argv.push_back("text");
	argv.push_back("-e");
	argv.push_back("s/=/ /g");
	shed.process(argv);
	shed.run(infile);
}



//////////////////////////////
//
// Tool_1520ify::adjustSystemDecoration --
//    !!!system-decoration: [(s1)(s2)(s3)(s4)]
// to:
//    !!!system-decoration: [*]
//

void Tool_1520ify::adjustSystemDecoration(HumdrumFile& infile) {
	for (int i=infile.getLineCount() - 1; i>=0; i--) {
		if (!infile[i].isReference()) {
			continue;
		}
		HTp token = infile.token(i, 0);
		if (token->compare(0, 21, "!!!system-decoration:") == 0) {
			token->setText("!!!system-decoration: [*]");
			break;
		}
	}
}



//////////////////////////////
//
// Tool_1520ify::deleteDummyTranspositions -- Somehow empty
//    transpositions that go to the same pitch can appear in the
//    MusicXML data, so remove them here.  Example:
// 		*Trd0c0
//

void Tool_1520ify::deleteDummyTranspositions(HumdrumFile& infile) {
	std::vector<int> ldel;
	for (int i=0; i<infile.getLineCount(); i++) {
		if (!infile[i].hasSpines()) {
			continue;
		}
		if (!infile[i].isInterpretation()) {
			continue;
		}
		bool empty = true;
		for (int j=0; j<infile[i].getFieldCount(); j++) {
			HTp token = infile.token(i, j);
			if (*token == "*") {
				continue;
			}
			if (!token->isKern()) {
				empty = false;
				continue;
			}
			if (*token == "*Trd0c0") {
				token->setText("*");
			} else {
				empty = false;
			}
		}
		if (empty) {
			ldel.push_back(i);
		}
	}

	if (ldel.size() == 1) {
		infile.deleteLine(ldel[0]);
	} else if (ldel.size() > 1) {
		cerr << "Warning: multiple transposition lines, not deleting them" << endl;
	}

}


//////////////////////////////
//
// Tool_1520ify::fixEditorialAccidentals -- checkDataLine() does
//       all of the work for this function, which only manages
//       key signature and barline processing.
//    Rules for accidentals in Tasso in Music Project:
//    (1) Only note accidentals printed in the source editions
//        are displayed as regular accidentals.  These accidentals
//        are postfixed with an "X" in the **kern data.
//    (2) Editorial accidentals are given an "i" marker but not
//        a "X" marker in the **kern data.  This editorial accidental
//        is displayed above the note.
//    This algorithm makes adjustments to the input data because
//    Sibelius will drop editorial information after the frist
//    editorial accidental on that pitch in the measure.
//    (3) If a note is the same pitch as a previous note in the
//        measure and the previous note has an editorial accidental,
//        then make the note an editorial note.  However, if the
//        accidental state of the note matches the key-signature,
//        then do not add an editorial accidental, and there will be
//        no accidental displayed on the note.  In that case, add a "y"
//        after the accidental to indicate that it is interpreted
//        and not visible in the original score.
//

void Tool_1520ify::fixEditorialAccidentals(HumdrumFile& infile) {
	m_pstates.resize(infile.getMaxTrack() + 1);
	m_estates.resize(infile.getMaxTrack() + 1);
	m_kstates.resize(infile.getMaxTrack() + 1);

	for (int i=0; i<(int)m_pstates.size(); i++) {
		m_pstates[i].resize(70);
		fill(m_pstates[i].begin(), m_pstates[i].end(), 0);
		m_kstates[i].resize(70);
		fill(m_kstates[i].begin(), m_kstates[i].end(), 0);
		m_estates[i].resize(70);
		fill(m_estates[i].begin(), m_estates[i].end(), false);
	}

	for (int i=0; i<infile.getLineCount(); i++) {
		if (infile[i].isInterpretation()) {
			updateKeySignatures(infile, i);
			continue;
		} else if (infile[i].isBarline()) {
			clearStates();
			continue;
		} else if (infile[i].isData()) {
			checkDataLine(infile, i);
		}
	}
}



//////////////////////////////
//
// Tool_1520ify::addTerminalLongs -- Convert all last notes to terminal longs
//    Also probably add terminal longs before double barlines as in JRP.
//

void Tool_1520ify::addTerminalLongs(HumdrumFile& infile) {
	int scount = infile.getStrandCount();
	for (int i=0; i<scount; i++) {
		HTp cur = infile.getStrandEnd(i);
		if (*cur != "*-") {
			continue;
		}
		if (!cur->isKern()) {
			continue;
		}
		while (cur) {
			if (!cur->isData()) {
				cur = cur->getPreviousToken();
				continue;
			}
			if (cur->isNull()) {
				cur = cur->getPreviousToken();
				continue;
			}
			if (cur->isRest()) {
				cur = cur->getPreviousToken();
				continue;
			}
			if (cur->isSecondaryTiedNote()) {
				cur = cur->getPreviousToken();
				continue;
			}
			if (cur->find("l") != std::string::npos) {
				// already marked so do not do it again
				break;
			}
			// mark this note with "l"
			string newtext = *cur;
			newtext += "l";
			cur->setText(newtext);
			break;
		}
	}
}



//////////////////////////////
//
// Tool_1520ify::fixInstrumentAbbreviations --
//

void Tool_1520ify::fixInstrumentAbbreviations(HumdrumFile& infile) {
	int iline = -1;
	int aline = -1;

	std::vector<HTp> kerns = infile.getKernSpineStartList();
	if (kerns.empty()) {
		return;
	}

	HTp cur = kerns[0];
	while (cur) {
		if (cur->isData()) {
			break;
		}
		if (cur->compare(0, 3, "*I\"") == 0) {
			iline = cur->getLineIndex();
		} else if (cur->compare(0, 3, "*I'") == 0) {
			aline = cur->getLineIndex();
		}
		cur = cur->getNextToken();
	}

	if (iline < 0) {
		// no names to create abbreviations for
		return;
	}
	if (aline < 0) {
		// not creating a new abbreviation for now
		// (could add later).
		return;
	}
	if (infile[iline].getFieldCount() != infile[aline].getFieldCount()) {
		// no spine splitting between the two lines.
		return;
	}
	// Maybe also require them to be adjacent to each other.
	HumRegex hre;
	for (int j=0; j<(int)infile[iline].getFieldCount(); j++) {
		if (!infile.token(iline, j)->isKern()) {
			continue;
		}
		if (!hre.search(*infile.token(iline, j), "([A-Za-z][A-Za-z .0-9]+)")) {
			continue;
		}
		string name = hre.getMatch(1);
		string abbr = "*I'";
		if (name == "Basso Continuo") {
			abbr += "BC";
		} else if (name == "Basso continuo") {
			abbr += "BC";
		} else if (name == "basso continuo") {
			abbr += "BC";
		} else {
			abbr += toupper(name[0]);
		}
		// check for numbers after the end of the name and add to abbreviation
		infile.token(aline, j)->setText(abbr);
	}
}



//////////////////////////////
//
// Tool_1520ify::deleteBreaks --
//

void Tool_1520ify::deleteBreaks(HumdrumFile& infile) {
	HumRegex hre;
	for (int i=infile.getLineCount()-1; i>= 0; i--) {
		if (!infile[i].isGlobalComment()) {
			continue;
		}
		if (hre.search(*infile.token(i, 0), "linebreak\\s*:\\s*original")) {
			infile.deleteLine(i);
		}
		else if (hre.search(*infile.token(i, 0), "pagebreak\\s*:\\s*original")) {
			infile.deleteLine(i);
		}
	}
}



////////////////////////////////
//
// Tool_1520ify::addBibliographicRecords --
//
// !!!!SEGMENT:
// !!!renid:
// !!!AGN:
// !!!voices:
// !!!COM:
// !!!CDT:
// !!!OTL:
// !!!OPR:
//
// At end:
// !!!RDF**kern: l = terminal long        (if needed)
// !!!RDF**kern: i = editorial accidental (if needed)
// !!!ENC: Benjamin Ory
// !!!END: 
// !!!EED: Benjamin Ory
// !!!EEV: <DATE>
// !!!YEC: Copyright <YEAR> Benjamin Ory, All Rights Reserved
// !!!ONB: Translated from MusicXML and edited on $DATE
//

void Tool_1520ify::addBibliographicRecords(HumdrumFile& infile) {
	std::vector<HLp> refinfo = infile.getReferenceRecords();
	std::map<string, HLp> refs;
	for (int i=0; i<(int)refinfo.size(); i++) {
		string key = refinfo[i]->getReferenceKey();
		refs[key] = refinfo[i];
	}

	// header records (inserted in reverse order)
	// automatic placement before/after existing records
	// could be improved later.
	if (refs.find("OPR") == refs.end()) {
		if (infile.token(0, 0)->find("!!!OPR") != std::string::npos) {
			infile.insertLine(1, "!!!OPR:");
		} else {
			infile.insertLine(0, "!!!OPR:");
		}
	}

	if (refs.find("OTL") == refs.end()) {
		if (infile.token(0, 0)->find("!!!OTL") != std::string::npos) {
			infile.insertLine(1, "!!!OTL:");
		} else {
			infile.insertLine(0, "!!!OTL:");
		}
	}

	if (refs.find("CDT") == refs.end()) {
		if (infile.token(0, 0)->find("!!!CDT") != std::string::npos) {
			infile.insertLine(1, "!!!CDT:");
		} else {
			infile.insertLine(0, "!!!CDT:");
		}
	}

	if (refs.find("COM") == refs.end()) {
		if (infile.token(0, 0)->find("!!!COM") != std::string::npos) {
			infile.insertLine(1, "!!!COM:");
		} else {
			infile.insertLine(0, "!!!COM:");
		}
	}

	if (refs.find("voices") == refs.end()) {
		if (infile.token(0, 0)->find("!!!voices") != std::string::npos) {
			infile.insertLine(1, "!!!voices:");
		} else {
			infile.insertLine(0, "!!!voices:");
		}
	}

	if (refs.find("AGN") == refs.end()) {
		if (infile.token(0, 0)->find("!!!AGN") != std::string::npos) {
			infile.insertLine(1, "!!!AGN:");
		} else {
			infile.insertLine(0, "!!!AGN:");
		}
	}

	if (refs.find("renid") == refs.end()) {
		if (infile.token(0, 0)->find("!!!renid") != std::string::npos) {
			infile.insertLine(1, "!!!renid:");
		} else {
			infile.insertLine(0, "!!!renid:");
		}
	}

	infile.insertLine(0, "!!!!SEGMENT:");

	int year = getYear();
	string date = getDate();



	// trailer records
	bool foundi = false;
	bool foundl = false;
	for (int i=0; i<infile.getLineCount(); i++) {
		if (!infile[i].isReference()) {
			continue;
		}
		if (infile.token(i, 0)->find("!!!RDF**kern:") == std::string::npos) {
			continue;
		}
		if (infile.token(i, 0)->find("terminal long") != std::string::npos) {
			foundl = true;
		} else if (infile.token(i, 0)->find("editorial accidental") != std::string::npos) {
			foundi = true;
		}
	}
	if (!foundi) {
		infile.appendLine("!!!RDF**kern: i = editorial accidental");
	}
	if (!foundl) {
		infile.appendLine("!!!RDF**kern: l = terminal long");
	}

	if (refs.find("ENC") == refs.end()) {
		infile.appendLine("!!!ENC: Benjamin Ory");
	}
	if (refs.find("END") == refs.end()) {
		infile.appendLine("!!!END:");
	}
	if (refs.find("EED") == refs.end()) {
		infile.appendLine("!!!EED: Benjamin Ory");
	}
	if (refs.find("EEV") == refs.end()) {
		string line = "!!!EEV: " + date;
		infile.appendLine(line);
	}
	if (refs.find("YEC") == refs.end()) {
		string line = "!!!YEC: Copyright ";
		line += to_string(year);
		line += " Benjamin Ory, All Rights Reserved";
		infile.appendLine(line);
	}
	if (refs.find("ONB") == refs.end()) {
		string line = "!!!ONB: Translated from MusicXML on " + date;
		infile.appendLine(line);
	}

}



////////////////////////////////
//
// Tool_1520ify::checkDataLine --
//

void Tool_1520ify::checkDataLine(HumdrumFile& infile, int lineindex) {
	HumdrumLine& line = infile[lineindex];

	HumRegex hre;
	HTp token;
	bool haseditQ;
	int base7;
	int accid;
	int track;
	bool removeQ;
	for (int i=0; i<line.getFieldCount(); i++) {
		token = line.token(i);
		track = token->getTrack();
		if (!token->isKern()) {
			continue;
		}
		if (token->isNull()) {
			continue;
		}
		if (token->isRest()) {
			continue;
		}
		if (token->isSecondaryTiedNote()) {
			continue;
		}

		base7 = Convert::kernToBase7(token);
		accid = Convert::kernToAccidentalCount(token);
		haseditQ = false;
		removeQ = false;

		// Hard-wired to "i" as editorial accidental marker
		if (token->find("ni") != string::npos) {
			haseditQ = true;
		} else if (token->find("-i") != string::npos) {
			haseditQ = true;
		} else if (token->find("#i") != string::npos) {
			haseditQ = true;
		} else if (token->find("nXi") != string::npos) {
			haseditQ = true;
			removeQ = true;
		} else if (token->find("-Xi") != string::npos) {
			haseditQ = true;
			removeQ = true;
		} else if (token->find("#Xi") != string::npos) {
			haseditQ = true;
			removeQ = true;
		}

		if (removeQ) {
			string temp = *token;
			hre.replaceDestructive(temp, "", "X");
			token->setText(temp);
		}

		bool explicitQ = false;
		if (token->find("#X") != string::npos) {
			explicitQ = true;
		} else if (token->find("-X") != string::npos) {
			explicitQ = true;
		} else if (token->find("nX") != string::npos) {
			explicitQ = true;
		} else if (token->find("n") != string::npos) {
			// add an explicit accidental marker
			explicitQ = true;
			string text = *token;
			hre.replaceDestructive(text, "nX", "n");
			token->setText(text);
		}

		if (haseditQ) {
			// Store new editorial pitch state.
			m_estates.at(track).at(base7) = true;
			m_pstates.at(track).at(base7) = accid;
			continue;
		}

		if (explicitQ) {
			// No need to make editorial since it is visible.
			m_estates.at(track).at(base7) = false;
			m_pstates.at(track).at(base7) = accid;
			continue;
		}

		if (accid == m_kstates.at(track).at(base7)) {
			// 	!m_estates.at(track).at(base7)) {
			// add !m_estates.at(track).at(base) as a condition if
			// you want editorial accidentals to be added to return the
			// note to the accidental in the key.
			//
			// The accidental matches the key-signature state,
			// so it should not be made editorial eventhough
			// it is not visible.
			m_pstates.at(track).at(base7) = accid;

			// Add a "y" marker of there is an interpreted accidental
			// state (flat or sharp) that is part of the key signature.
			int hasaccid = false;
			if (token->find("#") != std::string::npos) {
				hasaccid = true;
			} else if (token->find("-") != std::string::npos) {
				hasaccid = true;
			}
			int hashide = false;
			if (token->find("-y") != std::string::npos) {
				hashide = true;
			}
			else if (token->find("#y") != std::string::npos) {
				hashide = true;
			}
			if (hasaccid && !hashide) {
				string text = *token;
				hre.replaceDestructive(text, "#y", "#");
				hre.replaceDestructive(text, "-y", "-");
				token->setText(text);
			}

			continue;
		}

		// At this point the previous note with this pitch class
		// had an editorial accidental, and this note also has the
		// same accidental, or there was a previous visual accidental
		// outside of the key signature that will cause this note to have
		// an editorial accidental mark applied (Sibelius will drop
		// secondary editorial accidentals in a measure when exporting,
		// MusicXML, which is why this function is needed).

		m_estates[track][base7] = true;
		m_pstates[track][base7] = accid;

		string text = token->getText();
		string output = "";
		bool foundQ = false;
		for (int j=0; j<(int)text.size(); j++) {
			if (text[j] == 'n') {
				output += "ni";
				foundQ = true;
			} else if (text[j] == '#') {
				output += "#i";
				foundQ = true;
			} else if (text[j] == '-') {
				output += "-i";
				foundQ = true;
			} else {
				output += text[j];
			}
		}

		if (foundQ) {
			token->setText(output);
			continue;
		}

		// The note is natural, but has no natural sign.
		// add the natural sign and editorial mark.
		for (int j=(int)output.size()-1; j>=0; j--) {
			if ((tolower(output[j]) >= 'a') && (tolower(output[j]) <= 'g')) {
				output.insert(j+1, "ni");
				break;
			}
		}
		token->setText(output);
	}
}



////////////////////////////////
//
// Tool_1520ify::updateKeySignatures -- Fill in the accidental
//    states for each diatonic pitch.
//

void Tool_1520ify::updateKeySignatures(HumdrumFile& infile, int lineindex) {
	HumdrumLine& line = infile[lineindex];
	int track;
	for (int i=0; i<line.getFieldCount(); i++) {
		if (!line.token(i)->isKeySignature()) {
			continue;
		}
		HTp token = line.token(i);
		track = token->getTrack();
		string text = token->getText();
		fill(m_kstates[track].begin(), m_kstates[track].end(), 0);
		for (int j=3; j<(int)text.size()-1; j++) {
			if (text[j] == ']') {
				break;
			}
			switch (text[j]) {
				case 'a': case 'A':
					switch (text[j+1]) {
						case '#': m_kstates[track][5] = +1;
						break;
						case '-': m_kstates[track][5] = -1;
						break;
					}
					break;

				case 'b': case 'B':
					switch (text[j+1]) {
						case '#': m_kstates[track][6] = +1;
						break;
						case '-': m_kstates[track][6] = -1;
						break;
					}
					break;

				case 'c': case 'C':
					switch (text[j+1]) {
						case '#': m_kstates[track][0] = +1;
						break;
						case '-': m_kstates[track][0] = -1;
						break;
					}
					break;

				case 'd': case 'D':
					switch (text[j+1]) {
						case '#': m_kstates[track][1] = +1;
						break;
						case '-': m_kstates[track][1] = -1;
						break;
					}
					break;

				case 'e': case 'E':
					switch (text[j+1]) {
						case '#': m_kstates[track][2] = +1;
						break;
						case '-': m_kstates[track][2] = -1;
						break;
					}
					break;

				case 'f': case 'F':
					switch (text[j+1]) {
						case '#': m_kstates[track][3] = +1;
						break;
						case '-': m_kstates[track][3] = -1;
						break;
					}
					break;

				case 'g': case 'G':
					switch (text[j+1]) {
						case '#': m_kstates[track][4] = +1;
						break;
						case '-': m_kstates[track][4] = -1;
						break;
					}
					break;
			}
			for (int j=0; j<7; j++) {
				if (m_kstates[track][j] == 0) {
					continue;
				}
				for (int k=1; k<10; k++) {
					m_kstates[track][j+k*7] = m_kstates[track][j];
				}
			}
		}
	}

	// initialize m_pstates with contents of m_kstates
	for (int i=0; i<(int)m_kstates.size(); i++) {
		for (int j=0; j<(int)m_kstates[i].size(); j++) {
			m_pstates[i][j] = m_kstates[i][j];
		}
	}

}



////////////////////////////////
//
// Tool_1520ify::clearStates --
//

void Tool_1520ify::clearStates(void) {
	for (int i=0; i<(int)m_pstates.size(); i++) {
		fill(m_pstates[i].begin(), m_pstates[i].end(), 0);
	}
	for (int i=0; i<(int)m_estates.size(); i++) {
		fill(m_estates[i].begin(), m_estates[i].end(), false);
	}
}


//////////////////////////////
//
// Tool_1520ify::getDate --
//

string Tool_1520ify::getDate(void) {
	auto now = std::chrono::system_clock::now();
	std::time_t now_time = std::chrono::system_clock::to_time_t(now);
	std::tm* local_time = std::localtime(&now_time);
	int year = local_time->tm_year + 1900;
	int month = local_time->tm_mon + 1; 
	int day = local_time->tm_mday;
	stringstream ss;
	ss << year << "/";
	ss << std::setw(2) << std::setfill('0') << month << "/";
	ss << std::setw(2) << std::setfill('0') << day;
	return ss.str();
}



//////////////////////////////
//
// Tool_1520ify::getYear --
//

int Tool_1520ify::getYear(void) {
	auto now = std::chrono::system_clock::now();
	std::time_t now_time = std::chrono::system_clock::to_time_t(now);
	std::tm* local_time = std::localtime(&now_time);
	int year = local_time->tm_year + 1900;
	return year;
}





/////////////////////////////////
//
// Tool_addic::Tool_addic -- Set the recognized options for the tool.
//

Tool_addic::Tool_addic(void) {
	define("f|fix=b", "Fix instrument class values if different from expected for instrument code.");
}



/////////////////////////////////
//
// Tool_addic::run -- Do the main work of the tool.
//

bool Tool_addic::run(HumdrumFileSet& infiles) {
	bool status = true;
	for (int i=0; i<infiles.getCount(); i++) {
		status &= run(infiles[i]);
	}
	return status;
}


bool Tool_addic::run(const string& indata, ostream& out) {
	HumdrumFile infile(indata);
	bool status = run(infile);
	if (hasAnyText()) {
		getAllText(out);
	} else {
		out << infile;
	}
	return status;
}


bool Tool_addic::run(HumdrumFile& infile, ostream& out) {
	bool status = run(infile);
	if (hasAnyText()) {
		getAllText(out);
	} else {
		out << infile;
	}
	return status;
}


bool Tool_addic::run(HumdrumFile& infile) {
	initialize();
	processFile(infile);
	return true;
}


//////////////////////////////
//
// Tool_addic::initialize --
//

void Tool_addic::initialize(void) {
	m_instrumentList = Convert::getInstrumentList();
	m_fixQ = getBoolean("fix");
}



//////////////////////////////
//
// Tool_addic::processFile --
//

void Tool_addic::processFile(HumdrumFile& infile) {

	int codeIndex = getInstrumentCodeIndex(infile);
	int classIndex = getInstrumentClassIndex(infile);

	if (!codeIndex) {
		// No code index, so nothing to do.
		m_humdrum_text << infile;
	}
	if (classIndex) {
		// Instrument class line already exists so adjust it:
		updateInstrumentClassLine(infile, codeIndex, classIndex);
		m_humdrum_text << infile;
	} else {
		string classLine = makeClassLine(infile, codeIndex);
		for (int i=0; i<infile.getLineCount(); i++) {
			if (i == codeIndex) {
				m_humdrum_text << classLine << endl;
			}
			m_humdrum_text << infile[i] << endl;
		}
	}
}



//////////////////////////////
//
// Tool_addic::makeClassLine --
//

string Tool_addic::makeClassLine(HumdrumFile& infile, int codeIndex) {
	string output;
	HumRegex hre;
	int count = infile[codeIndex].getFieldCount();
	for (int i=0; i<count; i++) {
		HTp codeToken = infile.token(codeIndex, i);
		if (!hre.search(codeToken, "^\\*I([a-z].*)")) {
			output += "*";
			if (i < count - 1) {
				output += "\t";
			}
			continue;
		}
		string code = hre.getMatch(1);
		string iclass = getInstrumentClass(code);
		if (iclass.empty()) {
			output += "*UNKNOWN";
			if (i < count - 1) {
				output += "\t";
			}
			continue;
		}
		string text = "*IC" + iclass;
		output += text;
		if (i < count - 1) {
			output += "\t";
		}
	}
	return output;
}



//////////////////////////////
//
// Tool_addic::updateInstrumentClassLine --
//

void Tool_addic::updateInstrumentClassLine(HumdrumFile& infile, int codeIndex,
		int classIndex) {

	int codeSize  = infile[codeIndex].getFieldCount();
	int classSize = infile[classIndex].getFieldCount();
	if (codeSize != classSize) {
		cerr << "Instrument code line length does not match that of class line" << endl;
		return;
	}

	HumRegex hre;
	for (int i=0; i<infile[codeIndex].getFieldCount(); i++) {
		HTp codeToken  = infile.token(codeIndex, i);
		HTp classToken = infile.token(classIndex, i);
		if (*codeToken == "*") {
			continue;
		}
		if ((*classToken != "*") && !hre.search(classToken, "^\\*IC")) {
			cerr << "Not overwriting non-class content: " << classToken << endl;
			continue;
		}
		if ((!m_fixQ) && hre.search(classToken, "^\\*IC")) {
			// There is already an instrument class, but don't check to see if it
			// is correct.
			continue;
		}
		if (!hre.search(codeToken, "^\\*I([a-z].*)")) {
			continue;
		}
		string code = hre.getMatch(1);
		string iclass = getInstrumentClass(code);
		if (iclass.empty()) {
			continue;
		}
		string text = "*IC" + iclass;
		classToken->setText(text);
	}
	infile[classIndex].createLineFromTokens();
}



//////////////////////////////
//
// Tool_addic::getInstrumentCodeIndex --
//

int Tool_addic::getInstrumentCodeIndex(HumdrumFile& infile) {
	HumRegex hre;
	for (int i=0; i<infile.getLineCount(); i++) {
		if (!infile[i].hasSpines()) {
			continue;
		}
		if (infile[i].isData()) {
			break;
		}
		if (!infile[i].isInterpretation()) {
			continue;
		}
		for (int j=0; j<infile[i].getFieldCount(); j++) {
			HTp token = infile.token(i, j);
			if (*token == "*") {
				continue;
			}
			if (hre.search(token, "^\\*I[a-z]")) {
				return i;
			}
		}
	}
	return 0;
}



//////////////////////////////
//
// Tool_addic::getInstrumentClassIndex --
//

int Tool_addic::getInstrumentClassIndex(HumdrumFile& infile) {
	HumRegex hre;
	for (int i=0; i<infile.getLineCount(); i++) {
		if (!infile[i].hasSpines()) {
			continue;
		}
		if (infile[i].isData()) {
			break;
		}
		if (!infile[i].isInterpretation()) {
			continue;
		}
		for (int j=0; j<infile[i].getFieldCount(); j++) {
			HTp token = infile.token(i, j);
			if (*token == "*") {
				continue;
			}
			if (hre.search(token, "^\\*IC[a-z]")) {
				return i;
			}
		}
	}
	return 0;
}



//////////////////////////////
//
// Tool_addic::getInstrumentClass --
//

string Tool_addic::getInstrumentClass(const string& code) {
	HumRegex hre;
	string code1 = code;
	string code2;
	string divider;
	int count = 1;
	if (hre.search(code, "([^I]+)([&|])I(.*)")) {
		count   = 2;
		code1   = hre.getMatch(1);
		divider = hre.getMatch(2);
		code2   = hre.getMatch(3);
	}

	string class1 = "";
	string class2 = "";

	for (int i=0; i<(int)m_instrumentList.size(); i++) {
		if (code1 == m_instrumentList[i].first) {
			class1 = m_instrumentList[i].second;
		}
		if (count == 2) {
			if (code2 == m_instrumentList[i].first) {
				class2 = m_instrumentList[i].second;
			}
		}
	}

	if (count == 1) {
		if (class1 == "") {
			return "UNKNOWN" + code1;
		}
	} else {
		if ((class1 == "") && (class2 == "")) {
			return "UNKNOWN" + code1;
		}
	}

	if (count == 1) {
		return class1;
	}

	if (class1 == class2) {
		return class1;
	}

	// return two instrument classes:
	return class1 + divider + "IC" + class2;
}




/////////////////////////////////
//
// Tool_addkey::Tool_addkey -- Set the recognized options for the tool.
//

Tool_addkey::Tool_addkey(void) {
	define("k|key=s",           "Add given key designtation to data");
	define("K|reference-key=b", "Update or add !!!key: designation, used with -k");
}



/////////////////////////////////
//
// Tool_addkey::run -- Do the main work of the tool.
//

bool Tool_addkey::run(HumdrumFileSet& infiles) {
	bool status = true;
	for (int i=0; i<infiles.getCount(); i++) {
		status &= run(infiles[i]);
	}
	return status;
}


bool Tool_addkey::run(const string& indata, ostream& out) {
	HumdrumFile infile(indata);
	bool status = run(infile);
	if (hasAnyText()) {
		getAllText(out);
	} else {
		out << infile;
	}
	return status;
}


bool Tool_addkey::run(HumdrumFile& infile, ostream& out) {
	bool status = run(infile);
	if (hasAnyText()) {
		getAllText(out);
	} else {
		out << infile;
	}
	return status;
}


bool Tool_addkey::run(HumdrumFile& infile) {
	processFile(infile);
	return true;
}



//////////////////////////////
//
// Tool_addkey::initialize --  Initializations that only have to be done once
//    for all HumdrumFile segments.
//

void Tool_addkey::initialize(void) {
	m_addKeyRefQ = getBoolean("reference-key");
	m_keyQ       = getBoolean("key");
	m_key        = getString("key");
	HumRegex hre;
	hre.replaceDestructive(m_key, "", ":$");
	hre.replaceDestructive(m_key, "", "^\\*");
}



//////////////////////////////
//
// Tool_addkey::processFile --
//

void Tool_addkey::processFile(HumdrumFile& infile) {
	initialize();
	if (m_keyQ) {
		addInputKey(infile);
	} else {
		insertReferenceKey(infile);
	}
}



//////////////////////////////
//
// Tool_addkey::addInputKey -- Insert key from -k option into score
//    rather than !!!key: entry.  Insert input into !!!key: entry if
//    -K option is given.
//

void Tool_addkey::addInputKey(HumdrumFile& infile) {
	getLineIndexes(infile);

	if (m_refKeyIndex != -1) {
		string text = "!!!key: " + m_key;
		infile[m_refKeyIndex].token(0)->setText(text);
	}

	HumRegex hre;
	string keyDesig = "*" + m_key;
	if (!hre.search(m_key, ":")) {
		keyDesig += ":";
	}
	insertKeyDesig(infile, keyDesig);

	// Update the reference key record if -K option is used:
	if (m_addKeyRefQ) {
		if (m_refKeyIndex != -1) {
			string text = "!!!key: " + m_key;
			infile[m_refKeyIndex].setText(text);
		}
		// Or print just before exinterp line later if not found,
		// but needs to be created.
	}
}



//////////////////////////////
//
// Tool_addkey::insertKeyDesig --
//

void Tool_addkey::insertKeyDesig(HumdrumFile& infile, const string& keyDesig) {
	// Replace the key designation if any are found in the header.
	// If not found, then store in key signature for printing later.
	for (int i=0; i<infile.getLineCount(); i++) {
		if (i >= m_dataStartIndex) {
			break;
		}
		if (!infile[i].isInterpretation()) {
			continue;
		}
		for (int j=0; j<infile[i].getFieldCount(); j++) {
			HTp token = infile.token(i, j);
			if (token->isKeyDesignation()) {
				token->setText(keyDesig);
			} else if ((m_keyDesigIndex == -1) && (token->isKeySignature())) {
				// Store keyDesig later to print:
				token->setValue("auto", "keyDesig", keyDesig);
			}
		}
	}
}



//////////////////////////////
//
// Tool_addkey::insertReferenceKey -- Take the !!!key: value and insert
// into key designations in the header.  Add key designation line if not
// present already in header.
//

void Tool_addkey::insertReferenceKey(HumdrumFile& infile) {
	getLineIndexes(infile);

	if (m_refKeyIndex == -1) {
		// Nothing to do, add later before exinterp line.
		return;
	}

	HumRegex hre;
	string keyValue = infile[m_refKeyIndex].getReferenceValue();
	if (!hre.search(keyValue, ":")) {
		keyValue += ":";
	}
	if (!hre.search(keyValue, "^\\*")) {
		hre.replaceDestructive(keyValue, "*", "^");
	}
	if (m_keyDesigIndex > 0) {
		for (int i=m_exinterpIndex+1; i<=m_keyDesigIndex; i++) {
			if (!infile[i].isInterpretation()) {
				continue;
			}
			for (int j=0; j<infile[i].getFieldCount(); j++) {
				HTp token = infile.token(i, j);
				if (!token->isKeyDesignation()) {
					continue;
				}
				string text = "*" + keyValue;
				token->setText(text);
			}
		}
		infile.generateLinesFromTokens();
		m_humdrum_text << infile;
	} else if (m_keySigIndex > 0) {
		printKeyDesig(infile, m_keySigIndex, keyValue, +1);
	} else if (m_dataStartIndex > 0) {
		printKeyDesig(infile, m_dataStartIndex, keyValue, -1);
	}
}



//////////////////////////////
//
// Tool_addkey::printKeyDesig --
//

void Tool_addkey::printKeyDesig(HumdrumFile& infile, int index, const string& desig, int direction) {
	int index2 = index + direction;
	for (int i=0; i<infile.getLineCount(); i++) {
		if (i != index2) {
			m_humdrum_text << infile[i] << endl;
		} else {
			if (index > index2) {
				m_humdrum_text << infile[i] << endl;
			}
			for (int j=0; j<infile[index].getFieldCount(); j++) {
				HTp token = infile.token(index, j);
				if (j > 0) {
					m_humdrum_text << "\t";
				}
				if (token->isKern()) {
					m_humdrum_text << desig;
				} else {
					m_humdrum_text << "*";
				}
			}
			m_humdrum_text << endl;
			if (index < index2) {
				m_humdrum_text << infile[i] << endl;
			}
		}
	}
}



//////////////////////////////
//
// Tool_addkey::getLineIndexes --
//

void Tool_addkey::getLineIndexes(HumdrumFile& infile) {
	m_refKeyIndex    = -1;
	m_keyDesigIndex  = -1;
	m_keySigIndex    = -1;
	m_dataStartIndex = -1;

	for (int i=0; i<infile.getLineCount(); i++) {
		if (!infile[i].hasSpines()) {
			continue;
		}
		if (infile[i].isExclusiveInterpretation()) {
			m_exinterpIndex = i;
			continue;
		}
		if (infile[i].isData()) {
			m_dataStartIndex = i;
			break;
		}
		if (infile[i].isBarline()) {
			m_dataStartIndex = i;
			break;
		}
		if (infile[i].token(0)->compare(0, 7, "!!!key:") == 0) {
			m_refKeyIndex = i;
		}
		if (!infile[i].isInterpretation()) {
			continue;
		}
		for (int j=0; j<infile[i].getFieldCount(); j++) {
			HTp token = infile.token(i, j);
			if (token->isKeySignature()) {
				m_keySigIndex = i;
			} else if (token->isKeyDesignation()) {
				m_keyDesigIndex = i;
			}
		}
	}

	if (m_refKeyIndex == -1) {
		// !!!key: could be at bottom, so search backwards in file.
		for (int i=infile.getLineCount() - 1; i>=0; i--) {
			if (!infile[i].isReference()) {
				continue;
			}
			string key = infile[i].getReferenceKey();
			if (key == "key") {
				m_refKeyIndex   = i;
				break;
			}
		}
	}
}




/////////////////////////////////
//
// Tool_addlabels::Tool_addlabels -- Set the recognized options for the tool.
//

Tool_addlabels::Tool_addlabels(void) {
	define("d|default=s:", "Default expansion list");
	define("r|norep=s:",   "norep expansion list");
	define("l|labels=s:",  "List of labels to insert");
}



/////////////////////////////////
//
// Tool_addlabels::run -- Do the main work of the tool.
//

bool Tool_addlabels::run(HumdrumFileSet& infiles) {
	bool status = true;
	for (int i=0; i<infiles.getCount(); i++) {
		status &= run(infiles[i]);
	}
	return status;
}


bool Tool_addlabels::run(const string& indata, ostream& out) {
	HumdrumFile infile(indata);
	bool status = run(infile);
	if (hasAnyText()) {
		getAllText(out);
	} else {
		out << infile;
	}
	return status;
}


bool Tool_addlabels::run(HumdrumFile& infile, ostream& out) {
	bool status = run(infile);
	if (hasAnyText()) {
		getAllText(out);
	} else {
		out << infile;
	}
	return status;
}


bool Tool_addlabels::run(HumdrumFile& infile) {
	processFile(infile);
	return true;
}



//////////////////////////////
//
// Tool_addlabels::initialize -- Process input options.
//

void Tool_addlabels::initialize(void) {
	m_default = getString("default");
	m_norep   = getString("norep");
	m_zeroth.clear();

	HumRegex hre;
	if (!m_default.empty()) {
		if (!hre.search(m_default, "^\\[")) {
			m_default = "[" + m_default;
		}
		if (!hre.search(m_default, "\\]$")) {
			m_default += "]";
		}
	}
	if (!m_norep.empty()) {
		if (!hre.search(m_norep, "^\\[")) {
			m_norep = "[" + m_norep;
		}
		if (!hre.search(m_norep, "\\]$")) {
			m_norep += "]";
		}
	}

	string value = getString("labels");
	hre.replaceDestructive(value, "", "^[\\s;,]+");
	hre.replaceDestructive(value, "", "[\\s;,]+$");

	vector<string> pieces;
	hre.split(pieces, value, "\\s*[;,]\\s*");
	for (int i=0; i<(int)pieces.size(); i++) {
		if (hre.search(pieces[i], "^\\s$")) {
			continue;
		}
		if (hre.search(pieces[i], "^\\s*m?\\s*(\\d+)([a-z]?)\\s*:\\s*(.+)\\s*$")) {
			int barnum = hre.getMatchInt(1);
			string sub = hre.getMatch(2);
			int subbar = 0;
			if (!sub.empty()) {
				subbar = sub[0] - 'a';
			}
			string label = hre.getMatch(3);

			if ((barnum <= 0) && (subbar <= 0)) {
				m_zeroth = label;
				continue;
			}
			m_barnums.push_back(barnum);
			m_subbarnums.push_back(subbar);
			m_labels.push_back(label);
		} else {
			cerr << "Error parsing label (ignoring): " << pieces[i] << endl;
		}
	}
}



//////////////////////////////
//
// Tool_addlabels::processFile --
//

void Tool_addlabels::processFile(HumdrumFile& infile) {
	initialize();

	vector<string> llist;
	assignLabels(llist, infile);

	m_defaultIndex = getExpansionIndex(infile);

	for (int i=0; i<infile.getLineCount(); i++) {
		if (i == m_defaultIndex) {
			printExpansionLists(infile, i);
			m_humdrum_text << infile[i] << endl;
			continue;
		}
		m_humdrum_text << infile[i] << endl;
		if (!llist.at(i).empty()) {
			for (int j=0; j<infile[i].getFieldCount(); j++) {
				m_humdrum_text << "*>" << llist.at(i);
				if (j < infile[i].getFieldCount() - 1) {
					m_humdrum_text << "\t";
				}
			}
			m_humdrum_text << endl;
		}
	}
}



//////////////////////////////
//
// Tool_addlabels::getExpsnsionIndex -- Return index that is where the
//    expansion labels and 0th label should be printed ABOVE.
//

int Tool_addlabels::getExpansionIndex(HumdrumFile& infile) {
	int staffIndex    = -1;
	int partIndex     = -1;
	int groupIndex    = -1;
	int instIndex     = -1;
	int abbrIndex     = -1;
	int clefIndex     = -1;
	int keySigIndex   = -1;
	int keyDesigIndex = -1;
	int exIndex       = -1;

	for (int i=0; i<infile.getLineCount(); i++) {
		if (!infile[i].hasSpines()) {
			continue;
		}
		if (infile[i].isExclusiveInterpretation()) {
			exIndex = i;
			continue;
		}
		if (infile[i].isData()) {
			break;
		}
		if (infile[i].isBarline()) {
			break;
		}
		if (!infile[i].isInterpretation()) {
			continue;
		}
		for (int j=0; j<infile[i].getFieldCount(); j++) {
			HTp token = infile.token(i, j);
			if (*token == "*") {
				continue;
			}
			if ((clefIndex < 0) && token->isClef()) {
				clefIndex = i;
			}
			if ((instIndex < 0) && token->compare(0, 3, "*I\"") == 0) {
				instIndex = i;
			}
			if ((abbrIndex < 0) && token->compare(0, 3, "*I'") == 0) {
				abbrIndex = i;
			}
			if ((keySigIndex < 0) && token->isKeySignature()) {
				keySigIndex = i;
			}
			if ((keyDesigIndex < 0) && token->isKeyDesignation()) {
				keyDesigIndex = i;
			}
			if ((staffIndex != i) && (token->compare(0, 6, "*staff") == 0)) {
				staffIndex = i;
			}
			if ((partIndex != i) && (token->compare(0, 5, "*part") == 0)) {
				partIndex = i;
			}
			if ((groupIndex != i) && (token->compare(0, 6, "*group") == 0)) {
				groupIndex = i;
			}
		}
	}

	int spigaIndex = staffIndex;
	if (partIndex > spigaIndex) {
		spigaIndex = partIndex;
	}
	if (groupIndex > spigaIndex) {
		spigaIndex = groupIndex;
	}
	if (instIndex > spigaIndex) {
		spigaIndex = instIndex;
	}
	if (abbrIndex > spigaIndex) {
		spigaIndex = abbrIndex;
	}

	if (spigaIndex > 0) {
		return spigaIndex + 1;
	}

	int tindex = -1;

	if ((clefIndex > 0) && (tindex > 0)) {
		tindex = clefIndex;
	}

	if ((keySigIndex > 0) && (tindex > 0)) {
		if (keySigIndex < tindex) {
			tindex = keySigIndex;
		}
	}

	if ((keyDesigIndex > 0) && (tindex > 0)) {
		if (keyDesigIndex < tindex) {
			tindex = keyDesigIndex;
		}
	}
	if (tindex > 0) {
		if (exIndex < tindex - 1) {
			return tindex;
		}
	}
	return exIndex + 1;
}



//////////////////////////////
//
// Tool_addlabels::printExpansionLists -- printing above given line index
//   But use field count of next spined line in data if target line is
//   unspined.
//

void Tool_addlabels::printExpansionLists(HumdrumFile& infile, int index) {
	int ii = -1;
	for (int i=index; i<infile.getLineCount(); i++) {
		if (infile[i].hasSpines()) {
			ii = i;
			break;
		}
	}
	if (ii < 0) {
		cerr << "STRANGE ERROR ON LINE: " << infile[index] << endl;
	}

	if (!m_default.empty()) {
		for (int j=0; j<infile[ii].getFieldCount(); j++) {
			m_humdrum_text << "*>" << m_default;
			if (j < infile[ii].getFieldCount() - 1) {
				m_humdrum_text << "\t";
			}
		}
		m_humdrum_text << endl;
	}

	if (!m_norep.empty()) {
		for (int j=0; j<infile[ii].getFieldCount(); j++) {
			m_humdrum_text << "*>norep" << m_norep;
			if (j < infile[ii].getFieldCount() - 1) {
				m_humdrum_text << "\t";
			}
		}
		m_humdrum_text << endl;
	}

	if (!m_zeroth.empty()) {
		for (int j=0; j<infile[ii].getFieldCount(); j++) {
			m_humdrum_text << "*>" << m_zeroth;
			if (j < infile[ii].getFieldCount() - 1) {
				m_humdrum_text << "\t";
			}
		}
		m_humdrum_text << endl;
	}
}



//////////////////////////////
//
// Tool_addlabels::assignLabels -- assign labels to specific lines.
//

void Tool_addlabels::assignLabels(vector<string>& llist, HumdrumFile& infile) {
	llist.resize(infile.getLineCount());
	for (int i=0; i<(int)m_barnums.size(); i++) {
		addLabel(llist, infile, m_barnums.at(i), m_subbarnums.at(i), m_labels.at(i));
	}
}



//////////////////////////////
//
// Tool_addlabels::addLabel -- Add specified tempo to list.
//

void Tool_addlabels::addLabel(vector<string>& llist, HumdrumFile& infile,
		int barnum, int subbarnum, const string& label) {

	if (barnum <= 0) {
		return;
	}

	// find barnum index:
	int barIndex = -1;
	for (int i=0; i<infile.getLineCount(); i++) {
		if (!infile[i].isBarline()) {
			continue;
		}
		int bar = infile[i].getBarNumber();
		if (bar == barnum) {
			barIndex = i;
			break;
		}
	}
	if (barIndex < 0) {
		cerr << "WARNING: could not find measure number " << barnum << endl;
		return;
	}

	int counter = 0;
	if (subbarnum > 0) {
		for (int i=barIndex + 1; i<infile.getLineCount(); i++) {
			if (!infile[i].isBarline()) {
				continue;
			}
			counter++;
			if (counter >= subbarnum) {
				barIndex = i;
				break;
			}
		}
	}

	if (barIndex < 0) {
		return;
	}

	// insert at the next spined line (but figure that out later):
	llist.at(barIndex) = label;
}




/////////////////////////////////
//
// Tool_addtempo::Tool_addtempo -- Set the recognized options for the tool.
//

Tool_addtempo::Tool_addtempo(void) {
	define("q|quarter-notes-per-minute=d:120.0", "Quarter notes per minute (or list by measure)");
}



/////////////////////////////////
//
// Tool_addtempo::run -- Do the main work of the tool.
//

bool Tool_addtempo::run(HumdrumFileSet& infiles) {
	bool status = true;
	for (int i=0; i<infiles.getCount(); i++) {
		status &= run(infiles[i]);
	}
	return status;
}


bool Tool_addtempo::run(const string& indata, ostream& out) {
	HumdrumFile infile(indata);
	bool status = run(infile);
	if (hasAnyText()) {
		getAllText(out);
	} else {
		out << infile;
	}
	return status;
}


bool Tool_addtempo::run(HumdrumFile& infile, ostream& out) {
	bool status = run(infile);
	if (hasAnyText()) {
		getAllText(out);
	} else {
		out << infile;
	}
	return status;
}


bool Tool_addtempo::run(HumdrumFile& infile) {
	processFile(infile);
	return true;
}



//////////////////////////////
//
// Tool_addtempo::initialize --  Initializations that only have to be done once
//    for all HumdrumFile segments.
//

void Tool_addtempo::initialize(void) {
	string value = getString("quarter-notes-per-minute");
	HumRegex hre;
	m_tempos.clear();
	vector<string> pieces;
	hre.split(pieces, value, "\\s*;\\s*");
	for (int i=0; i<(int)pieces.size(); i++) {
		if (hre.search(pieces[i], "^\\s$")) {
			continue;
		}
		if (hre.search(pieces[i], "^\\s*m\\s*(\\d+)\\s*([a-z]?)\\s*:\\s*([\\d.]+)\\s*$")) {
			int measure = hre.getMatchInt(1);
			string soffset = hre.getMatch(2);
			int offset = 0;
			if (!soffset.empty()) {
				offset = soffset.at(0) - 'a';
			}
			double tempo = hre.getMatchDouble(3);
			m_tempos.emplace_back(measure, tempo, offset);
		} else if (hre.search(pieces[i], "^\\s*([\\d.]+)\\s*$")) {
			int measure = 0;
			int offset = 0;
			double tempo = hre.getMatchDouble(1);
			m_tempos.emplace_back(measure, tempo, offset);
		}
	}

	auto compareByFirst = [](const std::tuple<int, double, int>& a, const std::tuple<int, double, int>& b) {
		return std::get<0>(a) < std::get<0>(b);
	};
	std::sort(m_tempos.begin(), m_tempos.end(), compareByFirst);
}



//////////////////////////////
//
// Tool_addtempo::processFile --
//

void Tool_addtempo::processFile(HumdrumFile& infile) {
	initialize();

	vector<double> tlist;
	assignTempoChanges(tlist, infile);

	for (int i=0; i<infile.getLineCount(); i++) {
		if (tlist.at(i) > 0.0) {
			for (int j=0; j<infile[i].getFieldCount(); j++) {
				HTp token = infile.token(i, j);
				if (token->isKern()) {
					m_humdrum_text << "*MM" << tlist.at(i);
				} else {
					m_humdrum_text << "*";
				}
				if (j < infile[i].getFieldCount() - 1) {
					m_humdrum_text << "\t";
				}
			}
			m_humdrum_text << endl;
		}
		m_humdrum_text << infile[i] << endl;
	}
}



//////////////////////////////
//
// Tool_addtempo::assignTempoChanges -- add non-zero
//    tempo when it should change.
//

void Tool_addtempo::assignTempoChanges(vector<double>& tlist, HumdrumFile& infile) {
	tlist.resize(infile.getLineCount());
	std::fill(tlist.begin(), tlist.end(), 0.0);
	for (int i=0; i<(int)m_tempos.size(); i++) {
		addTempo(tlist, infile, std::get<0>(m_tempos[i]), std::get<1>(m_tempos[i]), std::get<2>(m_tempos[i]));
	}
}



//////////////////////////////
//
// Tool_addtempo::addTempo -- Add specified tempo to list.
//

void Tool_addtempo::addTempo(vector<double>& tlist, HumdrumFile& infile,
		int measure, double tempo, int offset) {

	if (measure == 0) {
		addTempoToStart(tlist, infile, tempo);
		return;
	}

	// find measure index:
	int barIndex = -1;
	for (int i=0; i<infile.getLineCount(); i++) {
		if (!infile[i].isBarline()) {
			continue;
		}
		int bar = infile[i].getBarNumber();
		if (bar == measure) {
			if (offset == 0) {
				barIndex = i;
				break;
			}
			int counter = 0;
			for (int j=i+1; j<infile.getLineCount(); j++) {
				if (infile[j].isBarline()) {
					counter++;
					if (counter == offset) {
						barIndex = j;
						break;
					}
				}
			}
			break;
		}
	}
	if (barIndex < 0) {
		return;
	}
	int sigIndex = -1;
	int symIndex = -1;
	int dataIndex = -1;
	for (int i=barIndex+1; i<infile.getLineCount(); i++) {
		if (infile[i].isData()) {
			dataIndex = i;
			break;
		}
		if (infile[i].isBarline()) {
			break;
		}
		if (!infile[i].isInterpretation()) {
			continue;
		}
		for (int j=0; j<infile[i].getFieldCount(); j++) {
			HTp token = infile.token(i, j);
			if (!token->isKern()) {
				continue;
			}
			if (token->isTimeSignature()) {
				sigIndex = i;
			} else if (token->isMetricSymbol()) {
				symIndex = i;
			}
		}
	}

	if (dataIndex < 0) {
		return;
	}

	if ((sigIndex >= 0) && (symIndex >= 0)) {
		if (sigIndex > symIndex) {
			tlist.at(sigIndex+1) = tempo;
		} else {
			tlist.at(symIndex+1) = tempo;
		}
		return;
	} else if (sigIndex >= 0) {
		tlist.at(sigIndex+1) = tempo;
		return;
	} else if (symIndex >= 0) {
		return;
	} else if (dataIndex >= 0) {
		int localIndex = dataIndex - 1;
		int lastSpineIndex = localIndex;
		if (infile[dataIndex-1].isLocalComment()) {
			while (infile[localIndex].isLocalComment() || !infile[localIndex].hasSpines()) {
				if (!infile[localIndex].hasSpines()) {
					localIndex--;
					continue;
				} else {
					lastSpineIndex = localIndex;
				}
				if (infile[localIndex].isLocalComment()) {
					lastSpineIndex = localIndex;
					localIndex--;
					continue;
				}
			}
			tlist.at(lastSpineIndex) = tempo;
			return;
		} else {
			tlist.at(dataIndex) = tempo;
		}
	}

}



//////////////////////////////
//
// Tool_addtempo::addTempoToStart -- Can't use letter postfix for 0 measure for now.
//

void Tool_addtempo::addTempoToStart(vector<double>& tlist,
		HumdrumFile& infile, double tempo) {

	// find first measure and data line indexes:
	int barIndex  = -1;
	int dataIndex = -1;
	int sigIndex  = -1;
	int symIndex  = -1;

	for (int i=0; i<infile.getLineCount(); i++) {
		if (infile[i].isData()) {
			dataIndex = i;
			break;
		}

		if (infile[i].isBarline()) {
			if (barIndex < 0) {
				barIndex = i;
			}
			continue;
		}

		if (!infile[i].isInterpretation()) {
			continue;
		}
		for (int j=0; j<infile[i].getFieldCount(); j++) {
			HTp token = infile.token(i, j);
			if (!token->isKern()) {
				continue;
			}
			if (token->isTimeSignature()) {
				sigIndex = i;
			} else if (token->isMetricSymbol()) {
				symIndex = i;
			}
		}
	}

	if (dataIndex < 0) {
		return;
	}

	if ((sigIndex >= 0) && (symIndex >= 0)) {
		if (sigIndex > symIndex) {
			tlist.at(sigIndex+1) = tempo;
		} else {
			tlist.at(symIndex+1) = tempo;
		}
		return;
	} else if (sigIndex >= 0) {
		tlist.at(sigIndex+1) = tempo;
		return;
	} else if (symIndex >= 0) {
		return;
	} else if (dataIndex >= 0) {
		if (infile[dataIndex-1].isLocalComment()) {
			int localIndex = dataIndex - 1;
			int lastSpineIndex = localIndex;
			while (infile[localIndex].isLocalComment() || !infile[localIndex].hasSpines()) {
				if (!infile[localIndex].hasSpines()) {
					localIndex--;
					continue;
				} else {
					lastSpineIndex = localIndex;
				}
				if (infile[localIndex].isLocalComment()) {
					lastSpineIndex = localIndex;
					localIndex--;
					continue;
				}
			}
			tlist.at(lastSpineIndex) = tempo;
			return;
		} else {
			tlist.at(dataIndex) = tempo;
		}
	}

}





/////////////////////////////////
//
// Tool_autoaccid::Tool_autoaccid -- Set the recognized options for the tool.
//

Tool_autoaccid::Tool_autoaccid(void) {
	define("x|visual=b",                        "mark visual accidentals only");
	define("y|suppressed=b",                    "mark hidden accidentals only");
	define("r|remove=b",                        "remove accidental qualifications");
	define("c|keep-cautionary|keep-courtesy=b", "keep cautionary accidentals when removing markers");
}



/////////////////////////////////
//
// Tool_autoaccid::run -- Do the main work of the tool.
//

bool Tool_autoaccid::run(HumdrumFileSet& infiles) {
	bool status = true;
	for (int i=0; i<infiles.getCount(); i++) {
		status &= run(infiles[i]);
	}
	return status;
}


bool Tool_autoaccid::run(const string& indata, ostream& out) {
	HumdrumFile infile(indata);
	bool status = run(infile);
	if (hasAnyText()) {
		getAllText(out);
	} else {
		out << infile;
	}
	return status;
}


bool Tool_autoaccid::run(HumdrumFile& infile, ostream& out) {
	bool status = run(infile);
	if (hasAnyText()) {
		getAllText(out);
	} else {
		out << infile;
	}
	return status;
}


bool Tool_autoaccid::run(HumdrumFile& infile) {
	processFile(infile);
	return true;
}



//////////////////////////////
//
// Tool_autoaccid::initialize --  Initializations that only have to be done once
//    for all HumdrumFile segments.
//

void Tool_autoaccid::initialize(void) {
	m_visualQ   = getBoolean("visual");
	m_hiddenQ   = getBoolean("suppressed");
	m_removeQ   = getBoolean("remove");
	m_cautionQ  = getBoolean("keep-cautionary");
}



//////////////////////////////
//
// Tool_autoaccid::processFile --
//

void Tool_autoaccid::processFile(HumdrumFile& infile) {
	initialize();
	if (m_removeQ) {
		removeAccidentalQualifications(infile);
	} else {
		infile.analyzeKernAccidentals();
		addAccidentalQualifications(infile);
	}
	infile.createLinesFromTokens();
}


//////////////////////////////
//
// Tool_autoaccid::addAccidentalQualifications --
//
//

void Tool_autoaccid::addAccidentalQualifications(HumdrumFile& infile) {
	int scount = infile.getStrandCount();
	HumRegex hre;
	for (int i=0; i<scount; i++) {
		HTp sbegin = infile.getStrandBegin(i);
		if (!sbegin->isKern()) {
			continue;
		}
		HTp send   = infile.getStrandEnd(i);
		HTp current = sbegin;
		while (current && (current != send)) {
			if (!current->isData()) {
				current = current->getNextToken();
				continue;
			}
			if (current->isNull()) {
				current = current->getNextToken();
				continue;
			}
			if (current->isRest()) {
				current = current->getNextToken();
				continue;
			}
			addAccidentalInfo(current);
			current = current->getNextToken();
		}
	}
}



//////////////////////////////
//
// Tool_autoaccid::addAccidentalInfo --
//
// Analysis have several parameters:
//      visualAccidental = true :: need to add an X after the accidental
//      visualAccidental = false :: need to add an y after the accidental
// also:
//      cautionaryAccidental = "true" ::
//      obligatoryAccidental = "true" ::
//

void Tool_autoaccid::addAccidentalInfo(HTp token) {
	vector<string> subtokens;
	subtokens = token->getSubtokens();
	if (subtokens.size() == 1) {
		bool visual = token->getValueBool("auto", "0", "visualAccidental");
		subtokens[0] = setVisualState(subtokens[0], visual);
	} else {
		for (int i=0; i<(int)subtokens.size(); i++) {
			bool visual = token->getValueBool("auto", to_string(i+1), "visualAccidental");
			subtokens[i] = setVisualState(subtokens[i], visual);
		}
	}
	string text;
	for (int i=0; i<(int)subtokens.size(); i++) {
		text += subtokens[i];
		if (i < (int)subtokens.size() - 1) {
			text += ' ';
		}
	}
	token->setText(text);
}



//////////////////////////////
//
// Tool_autoaccid::setVisualState --
//

string Tool_autoaccid::setVisualState(const string& input, bool state) {
	HumRegex hre;
	if (hre.search(input, "[-#n][Xy]")) {
		// do not remark accidental
		return input;
	}
	bool hasNatural = hre.search(input, "n");
	bool hasFlat    = hre.search(input, "-");
	bool hasSharp   = hre.search(input, "#");
	bool accidental = hasNatural || hasFlat || hasSharp;
	string output;
	if (m_visualQ) {
		if (state) {
			if (!accidental) {
				// need to show a natural accidental (and add the natural sign)
				output = hre.replaceCopy(input, "$1nX", "([A-Ga-g]+)");
			} else {
				// force accidental to display
				output = hre.replaceCopy(input, "$1X", "([-#n]+)");
			}
		} else {
			// do nothing
		}
	} else if (m_hiddenQ) {
		if (!state) {
			if (accidental) {
				// force accidental to be hidden
				output = hre.replaceCopy(input, "$1y", "([-#n]+)");
			} else {
				output = input;
			}
		}
	} else {
		// force display/hide state for accidental
		if (state) {
			if (!accidental) {
				// need to show a natural accidental (and add the natural sign)
				output = hre.replaceCopy(input, "$1nX", "([A-Ga-g]+)");
			} else {
				// force accidental to display
				output = hre.replaceCopy(input, "$1X", "([-#n]+)");
			}
		} else {
			if (accidental) {
				// force accidental to be hidden
				output = hre.replaceCopy(input, "$1y", "([-#n]+)");
			} else {
				output = input;
			}
		}
	}
	return output;
}



//////////////////////////////
//
// Tool_autoaccid::removeAccidentalQualifications --
//
//

void Tool_autoaccid::removeAccidentalQualifications(HumdrumFile& infile) {
	int scount = infile.getStrandCount();
	HumRegex hre;
	for (int i=0; i<scount; i++) {
		HTp sbegin = infile.getStrandStart(i);
		if (!sbegin->isKern()) {
			continue;
		}
		HTp send   = infile.getStrandEnd(i);
		HTp current = sbegin;
		while (current && (current != send)) {
			if (!current->isData()) {
				current = current->getNextToken();
				continue;
			}
			if (current->isNull()) {
				current = current->getNextToken();
				continue;
			}
			if (current->isRest()) {
				current = current->getNextToken();
				continue;
			}
			string text = current->getText();
			if (m_visualQ) {
				hre.replaceDestructive(text, "$1", "([-#n]+)X(?!X)", "g");
			} else if (m_hiddenQ) {
				hre.replaceDestructive(text, "$1", "([-#n]+)y(?!y)", "g");
			} else {
				hre.replaceDestructive(text, "$1", "([-#n]+)X(?!X)", "g");
				hre.replaceDestructive(text, "$1", "([-#n]+)y(?!y)", "g");
			}
		}
		current = current->getNextToken();
	}
}






/////////////////////////////////
//
// Tool_autobeam::Tool_autobeam -- Set the recognized options for the tool.
//

Tool_autobeam::Tool_autobeam(void) {
	define("k|kern=i:0",           "process specific kern spine number");
	define("t|track|tracks=s:0",   "process specific track number(s)");
	define("r|remove=b",           "remove all beams");
	define("g|grace=b",            "beam grace notes sequences");
	define("o|overwrite=b",        "over-write existing beams");
	define("l|lyric|lyrics=b",     "break beam by lyric syllables");
	define("L|lyric-info=b",       "return the number of breaks needed");
	define("rest|include-rests=b", "include rests in beam edges");
}



/////////////////////////////////
//
// Tool_autobeam::run -- Primary interfaces to the tool.
//

bool Tool_autobeam::run(HumdrumFileSet& infiles) {
	bool status = true;
	for (int i=0; i<infiles.getCount(); i++) {
		status &= run(infiles[i]);
	}
	return status;
}


bool Tool_autobeam::run(const string& indata, ostream& out) {
	HumdrumFile infile(indata);
	bool status = run(infile);
	if (hasAnyText()) {
		getAllText(out);
	} else {
		out << infile;
	}
	return status;
}


bool Tool_autobeam::run(HumdrumFile& infile, ostream& out) {
	bool status = run(infile);
	if (hasAnyText()) {
		getAllText(out);
	} else {
		out << infile;
	}
	return status;
}

//
// In-place processing of file:
//

bool Tool_autobeam::run(HumdrumFile& infile) {
	initialize(infile);
	if (getBoolean("remove")) {
		removeBeams(infile);
	} else if (getBoolean("grace")) {
		beamGraceNotes(infile);
	} else if (getBoolean("lyrics")) {
		breakBeamsByLyrics(infile);
	} else if (getBoolean("lyric-info")) {
		breakBeamsByLyrics(infile);
		m_free_text << m_splitcount << endl;
		return true;
	} else {
		addBeams(infile);
	}
	// Re-load the text for each line from their tokens.
	infile.createLinesFromTokens();
	m_humdrum_text << infile;
	return true;
}



//////////////////////////////
//
// Tool_autobeam::beamGraceNotes --  Using lazy beaming, and
//    not careful checking of beamed notes versus nonbeamed notes
//    such as a mix of quarter and eighth grace notes in the group.
//

void Tool_autobeam::beamGraceNotes(HumdrumFile& infile) {
	int strands = infile.getStrandCount();
	HTp endtok;
	HTp starttok;
	HTp gstart;
	HTp gend;
	HTp token;
	int track;
	string newstr;
	for (int i=0; i<strands; i++) {
		starttok = infile.getStrandStart(i);
		track = starttok->getTrack();
		if (!m_tracks.at(track)) {
			continue;
		}
		if (!starttok->isKernLike()) {
			continue;
		}
		endtok   = infile.getStrandEnd(i);
		token    = starttok;
		gstart   = NULL;
		gend     = NULL;

		while (token && (token != endtok)) {
			if (!token->isData()) {
				token = token->getNextToken();
				continue;
			}
			if (token->isNull()) {
				token = token->getNextToken();
				continue;
			}

			if (token->isGrace()) {
				if (!gstart) {
					gstart = token;
					gend   = token;
				} else {
					gend = token;
				}
			} else if (gstart && gend) {
				if (gstart == gend) {
					gstart = NULL;
					gend   = NULL;
					token = token->getNextToken();
					continue;
				}
				if (gstart->hasBeam() || gend->hasBeam()) {
					gstart = NULL;
					gend   = NULL;
					token = token->getNextToken();
					continue;
				}
				string stext = getBeamFromDur(gstart, "L");
				string etext = getBeamFromDur(gend, "J");
				if ((stext.size() == etext.size()) && !stext.empty()) {
					string text;
					text = gstart->getText() + stext;
					gstart->setText(text);
					text = gend->getText() + etext;
					gend->setText(text);
					removeQqMarks(gstart, gend);
				}
				gstart = NULL;
				gend   = NULL;
			} else {
				gstart = NULL;
				gend   = NULL;
			}
			token = token->getNextToken();
		}

		// Handle grace note at the end of a strand:
		if ((gstart && gend) && (gstart != gend)) {
			if (!(gstart->hasBeam() || gend->hasBeam())) {
				string stext = getBeamFromDur(gstart, "L");
				string etext = getBeamFromDur(gend, "J");
				if ((stext.size() == etext.size()) && !stext.empty()) {
					string text;
					text = gstart->getText() + stext;
					gstart->setText(text);
					text = gend->getText() + etext;
					gend->setText(text);
					removeQqMarks(gstart, gend);
				}
			}
			gstart = NULL;
			gend   = NULL;
		}
	}
}



//////////////////////////////
//
// Tool_autobeam::removeQqMarks -- qq means a grace note on the beat, and q means a grace note before
//   the beat.  When there are multiple grace notes, they are typically always considered to be before
//   the beat.
//

void Tool_autobeam::removeQqMarks(HTp stok, HTp etok) {
	if (!stok) {
		return;
	}
	if (!etok) {
		return;
	}
	removeQqMarks(etok);
	HTp curr = stok;
	while (curr && (curr != etok)) {
		if (curr->isGrace()) {
			removeQqMarks(curr);
		}
		curr = curr->getNextToken();
	}
}


void Tool_autobeam::removeQqMarks(HTp tok) {
	HumRegex hre;
	string text = tok->getText();
	hre.replaceDestructive(text, "q", "qq", "g");
	tok->setText(text);
}



//////////////////////////////
//
// Tool_autobeam::getBeamFromDur -- Not dealing with tuplet grace notes.
//

string Tool_autobeam::getBeamFromDur(HTp token, const string& text) {
	HumNum dur = Convert::recipToDurationIgnoreGrace(token);
	dur.invert();
	dur *= 2;
	if (dur.getDenominator() != 1) {
		return "";
	}
	int value = (int)(log(dur.getNumerator()/log(2.0)));
	string output;
	for (int i=0; i<value; i++) {
		output += text;
	}
	return output;
}



//////////////////////////////
//
// Tool_autobeam::removeBeams --
//

void Tool_autobeam::removeBeams(HumdrumFile& infile) {
	int strands = infile.getStrandCount();
	HTp endtok;
	HTp starttok;
	HTp token;
	int track;
	bool bfound = false;
	string newstr;
	for (int i=0; i<strands; i++) {
		starttok = infile.getStrandStart(i);
		track = starttok->getTrack();
		if (!m_tracks.at(track)) {
			continue;
		}
		if (!starttok->isKernLike()) {
			continue;
		}
		endtok   = infile.getStrandEnd(i);
		token    = starttok;

		while (token && (token != endtok)) {
			if (!token->isData()) {
				token = token->getNextToken();
				continue;
			}
			if (token->isNull()) {
				token = token->getNextToken();
				continue;
			}

			bfound = false;
			newstr.clear();
			for (int i=0; i<(int)((string)(*token)).size(); i++) {
				switch (((string)(*token))[i]) {
					case 'L':
					case 'J':
					case 'K':
					case 'k':
						bfound = true;
						break;
					default:
						newstr += ((string)(*token))[i];
				}
			}
			if (bfound) {
				((string)(*token))[0] = 'X';
				token->swap(newstr);
			}
			token = token->getNextToken();
		}
	}
}


//////////////////////////////
//
// Tool_autobeam::breakBeamsByLyrics --
//


void Tool_autobeam::breakBeamsByLyrics(HumdrumFile& infile) {
	infile.analyzeNonNullDataTokens();
	int strands = infile.getStrandCount();
	int track;
	for (int i=0; i<strands; i++) {
		HTp starttok = infile.getStrandStart(i);
		track = starttok->getTrack();
		if (!m_tracks.at(track)) {
			continue;
		}
		if (!starttok->isKernLike()) {
			continue;
		}
		HTp curtok = starttok->getNextFieldToken();
		bool hastext = false;
		while (curtok && !curtok->isKernLike()) {
			if (curtok->isDataType("**text")) {
				hastext = true;
				break;
			}
			curtok = curtok->getNextFieldToken();
		}
		if (!hastext) {
			continue;
		}
		processStrandForLyrics(infile.getStrandStart(i), infile.getStrandEnd(i));
	}
}



//////////////////////////////
//
// Tool_autobeam::processStrandForLyrics --
//

void Tool_autobeam::processStrandForLyrics(HTp stok, HTp etok) {
	HTp current = stok;
	current = current->getNextNNDT();
	while (current && (current != etok)) {
		if (hasSyllable(current)) {
			splitBeam(current, stok, etok);
		}
		current = current->getNextNNDT();
	}
}



//////////////////////////////
//
// Tool_autobeam::splitBeam --
//

void Tool_autobeam::splitBeam(HTp tok, HTp stok, HTp etok) {
	HumNum duration = Convert::recipToDuration(tok);
	if (duration >= 1) {
		// Should not be beamed
		return;
	}
	vector<HTp> seq;
	getBeamedNotes(seq, tok, stok, etok);
	if (seq.size() == 0) {
		// note is larger than eighth note
		return;
	}
	if (seq.size() == 1) {
		// Single note with no beam possible
		return;
	}
	splitBeam2(seq, tok);
}



//////////////////////////////
//
// Tool_autobeam::splitBeam2 --
//

void Tool_autobeam::splitBeam2(vector<HTp>& group, HTp tok) {
	int target = -1;
	for (int i=0; i<(int)group.size(); i++) {
		if (group[i] == tok) {
			target = i;
			break;
		}
	}

	if (target <= 0) {
		// problem or at start of beam, so do not modify beam.
		return;
	}

	m_splitcount++;
	if (group.size() <= 2) {
		// remove beam completely
		for (int i=0; i<(int)group.size(); i++) {
			string value = *group[i];
			string newvalue;
			for (int j=0; j<(int)value.size(); j++) {
				if ((value[j] == 'L') || (value[j] == 'J') || (toupper(value[j]) == 'K')) {
					continue;
				}
				newvalue += value[j];
			}
			group[i]->setText(newvalue);
		}
		return;
	}
	int lazyQ = isLazy(group);
	if (lazyQ) {
		splitBeamLazy(group, tok);
	} else {
		splitBeamNotLazy(group, tok);
	}
}



//////////////////////////////
//
// Tool_autobeam::splitBeamNotLazy --
//

void Tool_autobeam::splitBeamNotLazy(vector<HTp>& group, HTp tok) {
	int target = -1;

	for (int i=0; i<(int)group.size(); i++) {
		if (tok == group[i]) {
			target = i;
			break;
		}
	}
	if (target < 0) {
		return;
	}

	vector<int> sbeam(group.size(), 0);
	vector<int> ebeam(group.size(), 0);

	for (int i=0; i<(int)group.size(); i++) {
		string value = *group[i];
		int Lcount = 0;
		int Jcount = 0;
		for (int j=0; j<(int)value.size(); j++) {
			if (value[j] == 'L') {
				Lcount++;
			}
			if (value[j] == 'J') {
				Jcount++;
			}
		}
		sbeam[i] = Lcount;
		ebeam[i] = Jcount;
	}

	vector<int> sum(group.size(), 0);
	sum[0] = sbeam[0] - ebeam[0];
	for (int i=1; i<(int)sum.size(); i++) {
		sum[i] = sum[i-1] + sbeam[i] - ebeam[i];
	}
	vector<int> rsum(group.size(), 0);
	int rsize = (int)rsum.size();
	rsum[rsize - 1] = ebeam[rsize - 1] - sbeam[rsize - 1];
	for (int i=rsize-2; i>=0; i--) {
		rsum[i] = rsum[i+1] - sbeam[i] + ebeam[i];
	}

	if (target == 1) {
		// remove the first note from a beam group
		removeBeamCharacters(group[0]);
		string value = *group[1];
		for (int i=0; i<rsum[1]; i++) {
			value += 'L';
		}
		group[1]->setText(value);
	} else if (target == (int)group.size() - 1) {
		// remove the last note from the beam
		removeBeamCharacters(group[(int)group.size() - 1]);
		string value = *group[(int)group.size()-2];
		for (int i=0; i<sum[(int)group.size()-2]; i++) {
			value += 'J';
		}
		group[(int)group.size() - 2]->setText(value);
	} else {
		// split beam into two beams
		string value = *group[target];
		for (int i=0; i<rsum[target]; i++) {
			value += 'L';
		}
		group[target]->setText(value);

		value = *group[target-1];
		for (int i=0; i<sum[target-1]; i++) {
			value += 'J';
		}
		group[target-1]->setText(value);
	}
}



//////////////////////////////
//
// Tool_autobeam::splitBeamLazy -- Input will have more than two notes in the beam.
//

void Tool_autobeam::splitBeamLazy(vector<HTp>& group, HTp tok) {

	int target = -1;
	for (int i=0; i<(int)group.size(); i++) {
		if (tok == group[i]) {
			target = i;
			break;
		}
	}
	if (target < 0) {
		return;
	}
	if (target == 1) {
		// remove the first note from a beam group
		removeBeamCharacters(group[0]);
		string value = *group[1];
		value += 'L';
		group[1]->setText(value);
	} else if (target == (int)group.size() - 2) {
		// remove the last note from the beam
		removeBeamCharacters(group[(int)group.size() - 1]);
		string value = *group[(int)group.size()-2];
		value += 'J';
		group[(int)group.size() - 2]->setText(value);
	} else {
		// split beam into two beams
		string value = *group[target];
		value += 'L';
		group[target]->setText(value);
		value = *group[target-1];
		value += 'J';
		group[target-1]->setText(value);
	}

}



//////////////////////////////
//
// Tool_autobeam::removeBeamCharacters --
//

void Tool_autobeam::removeBeamCharacters(HTp token) {
	string value = *token;
	string newvalue;
	for (int i=0; i<(int)value.size(); i++) {
		if ((value[i] == 'L') || (value[i] == 'J') || (toupper(value[i]) == 'K')) {
			continue;
		}
		newvalue += value[i];
	}
	if (newvalue.size()) {
		token->setText(newvalue);
	} else {
		token->setText(".");
	}
}



//////////////////////////////
//
// Tool_autobeam::isLazy -- Return if just a single beam encoded
//   (even if should only have one beam).
//

bool Tool_autobeam::isLazy(vector<HTp>& group) {
	int Lcount = 0;
	int Jcount = 0;
	int Kcount = 0;
	for (int i=0; i<(int)group.size(); i++) {
		string value = *group[i];
		for (int j=0; j<(int)value.size(); j++) {
			if (value[j] == 'L') {
				Lcount++;
			} else if (value[j] == 'J') {
				Jcount++;
			} else if (toupper(value[j]) == 'K') {
				Kcount++;
			}
		}
	}
	if ((Lcount == 1) && (Jcount == 1) && (Kcount == 0)) {
		return true;
	}
	return false;
}



//////////////////////////////
//
// Tool_autobeam::getBeamedNotes --
//

void Tool_autobeam::getBeamedNotes(vector<HTp>& toks, HTp tok, HTp stok, HTp etok) {
	toks.resize(0);
	vector<HTp> backward;
	vector<HTp> forward;
	vector<HTp> seq;
	HTp current = tok;
	while (current && !current->isBarline()) {
		if (current->isNull()) {
			current = current->getNextToken();
			if (current && (current == etok)) {
				break;
			}
		}
		HumNum dur = Convert::recipToDuration(current);
		if (dur >= 1) {
			// No beams should be on this note
			break;
		}
		forward.push_back(current);
		current = current->getNextToken();
		if (current && (current == etok)) {
			break;
		}
	}

	current = tok->getPreviousToken();
	while (current && !current->isBarline()) {
		if (current->isNull()) {
			if (current == stok) {
				break;
			}
			current = current->getPreviousToken();
			if (current && (current == stok)) {
				break;
			}
			continue;
		}
		HumNum dur = Convert::recipToDuration(current);
		if (dur >= 1) {
			// No beams should be on this note
			break;
		}
		backward.push_back(current);
		if (current == stok) {
			break;
		}
		current = current->getPreviousToken();
	}

	seq.clear();
	for (int i=(int)backward.size() - 1; i>=0; i--) {
		seq.push_back(backward[i]);
	}
	for (int i=0; i<(int)forward.size(); i++) {
		seq.push_back(forward[i]);
	}

	if (seq.size() < 2) {
		// no beams possible
		return;
	}

	vector<int> sbeam(seq.size(), 0);
	vector<int> ebeam(seq.size(), 0);

	for (int i=0; i<(int)seq.size(); i++) {
		string value = *seq[i];
		int Lcount = 0;
		int Jcount = 0;
		for (int j=0; j<(int)value.size(); j++) {
			if (value[j] == 'L') {
				Lcount++;
			}
			if (value[j] == 'J') {
				Jcount++;
			}
		}
		sbeam[i] = Lcount;
		ebeam[i] = Jcount;
	}

	vector<int> sum(seq.size(), 0);
	sum[0] = sbeam[0] - ebeam[0];
	for (int i=1; i<(int)sum.size(); i++) {
		sum[i] = sum[i-1] + sbeam[i] - ebeam[i];
	}

	int target = -1;
	for (int i=0; i<(int)sum.size(); i++) {
		if (seq[i] == tok) {
			target = i;
			break;
		}
	}


	if ((target == 0) && (sum[0] == 0)) {
		// no beam on note
		return;
	}

	int sindex = -1;
	int eindex = -1;
	if (sum[target] == 0) {
		if (sum[target - 1] == 0) {
			// no beam on note
			return;
		} else {
			// There is a beam on target note and currently at
			// the end of the beam, so find the start:
			eindex = target;
			sindex = target;
			for (int i=target-1; i>=0; i--) {
				if (sum[i] != 0) {
					sindex = i;
				} else {
					break;
				}
			}
		}
	} else {
		// In the middle of a beam so expand outwards to find
		// the start and end of the beam group.
		for (int i=target; i>=0; i--) {
			if (sum[i] != 0) {
				sindex = i;
			} else {
				break;
			}
		}
		for (int i=target; i<(int)sum.size(); i++) {
			if (sum[i] == 0) {
				eindex = i;
				break;
			}
		}
	}

	if (eindex < 0) {
		// in case where beam is not closed properly, assume last note is beam end.
		eindex = (int)sum.size() - 1;
	}
	if (sindex < 0) {
		// in case where beam is not opened properly, assume last note is beam end.
		sindex = 0;
	}

	toks.clear();
	for (int i=sindex; i<=eindex; i++) {
		toks.push_back(seq[i]);
	}
}



//////////////////////////////
//
// Tool_autobeam::hasSyllable -- Only checking the first verse.
//

bool Tool_autobeam::hasSyllable(HTp token) {
	HTp current = token->getNextFieldToken();
	while (current && !current->isKernLike()) {
		if (current->isDataType("**text")) {
			if (current->isNull()) {
				return false;
			} else {
				return true;
			}
		}
		current = current->getNextFieldToken();
	}
	return false;
}



//////////////////////////////
//
// Tool_autobeam::addBeams --
//

void Tool_autobeam::addBeams(HumdrumFile& infile) {
	infile.analyzeNonNullDataTokens();
	int strands = infile.getStrandCount();
	int track;
	for (int i=0; i<strands; i++) {
		HTp starttok = infile.getStrandStart(i);
		track = starttok->getTrack();
		if (!m_tracks.at(track)) {
				continue;
		}
		if (!starttok->isKernLike()) {
			continue;
		}
		processStrand(infile.getStrandStart(i), infile.getStrandEnd(i));
	}
}



//////////////////////////////
//
// Tool_autobeam::initialize -- extract time signature lines for
//    each **kern spine in file.
//

void Tool_autobeam::initialize(HumdrumFile& infile) {
	m_splitcount = 0;
	m_kernspines = infile.getKernLikeSpineStartList();
	vector<HTp>& ks = m_kernspines;
	m_timesigs.resize(infile.getTrackCount() + 1);
	for (int i=0; i<(int)ks.size(); i++) {
		infile.getTimeSigs(m_timesigs[ks[i]->getTrack()], ks[i]->getTrack());
	}
	m_overwriteQ = getBoolean("overwrite");

	int maxtrack = infile.getMaxTrack();
	if (getBoolean("track")) {
		string tracklist = getString("track");
		Convert::makeBooleanTrackList(m_tracks, tracklist, maxtrack);
	} else {
		// process all (kern) tracks:
		m_tracks.resize(maxtrack+1);
		fill(m_tracks.begin(), m_tracks.end(), true);
	}

	m_includerests = getBoolean("include-rests");
}



//////////////////////////////
//
// Tool_autobeam::processStrand -- Add beams to a single strand.
//

void Tool_autobeam::processStrand(HTp strandstart, HTp strandend) {
	HTp token = strandstart;
	vector<HTp> measure;
	while (token && (token != strandend)) {
		if (token->isBarline()) {
			processMeasure(measure);
			measure.clear();
			token = token->getNextToken();
			continue;
		}
		if (!token->isData()) {
			token = token->getNextToken();
			continue;
		}
		if (token->isNull()) {
			token = token->getNextToken();
			continue;
		}
		measure.push_back(token);
		token = token->getNextToken();
	}

}



//////////////////////////////
//
// Tool_autobeam::processMeasure -- Need to deal with rests starting or ending
//    a beamed group.
//

void Tool_autobeam::processMeasure(vector<HTp>& measure) {
	if (measure.empty()) {
		return;
	}

	vector<HumNum> beatsize;
	vector<HumNum> beatpos;
	vector<HumNum> notedurnodots;

	// default beat duration is a quarter note.
	pair<int, HumNum> current;
	current.first = 1;
	current.second = 4;
	HumNum beatdur(1);

	// First, get the beat positions of all notes in the measure:
	vector<pair<int, HumNum> >& timesig = m_timesigs[measure[0]->getTrack()];
	// Force 4/4 time signature if no time siguature (perhaps refine the 
	// top number of the time signature to the duration of the measure, but
	// currently this is probably not necessary.
	for (int i=0; i<(int)timesig.size(); i++) {
		if (timesig[i].first == 0) {
			timesig[i].first = 4;
		}
		if (timesig[i].second == 0) {
			timesig[i].second = 4;
		}
	}

	for (int i=0; i<(int)measure.size(); i++) {
		int line = measure[i]->getLineIndex();
		if ((current.first != timesig.at(line).first) ||
		    (current.second != timesig.at(line).second)) {
			current = timesig[line];
			beatdur = 1;
			beatdur /= current.second;
			beatdur *= 4; // convert to quarter-notes units from whole-notes.
			if ((current.first % 3 == 0) && (current.first != 3)) {
				// compound meter, so shift the beat to 3x the denominator
				beatdur *= 3;
			} else if (current.first == 3 && (current.second < 4)) {
				// time signatures such as 3/8 and 3/16 which should
				// beam together at the measure level (3/4 and 3/2 not included).
				beatdur *= 3;
			}
		}
		beatsize.push_back(beatdur);
		notedurnodots.push_back(measure[i]->getDurationNoDots());
		beatpos.push_back(measure[i]->getDurationFromBarline() / beatdur);
	}

	// Now identify notes that should be beamed together
	// (using lazy beaming for now).
	HumNum eighthnote(1, 2);
	int beat1 = 0;
	#define INVALID -1000000
	int beamstart = INVALID;
	for (int i=0; i<(int)measure.size(); i++) {
		if (notedurnodots[i] > eighthnote) {
			// note does not need a beam, but first check if an open
			// beam should be closed:
			if ((beamstart >= 0) && (i - beamstart > 1)) {
				addBeam(measure[beamstart], measure[i-1]);
				beamstart = INVALID;
				continue;
			}
			beamstart = INVALID;
			continue;
		}

		if (beamstart == INVALID) {
			// possible beam start
			beamstart = i;
			beat1 = (int)beatpos[i].getFloat();
			continue;
		}

		int beat2 = (int)beatpos[i].getFloat();
		if (beat1 == beat2) {
			// note should be added to current beam, but could
			// be beamed to notes after it, so continue on to next note.
			continue;
		} else {
			// check if previous beam should be ended.
			if ((beamstart >= 0) && (i - beamstart > 1)) {
				addBeam(measure[beamstart], measure[i-1]);
				beamstart = i;
				beat1 = (int)beatpos[i].getFloat();
				continue;
			}
			beamstart = i;
			beat1 = (int)beatpos[i].getFloat();
		}

		// Note should not be attached to previous beam.

		// The current note should not be beamed to current note,
		// but perhaps will be beams to the following notes.
		beamstart = i;
		beat1 = (int)beatpos[i].getFloat();
	}

	// close the last beam
	if ((beamstart >= 0) && ((int)measure.size() - beamstart > 1)) {
		addBeam(measure[beamstart], measure[(int)measure.size()-1]);
		beamstart = INVALID;
	}
}



//////////////////////////////
//
// Tool_autobeam::addBeam -- Lazy beaming for now.
//

void Tool_autobeam::addBeam(HTp startnote, HTp endnote) {
	if (!startnote) {
		return;
	}
	if (!endnote) {
		return;
	}
	if (!m_includerests) {
		removeEdgeRests(startnote, endnote);
	}
	if (startnote == endnote) {
		// Nothing to do since only one note in beam.
		return;
	}
	if (!m_overwriteQ) {
		HTp token = startnote;
		while (token && (token != endnote)) {
			if (token->hasBeam()) {
				return;
			}
			token = token->getNextToken();
		}
	}
	startnote->push_back('L');
	endnote->push_back('J');
}



//////////////////////////////
//
// Tool_autobeam::removeEdgeRests -- If the regions to be beams contain
//     beams at the endpoints, shrink the beam until it finds notes.
//     If there are no notes, then set startnote and endnote both to NULL.
//

void Tool_autobeam::removeEdgeRests(HTp& startnote, HTp& endnote) {
	HTp current = startnote;

	int startindex = startnote->getLineIndex();
	int endindex = endnote->getLineIndex();


	if (startnote->isRest()) {
		current = current->getNextNNDT();
		while (current && current->isRest()) {
			if (current == endnote) {
				startnote = current;
				return;
			}
			current = current->getNextNNDT();
		}
		if (!current) {
			// Handle problem when spine is not terminated.
			return;
		}

		if (current->getLineIndex() >= endindex) {
			startnote = endnote;
			return;
		} else {
			startnote = current;
		}
	}

	if (endnote->isRest()) {
		HTp newcurrent = endnote;

		newcurrent = newcurrent->getPreviousNNDT();
		while (newcurrent && newcurrent->isRest()) {
			if (newcurrent == startnote) {
				endnote = newcurrent;
				return;
			}
			newcurrent = newcurrent->getPreviousNNDT();
		}

		if (newcurrent->getLineIndex() <= startindex) {
			endnote = startnote;
			return;
		} else {
			endnote = newcurrent;
		}
	}

}





/////////////////////////////////
//
// Tool_autocadence::Tool_autocadence -- Set the recognized options for the tool.
//

Tool_autocadence::Tool_autocadence(void) {
	define("c|color-cadence-notes=b", "Color cadence formula notes that match");
	define("d|show-formula-index=b",  "Append formula index after CVF label");
	define("e|even-note-spacing=b",   "Compress notation as much as possible");
	define("i|include-intervals=b",   "Display interval strings for notes in score (no further analysis)");
	define("m|matches=b",             "Give list of matching sequences only");
	define("p|pitches=b",             "Display extracted base-7 pitches only");
	define("r|regex=b",               "Display table of cadence formula regexes");
	define("s|sequences=b",           "Give list of extracted sequences only");
	define("I|intervals-only=b",      "Display interval strings for notes in score (no further analysis)");
	define("color=s:dodgerblue",      "Color cadence formula notes with given color");
	define("count|match-count=b",     "Return number of cadence formulas that match");
	define("B|no-back-highlight=b",   "Do not color start of sustain note at start of cadence definition");
	define("S|no-suspensions=b",      "Do not use suspensions from dissonance analysis");
}



/////////////////////////////////
//
// Tool_autocadence::run -- Do the main work of the tool.
//

bool Tool_autocadence::run(HumdrumFileSet& infiles) {
	bool status = true;
	for (int i=0; i<infiles.getCount(); i++) {
		status &= run(infiles[i]);
	}
	return status;
}


bool Tool_autocadence::run(const string& indata, ostream& out) {
	HumdrumFile infile(indata);
	bool status = run(infile);
	if (hasAnyText()) {
		getAllText(out);
	} else {
		out << infile;
	}
	return status;
}


bool Tool_autocadence::run(HumdrumFile& infile, ostream& out) {
	bool status = run(infile);
	if (hasAnyText()) {
		getAllText(out);
	} else {
		out << infile;
	}
	return status;
}


bool Tool_autocadence::run(HumdrumFile& infile) {
	initialize();
	processFile(infile);
	return true;
}



//////////////////////////////
//
// Tool_autocadence::initialize --  Initializations that only have to be done once
//    for all HumdrumFile segments.
//

void Tool_autocadence::initialize(void) {
	m_printRawDiatonicPitchesQ =  getBoolean("pitches");
	m_intervalsOnlyQ           =  getBoolean("intervals-only");
	m_intervalsQ               =  getBoolean("include-intervals");
	m_matchesQ                 =  getBoolean("matches");
	m_printSequenceInfoQ       =  getBoolean("sequences");
	m_countQ                   =  getBoolean("match-count");
	m_colorQ                   =  getBoolean("color-cadence-notes");
	m_color                    =  getString("color");
	m_showFormulaIndexQ        =  getBoolean("show-formula-index");
	m_evenNoteSpacingQ         =  getBoolean("even-note-spacing");
	m_regexQ                   =  getBoolean("regex");
	m_nobackQ                  = !getBoolean("no-back-highlight");
	m_suspensionsQ             = !getBoolean("no-suspensions");

	prepareCadenceDefinitions();
	prepareCvfNames();
	prepareDissonanceNames();
}



//////////////////////////////
//
// Tool_autocadence::processFile --
//

void Tool_autocadence::processFile(HumdrumFile& infile) {


	// fill m_pitches and m_lowestPitch
	preparePitchInfo(infile);
	if (m_printRawDiatonicPitchesQ) {
		printExtractedPitchInfo(infile);
		return;
	}

	// identify dissonances
	if (m_suspensionsQ) {
		prepareDissonances(infile);
	}

	// fill m_intervals
	prepareIntervalInfo(infile);
	if (m_intervalsOnlyQ) {
		printExtractedIntervalInfo(infile);
		return;
	}

	// fill m_sequences
	prepareIntervalSequences(infile);
	if (m_printSequenceInfoQ) {
		printSequenceInfo();
		return;
	}

	// identify cadences
	searchIntervalSequences();
	if (m_matchesQ) {
		printSequenceMatches();
		return;
	} else if (m_countQ) {
		printMatchCount();
		return;
	}

	// markup score with matches and CVF
	markupScore(infile);
	printScore(infile);

}



//////////////////////////////
//
// Tool_autocadence::printScore --
//

void Tool_autocadence::printScore(HumdrumFile& infile) {
	infile.createLinesFromTokens();

	prepareAbbreviations(infile);
	vector<HTp> kspines;
	infile.getKernSpineStartList(kspines);
	int kcount = (int)kspines.size();
	if (!m_intervalsQ) {
		kcount = 0;
	}

	for (int i=0; i < infile.getLineCount(); i++) {
		if (!infile[i].hasSpines()) {
			m_humdrum_text << infile[i] << endl;
		} else if (infile[i].isEmpty()) {
			m_humdrum_text << endl;
		} else if (infile[i].isManipulator()) {
			printIntervalManipulatorLine(infile, i, kcount);
		} else if (infile[i].isData()) {
			printIntervalDataLineScore(infile, i, kcount);
		} else if (infile[i].isBarline()) {
			string bartok = *infile.token(i, 0);
			printIntervalLine(infile, i, kcount, bartok);
		} else if (infile[i].isCommentLocal()) {
			printIntervalLine(infile, i, kcount, "!");
		} else if (infile[i].isInterpretation()) {
			printIntervalLine(infile, i, kcount, "*");
		} else {
			m_humdrum_text << "!! UNKNOWN LINE: " << infile[i] << endl;
		}
	}

	if (m_colorQ) {
		m_humdrum_text << "!!!RDF**kern: " << m_marker << " = marked note, color=" << m_color << endl;
	}
	if (m_hasSuspensionMarkersQ) {
		m_humdrum_text << "!!!RDF**kern: " << m_suspensionMarker << " = marked note, color=" << m_suspensionColor << endl;
	}
	if (m_evenNoteSpacingQ) {
		m_humdrum_text << "!!!verovio: evenNoteSpacing" << endl;
	}

	if (m_regexQ) {
		printRegexTable();
	}
}



//////////////////////////////
//
// Tool_autocadence::printRegexTable --
//

void Tool_autocadence::printRegexTable(void) {

	// pair::first: index into m_definitions;
	// pair::second: occurrence count for given cadence in score.
	set<int> definitionList;;
	prepareDefinitionList(definitionList);

	if (definitionList.empty()) {
		m_humdrum_text << "!!@@BEGIN: PREHTML" << endl;
		m_humdrum_text << "!!@CONTENT: <i style='color:red'>No cadences found in music</i>" << endl;
		m_humdrum_text << "!!@@END: PREHTML" << endl;
		return;
	}

	m_humdrum_text << "!!@@BEGIN: PREHTML" << endl;
	m_humdrum_text << "!!@CONTENT:" << endl;
	m_humdrum_text << "!! <table class=regex>" << endl;

	m_humdrum_text << "!! <tr>" << endl;
	m_humdrum_text << "!! <th class=lcvf title='lower cadence vocal function'>LCVF</th>" << endl;
	m_humdrum_text << "!! <th class=ucvf title='upper cadence vocal function'>UCVF</th>" << endl;
	m_humdrum_text << "!! <th class=name title='cadence name (abbreviation)'>Name</th>" << endl;
	m_humdrum_text << "!! <th class='index' title='cadence enumeration'>Index</th>" << endl;
	m_humdrum_text << "!! <th title='regular expression definition for cadence formula'>Cadence formula</th>" << endl;
	m_humdrum_text << "!! </tr>" << endl;

	for (int index : definitionList) {
		printDefinitionRow(index);
	}

	m_humdrum_text << "!! </table>" << endl;
	m_humdrum_text << "!!@@END: PREHTML" << endl;

}



//////////////////////////////
//
// Tool_autocadence::printDefinitionRow --
//

void Tool_autocadence::printDefinitionRow(int index) {
	if (index > (int)m_definitions.size() - 1) {
		cerr << "ERROR: definition index out of range: " << index << endl;
		cerr << "Maximum value is " << (m_definitions.size() - 1) << endl;
		return;
	}
	auto& def = m_definitions.at(index);

	m_humdrum_text << "!! <tr>" << endl;

	string nameL = m_functionNames[def.m_funcL];
	string nameU = m_functionNames[def.m_funcU];

	m_humdrum_text << "!! <td class=lcvf";
	if (!nameL.empty()) {
		m_humdrum_text << " title='" << nameL << "'";
	}
	m_humdrum_text << ">" << def.m_funcL << "</td>" << endl;


	m_humdrum_text << "!! <td class=ucvf";
	if (!nameU.empty()) {
		m_humdrum_text << " title='" << nameU << "'";
	}
	m_humdrum_text << ">" << def.m_funcU << "</td>" << endl;

	m_humdrum_text << "!! <td class=name>" << def.m_name  << "</td>" << endl;
	m_humdrum_text << "!! <td class=index>" << index << "</td>" << endl;
	m_humdrum_text << "!! <td class=definition>" << def.m_regex << "</td>" << endl;

	m_humdrum_text << "!! </tr>" << endl;

	string style = R"(!! <style>
!! table.regex tr:hover { background: orange; }
!! table.regex {
!!    border-collapse: collapse;
!!    border: 1px solid orange;
!!    margin-top: 40px;
!!    margin-left: auto;
!!    margin-right: auto;
!!    max-width: 800px;
!! }
!! table.regex td.definition {
!!    word-spacing: 5px;
!!    padding-left: 30px;
!!    text-indent: -30px;
!! }
!! table.regex td.lcvf,
!! table.regex td.ucvf,
!! table.regex td.name {
!!    text-align: center;
!!    cursor: help;
!! }
!! table.regex th {
!!    vertical-align: top;
!!    background-color: bisque;
!!    padding-right: 10px;
!!    text-align: left;
!! }
!! table.regex td {
!!    vertical-align: top;
!!    text-align: left;
!!    padding-right: 20px;
!! }
!! table.regex td.index, table.regex th.index {
!!    text-align: right;
!! }
!! </style>)";
	m_humdrum_text << style << endl;


}



//////////////////////////////
//
// Tool_autocadence::markupScore --
//

void Tool_autocadence::markupScore(HumdrumFile& infile) {
	for (int i=0; i<(int)m_matches.size(); i++) {
		addMatchToScore(infile, i);
	}
}



//////////////////////////////
//
// Tool_autocadence::addMatchToScore --
//

void Tool_autocadence::addMatchToScore(HumdrumFile& infile, int matchIndex) {
	vector<int>& coord = m_matches.at(matchIndex);
	int vindex = coord.at(0);
	int pindex = coord.at(1);
	int nindex = coord.at(2);
	auto& info = m_sequences.at(vindex).at(pindex).at(nindex);
	// get<0> is the sequence string.
	HTp startL = get<1>(info);  // starting token of cadence formula, lower voice
	HTp startU = get<2>(info);  // starting token of cadence formula, upper voice

	if (startL == NULL) {
		cerr << "WARNING: startL is NULL" << endl;
		return;
	}
	if (startU == NULL) {
		cerr << "WARNING: startU is NULL" << endl;
		return;
	}

	int lindex = startL->getLineIndex();
	vector<int>& dindexes = get<3>(info);
	if (dindexes.empty()) {
		cerr << "WARNING: dindexes is empty" << endl;
		return;
	}
	// Ignoring any secondary matches for same sequence for now:
	int dindex = dindexes.at(0);
	Tool_autocadence::CadenceDefinition& definition = m_definitions.at(dindex);
	string& funcL = definition.m_funcL; // CVF of lower voice
	string& funcU = definition.m_funcU; // CVF of upper voice
	// string& name  = definition.m_name;  // name of cadence formula
	string& regex = definition.m_regex; // regular expression defining formula
	int count = getRegexSliceCount(regex);
	HTp endL = NULL;  // ending token of cadence formula, lower voice
	HTp endU = NULL;  // ending token of cadence formula, upper voice
	int status = getCadenceEndSliceNotes(endL, endU, count, infile, lindex, vindex, pindex);
	if (!status) {
		cerr << "Problem extracting slide end for cadence" << endl;
		return;
	}

	// cerr << "ENDING LOWER: " << endL << "\tENDING UPPER: " << endU << endl;

	// Add CVF to last note of cadence formulas:
	string valueL = endL->getValue("auto", "cvf");
	if (valueL.empty()) {
		valueL = funcL;
	} else {
		valueL += ",";
		valueL += funcL;
	}
	if (m_showFormulaIndexQ) {
		valueL += to_string(dindex);
	}
	// Indicate voice indexes of two voices involved in CVF pair:
	// valueL += "V" + to_string(vindex);
	// valueL += "P" + to_string(pindex);
	endL->setValue("auto", "cvf", valueL);

	string valueU = endU->getValue("auto", "cvf");
	if (valueU.empty()) {
		valueU = funcU;
	} else {
		valueU += ",";
		valueU += funcU;
	}
	if (m_showFormulaIndexQ) {
		valueU += to_string(dindex);
	}
	endU->setValue("auto", "cvf", valueU);

	if (m_colorQ) {
		colorNotes(startL, endL);
		colorNotes(startU, endU);
	}

}



//////////////////////////////
//
// Tool_autocadence::colorNotes -- Color notes inclusive between two notes.
//

void Tool_autocadence::colorNotes(HTp startTok, HTp endTok) {
	if (startTok == NULL) {
		cerr << "Warning: startTok is null" << endl;
	}
	if (endTok == NULL) {
		cerr << "Warning: endTok is null" << endl;
	}
	int startTrack = startTok->getTrack();
	int endTrack = startTok->getTrack();
	if (startTrack != endTrack) {
		cerr << "Start and ending tracks are not the same: " << startTrack << " and " << endTrack << endl;
		return;
	}
	// cerr << "\n\tcolor notes startLine = " << startTok->getLineIndex() << endl;
	// cerr << "\tcolor notes endLine   = " << endTok->getLineIndex() << endl;
	HTp current = startTok;
	while (current) {
		if (!current->isData()) {
			current = current->getNextToken();
			continue;
		}
		if (current->isNull()) {
			current = current->getNextToken();
			continue;
		}
		//	if (current->isRest()) {
		//		current = current->getNextToken();
		//		continue;
		//	}
		string text = *current;
		text += m_marker;
		current->setText(text);
	
		if (current == endTok) {
			break;
		}

		current = current->getNextToken();
	}

	if (m_nobackQ && (startTok->isNullToken() || startTok->isSustainedNote())) {
		highlightNoteAttack(startTok);
	}
}



//////////////////////////////
//
// Tool_autocadence::highlightNoteAttack -- search backards from a sustain
//    to highlight start of note.
//

void Tool_autocadence::highlightNoteAttack(HTp startTok) {
	bool markedQ = true;
	if (startTok->isNullToken()) {
		HTp resolved = startTok->resolveNullToken();
		if (!resolved) {
			return;
		}
		if (resolved->isRest()) {
			// maybe color?
			return;
		}
		if (resolved->isNoteAttack()) {
			string text = resolved->getText();
			text += m_marker;
			resolved->setText(text);
			return;
		} else {
			startTok = resolved;
			string text = resolved->getText();
			text += m_marker;
			resolved->setText(text);
			markedQ = true;
		}
	}
	if (startTok->isRest()) {
		return;
	}
	int b40 = Convert::kernToBase40(startTok);
	if (!markedQ) {
		string text = startTok->getText();
		text += m_marker;
		startTok->setText(text);
	}
	HTp current = startTok->getPreviousToken();
	while (current) {
		if (!current->isData()) {
			current = current->getPreviousToken();
			continue;
		}
		if (current->isNullToken()) {
			current = current->getPreviousToken();
			continue;
		}
		if (current->isRest()) {
			break;
		}
		int b40t = Convert::kernToBase40(current);
		if (b40t != b40) {
			break;
		}
		string text = current->getText();
		text += m_marker;
		current->setText(text);
		if (current->isNoteAttack()) {
			break;
		}
		current = current->getPreviousToken();
	}
}



//////////////////////////////
//
// Tool_autocadence::getCadenceEndSliceNotes --
//    lindex = starting line of the cadence definition
//    vindex = voice index (nth **kern spine, starting with lowest, indexed by 0
//    pindex = pairing index between two voices
//
// m_intervals: The counterpoint intervals for each pair of notes.
// The data is store in a 3-D vector, where the first dimension is the
// line in the score, the second dimension is the voice (kern spine) index in
// the score for the lower voice.  For example, if the score has four **kern
// spines, the second dimension size will be four, and the third dimensions
// will be 3, 2, 1 and 0.  
// Dimensions:
//     0: line index in the Humdrum data
//     1: voice index of the lower voice
//     2: pairing interval of two voice generating the interval
// Parameters for tuple:
//     0: interval string for the note pair (or empty if no interval.
//     1: the token for the lower voice note
//     2: the token for the upper voice note
// std::vector<std::vector<std::vector<std::tuple<std::string, HTp, HTp>>>> m_intervals;

bool Tool_autocadence::getCadenceEndSliceNotes(HTp& endL, HTp& endU, int count,
		HumdrumFile& infile, int lindex, int vindex, int pindex) {
	endL = NULL;
	endU = NULL;
	int counter = 0;
	int lineIndex = lindex;
	int endIndex = -1;
	while ((counter < count) && (lineIndex < (int)m_intervals.size())) {
		if (!infile[lineIndex].isData()) {
			lineIndex++;
			continue;
		}
		string& interval = get<0>(m_intervals.at(lineIndex).at(vindex).at(pindex));
		if (!interval.empty()) {
			counter++;
			if (counter == count) {
				endIndex = lineIndex;
				break;
			}
		}
		lineIndex++;
	}
	if (counter != count) {
		cerr << "ERROR: Could not find full cadence definition in score" << endl;
		return false;
	}

	// find the lower voice token:
	int fieldIndex = -1;
	int kcounter = 0;
	for (int i=0; i<infile[endIndex].getFieldCount(); i++) {
		HTp token = infile.token(endIndex, i);
		if (!token->isKern()) {
			continue;
		}
		int subtrack = token->getSubtrack();
		if (subtrack > 1) {
			// Multiple layers on staff, ignore secondary layers:
			continue;
		}
		if (kcounter == vindex) {
			fieldIndex = i;
			endL = token;
			break;
		} else {
			kcounter++;
		}
	}

	if (endL == NULL) {
		cerr << "ERROR: Problem finding lower voice ending token" << endl;
		return false;
	}

	int pcounter = 0;
	for (int i=fieldIndex+1; i<infile[endIndex].getFieldCount(); i++) {
		HTp token = infile.token(endIndex, i);
		if (!token->isKern()) {
			continue;
		}
		int subtrack = token->getSubtrack();
		if (subtrack > 1) {
			// Multiple layers on staff, ignore secondary layers:
			continue;
		}
		if (pcounter == pindex) {
			endU = token;
			break;
		} else {
			pcounter++;
		}
	}
	
	if (endU == NULL) {
		cerr << "ERROR: Problem finding upper voice ending token" << endl;
		return false;
	}

	return true;
}



//////////////////////////////
//
// Tool_autocadence::getRegexSliceCount --
//      Example: R"(^(?:R_1|7_1|-?\d+_-?[^1]):1, 7_1:2, 8_1:-3, 6_1:1, 6_-2:2, 8_)"
//      Count the number of commas and add 1.
//      Maybe need to be careful with look behinds, but probably ok.
//

int Tool_autocadence::getRegexSliceCount(const string& regex) {
	int count = (int)std::count(regex.begin(), regex.end(), ',');
	return count + 1;

}



//////////////////////////////
//
// Tool_autocadence::printMatchCount -- Print number of cadence formula
//      match counts for score.  If there are duplicate matches for any
//      sequence (more than one cadence formula matches a single sequence),
//      then these secondary matches are give after the main number of
//      matches in parentheses.
//

void Tool_autocadence::printMatchCount(void) {
	int count = (int)m_matches.size();
	int subcount = 0;
	for (int i=0; i<(int)m_matches.size(); i++) {
		auto& info = m_sequences.at(m_matches[i][0]).at(m_matches[i][1]).at(m_matches[i][2]);
		vector<int>& matches = get<3>(info);
		subcount += (int)matches.size() - 1;
	}

	m_humdrum_text << count;
	if (subcount > 0) {
		m_humdrum_text << "(" << subcount << ")";
	}
	m_humdrum_text << endl;
}



//////////////////////////////
//
// Tool_autocadence::searchIntervalSequences --
//
//
// m_sequences dimensions:
// 	0: voice index
// 	1: voice pair index
//    2: an array of tuples containing sequences
// Parameters for tuple:
// 	0: sequence to search
// 	1: first note of sequence in score (lower voice)
// 	2: first note of sequence in score (upper voice)
//    3: vector of ints: int is the m_definitions cadence index
//

void Tool_autocadence::searchIntervalSequences(void) {
	HumRegex hre;
	m_matches.clear();
	for (int i=0; i<(int)m_sequences.size(); i++) {
		for (int j=0; j<(int)m_sequences[i].size(); j++) {
			for (int k=0; k<(int)m_sequences[i][j].size(); k++) {
				string& feature = get<0>(m_sequences.at(i).at(j).at(k));
				for (int m=0; m<(int)m_definitions.size(); m++) {
					if (hre.search(feature, m_definitions.at(m).m_regex)) {
						vector<int>& matches = get<3>(m_sequences.at(i).at(j).at(k));
						// cerr << "FOUND MATCH: " << m << endl;
						matches.push_back(m);
						m_matches.emplace_back(vector<int>{i, j, k});
					}
				}
			}
		}
	} 
}



//////////////////////////////
//
// Tool_autocadence::prepareDefinitionList -- Extract a list of definition indexes
//    for use with PREHTML table.  Could count number of times definition was
//    used in score.
//

void Tool_autocadence::prepareDefinitionList(set<int>& list) {
	list.clear();

	for (int i=0; i<(int)m_matches.size(); i++) {
		int& vindex = m_matches.at(i).at(0);
		int& pindex = m_matches.at(i).at(1);
		int& nindex = m_matches.at(i).at(2);
		auto& info  = m_sequences.at(vindex).at(pindex).at(nindex);
		vector<int>& matches = get<3>(info);
		for (int m=0; m<(int)matches.size(); m++) {
			int dindex = matches.at(m);
			list.insert(dindex);
		}
	}
}



//////////////////////////////
//
// Tool_autocadence::printSequenceMatches -- Print only sequences that are matches to cadence formulas.
// 	The first column indicate which formula index (empty if none).
//
// std::vector< // voice index
// 	std::vector< // voice pair index
// 		std::vector< // list of sequences
// 			std::tuple<std::string, HTp, HTp, std::vector<int>>
// 		>
// 	>
// > m_sequences;
//
// Dimensions of m_sequences:
//    0: voice index
//    1: voice pair index
//    2: an array of tuples containing sequences
// Parameters for tuple:
//    0: sequence to search
//    1: first note of sequence in score (lower voice)
//    2: first note of sequence in score (upper voice)
//    3: vector of ints: int is the m_definitions cadence index
// std::vector<std::vector<int>> m_matches;

void Tool_autocadence::printSequenceMatches(void) {
	for (int i=0; i<(int)m_matches.size(); i++) {
		int& vindex = m_matches.at(i).at(0);
		int& pindex = m_matches.at(i).at(1);
		int& nindex = m_matches.at(i).at(2);
		auto& info = m_sequences.at(vindex).at(pindex).at(nindex);
		vector<int>& matches = get<3>(info);
		if (matches.empty()) {
			continue;
		}


		for (int m=0; m<(int)matches.size(); m++) {
			int dindex = matches.at(m);
			auto& cinfo = m_definitions.at(dindex);
			string& name = cinfo.m_name;
			m_humdrum_text << name;
			if (m<(int)matches.size() - 1) {
				m_humdrum_text << ",";
			}
		}

		m_humdrum_text << "\t";

		for (int m=0; m<(int)matches.size(); m++) {
			m_humdrum_text << matches[m];
			if (m<(int)matches.size() - 1) {
				m_humdrum_text << ",";
			}
		}

		m_humdrum_text << "\t";
		string& sequence = get<0>(info);
		m_humdrum_text << sequence << endl;
	}
}


// Version that prints directly from m_sequences:

void Tool_autocadence::printSequenceMatches2(void) {
	for (int i=0; i<(int)m_sequences.size(); i++) {
		for (int j=0; j<(int)m_sequences.at(i).size(); j++) {
			if (!(i==0 && j==0)) {
				m_humdrum_text << endl;
			}
			m_humdrum_text << "# Matches for voices " << (i+1) << " TO " << (i+1+j+1) << endl;
			for (int k=0; k<(int)m_sequences.at(i).at(j).size(); k++) {
				string& sequence = get<0>(m_sequences.at(i).at(j).at(k));
				vector<int>& matches = get<3>(m_sequences.at(i).at(j).at(k));
				if (matches.empty()) {
					continue;
				}
				for (int m=0; m<(int)matches.size(); m++) {
					m_humdrum_text << matches[m];
					if (m<(int)matches.size() - 1) {
						m_humdrum_text << ",";
					}
				}
				m_humdrum_text << "\t";
				for (int m=0; m<(int)matches.size(); m++) {
					auto& cinfo = m_definitions.at(matches[m]);
					string& name = cinfo.m_name;
					m_humdrum_text << name;
					if (m<(int)matches.size() - 1) {
						m_humdrum_text << ",";
					}
				}
				m_humdrum_text << "\t";
				m_humdrum_text << sequence << endl;
			}
		}
	}
}



//////////////////////////////
//
// Tool_autocadence::printSequenceInfo --
//
// std::vector<std::vector<std::vector<std::tuple<std::string, HTp, HTp, std::vector<int>>>>> m_sequences;
// Dimensions of m_sequences:
//    0: voice index
//    1: voice pair index
//    2: an array of tuples containing sequences
// Parameters for tuple:
//    0: sequence to search
//    1: first note of sequence in score (lower voice)
//    2: first note of sequence in score (upper voice)
//    3: vector of ints: int is the m_definitions cadence index

void Tool_autocadence::printSequenceInfo(void) {
	for (int i=0; i<(int)m_sequences.size(); i++) {
		for (int j=0; j<(int)m_sequences[i].size(); j++) {
			m_humdrum_text << endl;
			m_humdrum_text << "# Sequences for voices " << (i+1) << " TO " << (i+1+j+1) << endl;
			for (int k=0; k<(int)m_sequences[i][j].size(); k++) {
				string& sequence = get<0>(m_sequences[i][j][k]);
				m_humdrum_text << sequence << endl;
			}
		}
	}
}



//////////////////////////////
//
// Tool_autocadence::prepareIntervalSequences --
// Dimensions of m_seqences:
// 	0: voice index of the lower voice
// 	1: voice pair index for upper voice
// Parameters for tuple:
// 	0: sequence to search
// 	1: first note of sequence in score (lower voice)
// 	2: first note of sequence in score (upper voice)
//    3: vector of ints: int is the m_definitions cadence index
//

void Tool_autocadence::prepareIntervalSequences(HumdrumFile& infile) {
	vector<HTp> kstarts;
	infile.getKernSpineStartList(kstarts);
	int ksize = (int)kstarts.size();

	// Allocate space for m_sequence data:
	m_sequences.clear();
	m_sequences.resize(ksize);
	for (int i=0; i<ksize; i++) {
		m_sequences[i].resize(ksize-1-i);
		for (int j=0; j<(int)m_sequences[i].size(); j++) {
			prepareSinglePairSequences(infile, i, j);
		}
	}
}



//////////////////////////////
//
// Tool_autocadence::prepareSinglePairSequences -- Given a voice index and a pairing index, generate
//     sequences of intervals based with the length determined by m_sequenceLength.
//
// Dimensions of m_intervals:
//     0: line index in the Humdrum data
//     1: voice index of the lower voice
//     2: pairing index for two voices
// Parameters for tuple:
//     0: interval string for the note pair (or empty if no interval).
//     1: the token for the lower voice note
//     2: the token for the upper voice note
//
// Dimensions of m_sequences:
//    0: voice index
//    1: voice pair index
//    2: an array of tuples containing sequences
// Parameters for tuple:
//    0: sequence to search
//    1: first note of sequence in score (lower voice)
//    2: first note of sequence in score (upper voice)
//    3: vector of ints: int is the m_definitions cadence index that matches.
//

void Tool_autocadence::prepareSinglePairSequences(HumdrumFile& infile, int vindex, int pindex) {
	for (int i=0; i<infile.getLineCount(); i++) {
		if (!infile[i].isData()) {
			continue;
		}
		string interval = get<0>(m_intervals.at(i).at(vindex).at(pindex));
		if (interval.empty()) {
			continue;
		}
		HTp lower = get<1>(m_intervals.at(i).at(vindex).at(pindex));
		HTp upper = get<2>(m_intervals.at(i).at(vindex).at(pindex));
		string sequence = generateSequenceString(infile, i, vindex, pindex);
// cerr << "ADDING SEQUENCE: " << sequence << endl;
		m_sequences.at(vindex).at(pindex).emplace_back(sequence, lower, upper, vector<int>{});
	}
}



//////////////////////////////
//
// Tool_autocadence::generateSequenceString -- Combine multiple interval strings from
//      m_intervals for the given note pairing.  If there are not enough notes at the
//      end of a score, the length will be shortened based on the number of remaining
//      intervals that are available.
//
// Dimensions of m_intervals:
//     0: line index in the Humdrum data
//     1: voice index of the lower voice
//     2: pairing index for two voices
// Parameters for tuple:
//     0: interval string for the note pair (or empty if no interval).
//     1: the token for the lower voice note
//     2: the token for the upper voice note

string Tool_autocadence::generateSequenceString(HumdrumFile& infile, int lindex, int vindex, int pindex) {
	vector<string> pieces;
	for (int i=lindex; i<infile.getLineCount(); i++) {
		string interval = get<0>(m_intervals.at(i).at(vindex).at(pindex));
		if (interval.empty()) {
			continue;
		}
		pieces.push_back(interval);
		if ((int)pieces.size() >= m_sequenceLength) {
			break;
		}
	}

	string output;
	for (int i=0; i<(int)pieces.size(); i++) {
		output += pieces[i];
		if (i < (int)pieces.size() - 1) {
			output += ", ";
		}
	}
	return output;
}



//////////////////////////////
//
// Tool_autocadence::printExtractedPitchInfo -- Print only the extracted pitch infomation.
//

void Tool_autocadence::printExtractedPitchInfo(HumdrumFile& infile) {
	for (int i=0; i < infile.getLineCount(); i++) {
		if (infile[i].isManipulator()) {
			HTp token = infile.token(i, 0);

			if (token->isExInterp()) {
				m_humdrum_text << "**low";
			} else if (token->isTerminator()) {
				m_humdrum_text << "*-";
			} else {
				m_humdrum_text << "*";
			}

			if (token->isExInterp()) {
				for (int j=0; j<infile[i].getFieldCount(); j++) {
					HTp tok = infile.token(i, j);
					if (tok->isKern()) {
						m_humdrum_text << "\t" << "**b7";
					}
				}
				m_humdrum_text << endl;
			} else {
				for (int j=0; j<infile[i].getFieldCount(); j++) {
					HTp tok = infile.token(i, j);
					if (tok->isKern()) {
						m_humdrum_text << "\t" << tok;
					}
				}
				m_humdrum_text << endl;

			}
		} else if (infile[i].isData()) {
			m_humdrum_text << m_lowestPitch.at(i);
			for (int j=0; j<infile[i].getFieldCount(); j++) {
				HTp tok = infile.token(i, j);
				if (!tok->isKern()) {
					continue;
				}
				m_humdrum_text << "\t" << m_pitches.at(i).at(j);
			}
			m_humdrum_text << endl;
		} else if (infile[i].isBarline()) {
			HTp tok = infile.token(i, 0);
			m_humdrum_text << tok;
			for (int j=0; j<infile[i].getFieldCount(); j++) {
				HTp tok2 = infile.token(i, j);
				if (!tok2->isKern()) {
					continue;
				}
				int subtrack = tok2->getSubtrack();
				if (subtrack > 1) {
					continue;
				}
				m_humdrum_text << "\t" << tok;
			}
			m_humdrum_text << endl;
		}
	}
}



//////////////////////////////
//
// Tool_autocadence::printExtractedIntervalInfo -- Print only the extracted interval infomation.
//

void Tool_autocadence::printExtractedIntervalInfo(HumdrumFile& infile) {

	prepareAbbreviations(infile);
	vector<HTp> kspines;
	infile.getKernSpineStartList(kspines);
	int kcount = (int)kspines.size();

	for (int i=0; i < infile.getLineCount(); i++) {
		if (!infile[i].hasSpines()) {
			m_humdrum_text << infile[i] << endl;
		} else if (infile[i].isEmpty()) {
			m_humdrum_text << endl;
		} else if (infile[i].isManipulator()) {
			printIntervalManipulatorLine(infile, i, kcount);
		} else if (infile[i].isData()) {
			printIntervalDataLine(infile, i, kcount);
		} else if (infile[i].isBarline()) {
			string bartok = *infile.token(i, 0);
			printIntervalLine(infile, i, kcount, bartok);
		} else if (infile[i].isCommentLocal()) {
			printIntervalLine(infile, i, kcount, "!");
		} else if (infile[i].isInterpretation()) {
			printIntervalLine(infile, i, kcount, "*");
		} else {
			m_humdrum_text << "!! UNKNOWN LINE: " << infile[i] << endl;
		}
	}

}



//////////////////////////////
//
// Tool_autocadence::printIntervalManipulatorLine --
//

void Tool_autocadence::printIntervalManipulatorLine(HumdrumFile& infile, int index, int kcount) {
	stringstream line;
	int fcount = infile[index].getFieldCount();
	for (int i=0; i<fcount; i++) {
		HTp token = infile.token(index, i);
		if (i != 0) {
			line << "\t";
		}
		line << token;
		if (!token->isKern()) {
			continue;
		}
		int track = token->getTrack();
		int ntrack = 0;
		if (i < fcount - 1) {
			ntrack = infile.token(index, i+1)->getTrack();
		}
		if (track == ntrack) {
			continue;
		}
		if (kcount > 0) {
			int vindex = m_trackToVoiceIndex.at(track);
			int tcount = kcount - vindex - 1;
			for (int j=0; j<tcount; j++) {
				string value;
				if (token->isExInterp()) {
					value = "**adata";
					if (!m_abbr.at(vindex+1+j).empty()) {
						value += "-";
						value += m_abbr.at(vindex+1+j);
					}
				} else if (token-> isTerminator()) {
					value = "*-";
				} else {
					value = "*";
				}
				line << "\t" << value;
			}
		}
	}
	m_humdrum_text << line.str() << endl;
}



//////////////////////////////
//
// Tool_autocadence::printIntervalDataLine --
//

void Tool_autocadence::printIntervalDataLine(HumdrumFile& infile, int index, int kcount) {
	stringstream line;
	int fcount = infile[index].getFieldCount();
	for (int i=0; i<fcount; i++) {
		HTp token = infile.token(index, i);
		if (i != 0) {
			line << "\t";
		}
		line << token;
		if (!token->isKern()) {
			continue;
		}
		int track = token->getTrack();
		int ntrack = 0;
		if (i < fcount - 1) {
			ntrack = infile.token(index, i+1)->getTrack();
		}
		if (track == ntrack) {
			continue;
		}

		if (kcount > 0) {
			int vindex = m_trackToVoiceIndex.at(track);
			int tcount = kcount - vindex - 1;
			for (int j=0; j<tcount; j++) {
				string value = get<0>(m_intervals.at(index).at(vindex).at(j));
				if (value == "") {
					value = ".";
				}
				line << "\t" << value;
			}
		}
	}
	m_humdrum_text << line.str() << endl;
}



//////////////////////////////
//
// Tool_autocadence::printIntervalDataLineScore -- Data line printing for score, adding
//     markers for CFV if present.
//

void Tool_autocadence::printIntervalDataLineScore(HumdrumFile& infile,
		int index, int kcount) {

	vector<string> labels(infile[index].getFieldCount());
	bool foundLabelQ = false;

	vector<string> dissonances(infile[index].getFieldCount());
	bool foundDissonanceQ = false; // just for suspensions

	int fcount = infile[index].getFieldCount();

	stringstream dataline;
	for (int i=0; i<fcount; i++) {
		HTp token = infile.token(index, i);
		if (i != 0) {
			dataline << "\t";
		}
		dataline << token;
		if (!token->isKern()) {
			continue;
		}
		string label = token->getValue("auto", "cvf");
		if (!label.empty()) {
			labels.at(i) = label;
			foundLabelQ = true;
		}
		string dissonance = token->getValue("auto", "dissonance");
		if ((dissonance == "s") || (dissonance == "S") || (dissonance == "g") || (dissonance == "G")) {
			if (!dissonance.empty()) {
				dissonances.at(i) = dissonance;
				foundDissonanceQ = true;
			}
		}
		int track = token->getTrack();
		int ntrack = 0;
		if (i < fcount - 1) {
			ntrack = infile.token(index, i+1)->getTrack();
		}
		if (track == ntrack) {
			continue;
		}
		if (kcount > 0) {
			int vindex = m_trackToVoiceIndex.at(track);
			int tcount = kcount - vindex - 1;
			for (int j=0; j<tcount; j++) {
				string value = get<0>(m_intervals.at(index).at(vindex).at(j));
				if (value == "") {
					value = ".";
				}
				dataline << "\t" << value;
			}
		}

	}

	stringstream labelline;
	if (foundLabelQ) {
		for (int i=0; i<fcount; i++) {
			HTp token = infile.token(index, i);
			int track = token->getTrack();
			int vindex = m_trackToVoiceIndex.at(track);
			if (i != 0) {
				labelline << "\t";
			}
			string label = labels.at(i);
			if (label.empty()) {
				labelline << "!";
			} else {
				labelline << "!LO:TX:a:B:cvf";
				labelline << ":color=" << m_color;
				labelline << ":t=" << label;
				if (m_popupQ) {
				 	string fname = getFunctionNames(label);
				 	if (!fname.empty()) {
				 		labelline << ":pop=" << fname;
				 	}
				}
			}
			if ((kcount > 0) && (vindex >= 0))  {
				int tcount = kcount - vindex - 1;
				for (int j=0; j<tcount; j++) {
					labelline << "\t!";
				}
			}
		}
	}

	stringstream dissonanceline;
	if (foundDissonanceQ) {
		for (int i=0; i<fcount; i++) {
			HTp token = infile.token(index, i);
			int track = token->getTrack();
			int vindex = m_trackToVoiceIndex.at(track);
			if (i != 0) {
				dissonanceline << "\t";
			}
			string dissonance = dissonances.at(i);
			if (dissonance.empty()) {
				dissonanceline << "!";
			} else {
				dissonanceline << "!LO:TX:a:diss";
				dissonanceline << ":color=" << m_suspensionColor;
				dissonanceline << ":t=" << dissonance;
				if (m_popupQ) {
				 	string dname = getDissonanceNames(dissonance);
				 	if (!dname.empty()) {
				 		dissonanceline << ":pop=" << dname;
				 	}
				}
			}
			if ((kcount > 0) && (vindex >= 0))  {
				int tcount = kcount - vindex - 1;
				for (int j=0; j<tcount; j++) {
					dissonanceline << "\t!";
				}
			}
		}
	}

	if (!dissonanceline.str().empty()) {
		m_humdrum_text << dissonanceline.str() << endl;
	}
	if (!labelline.str().empty()) {
		m_humdrum_text << labelline.str() << endl;
	}
	if (!dataline.str().empty()) {
		m_humdrum_text << dataline.str() << endl;
	}
}



//////////////////////////////
//
// Tool_cadence::getDissonanceNames --
//

string Tool_autocadence::getDissonanceNames(const string& input) {
	HumRegex hre;
	string output;
	vector<string> pieces;
	hre.split(pieces, input, "[^A-Za-z]");
	map<string, bool> found;
	int counter = 0;
	for (int i=0; i<(int)pieces.size(); i++) {
		if (pieces[i].empty()) {
			continue;
		}
		if (found[pieces[i]] == true) {
			continue;
		}
		found[pieces[i]] = true;
		string name = m_dissonanceNames[pieces[i]];
		if (name.empty()) {
			name = pieces[i];
		}
		if (counter > 0) {
			output += ", ";
		}
		output += name;
	}
	return output;
}



//////////////////////////////
//
// Tool_autocadence::getFunctionNames -- convert CVF name list into a popup title string.
//     The names can be postfixed with the cadence formula index number.
//        Example: C73,C29
//     In this case the output will be "Cantizans".  
//        Example: C73,T29
//     In this case the output will be "Cantizans, Tenorzans".
//

string Tool_autocadence::getFunctionNames(const string& input) {
	HumRegex hre;
	string output;
	vector<string> pieces;
	hre.split(pieces, input, "[^A-Za-z]");
	map<string, bool> found;
	int counter = 0;
	for (int i=0; i<(int)pieces.size(); i++) {
		if (pieces[i].empty()) {
			continue;
		}
		if (found[pieces[i]] == true) {
			continue;
		}
		found[pieces[i]] = true;
		string name = m_functionNames[pieces[i]];
		if (name.empty()) {
			name = pieces[i];
		}
		if (counter > 0) {
			output += ", ";
		}
		output += name;
	}
	return output;
}



//////////////////////////////
//
// Tool_autocadence::printIntervalLine --
//

void Tool_autocadence::printIntervalLine(HumdrumFile& infile, int index, int kcount, const string& tok) {
	stringstream line;
	int fcount = infile[index].getFieldCount();
	for (int i=0; i<fcount; i++) {
		HTp token = infile.token(index, i);
		if (i != 0) {
			line << "\t";
		}
		line << token;
		if (!token->isKern()) {
			continue;
		}
		int track = token->getTrack();
		int ntrack = 0;
		if (i < fcount - 1) {
			ntrack = infile.token(index, i+1)->getTrack();
		}
		if (track == ntrack) {
			continue;
		}
		if (kcount > 0) {
			int vindex = m_trackToVoiceIndex.at(track);
			int tcount = kcount - vindex - 1;
			for (int j=0; j<tcount; j++) {
				line << "\t" << tok;
			}
		}
	}
	m_humdrum_text << line.str() << endl;
}



//////////////////////////////
//
// Tool_autocadence::preparePitchInfo -- Fill in the m_pitches and m_lowestPitch
//     variables.  These are used to generate the counterpoing interval
//     information later.  Gets the lowest pitch for each sonority in the
//     score.   First extract all diatonic note numbers into a parallel
//     structure to the Humdrum file (for computational efficiency).  This
//     function also store diatonic pitches for each note in the score,
//     in a 2-D array that matches the dimensions of the score (so that
//     the pitches do not have to be recalculated later for generating
//     interval chains for searching for cadence patterns.
//

void Tool_autocadence::preparePitchInfo(HumdrumFile& infile) {
	// First extract the diatonic pitches:
	prepareDiatonicPitches(infile);

	// Now find the lowest sounding pitch for each data row in the file:
	prepareLowestPitches();
}



//////////////////////////////
//
// Tool_autocadence::prepareLowestPitches -- Find the lowest sounding pitch for
//     each data row in the file.  The pitch is given as a non-negative integer,
//     with middle C being 28 (7 * 4).  Rests are 0.
//

void Tool_autocadence::prepareLowestPitches(void) {
	m_lowestPitch.clear();
	m_lowestPitch.resize(m_pitches.size());
	std::fill(m_lowestPitch.begin(), m_lowestPitch.end(), 0);

	for (int i=0; i<(int)m_pitches.size(); i++) {
		int lowest = -1;
		for (int j=0; j<(int)m_pitches[i].size(); j++) {
			// Using abs since negative integers represent sustained notes:
			int b7 = std::abs(m_pitches.at(i).at(j));
			if (b7 > 0) {
				if (lowest == -1) {
					lowest = b7;
				} else if (b7 < lowest) {
					lowest = b7;
				}
			}
		}
		if (lowest < 0) {
			m_lowestPitch.at(i) = 0;
		} else {
			m_lowestPitch.at(i) = lowest;
		}
	}
}



//////////////////////////////
//
// Tool_autocadence::prepareTrackToVoiceIndex --
//

void Tool_autocadence::prepareTrackToVoiceIndex(HumdrumFile& infile, vector<HTp> kspines) {
	m_trackToVoiceIndex.resize(infile.getMaxTrack() + 1);
	fill(m_trackToVoiceIndex.begin(), m_trackToVoiceIndex.end(), -1);
	for (int i=0; i<(int)kspines.size(); i++) {
		int track = kspines[i]->getTrack();
		m_trackToVoiceIndex.at(track) = i;
	}
}



//////////////////////////////
//
// Tool_autocadence::prepareIntervalInfo -- Generate intervals used in analysis.
//      m_pitches and m_lowestSlidePitch variables need to be filled in before
//      calling this function.
//
void Tool_autocadence::prepareIntervalInfo(HumdrumFile& infile) {

	// Initialize space to store counterpoint intervals:
	//    hint_mintL:mintU
	//    hint = harmonic interval between two parts
	//    mintL = melodic interval to next note in lower part
	//    mintH = melodic interval to next note in upper part
	//    Intervals are diatonic in the range from 1 to 8 (8 = octave or two octaves, etc.)

	vector<HTp> kspines = infile.getKernSpineStartList();
	prepareTrackToVoiceIndex(infile, kspines);

	// Initialize space for interval strings:
	m_intervals.clear();
	m_intervals.resize(infile.getLineCount());
	for (int i=0; i<(int)m_intervals.size(); i++) {
		int vcount = (int)kspines.size();
		m_intervals[i].resize(vcount);
		for (int j=0; j<vcount; j++) {
			int pcount = vcount - j - 1;
			m_intervals[i][j].resize(pcount);
			for (int k=0; k<pcount; k++) {
				get<1>(m_intervals[i][j][k]) = NULL;
				get<2>(m_intervals[i][j][k]) = NULL;
			}
		}
	}

	for (int i=0; i<(int)kspines.size()-1; i++) {
		for (int j=i+1; j<(int)kspines.size(); j++) {
			generateCounterpointStrings(kspines, i, j);
		}
	}
}



//////////////////////////////
//
// Tool_autocadence::prepareDiatonicPitches -- Extra the absolute diatonic pitches
//      from the score.  These are stored in m_pitches which is a 2-D array that
//      matches the dimentions of the score.  Middle C is number 28 (7 * 4), rests
//      are 0, and sustained notes are negative pitches.
//

void Tool_autocadence::prepareDiatonicPitches(HumdrumFile& infile) {
	m_pitches.clear();
	m_pitches.resize(infile.getLineCount());
	for (int i=0; i<infile.getLineCount(); i++) {
		m_pitches.at(i).resize(infile[i].getFieldCount());
		std::fill(m_pitches[i].begin(), m_pitches[i].end(), 0);
		if (!infile[i].isData()) {
			continue;
		}
		for (int j=0; j<infile[i].getFieldCount(); j++) {
			HTp token = infile.token(i, j);
			if (!token->isKern()) {
				continue;
			}
			int sign = 1;
			HTp resolve = NULL;
			if (token->isNullToken()) {
				resolve = token->resolveNullToken();
				// Negative pitch value is a sustained note:
				sign = -1;

			} else {
				resolve = token;
			}
			if (resolve == NULL) {
				continue;
			}
			if (resolve->isRest()) {
				continue;
			}

			int pitch = Convert::kernToBase7(resolve);
			pitch *= sign;
			m_pitches.at(i).at(j) = pitch;
		}
	}
}



//////////////////////////////
//
// Tool_autocadence::getPairIndex -- Return the index for the interval pairing
//      info stored in m_intervals for this particular pair of voices (array index
//      add the token level).
//

int Tool_autocadence::getPairIndex(HTp lowerExInterp, HTp upperExInterp) {
	int count = -1;
	HTp current = lowerExInterp->getNextFieldToken();
	while (current) {
		if (*current == "**kern") {
			count++;
		}
		if (current == upperExInterp) {
			break;
		}
		current = current->getNextFieldToken();
	}
	return count;
}



//////////////////////////////
//
// Tool_autocadence::generateCounterpointStrings -- Calculate individual
//    counterpoint intervals strings for a pair of voices.
//
//

void Tool_autocadence::generateCounterpointStrings(vector<HTp>& kspines, int vindexL, int vindexU) {
	// cerr << "Creating counterpoint strings for " << vindexL << " and " << vindexU << endl;

	HTp partLstart = kspines.at(vindexL);
	HTp partUstart = kspines.at(vindexU);

	vector<vector<HTp>> pairings(2);
	pairings[0].reserve(10000);
	pairings[1].reserve(10000);
	fillNotes(pairings[0], partLstart);
	fillNotes(pairings[1], partUstart);

	if (pairings[0].size() != pairings[1].size()) {
		cerr << "Error: length of each note sequence is not equal: "
		     << pairings[0].size() << " compared to " << pairings[1].size() << endl;
		return;
	}

	int track = partLstart->getTrack();
	int voiceIndex = m_trackToVoiceIndex.at(track);
	int pairIndex = getPairIndex(kspines.at(vindexL), kspines.at(vindexU));

	for (int i=0; i<(int)pairings[0].size(); i++) {
		if (!(pairings[0][i]->isNoteAttack() || pairings[1][i]->isNoteAttack())) {
			// both notes are sustaining in this slice.
			// deal with rests here as well, allowing one rest slice between note pairings.
			continue;
		}

		string entry = generateCounterpointString(pairings, i);

		int lineIndex = pairings[0][i]->getLineIndex();

		std::get<0>(m_intervals.at(lineIndex).at(voiceIndex).at(pairIndex)) = entry;
		std::get<1>(m_intervals.at(lineIndex).at(voiceIndex).at(pairIndex)) = pairings[0].at(i);
		std::get<2>(m_intervals.at(lineIndex).at(voiceIndex).at(pairIndex)) = pairings[1].at(i);
	}
}



//////////////////////////////
//
// Tool_autocadence::generateCounterpointString -- Generate the counterpoint interval string
// between a pair of voices in the form: hint{low4th}_mintL:mintU.
//

string Tool_autocadence::generateCounterpointString(vector<vector<HTp>>& pairings, int index) {
	HTp lower = pairings[0][index];
	HTp upper = pairings[1][index];

	// Get pitches of the current pair of notes in the parts:
	int lineIndex   = lower->getLineIndex();
	int fieldIndexL = lower->getFieldIndex();
	int fieldIndexU = upper->getFieldIndex();
	int b7L = m_pitches.at(lineIndex).at(fieldIndexL);
	int b7U = m_pitches.at(lineIndex).at(fieldIndexU);

	// Get pitches for the next pair of notes in the two parts:
	int b7Ln = 0;
	int b7Un = 0;
	if (index < (int)pairings[0].size() - 1) {
		HTp lowern = pairings[0][index+1];
		HTp uppern = pairings[1][index+1];
		int nlineIndex   = lowern->getLineIndex();
		int nfieldIndexL = lowern->getFieldIndex();
		int nfieldIndexU = uppern->getFieldIndex();
		b7Ln = std::abs(m_pitches.at(nlineIndex).at(nfieldIndexL));
		b7Un = std::abs(m_pitches.at(nlineIndex).at(nfieldIndexU));
	}

	// Determine if there is a fourth above the lowest sounding note
	// for the current pair of voices:
	int lowL = 0;
	int lowU = 0;
	int lowest = m_lowestPitch.at(lineIndex);
	if (lowest == 0) {
		// do nothing
	} else {
		if (b7L != 0) {
			lowL = getDiatonicInterval(lowest, b7L);
		}
		if (b7U != 0) {
			lowU = getDiatonicInterval(lowest, b7U);
		}
	}
	string dissonant4;
	if ((lowL == 4) || (lowU == 4)) {
		dissonant4 = "D";
	}

	string dissonanceU;
	string dissonanceL;
	if (m_suspensionsQ) {
		dissonanceL = lower->getValue("auto", "dissonance");
		dissonanceU = upper->getValue("auto", "dissonance");
cerr << "DISSONANCES: " << dissonanceL << "\t" << dissonanceU << endl;
	}

	string mintL = "R";
	string mintU = "R";

	string hint  = getDiatonicIntervalString(b7L, b7U);
	if (index < (int)pairings[0].size() - 1){
		mintL = getDiatonicIntervalString(b7L, b7Ln);
		mintU = getDiatonicIntervalString(b7U, b7Un);
	}

	if ((mintL == "R") || (mintU == "R")) {
		dissonant4 = "";
	}

	string output = hint;
	output += dissonant4;
	output += "_";
	output += mintL;
	output += ":";
	output += mintU;

	return output;
}



//////////////////////////////
//
// Tool_autocadence::getDiatonicIntervalString --
//    lower is the lower staff note, or the first note in a melodic sequence.
//    Both numbers are diagonic pitch numbers (28 = middle C).
//

string Tool_autocadence::getDiatonicIntervalString(int lower, int upper) {
	if ((lower == 0) || (upper == 0)) {
		return "R";
	} else {
		int interval = getDiatonicInterval(lower, upper);
		return to_string(interval);
	}
}



//////////////////////////////
//
// Tool_autocadence::getDiatonicInterval -- A negative pitch number means sustained note.
//     upper = pitch from higher staff
//     lower = pitch from lower staff
//     Or for melodic calculations:
//         lower = first pitch (or rest)
//         upper = second pitch (or rest)
//

int Tool_autocadence::getDiatonicInterval(int lower, int upper) {
	int interval = abs(upper) - abs(lower);
	if (interval > 7) {
		interval = interval % 7;
		if (interval == 0) {
			interval = 8;
		} else {
			interval++;
		}
	} else if (interval < 0) {
		interval = -interval % 7;
		if (interval == 0) {
			interval = -8;
		} else {
			interval = -interval -1;
		}
	} else {
		interval++;
	}
	return interval;
}



//////////////////////////////
//
// Tool_autocadence::fillNotes --
//

void Tool_autocadence::fillNotes(vector<HTp>& voice, HTp exinterp) {
	HTp current = exinterp->getNextToken();
	while (current) {
		if (current->isData()) {
			voice.push_back(current);
		}
		current = current->getNextToken();
	}
}



//////////////////////////////
//
// Tool_autocadence::prepareCadenceDefinitions --
//

void Tool_autocadence::prepareCadenceDefinitions(void) {
	m_definitions.clear();
	m_definitions.reserve(200);

	// /* Index */                 LowserCVF, UpperCVF, Name, Regex
	/*   0 */ addCadenceDefinition("", "",		"__1",	R"(^(?:-?\d+|R)_1:-?\d+, 7_1:-2, 6_R:-2, R_)");
	/*   1 */ addCadenceDefinition("", "",		"__2",	R"(^[^R]_1, 2_1:-2, (?:1|8)_-3:2, 4D?_)");
	/*   2 */ addCadenceDefinition("", "",		"__3",	R"(^[^R]_1:, 4D_1:-2, 3_1:-2, 2_1:2, 3_-3:2, 6_)");
	/*   3 */ addCadenceDefinition("", "",		"__4",	R"(^[^R]_1:-?\d+, 7_1:-2, 6_-2:2, 8_)");
	/*   4 */ addCadenceDefinition("A", "T",	"AT1",	R"(^(?:-?\d+|R)_1:-?\d+, -4D_-2:1, -3_-2:1, -2_2:1, -3_2:-2, -5_)");
	/*   5 */ addCadenceDefinition("A", "T",	"AT2",	R"(^(?:-?\d+|R)_1:-?\d+, -4D_-2:1, -3_-2:1, -2_3:-2, -5_)");
	/*   6 */ addCadenceDefinition("A", "T",	"AT3",	R"(^(?:-?\d+|R)_1:-?\d+, -4D_-2:1, -3_1:1, -3_-2:1, -2_3:-2, -5_)");
	/*   7 */ addCadenceDefinition("A", "T",	"AT4",	R"(^(?:-?\d+|R)_1:-?\d+, -4D_-2:1, -3_2:-2, -5_)");
	/*   8 */ addCadenceDefinition("B", "C",	"BC1",	R"("^(?:R_1|4D?_1|-?\d+_-?[^1]):1, 4D_1:-2, 3_1:-2, 2_1:2, 3_4:2, (?:1|8)_)");
	/*   9 */ addCadenceDefinition("B", "C",	"BC2",	R"(^(?:R_1|4D?_1|-?\d+_-?[^1]), 4D_1:-2, 3_1:-2, 2_1:2, 3_1:1, 3_-5:2, 8_)");
	/*  10 */ addCadenceDefinition("B", "C",	"BC3",	R"(^(?:R_1|4D?_1|-?\d+_-?[^1]):1, 4D_1:-2, 3_-5:2, 8_)");
	/*  11 */ addCadenceDefinition("B", "C",	"BC4",	R"(^(?:R_1|4D?_1|-?\d+_-?[^1]):1, 4D_1:-2, 3_1:-2, 2_-5:3, 8_)");
	/*  12 */ addCadenceDefinition("B", "C",	"BC5",	R"(^(?:R_1|4D?_1|-?\d+_-?[^1]):1, 4D_1:-2, 3_1:-2, 2_1:2, 3_-5:2, 8_)");
	/*  13 */ addCadenceDefinition("B", "C",	"BC6",	R"(^(?:R_1|4D?_1|-?\d+_-?[^1]):1, 4D_1:-2, 3_1:1, 3_-5:2, 8_)");
	/*  14 */ addCadenceDefinition("B", "C",	"BC7",	R"(^(?:R_1|4D?_1|-?\d+_-?[^1]):1, 4D_1:-2, 3_1:1, 3_1:-2, 2_-5:3, 8_)");
	/*  15 */ addCadenceDefinition("B", "C",	"BC8",	R"(^(?:R_1|4D?_1|-?\d+_-?[^1]):1, 4D_1:-2, 3_1:1, 3_1:-2, 2_1:2, 3_-5:2, 8_)");
	/*  16 */ addCadenceDefinition("B", "C",	"BC9",	R"(^(?:R_1|4D?_1|-?\d+_-?[^1]):1, 4D_1:-2, 3_4:2, (?:8|1)_)");
	/*  17 */ addCadenceDefinition("B", "C",	"BC10",	R"(^3_1:2, 4D_1:-2, 3_(?:4|-5):2, (?:1|8)_)");
	/*  18 */ addCadenceDefinition("B", "C",	"BC11",	R"(^5_1:-2, 4D_1:-2, 3_(?:4|-5):2, (?:1|8)_)");
	/*  19 */ addCadenceDefinition("B", "c",	"Bc1",	R"(^(?:R_1|4D?_1|-?\d+_-?[^1]):1, 4D_1:-2, 3_(?:4|-5):(?:4|-5), 3_)");
	/*  20 */ addCadenceDefinition("C", "B",	"CB1",	R"(^(?:-?\d+|R)_1:-?\d+, -4D_-2:1, -3_-2:1, -2_2:1, -3_2:(?:-5|4), -8_)");
	/*  21 */ addCadenceDefinition("C", "B",	"CB2",	R"(^(?:-?\d+|R)_1:-?\d+, -4D_-2:1, -3_-2:1, -2_2:1, -3_2:4, (?:1|-8)_)");
	/*  22 */ addCadenceDefinition("C", "B",	"CB3",	R"(^(?:-?\d+|R)_1:-?\d+, -4D_-2:1, -3_-2:1, -2_3:-5, -8_)");
	/*  23 */ addCadenceDefinition("C", "B",	"CB4",	R"(^(?:-?\d+|R)_1:-?\d+, -4D_-2:1, -3_-2:1, -2_3:4, (?:1|-8)_)");
	/*  24 */ addCadenceDefinition("C", "B",	"CB5",	R"(^(?:-?\d+|R)_1:-?\d+, -4D_-2:1, -3_1:1, -3_-2:1, -2_3:-5, -8_)");
	/*  25 */ addCadenceDefinition("C", "B",	"CB6",	R"(^(?:-?\d+|R)_1:-?\d+, -4D_-2:1, -3_1:1, -3_-2:1, -2_3:4, (?:1|-8)_)");
	/*  26 */ addCadenceDefinition("C", "B",	"CB7",	R"(^(?:-?\d+|R)_1:-?\d+, -4D_-2:1, -3_2:-5, -8_)");
	/*  27 */ addCadenceDefinition("C", "B",	"CB8",	R"(^(?:-?\d+|R)_1:-?\d+, -4D_-2:1, -3_2:4, -8_)");
	/*  28 */ addCadenceDefinition("C", "B",	"CB9",	R"(^(?:-?\d+|R)_1:-?\d+, -4D_-2:1, -3_2:4, 1_)");
	/*  29 */ addCadenceDefinition("C", "Q",	"CQ1",	R"(^(?:-?\d+|R)_1:-?\d+, 2_-2:1, 3_2:(?:-5|4), 5_)");
	/*  30 */ addCadenceDefinition("C", "T",	"CT1",	R"(^(?:-?\d+|R)_1:-?\d+, -7_-2:1, -6_-2:1, -5_2:1, -6_2:-2, -8_)");
	/*  31 */ addCadenceDefinition("C", "T",	"CT2",	R"(^(?:-?\d+|R)_1:-?\d+, -7_-2:1, -6_-2:1, -5_3:-2, -8_)");
	/*  32 */ addCadenceDefinition("C", "T",	"CT3",	R"(^(?:-?\d+|R)_1:-?\d+, -7_-2:1, -6_1:1, -6_-2:1, -5_3:-2, -8_)");
	/*  33 */ addCadenceDefinition("C", "T",	"CT4",	R"(^(?:-?\d+|R)_1:-?\d+, -7_-2:1, -6_2:-2, -8_)");
	/*  34 */ addCadenceDefinition("C", "T",	"CT5",	R"(^(?:-?\d+|R)_1:-?\d+, 2_-2:1, 3_-2:1, 4D?_2:1, 3_2:-2, (?:1|8)_)");
	/*  35 */ addCadenceDefinition("C", "T",	"CT6",	R"(^(?:-?\d+|R)_1:-?\d+, 2_-2:1, 3_-2:1, 4D?_3:-2, (?:1|8)_)");
	/*  36 */ addCadenceDefinition("C", "T",	"CT7",	R"(^(?:-?\d+|R)_1:-?\d+, 2_-2:1, 3_1:1, 3_-2:1, 4D?_2:1, 3_2:-2, (?:1|8)_)");
	/*  37 */ addCadenceDefinition("C", "T",	"CT8",	R"(^(?:-?\d+|R)_1:-?\d+, 2_-2:1, 3_1:1, 3_-2:1, 4D?_3:-2, (?:1|8)_)");
	/*  38 */ addCadenceDefinition("C", "T",	"CT9",	R"(^(?:-?\d+|R)_1:-?\d+, 2_-2:1, 3_1:1, 3_2:-2, (?:1|8)_)");
	/*  39 */ addCadenceDefinition("C", "T",	"CT10",	R"(^(?:-?\d+|R)_1:-?\d+, 2_-2:1, 3_2:-2, (?:1|8)_)");
	/*  40 */ addCadenceDefinition("C", "T",	"CT11",	R"(^3_2:1, 2_-2:1, 3_2:-2, (?:1|8)_)");
	/*  41 */ addCadenceDefinition("C", "T",	"CT11",	R"(^8_-2:1, 2_-2:1, 3_2:-2, (?:1|8)_)");
	/*  42 */ addCadenceDefinition("C", "t",	"ct1",	R"(^(?:-?\d+|R)_1:-?\d+, 2_-2:1, 3_-2:1, 4D?_2:1, 3_1:1, 3_2:2, 3_)");
	/*  43 */ addCadenceDefinition("C", "t",	"ct2",	R"(^(?:-?\d+|R)_1:-?\d+, 2_-2:1, 3_1:1, 3_-2:1, 4D?_2:1, 3_2:2, 3_)");
	/*  44 */ addCadenceDefinition("C", "t",	"ct3",	R"(^(?:-?\d+|R)_1:-?\d+, 2_-2:1, 3_2:2, 3_)");
	/*  45 */ addCadenceDefinition("C", "t",	"ct4",	R"(^3_2:1, 2_-2:1, 3_2:2, 3_)");
	/*  46 */ addCadenceDefinition("C", "u",	"Cu1",	R"(^(?:-?\d+|R)_1:-?\d+, -4D_-2:1, -3_-2:1, -2_2:1, -3_2:-3, -6_)");
	/*  47 */ addCadenceDefinition("C", "u",	"Cu2",	R"(^(?:-?\d+|R)_1:-?\d+, -4D_-2:1, -3_-2:1, -2_3:-3, -6_)");
	/*  48 */ addCadenceDefinition("C", "u",	"Cu3",	R"(^(?:-?\d+|R)_1:-?\d+, -4D_-2:1, -3_1:1, -3_-2:1, -2_3:-3, -6_)");
	/*  49 */ addCadenceDefinition("C", "u",	"Cu4",	R"(^(?:-?\d+|R)_1:-?\d+, -4D_-2:1, -3_2:-3, -6_)");
	/*  50 */ addCadenceDefinition("C", "z",	"Cz1",	R"(^(?:-?\d+|R)_1:-?\d+, 2_-2:1, 3_2:R, R_)");
	/*  51 */ addCadenceDefinition("L", "C",	"LC1",	R"(^(?:R_1|4D?_1|-?\d+_-?[^1]):1, 4D_1:-2, 3_1:-2, 2_1:2, 3_8:2, (?:4|-5)_)");
	/*  52 */ addCadenceDefinition("L", "C",	"LC2",	R"(^(?:R_1|4D?_1|-?\d+_-?[^1]):1, 4D_1:-2, 3_1:-2, 2_8:3, (?:4|-5)_)");
	/*  53 */ addCadenceDefinition("L", "C",	"LC3",	R"(^(?:R_1|4D?_1|-?\d+_-?[^1]):1, 4D_1:-2, 3_1:1, 3_1:-2, 2_1:2, 3_8:2, (?:4|-5)_)");
	/*  54 */ addCadenceDefinition("L", "C",	"LC4",	R"(^(?:R_1|4D?_1|-?\d+_-?[^1]):1, 4D_1:-2, 3_1:1, 3_1:-2, 2_8:3, (?:4|-5)_)");
	/*  55 */ addCadenceDefinition("L", "C",	"LC5",	R"(^(?:R_1|4D?_1|-?\d+_-?[^1]):1, 4D_1:-2, 3_1:1, 3_8:2, (?:4|-5)_)");
	/*  56 */ addCadenceDefinition("L", "C",	"LC6",	R"(^(?:R_1|4D?_1|-?\d+_-?[^1]):1, 4D_1:-2, 3_8:2, (?:4|-5)_)");
	/*  57 */ addCadenceDefinition("P", "C",	"PC1",	R"(^(?:R_1|-?\d+_-?[^1]):1, 2_1:-2, 1_-4:2, 5_)");
	/*  58 */ addCadenceDefinition("P", "C",	"PC2",	R"(^(?:R_1|-?\d+_-?[^1]):1, 2_1:-2, 8_(?:5|-4):2, 5_)");
	/*  59 */ addCadenceDefinition("P", "C",	"PC3",	R"(^(?:R_1|-?\d+_-?[^1]):1, 2_1:-2, 8_1:1, 8_(?:5|-4):2, 5_)");
	/*  60 */ addCadenceDefinition("Q", "C",	"QC1",	R"(^(?:R_1|-?\d+_-?[^1]):1, -2_1:-2, -3_-5:2, 4D?_)");
	/*  61 */ addCadenceDefinition("Q", "C",	"QC2",	R"(^(?:R_1|-?\d+_-?[^1]):1, -2_1:-2, -3_4:2, -5_)");
	/*  62 */ addCadenceDefinition("Q", "C",	"QC3",	R"(^(?:R_1|-?\d+_-?[^1]):1, 7_1:-2, 6_(?:-5|4):2, 4D?_)");
	/*  63 */ addCadenceDefinition("Q", "C",	"QC4",	R"(^(?:R_1|-?\d+_-?[^1]):1, 7_1:-2, 6_1:1, 6_(?:-5|4):2, 4D?_)");
	/*  64 */ addCadenceDefinition("S", "C",	"SC1",	R"(^(?:R_1|-?\d+_-?[^1]):1, 2_1:-2, (?:1|8)_-3:2, 4D?_)");
	/*  65 */ addCadenceDefinition("S", "C",	"SC2",	R"(^(?:R_1|-?\d+_-?[^1]):1, 2_1:-2, (?:1|8)_1:1, (?:1|8)_-3:2, 4D?_)");
	/*  66 */ addCadenceDefinition("T", "A",	"TA1",	R"(^(?:R_1|-?\d+_-?[^1]):1, 4D_1:-2, 3_-2:2, 5_)");
	/*  67 */ addCadenceDefinition("T", "A",	"TA2",	R"(^(?:R_1|-?\d+_-?[^1]):1, 4D_1:-2, 3_-2:2, 5_)");
	/*  68 */ addCadenceDefinition("T", "A",	"TA3",	R"(^(?:R_1|-?\d+_-?[^1]):1, 4D_1:-2, 3_-2:2, 5_)");
	/*  69 */ addCadenceDefinition("T", "A",	"TA4",	R"(^(?:R_1|-?\d+_-?[^1]):1, 4D_1:-2, 3_1:-2, 2_-2:3, 5_)");
	/*  70 */ addCadenceDefinition("T", "A",	"TA5",	R"(^(?:R_1|-?\d+_-?[^1]):1, 4D_1:-2, 3_1:-2, 2_1:2, 3_-2:2, 5_)");
	/*  71 */ addCadenceDefinition("T", "A",	"TA6",	R"(^(?:R_1|-?\d+_-?[^1]):1, 4D_1:-2, 3_1:-2, 2_1:2, 3_1:1, 3_-2:2, 5_)");
	/*  72 */ addCadenceDefinition("T", "A",	"TA7",	R"(^(?:R_1|-?\d+_-?[^1]):1, 4D_1:-2, 3_1:1, 3_1:-2, 2_-2:3, 5_)");
	/*  73 */ addCadenceDefinition("T", "C",	"TC1",	R"(^(?:R_1|7_1|-?\d+_-?[^1]):1, 7_1:-2, 6_-2:2, 8_)");
	/*  74 */ addCadenceDefinition("T", "C",	"TC2",	R"(^(?:R_1|7_1|-?\d+_-?[^1]):1, 7_1:-2, 6_1:-2, 5_-2:3, 8_)");
	/*  75 */ addCadenceDefinition("T", "C",	"TC3",	R"(^(?:R_1|7_1|-?\d+_-?[^1]):1, 7_1:-2, 6_1:-2, 5_1:2, 6_-2:2, 8_)");
	/*  76 */ addCadenceDefinition("T", "C",	"TC4",	R"(^(?:R_1|7_1|-?\d+_-?[^1]):1, 7_1:-2, 6_1:-2, 5_1:2, 6_1:1, 6_-2:2, 8_)");
	/*  77 */ addCadenceDefinition("T", "C",	"TC5",	R"(^(?:R_1|7_1|-?\d+_-?[^1]):1, 7_1:-2, 6_1:1, 6_-2:2, 8_)");
	/*  78 */ addCadenceDefinition("T", "C",	"TC6",	R"(^(?:R_1|7_1|-?\d+_-?[^1]):1, 7_1:-2, 6_1:1, 6_1:-2, 5_-2:3, 8_)");
	/*  79 */ addCadenceDefinition("T", "C",	"TC7",	R"(^(?:R_1|7_1|-?\d+_-?[^1]):1, 7_1:-2, 6_1:1, 6_1:-2, 5_1:2, 6_-2:2, 8_)");
	/*  80 */ addCadenceDefinition("T", "C",	"TC8",	R"(^(?:R_1|7_1|-?\d+_-?[^1]):1, 7_1:-2, 6_1:2, 7_1:-2, 6_1:-2, 5_-2:3, 8_)");
	/*  81 */ addCadenceDefinition("T", "C",	"TC9",	R"(^(?:R_1|7_1|-?\d+_-?[^1]):1, 7_1:-2, 6_1:2, 7_1:2, 8_1:-3, 6_-2:2, 8_)");
	/*  82 */ addCadenceDefinition("T", "C",	"TC10",	R"(^(?:R_1|7_1|-?\d+_-?[^1]):1, 7_1:-2, 6_1:2, 7_1:2, 8_1:-3, 6_1:-2, 5_-2:3, 8_)");
	/*  83 */ addCadenceDefinition("T", "C",	"TC11",	R"(^(?:R_1|7_1|-?\d+_-?[^1]):1, 7_1:-2, 6_1:3, 8_1:-3, 6_-2:2, 8_)");
	/*  84 */ addCadenceDefinition("T", "C",	"TC12",	R"(^(?:R_1|7_1|-?\d+_-?[^1]):1, 7_1:-3, 5_1:2, 6_-2:2, 8_)");
	/*  85 */ addCadenceDefinition("T", "C",	"TC13",	R"(^(?:R_1|7_1|-?\d+_-?[^1]):1, 7_1:2, 8_1:-3, 6_-2:2, 8_)");
	/*  86 */ addCadenceDefinition("T", "C",	"TC14",	R"(^(?:R_1|7_1|-?\d+_-?[^1]):1, 7_1:2, 8_1:-3, 6_1:-2, 5_-2:3, 8_)");
	/*  87 */ addCadenceDefinition("T", "C",	"TC15",	R"(^(?:R_1|7_1|-?\d+_-?[^1]):1, 7_1:2, 8_1:-3, 6_1:1, 6_-2:2, 8_)");
	/*  88 */ addCadenceDefinition("T", "C",	"TC16",	R"(^(?:R_1|-?\d+_-?[^1]):1, -2_1:-2, -3_-2:2, (?:1|-8)_)");
	/*  89 */ addCadenceDefinition("T", "C",	"TC17",	R"(^(?:R_1|-?\d+_-?[^1]):1, -2_1:-2, -3_1:-2, -4D?_-2:3, (?:1|-8)_)");
	/*  90 */ addCadenceDefinition("T", "C",	"TC18",	R"(^(?:R_1|-?\d+_-?[^1]):1, -2_1:-2, -3_1:-2, -4D?_1:2, -3_-2:2, (?:1|-8)_)");
	/*  91 */ addCadenceDefinition("T", "C",	"TC19",	R"(^(?:R_1|-?\d+_-?[^1]):1, -2_1:-2, -3_1:1, -3_1:-2, -4D?_-2:3, (?:1|-8)_)");
	/*  92 */ addCadenceDefinition("T", "C",	"TC20",	R"(^6_1:2, 7_1:-2, 6_-2:2, 8_)");
	/*  93 */ addCadenceDefinition("T", "C",	"TC21",	R"(^8_1:-2, 7_1:-2, 6_-2:2, 8_)");
	/*  94 */ addCadenceDefinition("T", "a",	"Ta1",	R"(^(?:-?\d+_-?[^1]):1, 4D_1:-2, 3_-2:-2, 3_)");
	/*  95 */ addCadenceDefinition("T", "c",	"Tc1",	R"(^(?:R_1|-?\d+_-?[^1]):1, 7_1:-2, 6_-2:4, 3_)");
	/*  96 */ addCadenceDefinition("T", "y",	"Ty1",	R"(^(?:R_1|-?\d+_-?[^1]):1, 7_1:-2, 6_-2:R, R_)");
	/*  97 */ addCadenceDefinition("b", "C",	"bC1",	R"(^(?:R_1|4D?_1|-?\d+_-?[^1]):1, 4D_1:-2, 3_1:-2, 2_1:2, 3_2:2, 3_)");
	/*  98 */ addCadenceDefinition("b", "C",	"bC2",	R"(^(?:R_1|4D?_1|-?\d+_-?[^1]):1, 4D_1:-2, 3_1:-2, 2_2:2, 3_)");
	/*  99 */ addCadenceDefinition("b", "C",	"bC3",	R"(^(?:R_1|4D?_1|-?\d+_-?[^1]):1, 4D_1:-2, 3_1:1, 3_1:-2, 2_2:2, 3_)");
	/* 100 */ addCadenceDefinition("b", "C",	"bC4",	R"(^(?:R_1|4D?_1|-?\d+_-?[^1]):1, 4D_1:-2, 3_1:1, 3_2:2, 3_)");
	/* 101 */ addCadenceDefinition("b", "C",	"bC5",	R"(^(?:R_1|4D?_1|-?\d+_-?[^1]):1, 4D_1:-2, 3_1:2, 4D_1:2, 5_1:-3, 3_2:2, 3_)");
	/* 102 */ addCadenceDefinition("b", "C",	"bC6",	R"(^(?:R_1|4D?_1|-?\d+_-?[^1]):1, 4D_1:-2, 3_2:2, 3_)");
	/* 103 */ addCadenceDefinition("c", "B",	"cB1",	R"(^(?:-?\d+|R)_1:-?\d+, -4D_-2:1, -3_(?:4|-5):4, -3_)");
	/* 104 */ addCadenceDefinition("c", "B",	"cB2",	R"(^(?:-?\d+|R)_1:-?\d+, -4D_-2:1, -3_-2:(?:4|-5), (?:-6|3)_)");
	/* 105 */ addCadenceDefinition("c", "T",	"cT1",	R"(^(?:-?\d+|R)_1:-?\d+, 2_-2:1, 3_-2:-2, 3_)");
	/* 106 */ addCadenceDefinition("c", "T",	"cT2",	R"(^[^R]_1, 7_1:-2, 6_-2:4, 3_)");
	/* 107 */ addCadenceDefinition("p", "C",	"pC1",	R"(^(?:R_1|-?\d+_-?[^1]):1, 7_1:-2, 6_(?:5|-4):2, 3_)");
	/* 108 */ addCadenceDefinition("s", "",		"s_1",	R"(^(?:R_1|-?\d+_-?[^1]):1, 2_1:-2, 8_-2:2, 3_)");
	/* 109 */ addCadenceDefinition("t", "C",	"tC1",	R"(^(?:R_1|-?\d+_-?[^1]):1, -2_1:-2, -3_1:-2, -4D?_1:2, -3_2:2, -3_)");
	/* 110 */ addCadenceDefinition("t", "C",	"tC2",	R"(^(?:R_1|-?\d+_-?[^1]):1, -2_1:-2, -3_2:2, -3_)");
	/* 111 */ addCadenceDefinition("t", "C",	"tC3",	R"(^(?:R_1|-?\d+_-?[^1]):1, 7_1:-2, 6_1:1, 6_1:-2, 5_1:2, 6_2:2, 6_)");
	/* 112 */ addCadenceDefinition("t", "C",	"tC4",	R"(^(?:R_1|-?\d+_-?[^1]):1, 7_1:-2, 6_2:2, 6_)");
	/* 113 */ addCadenceDefinition("u", "C",	"uC1",	R"(^(?:R_1|4D?_1|-?\d+_-?[^1]):1, 4D_1:-2, 3_-2:1, 4D_-2:2, 6_)");
	/* 114 */ addCadenceDefinition("u", "C",	"uC2",	R"(^(?:R_1|4D?_1|-?\d+_-?[^1]):1, 4D_1:-2, 3_-3:2, 6_)");
	/* 115 */ addCadenceDefinition("u", "C",	"uC3",	R"(^(?:R_1|4D?_1|-?\d+_-?[^1]):1, 4D_1:-2, 3_1:-2, 2_-3:3, 6_)");
	/* 116 */ addCadenceDefinition("u", "C",	"uC4",	R"(^(?:R_1|4D?_1|-?\d+_-?[^1]):1, 4D_1:-2, 3_1:-2, 2_1:2, 3_-3:2, 6_)");
	/* 117 */ addCadenceDefinition("u", "C",	"uC5",	R"(^(?:R_1|4D?_1|-?\d+_-?[^1]):1, 4D_1:-2, 3_1:1, 3_-3:2, 6_)");
	/* 118 */ addCadenceDefinition("u", "C",	"uC6",	R"(^(?:R_1|4D?_1|-?\d+_-?[^1]):1, 4D_1:-2, 3_1:1, 3_1:-2, 2_-3:3, 6_)");
	/* 119 */ addCadenceDefinition("u", "C",	"uC7",	R"(^(?:R_1|4D?_1|-?\d+_-?[^1]):1, 4D_1:-2, 3_1:1, 3_1:-2, 2_1:2, 3_-3:2, 6_)");
	/* 120 */ addCadenceDefinition("x", "C",	"xC1",	R"(^(?:-?\d+_-?[^1]):1, 4D_1:-2, 3_R:2, R_)");
	/* 121 */ addCadenceDefinition("x", "C",	"xC2",	R"(^3_1:2, 4D_1:-2, 3_R:2, R_)");
	/* 122 */ addCadenceDefinition("x", "C",	"xC3",	R"(^5_1:-2, 4D_1:-2, 3_R:2, R_)");
	/* 123 */ addCadenceDefinition("x", "c",	"xc1",	R"(^(?:R_1|4_1|-?\d+_-?[^1]):1, 4D_1:-2, 3_R:-2, R_)");
	/* 124 */ addCadenceDefinition("y", "z",	"yz1",	R"(^(?:-?\d+|R)_1:-?\d+, 2_-2:1, 3_R:R, R_)");
	/* 125 */ addCadenceDefinition("z", "C",	"zC2",	R"(^(?:R_1|7_1|-?\d+_-?[^1]):1, 7_1:-2, 6_1:1, 6_R:2, R_)");
	/* 126 */ addCadenceDefinition("z", "C",	"zC3",	R"(^(?:R_1|-?\d+_-?[^1]):1, -2_1:-2, -3_1:1, -3_R:2, R_)");
	/* 127 */ addCadenceDefinition("z", "C",	"zC4",	R"(^(?:R_1|-?\d+_-?[^1]):1, -2_1:-2, -3_R:2, R_)");
	/* 128 */ addCadenceDefinition("z", "C",	"zC5",	R"(^(?:R_1|-?\d+_-?[^1]):1, 7_1:-2, 6_R:2, R_)");
	/* 129 */ addCadenceDefinition("z", "c",	"zc1",	R"(^(?:R_1|-?\d+_-?[^1]):1, -2_1:-2, -3_R:-2, R_)");
	/* 130 */ addCadenceDefinition("z", "c",	"zc2",	R"(^(?:R_1|-?\d+_-?[^1]):1, 7_1:-2, 6_R:-2, R_)");
	/* 131 */ addCadenceDefinition("z", "y",	"zy1",	R"(^(?:R_1|-?\d+_-?[^1]):1, -2_1:-2, -3_1:1, -3_R:R, R_)");
	/* 132 */ addCadenceDefinition("z", "y",	"zy2",	R"(^(?:R_1|-?\d+_-?[^1]):1, -2_1:-2, -3_R:R, R_)");
}



//////////////////////////////
//
// Tool_autocadence::addCadenceDefinition --
//

void Tool_autocadence::addCadenceDefinition(const std::string& funcL, const std::string& funcU,
		const std::string& name, const std::string& regex) {
	m_definitions.resize(m_definitions.size() + 1);
	m_definitions.back().setDefinition(funcL, funcU, name, regex);
}



//////////////////////////////
//
// Tool_autocadence::prepareAbbreviations --
//

void Tool_autocadence::prepareAbbreviations(HumdrumFile& infile) {
	vector<HTp> kstarts;
	infile.getKernSpineStartList(kstarts);
	m_abbr.clear();
	m_abbr.resize(kstarts.size());
	for (int i=0; i<(int)kstarts.size(); i++) {
		HTp current = kstarts[i]->getNextToken();
		while (current && !current->isData()) {
			if (current->isInstrumentAbbreviation()) {
				m_abbr.at(i) = (*current).substr(3);
				break;
			}
			current = current->getNextToken();
		}
	}
}



//////////////////////////////
//
// Tool_autocadence::prepareDissonanceNames -- Only labeling suspensions for now.
//

void Tool_autocadence::prepareDissonanceNames(void) {
	m_dissonanceNames.clear();

	m_dissonanceNames.emplace("s", "binary suspension");
	m_dissonanceNames.emplace("S", "ternary suspension");
	m_dissonanceNames.emplace("g", "binary suspension agent");
	m_dissonanceNames.emplace("G", "ternary suspension agent");
}



//////////////////////////////
//
// Tool_autocadence::prepareCvfNames -- Counterpoint voice function names and abbreviations.
//

void Tool_autocadence::prepareCvfNames(void) {
	m_functionNames.clear();

	// Realized Cadential Voice Functions (uppercase letters):
	m_functionNames.emplace("C", "Cantizans");
	m_functionNames.emplace("A", "Altizans");
	m_functionNames.emplace("T", "Tenorizans");
	m_functionNames.emplace("B", "Bassizans");
	m_functionNames.emplace("L", "Leaping Contratenor");
	m_functionNames.emplace("P", "Plagal Bassizans");
	m_functionNames.emplace("S", "Sestizans");
	m_functionNames.emplace("Q", "Quintizans");

	// Evaded Cadential Voice Functions (lowercase letters):
	m_functionNames.emplace("c", "Evaded Cantizans");
	m_functionNames.emplace("a", "Evaded Altizans");
	m_functionNames.emplace("t", "Evaded Tenorizans");
	m_functionNames.emplace("b", "Evaded Bassizans");
	m_functionNames.emplace("u", "Evaded Bassizans (down third");

	// Abandoned Cadential Voice Functions (also lowercase letters):
	m_functionNames.emplace("x", "Abandoned Bassizans");
	m_functionNames.emplace("y", "Abandoned Cantizans");
	m_functionNames.emplace("z", "Abandoned Tenorizans");

}



//////////////////////////////
//
// Tool_autocadence::prepareDissonances --
//

void Tool_autocadence::prepareDissonances(HumdrumFile& infile) {
	HumdrumFile dfile;
	stringstream ss;
	ss << infile;
	dfile.readString(ss.str());
   hum::Tool_dissonant dissonant;
	dissonant.run(dfile);
	// cout << dfile;
	int dsize = dfile.getLineCount();
	int isize = infile.getLineCount();
	if (dsize != isize) {
		// number of lines in input/output are expected to be the same.
		cerr << "LINE COUNTS OF FILES FOR DISSONANCE ANALYSIS DO NOT MATCH." << endl;
		m_suspensionsQ = false;
		return;
	}
	for (int i=0; i<infile.getLineCount(); i++) {
		if (infile[i].isData()) {
			prepareDissonancesForLine(infile[i], dfile[i]);
		}
	}
}



//////////////////////////////
//
// Tool_autocadence::prepareDissonancesForLine -- Transfer dissonance analysis to
//     input file for a single data line.
//

void Tool_autocadence::prepareDissonancesForLine(HumdrumLine& iline, HumdrumLine& dline) {
	vector<HTp> ikern;
	for (int i=0; i<iline.getFieldCount(); i++) {
		HTp token = iline.token(i);
		if (token->isKern()) {
			ikern.push_back(token);
		}
	}

	int kindex = -1;
	for (int i=0; i<dline.getFieldCount(); i++) {
		HTp token = dline.token(i);
		if (token->isKern()) {
			kindex++;
			continue;
		}
		if (token->isDataType("**cdata-rdiss")) {
			if (kindex >= 0) {
				string text = token->getText();
				if (text != ".") {
					ikern.at(kindex)->setValue("auto", "dissonance", text);
				}
			}
		}
	}
}




/////////////////////////////////
//
// Tool_autostem::Tool_autostem -- Set the recognized options for the tool.
//

Tool_autostem::Tool_autostem(void) {
	define("d|debug=b",                      "Debugging information");
	define("r|remove=b",                     "Remove stems");
	define("R|removeall=b",                  "Remove all stems including explicit beams");
	define("o|overwrite|replace=b",          "Overwrite non-explicit stems in input");
	define("O|overwriteall|replaceall=b",    "Overwrite all stems in input");
	define("L|no-long|not-long|not-longs=b", "Do not put stems one whole notes or breves");
	define("u|up=b",                         "Middle note on staff has stem up");
	define("p|pos=b",                        "Display only note vertical positions on staves");
	define("v|voice=b",                      "Display only voice/layer information");
	define("author=b",                       "Program author");
	define("version=b",                      "Program version");
	define("example=b",                      "Program examples");
	define("h|help=b",                       "Short description");
}



/////////////////////////////////
//
// Tool_autostem::run -- Primary interfaces to the tool.
//

bool Tool_autostem::run(HumdrumFileSet& infiles) {
	bool status = true;
	for (int i=0; i<infiles.getCount(); i++) {
		status &= run(infiles[i]);
	}
	return status;
}


bool Tool_autostem::run(const string& indata, ostream& out) {
	HumdrumFile infile(indata);
	bool status = run(infile, out);
	if (hasAnyText()) {
		getAllText(out);
	} else {
		out << infile;
	}
	return status;
}


bool Tool_autostem::run(HumdrumFile& infile, ostream& out) {
	bool status = run(infile);
	if (hasAnyText()) {
		getAllText(out);
	} else {
		out << infile;
	}
	return status;
}

//
// In-place processing of file:
//

bool Tool_autostem::run(HumdrumFile& infile) {
	initialize(infile);
	if (m_quit) {
		return true;
	}
	if (removeQ || overwriteQ) {
		removeStems(infile);
		if (removeQ) {
			infile.createLinesFromTokens();
			return true;
		}
	}
	bool status = autostem(infile);
	// Re-load the text for each line from their tokens.
	if (status) {
		infile.createLinesFromTokens();
		return true;
	} else {
		return false;
	}
}



//////////////////////////////
//
// Tool_autostem::initialize --
//

void Tool_autostem::initialize(HumdrumFile& infile) {
	// handle basic options:

	if (getBoolean("author")) {
		m_free_text << "Written by Craig Stuart Sapp, "
			  << "craig@ccrma.stanford.edu, December 2010" << endl;
		m_quit = true;
	} else if (getBoolean("version")) {
		m_free_text << getCommand() << ", version: 17 June 2019" << endl;
		m_free_text << "compiled: " << __DATE__ << endl;
		m_quit = true;
	} else if (getBoolean("help")) {
		usage();
		m_quit = true;
	} else if (getBoolean("example")) {
		example();
		m_quit = true;
	}

	debugQ        = getBoolean("debug");
	removeQ       = getBoolean("remove");
	removeallQ    = getBoolean("removeall");
	noteposQ      = getBoolean("pos");
	voiceQ        = getBoolean("voice");
	overwriteQ    = getBoolean("overwrite");
	overwriteallQ = getBoolean("overwriteall");
	notlongQ      = getBoolean("no-long");

	if (getBoolean("up")) {
		Middle = 4;
		Borderline = 1;
	}
	removeallQ = getBoolean("removeall");
	if (removeallQ) {
		removeQ = 1;
	}
	if (overwriteallQ) {
		overwriteQ = 1;
	}
}



//////////////////////////////////////////////////////////////////////////


//////////////////////////////
//
// Tool_autostem::removeStems --
//

void Tool_autostem::removeStems(HumdrumFile& infile) {
	for (int i=0; i<infile.getLineCount(); i++) {
		if (!infile[i].isData()) {
			continue;
		}
		for (int j=0; j<infile[i].getFieldCount(); j++) {
			if (!infile.token(i, j)->isKern()) {
				continue;
			}
			HumRegex hre;
			// string& buffer = static_cast<string&>(*infile.token(i, j));
			if (removeallQ || overwriteallQ) {
				hre.replaceDestructive(infile.token(i, j), "", "[\\\\/]x(?!x)", "g");
				hre.replaceDestructive(infile.token(i, j), "", "[\\\\/](?!x)", "g");
			} else {
				hre.replaceDestructive(infile.token(i, j), "", "[\\\\/](?!x)", "g");
			}
		}
	}
}



//////////////////////////////
//
// Tool_autostem::autostem -- add an up/down stem on notes in **kern data
//     that do not already have stem information.
//

bool Tool_autostem::autostem(HumdrumFile& infile) {
	vector<vector<int> > baseline;
	getClefInfo(baseline, infile);

	// get staff-line position of all notes:
	vector<vector<vector<int> > > notepos;
	getNotePositions(notepos, baseline, infile);
	if (noteposQ) {
		printNotePositions(infile, notepos);
		return true;
	}

	// get voice/layer number in track:
	vector<vector<int> > voice;
	getVoiceInfo(voice, infile);
	if (voiceQ) {
		printVoiceInfo(infile, voice);
		return true;
	}

	// get stem directions:
	vector<vector<int> > stemdir;
	bool status = assignStemDirections(stemdir, voice, notepos, infile);
	if (!status) {
		return false;
	}
	insertStems(infile, stemdir);
	return true;
}



//////////////////////////////
//
// Tool_autostem::insertStems -- put stem directions into the data.
//

void Tool_autostem::insertStems(HumdrumFile& infile,
		vector<vector<int> >& stemdir) {
	for (int i=0; i<infile.getLineCount(); i++) {
		if (!infile[i].isData()) {
			continue;
		}
		for (int j=0; j<infile[i].getFieldCount(); j++) {
			if (!infile.token(i, j)->isKern()) {
				continue;
			}
			setStemDirection(infile, i, j, stemdir[i][j]);
		}
	}
}



//////////////////////////////
//
// Tool_autostem::setStemDirection -- don't change stem direction if
//    there is already a stem on the note.
//

void Tool_autostem::setStemDirection(HumdrumFile& infile, int row, int col,
			int direction) {
	int& i = row;
	int& j = col;

	if (infile.token(i, j)->isNull()) {
		return;
	}
	if (infile.token(i, j)->isRest()) {
		return;
	}

	if (notlongQ) {
		// Don't print stems on whole notes and breves.
		// Duration units are in quarter notes.
		HumNum value = Convert::recipToDuration(*infile.token(row,col));
		double duration = value.getFloat();
		if ((duration >= 4.0) && (duration < 16.0)) {
			return;
		}
	}

	string buffer;
	string output;
	int tokencount = infile.token(i, j)->getSubtokenCount();
	for (int k=0; k<tokencount; k++) {
		buffer = infile.token(i, j, k);
		if ((!Convert::contains(buffer, '/')) &&
		    (!Convert::contains(buffer, '\\'))) {
			if (direction > 0) {
				addStem(buffer, "/");
			} else if (direction < 0) {
				addStem(buffer, "\\");
			}
		}
		output += buffer;
		output += ' ';
	}
	if (output.back() == ' ') {
		output.pop_back();
	}
	infile.token(i, j)->setText(output);
}



//////////////////////////////
//
// Tool_autostem::assignStemDirections --
//

bool Tool_autostem::assignStemDirections(vector<vector<int> >& stemdir,
		vector<vector<int> > & voice,
		vector<vector<vector<int> > >& notepos, HumdrumFile& infile) {

	// the dimensions are not correct:
	stemdir.resize(infile.getLineCount());
	for (int i=0; i<(int)stemdir.size(); i++) {
		stemdir[i].resize(infile[i].getFieldCount());
		fill(stemdir[i].begin(), stemdir[i].end(), 0);
	}

	vector<int> maxlayer;
	getMaxLayers(maxlayer, voice, infile);

	assignBasicStemDirections(stemdir, voice, notepos, infile);

	vector<vector<string > > beamstates;
	bool status = getBeamState(beamstates, infile);
	if (!status) {
		return status;
	}

	vector<vector<Coord> > beamednotes;
	getBeamSegments(beamednotes, beamstates, infile, maxlayer);

	// print notes which are beamed together for debugging:

	if (debugQ) {
		for (int i=0; i<(int)beamednotes.size(); i++) {
			m_humdrum_text << "!! ";
			for (int j=0; j<(int)beamednotes[i].size(); j++) {
				m_humdrum_text << infile[beamednotes[i][j].i][beamednotes[i][j].j] << "\t";
			}
			m_humdrum_text << "\n";
		}
	}

	int direction;
	for (int i=0; i<(int)beamednotes.size(); i++) {
		direction = getBeamDirection(beamednotes[i], voice, notepos);
		setBeamDirection(stemdir, beamednotes[i], direction);
	}

	if (debugQ) {
		cerr << "STEM DIRECTION ASSIGNMENTS ==================" << endl;
		for (int i=0; i<(int)stemdir.size(); i++) {
			for (int j=0; j<(int)stemdir[i].size(); i++) {
				cerr << stemdir[i][j] << "\t";
			}
			cerr << endl;
		}
	}
	return true;
}



//////////////////////////////
//
// Tool_autostem::setBeamDirection --
//

void Tool_autostem::setBeamDirection(vector<vector<int> >& stemdir,
		vector<Coord>& bnote, int direction) {
	int x;
	int i, j;
	for (x=0; x<(int)bnote.size(); x++) {
		i = bnote[x].i;
		j = bnote[x].j;
		stemdir[i][j] = direction;
	}
}




//////////////////////////////
//
// Tool_autostem::getBeamDirection -- return a consensus stem direction
//     for beamed notes.
//

int Tool_autostem::getBeamDirection(vector<Coord>& coords,
		vector<vector<int> >& voice, vector<vector<vector<int> > >& notepos) {

	// voice values are presumed to be 0 at the moment.

	int minn = 1000;
	int maxx = -1000;

	int x;
	int i, j, k;
	for (x=0; x<(int)coords.size(); x++) {
		i = coords[x].i;
		j = coords[x].j;
		if (voice[i][j] == 1) {
			return +1;
		}
		if (voice[i][j] == 2) {
			return -1;
		}
		for (k=0; k<(int)notepos[i][j].size(); k++) {
			if (minn > notepos[i][j][k]) {
				minn = notepos[i][j][k];
			}
			if (maxx < notepos[i][j][k]) {
				maxx = notepos[i][j][k];
			}
		}
	}

	if (maxx < 0 + Borderline) {
		// both minn and maxx are less than zero, so place stems up
		return +1;
	}
	if (minn > 0) {
		// both minn and maxx are greater than zero, so place stems down
		return -1;
	}

	if (abs(maxx) > abs(minn)) {
		// highest note is higher than lower note is lower, so place
		// stems down
		return -1;
	}
	if (abs(maxx) > abs(minn)) {
		// highest note is lower than lower note is lower, so place
		// stems up
		return +1;
	}

	// its a draw, so place stem up.
	return +1;
}



//////////////////////////////
//
// Tool_autostem::getBeamSegments -- arrange the beamed notes into
//     a long list with each entry being a list of notes containing one beam.
//     Each beamed note set should have their beams all pointing in the same
//     direction.
//

void Tool_autostem::getBeamSegments(vector<vector<Coord> >& beamednotes,
		vector<vector<string > >& beamstates, HumdrumFile& infile,
		vector<int> maxlayer) {
	beamednotes.clear();
	beamednotes.reserve(10000);
	vector<vector<vector<Coord> > > beambuffer;
	beambuffer.resize(infile.getMaxTrack() + 1);
	int i, j;
	for (i=0; i<(int)beambuffer.size(); i++) {
		beambuffer[i].resize(10); // layer  max 10, all more later if needed
		for (j=0; j<(int)beambuffer[i].size(); j++) {
			beambuffer[i][j].reserve(1000);
		}
	}

	Coord tcoord;
	char beamchar;
	int track, oldtrack, layer;
	for (i=0; i<infile.getLineCount(); i++) {
		if (!infile[i].isData()) {
			continue;
		}
		oldtrack = 0;
		layer = 0;
		for (j=0; j<infile[i].getFieldCount(); j++) {
			if (!infile.token(i, j)->isKern()) {
				continue;
			}
			track = infile.token(i, j)->getTrack();
			if (track == oldtrack) {
				layer++;
			} else {
				layer = 0;
			}
			oldtrack = track;
			if (infile.token(i, j)->isNull()) {
				continue;
			}
			if (infile.token(i, j)->isRest()) {
				continue;
			}

			if (beamstates[i][j].empty()) {
				beambuffer[track][layer].resize(0);  // possible unter. beam
				continue;
			}
			beamchar = beamstates[i][j][0];

			if ((beamchar == '[') || (beamchar == '=')) {
				// add a beam to the buffer and wait for more
				tcoord.i = i;
				tcoord.j = j;
				beambuffer[track][layer].push_back(tcoord);
				continue;
			}
			if (beamchar == ']') {
				// ending of a beam so store in permanent storage
				tcoord.i = i;
				tcoord.j = j;
				beambuffer[track][layer].push_back(tcoord);
				beamednotes.push_back(beambuffer[track][layer]);
				beambuffer[track][layer].resize(0);
			}
		}
	}
}



//////////////////////////////
//
// Tool_autostem::getMaxLayers --
//

void Tool_autostem::getMaxLayers(vector<int>& maxlayer,
		vector<vector<int> >& voice, HumdrumFile& infile) {

	int track;
	maxlayer.resize(infile.getMaxTrack() + 1);
	fill(maxlayer.begin(), maxlayer.end(), 0);
	int i, j;
	for (i=0; i<infile.getLineCount(); i++) {
		if (!infile[i].isData()) {
			continue;
		}
		for (j=0; j<infile[i].getFieldCount(); j++) {
			if (!infile.token(i, j)->isKern()) {
				continue;
			}
			if (infile.token(i, j)->isNull()) {
				continue;
			}
			if (infile.token(i, j)->isRest()) {
				continue;
			}
			track = infile.token(i, j)->getTrack();
			if (voice[i][j] + 1 > maxlayer[track]) {
				maxlayer[track] = voice[i][j] + 1;
			}
		}
	}
}



//////////////////////////////
//
// Tool_autostem::getVoiceInfo -- 0 = only voice in track, 1 = layer 1,
//     2 = layer 2, etc.
//
// 0 voices will be stemmed up or down based on vertical positions of notes
// 1 voices will be stemmed up always
// 2 voices will be stemmed down always.
// 3 and higher are still to be determined.
//
// Future enhancement of this algorithm: if one voice contains an invisible
// rest, then it will be ignored in the voice calculation.
//

void Tool_autostem::getVoiceInfo(vector<vector<int> >& voice,
		HumdrumFile& infile) {

	voice.resize(infile.getLineCount());

	int i, j, v;
	for (i=0; i<infile.getLineCount(); i++) {
		if (!infile[i].isData()) {
			continue;
		}
		voice[i].resize(infile[i].getFieldCount());
		fill(voice[i].begin(), voice[i].end(), -1);
		for (j=0; j<infile[i].getFieldCount(); j++) {
			if (!infile.token(i, j)->isKern()) {
				continue;
			}
			if (infile.token(i, j)->isNull()) {
				continue;
			}
			v = getVoice(infile, i, j);
			voice[i][j] = v;
		}
	}
}



//////////////////////////////
//
// Tool_autostem::printVoiceInfo --
//

void Tool_autostem::printVoiceInfo(HumdrumFile& infile,
		vector<vector<int> >& voice) {
	vector<string> voiceinfo(infile.getLineCount());
	for (int i=0; i<infile.getLineCount(); i++) {
		if (!infile[i].isData()) {
			continue;
		}
		for (int j=0; j<infile[i].getFieldCount(); j++) {
			if (!infile.token(i, j)->isKern()) {
				continue;
			}
			if (infile.token(i, j)->isNull()) {
				continue;
			}
			if (infile.token(i, j)->isRest()) {
				continue;
			}
			voiceinfo[i] += voice[i][j];
		}
		if (voiceinfo[i].back() == ' ') {
			voiceinfo[i].pop_back();
		}
	}
	infile.appendDataSpine(voiceinfo, "", "**voice");
}




//////////////////////////////
//
// Tool_autostem::printNotePositions -- prints the vertical position of notes on the
//    staves.  Mostly for debugging purposes.  A spine at the end of the
//    data will be added containing all positions for notes on the line
//    in the sequence in which the notes occur from left to right.
//
//    The middle line of a 5-line staff is the zero position, and
//    position values are diatonic steps above or below that level:
//
//    ===== +4
//          +3
//    ===== +2
//          +1
//    =====  0
//          -1
//    ===== -2
//          -3
//    ===== -4
//

void Tool_autostem::printNotePositions(HumdrumFile& infile,
		vector<vector<vector<int> > >& notepos) {
	vector<string> posinfo(infile.getLineCount());
	for (int i=0; i<infile.getLineCount(); i++) {
		if (!infile[i].isData()) {
			continue;
		}
		for (int j=0; j<infile[i].getFieldCount(); j++) {
			if (!infile.token(i, j)->isKern()) {
				continue;
			}
			if (infile.token(i, j)->isNull()) {
				// ignore null tokens
				continue;
			}
			if (infile.token(i, j)->isRest()) {
				// ignore rests
				continue;
			}
			for (int k=0; k<(int)notepos[i][j].size(); k++) {
				posinfo[i] += notepos[i][j][k];
			}
			if (posinfo[i].back() == ' ') {
				posinfo[i].pop_back();
			}
		}
	}
	infile.appendDataSpine(posinfo, "", "**vpos");
}



//////////////////////////////
//
// Tool_autostem::getNotePositions -- Extract the vertical position of the notes
// on the staves, with the centerline of the staff being the 0 position
// and each diatonic step equal to 1, so that lines of 5-lined staff are
// at positions from bottom to top: -4, -2, 0, +2, +4.
//

void Tool_autostem::getNotePositions(vector<vector<vector<int> > >& notepos,
		vector<vector<int> >& baseline, HumdrumFile& infile) {

	notepos.resize(infile.getLineCount());

	int location;
	string buffer;
	int i, j, k;
	int tokencount;

	for (i=0; i<infile.getLineCount(); i++) {
		if (!infile[i].isData()) {
			continue;
		}
		notepos[i].resize(infile[i].getFieldCount());
		for (j=0; j<infile[i].getFieldCount(); j++) {
			if (!infile.token(i, j)->isKern()) {
				continue;
			}
			if (infile.token(i, j)->isNull()) {
				// ignore null-tokens
				continue;
			}
			if (infile.token(i, j)->isRest()) {
				// ignore rests
				continue;
			}

			tokencount = infile.token(i, j)->getSubtokenCount();
			notepos[i][j].resize(tokencount);
			for (k=0; k<tokencount; k++) {
				buffer = infile.token(i, j)->getSubtoken(k);
				location = Convert::kernToBase7(buffer) -
						baseline[i][j] - 4;
				notepos[i][j][k] = location;
			}
		}
	}
}



//////////////////////////////
//
// Tool_autostem::processKernTokenStems --
//

void Tool_autostem::processKernTokenStems(HumdrumFile& infile,
		vector<vector<int> >& baseline, int row, int col) {
	exit(1);
}



//////////////////////////////
//
// Tool_autostem::assignBasicStemDirections -- don't take beams into
//     consideration.
//

void Tool_autostem::assignBasicStemDirections(vector<vector<int> >& stemdir,
		vector<vector<int> >& voice, vector<vector<vector<int> > >& notepos,
		HumdrumFile& infile) {

	int i, j;
	for (i=0; i<infile.getLineCount(); i++) {
		if (!infile[i].isData()) {
			continue;
		}
		for (j=0; j<infile[i].getFieldCount(); j++) {
			if (!infile.token(i, j)->isKern()) {
				continue;
			}
			if (infile.token(i, j)->isNull()) {
				continue;
			}
			if (infile.token(i, j)->isRest()) {
				continue;
			}
			if (removeQ) {
				removeStem2(infile, i, j);
			}

			if (Convert::contains(infile.token(i, j), '/')) {
				stemdir[i][j] = +1;
			} else if (Convert::contains(infile.token(i, j), '\\')) {
				stemdir[i][j] = -1;
			} else {
				stemdir[i][j] = determineChordStem(voice, notepos, infile, i, j);
			}
		}
	}
}



//////////////////////////////
//
// Tool_autostem::determineChordStem --
//

int Tool_autostem::determineChordStem(vector<vector<int> >& voice,
		vector<vector<vector<int> > >& notepos, HumdrumFile& infile, int row,
		int col) {

	if (notepos[row][col].size() == 0) {
		return 0;
	}

	if (voice[row][col] == 1) {
		return +1;
	}
	if (voice[row][col] == 2) {
		return -1;
	}
	if (voice[row][col] == 3) {
		return +1;
	}
	// voice == 0 means determine by vertical position

	if (notepos[row][col].size() == 1) {
		int location = notepos[row][col][0];
		if (location >= 0 + Borderline) {
			return -1;
		} else {
			return +1;
		}
	}

	// chord with more than one note so choose the extreme note as the
	// one which decides the direction

	int i;
	int minn = notepos[row][col][0];
	int maxx = notepos[row][col][0];
	for (i=1; i<(int)notepos[row][col].size(); i++) {
		if (minn > notepos[row][col][i]) {
			minn = notepos[row][col][i];
		}
		if (maxx < notepos[row][col][i]) {
			maxx = notepos[row][col][i];
		}
	}

	if (maxx < 0 + Borderline) {
		// all stems want to point upwards:
		return +1;
	}
	if (minn > 0) {
		// all stems want to point downwards:
		return -1;
	}

	if (abs(maxx) > abs(minn)) {
		return -1;
	}
	if (abs(minn) > abs(maxx)) {
		return +1;
	}

	return +1;
}



//////////////////////////////
//
// Tool_autostem::processKernTokenStemsSimpleModel --
//

void Tool_autostem::processKernTokenStemsSimpleModel(HumdrumFile& infile,
		vector<vector<int> >& baseline, int row, int col) {
	int& i = row;
	int& j = col;
	int tokencount = infile.token(i, j)->getSubtokenCount();

	HumNum duration;
	if (tokencount == 1) {
		duration = Convert::recipToDuration(*infile.token(i, j));
		if (duration >= 4) {
			// whole note or larger for note/chord, to not append a stem
			return;
		}
		if (Convert::contains(infile.token(i, j), '/')) {
			if (removeallQ || overwriteallQ) {
				if (Convert::contains(infile.token(i, j), "/x")) {
					if (Convert::contains(infile.token(i, j), "/xx")) {
						return;
					}
				} else if (Convert::contains(infile.token(i, j), "\\x")) {
					if (Convert::contains(infile.token(i, j), "\\xx")) {
						return;
					}
				}
			} else if (removeallQ || overwriteallQ) {
				removeStem2(infile, i, j);
			} else {
				// nothing to do
				return;
			}
		}
		if (infile.token(i, j)->isRest()) {
			// rest which does not have a stem
			return;
		}
	}

	if (removeQ) {
		removeStem2(infile, i, j);
	}

	int voice = getVoice(infile, row, col);
	int location;
	string buffer;
	string output;
	for (int k=0; k<tokencount; k++) {
		buffer = infile.token(i, j, k);
		if (i == 0) {
			duration = Convert::recipToDuration(buffer);
			// if (duration >= 4) {
			//    // whole note or larger for note/chord, do not append a stem
			//    return;
			// }
		}
		if (!(Convert::contains(infile.token(i, j), '/') ||
				Convert::contains(infile.token(i, j), '\\'))) {
			location = Convert::kernToBase7(buffer) -
					baseline[row][col] - Middle;
		  if (voice == 1) {
			  addStem(buffer, "/");
			} else if (voice == 2) {
				addStem(buffer, "\\");
			} else {
				addStem(buffer, location > 0 ? "\\" : "/");
			}
			output += buffer;
			output += ' ';
		} else {
			output += buffer;
			output += ' ';
		}
	}
	if (output.back() == ' ') {
		output.pop_back();
	}
	infile.token(i, j)->setText(output);
}



//////////////////////////////
//
// Tool_autostem::getVoice -- return 0 if the only spine in primary track, otherwise, return
// the nth column offset from 1 in the primary track.
//

int Tool_autostem::getVoice(HumdrumFile& infile, int row, int col) {
	int output = 0;
	int tcount = 0;
	int track = infile.token(row, col)->getTrack();
	int j;
	int testtrack;
	for (j=0; j<infile[row].getFieldCount(); j++) {
		testtrack = infile.token(row, j)->getTrack();
		if (testtrack == track) {
			tcount++;
		}
		if (col == j) {
			output = tcount;
		}
	}
	if (tcount == 1) {
		output = 0;
	}
	return output;
}



//////////////////////////////
//
// Tool_autostem::removeStem2 -- remove stem and any single x after the stem.
//

void Tool_autostem::removeStem2(HumdrumFile& infile, int row, int col) {
	HumRegex hre;
	hre.replaceDestructive(infile.token(row, col), "", "[\\\\/]x(?!x)", "g");
	hre.replaceDestructive(infile.token(row, col), "", "[\\\\/](?!x)", "g");
}



//////////////////////////////
//
// Tool_autostem::addStem --
//

void Tool_autostem::addStem(string& input, const string& piece) {
	string output;
	HumRegex hre;
	if (hre.search(input, "(.*[ABCDEFG][n#-]*[xyXY<>]*)(.*)$", "i")) {
		output = hre.getMatch(1);
		output += piece;
		output += hre.getMatch(2);
	} else {
		output = input;
		output += piece;
	}
	input = output;
}



///////////////////////////////
//
// Tool_autostem::getClefInfo -- Identify the clef of each note in the score.
//     Does not consider the case where a primary track contains more
//     than one clef at a time (but that should not reasonable happen
//     in more scores).
//

void Tool_autostem::getClefInfo(vector<vector<int> >& baseline,
		HumdrumFile& infile) {
	vector<int> states(infile.getMaxTrack()+1,
			Convert::kernClefToBaseline("*clefG2"));
	baseline.resize(infile.getLineCount());

	int track;
	for (int i=0; i<infile.getLineCount(); i++) {
		if (infile[i].isInterpretation()) {
			for (int j=0; j<infile[i].getFieldCount(); j++) {
				if (!infile.token(i, j)->isKern()) {
					continue;
				}
				if (infile.token(i, j)->compare(0, 5, "*clef") == 0) {
					track = infile.token(i, j)->getTrack();
					states[track] = Convert::kernClefToBaseline(*infile.token(i, j));
				}
			}
		}
		if (!infile[i].isData()) {
			continue;
		}
		baseline[i].resize(infile[i].getFieldCount());
		for (int j=0; j<infile[i].getFieldCount(); j++) {
			if (!infile.token(i, j)->isKern()) {
				continue;
			}
			track = infile.token(i, j)->getTrack();
			baseline[i][j] = states[track];
		}
	}
}



//////////////////////////////
//
// Tool_autostem::example -- example function calls to the program.
//

void Tool_autostem::example(void) {
	m_error_text << getCommand() << " file.krn" << endl;
}



//////////////////////////////
//
// Tool_autostem::usage -- command-line usage description and brief summary
//

void Tool_autostem::usage(void) {
	m_error_text << "Usage: " << getCommand() << " [file(s)] " << endl;
}



//////////////////////////////
//
// Tool_autostem::getBeamState -- Analyze structure of beams and store note layout
//      directives at the same time.
//
// Type          Humdrum     MuseData
// start          L           [
// continue                   =
// end            J           ]
// forward hook   K           /
// backward hook  k           \  x
//

bool Tool_autostem::getBeamState(vector<vector<string > >& beams,
		HumdrumFile& infile) {
	int len;
	int contin;
	int start;
	int stop;
	int flagr;
	int flagl;
	int track;
	HumNum rn;

	vector<vector<int> > beamstate;   // state of beams in tracks/layers
	vector<vector<int> > gracestate;  // independents state for grace notes

	string gbinfo;

	beamstate.resize(infile.getMaxTrack() + 1);
	gracestate.resize(infile.getMaxTrack() + 1);
	for (int i=0; i<(int)beamstate.size(); i++) {
		beamstate[i].resize(100);     // maximum of 100 layers in each track...
		gracestate[i].resize(100);    // maximum of 100 layers in each track...
		fill(beamstate[i].begin(), beamstate[i].end(), 0);
		fill(gracestate[i].begin(), gracestate[i].end(), 0);
	}

	beams.resize(infile.getLineCount());
	vector<int> curlayer;
	curlayer.resize(infile.getMaxTrack() + 1);
	vector<int> laycounter;

	for (int i=0; i<infile.getLineCount(); i++) {
		if (infile[i].isBarline()) {
			// don't allow beams across barlines.  Mostly for
			// preventing buggy beams from propagating...
			for (int t=1; t<=infile.getMaxTrack(); t++) {
				fill(beamstate[t].begin(), beamstate[t].end(), 0);
				fill(gracestate[t].begin(), gracestate[t].end(), 0);
			}
		}

		if (!infile[i].isData() && !infile[i].isBarline()) {
			continue;
		}

		if (!infile[i].isData()) {
			continue;
		}

		beams[i].resize(infile[i].getFieldCount());
		for (int j=0; j<(int)beams[i].size(); j++) {
			beams[i][j].resize(1);
			beams[i][j] = "";
		}

		fill(curlayer.begin(), curlayer.end(), 0);
		for (int j=0; j<infile[i].getFieldCount(); j++) {
			HTp token = infile.token(i, j);
			if (!token->isKern()) {
				continue;
			}
			track = token->getTrack();
			curlayer[track]++;
			if (token->isNull()) {
				continue;
			}
			if (infile.token(i, j)->isRest()) {
				// ignore rests.  Might be useful to not ignore
				// rests if beams extend over rests...
				continue;
			}
			rn = Convert::recipToDuration(*infile.token(i, j));
			if (rn >= 1) {
				beamstate[track][curlayer[track]] = 0;
				continue;
			}
			if (rn == 0) {
				// grace notes;
				countBeamStuff(infile.token(i, j)->c_str(), start, stop, flagr, flagl);
				if ((start != 0) && (stop != 0)) {
					cerr << "Funny error in grace note beam calculation" << endl;
					exit(1);
				}
				if (start > 7) {
					cerr << "Too many beam starts" << endl;
				}
				if (stop > 7) {
					cerr << "Too many beam ends" << endl;
				}
				if (flagr > 7) {
					cerr << "Too many beam flagright" << endl;
				}
				if (flagl > 7) {
					cerr << "Too many beam flagleft" << endl;
				}
				contin = gracestate[track][curlayer[track]];
				contin -= stop;
				gbinfo.clear();
				if (contin > 0) {
					gbinfo.resize(contin);
				}
				for (int ii=0; ii<contin; ii++) {
					gbinfo[ii] = '=';
				}
				if (start > 0) {
					for (int ii=0; ii<start; ii++) {
						gbinfo += "[";
					}
				} else if (stop > 0) {
					for (int ii=0; ii<stop; ii++) {
						gbinfo += "]";
					}
				}
				for (int ii=0; ii<flagr; ii++) {
					gbinfo += "/";
				}
				for (int ii=0; ii<flagl; ii++) {
					gbinfo += "\\";
				}
				len = (int)gbinfo.size();
				if (len > 6) {
					cerr << "Error too many grace note beams" << endl;
					exit(1);
				}
				beams.at(i).at(j) = gbinfo;
				gracestate.at(track).at(curlayer.at(track)) = contin;
				gracestate.at(track).at(curlayer.at(track)) += start;

			} else {
				// regular notes which are shorter than a quarter note
				// (including tuplet quarter notes which should be removed):

				countBeamStuff(infile.token(i, j)->c_str(), start, stop, flagr, flagl);
				if ((start != 0) && (stop != 0)) {
					cerr << "Funny error in note beam calculation" << endl;
					exit(1);
				}
				if (start > 7) {
					cerr << "Too many beam starts" << endl;
				}
				if (stop > 7) {
					cerr << "Too many beam ends" << endl;
				}
				if (flagr > 7) {
					cerr << "Too many beam flagright" << endl;
				}
				if (flagl > 7) {
					cerr << "Too many beam flagleft" << endl;
				}
				contin = beamstate.at(track).at(curlayer.at(track));
				contin -= stop;
				if (contin < 0) {
					cerr << "ERROR at line " << token->getLineNumber() << " column " << token->getFieldNumber() << ": ";
					cerr << "Unbalanced beaming information in measure." << endl;
					return false;
				}
				gbinfo.resize(contin);
				for (int ii=0; ii<contin; ii++) {
					gbinfo[ii] = '=';
				}
				if (start > 0) {
					for (int ii=0; ii<start; ii++) {
						gbinfo += "[";
					}
				} else if (stop > 0) {
					for (int ii=0; ii<stop; ii++) {
						gbinfo += "[";
					}
				}
				for (int ii=0; ii<flagr; ii++) {
					gbinfo += "/";
				}
				for (int ii=0; ii<flagl; ii++) {
					gbinfo += "\\";
				}
				len = (int)gbinfo.size();
				if (len > 6) {
					cerr << "Error too many grace note beams" << endl;
					return false;
				}
				beams.at(i).at(j) = gbinfo;
				beamstate.at(track).at(curlayer.at(track)) = contin;
				beamstate.at(track).at(curlayer.at(track)) += start;
			}
		}
	}

	return true;
}



//////////////////////////////
//
// Tool_autostem::countBeamStuff --
//

void Tool_autostem::countBeamStuff(const string& token, int& start, int& stop,
		int& flagr, int& flagl) {
	start = stop = flagr = flagl = 0;
	for (int i=0; i<(int)token.size(); i++) {
		switch (token[i]) {
			case 'L': start++;  break;
			case 'J': stop++;   break;
			case 'K': flagr++;  break;
			case 'k': flagl++;  break;
		}
	}
}




/////////////////////////////////
//
// Tool_binroll::Tool_binroll -- Set the recognized options for the tool.
//

Tool_binroll::Tool_binroll(void) {
	// add options here
	define("t|timebase=s:16", "timebase to do analysis at");
}



/////////////////////////////////
//
// Tool_binroll::run -- Do the main work of the tool.
//

bool Tool_binroll::run(HumdrumFileSet& infiles) {
	bool status = true;
	for (int i=0; i<infiles.getCount(); i++) {
		status &= run(infiles[i]);
	}
	return status;
}


bool Tool_binroll::run(const string& indata, ostream& out) {
	HumdrumFile infile(indata);
	bool status = run(infile);
	if (hasAnyText()) {
		getAllText(out);
	} else {
		out << infile;
	}
	return status;
}


bool Tool_binroll::run(HumdrumFile& infile, ostream& out) {
	bool status = run(infile);
	if (hasAnyText()) {
		getAllText(out);
	} else {
		out << infile;
	}
	return status;
}


bool Tool_binroll::run(HumdrumFile& infile) {
	m_duration.setValue(1, 4); // 16th note
	processFile(infile);
	return true;
}



//////////////////////////////
//
// Tool_binroll::processFile --
//

void Tool_binroll::processFile(HumdrumFile& infile) {
	vector<vector<char>> output;
	output.resize(128);
	int count = (infile.getScoreDuration() / m_duration).getInteger() + 1;
	for (int i=0; i<(int)output.size(); i++) {
		output[i].resize(count);
		std::fill(output[i].begin(), output[i].end(), 0);
	}

	int strandcount = infile.getStrandCount();
	for (int i=0; i<strandcount; i++) {
		HTp starting = infile.getStrandStart(i);
		if (!starting->isKern()) {
			continue;
		}
		HTp ending = infile.getStrandEnd(i);
		processStrand(output, starting, ending);
	}

	printAnalysis(infile, output);

}



//////////////////////////////
//
// Tool_binroll::printAnalysis --
//

void Tool_binroll::printAnalysis(HumdrumFile& infile,
		vector<vector<char>>& roll) {
	HumRegex hre;

	for (int i=0; i<infile.getLineCount(); i++) {
		if (infile[i].isExclusive()) {
			break;
		}
		if (infile[i].isEmpty()) {
			continue;
		}
		string line = infile[i].getText();
		int found = 0;
		for (int j=0; j<(int)line.size(); j++) {
			if ((line[j] == '!') && !found) {
				m_free_text << "#";
			} else {
				found = 1;
				m_free_text << line[j];
			}
		}
		m_free_text << "\n";
	}

	for (int i=0; i<(int)roll[0].size(); i++) {
		for (int j=0; j<(int)roll.size(); j++) {
			m_free_text << (int)roll[j][i];
			if (j < (int)roll.size() - 1) {
				m_free_text << ' ';
			}
		}
		m_free_text << "\n";
	}

	int startindex = infile.getLineCount() - 1;
	for (int i=infile.getLineCount()-1; i>=0; i--) {
		if (infile[i].isManipulator()) {
			startindex = i+1;
			break;
		}
		startindex = i;
	}

	for (int i=startindex; i<infile.getLineCount(); i++) {
		if (infile[i].isEmpty()) {
			continue;
		}
		string line = infile[i].getText();
		int found = 0;
		for (int j=0; j<(int)line.size(); j++) {
			if ((line[j] == '!') && !found) {
				m_free_text << "#";
			} else {
				found = 1;
				m_free_text << line[j];
			}
		}
		m_free_text << "\n";
	}
}



//////////////////////////////
//
// Tool_binroll::processStrand --
//

void Tool_binroll::processStrand(vector<vector<char>>& roll, HTp starting,
		HTp ending) {
	HTp current = starting;
	int base12;
	HumNum starttime;
	HumNum duration;
	int startindex;
	int endindex;
	while (current && (current != ending)) {
		if (!current->isNonNullData()) {
			current = current->getNextToken();
			continue;
		}
		if (current->isRest()) {
			current = current->getNextToken();
			continue;
		}

		if (current->isChord()) {
			int stcount = current->getSubtokenCount();
			starttime = current->getDurationFromStart();
			startindex = (starttime / m_duration).getInteger();
			for (int s=0; s<stcount; s++) {
				string tok = current->getSubtoken(s);
				base12 = Convert::kernToMidiNoteNumber(tok);
				if ((base12 < 0) || (base12 > 127)) {
					continue;
				}
				duration = Convert::recipToDuration(tok);
				endindex = ((starttime+duration) / m_duration).getInteger();
				roll[base12][startindex] = 2;
				for (int i=startindex+1; i<endindex; i++) {
					roll[base12][i] = 1;
				}
			}
		} else {
			base12 = Convert::kernToMidiNoteNumber(current);
			if ((base12 < 0) || (base12 > 127)) {
				current = current->getNextToken();
				continue;
			}
			starttime = current->getDurationFromStart();
			duration = current->getDuration();
			startindex = (starttime / m_duration).getInteger();
			endindex   = ((starttime+duration) / m_duration).getInteger();
			roll[base12][startindex] = 2;
			for (int i=startindex+1; i<endindex; i++) {
				roll[base12][i] = 1;
			}
		}
		current = current->getNextToken();
	}
}






/////////////////////////////////
//
// Tool_bstyle::Tool_bstyle -- Set the recognized options for the tool.
//

Tool_bstyle::Tool_bstyle(void) {
	define("r|remove=b",    "remove any dot/dash/invisible barline stylings");
}



///////////////////////////////
//
// Tool_bstyle::run -- Primary interfaces to the tool.
//

bool Tool_bstyle::run(HumdrumFileSet& infiles) {
	bool status = true;
	for (int i=0; i<infiles.getCount(); i++) {
		status &= run(infiles[i]);
	}
	return status;
}


bool Tool_bstyle::run(const string& indata, ostream& out) {
	HumdrumFile infile(indata);
	return run(infile, out);
}


bool Tool_bstyle::run(HumdrumFile& infile, ostream& out) {
	bool status = run(infile);
	out << m_free_text.str();
	return status;
}


bool Tool_bstyle::run(HumdrumFile& infile) {
   initialize();
	processFile(infile);
	return true;
}



//////////////////////////////
//
// Tool_bstyle::initialize --
//

void Tool_bstyle::initialize(void) {
	m_removeQ = getBoolean("remove");
}



//////////////////////////////
//
// Tool_bstyle::processFile --
//

void Tool_bstyle::processFile(HumdrumFile& infile) {
	if (m_removeQ) {
		removeBarStylings(infile);
	} else {
		applyBarStylings(infile);
	}
	infile.createLinesFromTokens();
	m_humdrum_text << infile;
}



//////////////////////////////
//
// Tool_bstyle::removeBarStylings --
//

void Tool_bstyle::removeBarStylings(HumdrumFile& infile) {
	vector<HTp> kstarts = infile.getKernSpineStartList();
	for (int i=0; i<(int)kstarts.size(); i++) {
		removeBarStylings(kstarts.at(i));
	}
}



//////////////////////////////
//
// Tool_bstyle::removeBarStylings --
//

void Tool_bstyle::removeBarStylings(HTp spine) {

	HTp current = spine->getNextToken();
	bool activeQ = false;
	bool dashQ   = false;
	bool dotQ    = false;
	bool invisQ  = false;
	HumRegex hre;
	while (current) {
		if (current->isInterpretation()) {
			if (hre.search(current, "^\\*bstyle:")) {
				if (hre.search(current, "stop")) {
					activeQ = false;
					dashQ  = false;
					dotQ   = false;
					invisQ = false;
				} else {
					activeQ = true;
					if (hre.search(current, "^\\*bstyle:.*dash=(\\d+)")) {
						dashQ  = true;
						dotQ   = false;
						invisQ = false;
					} else if (hre.search(current, "^\\*bstyle:.*dot=(\\d+)")) {
						dashQ  = false;
						dotQ   = true;
						invisQ = false;
					} else if (hre.search(current, "^\\*bstyle:.*invis=(\\d+)")) {
						dashQ  = false;
						dotQ   = false;
						invisQ = true;
					}
				}
			}
		}
		if (!current->isBarline()) {
			current = current->getNextToken();
			return;
		}
		if (!activeQ) {
			current = current->getNextToken();
			return;
		}
		int track = current->getTrack();
		HTp rcurrent = current;
		while (rcurrent) {
			if (track != rcurrent->getTrack()) {
				break;
			}
			string text = *rcurrent;
			int length = (int)text.size();
			if (dashQ) {
				hre.replaceDestructive(text, "", ":", "g");
			} else if (dotQ) {
				hre.replaceDestructive(text, "", ".", "g");
			} else if (invisQ) {
				hre.replaceDestructive(text, "", "-", "g");
			}
			if (length > (int)text.size()) {
				rcurrent->setText(text);
			}
			rcurrent = rcurrent->getNextFieldToken();
		}
		current = current->getNextToken();
	}

}



//////////////////////////////
//
// Tool_bstyle::applyBarStylings --
//

void Tool_bstyle::applyBarStylings(HumdrumFile& infile) {
	vector<HTp> kstarts = infile.getKernSpineStartList();
	for (int i=0; i<(int)kstarts.size(); i++) {
		applyBarStylings(kstarts.at(i));
	}
}



//////////////////////////////
//
// Tool_bstyle::applyBarStylings --
//

void Tool_bstyle::applyBarStylings(HTp spine) {

	HTp current = spine->getNextToken();
	bool activeQ = false;
	bool dashQ   = false;
	bool dotQ    = false;
	bool invisQ  = false;
	int dash  = 0;
	int dot   = 0;
	int invis = 0;
	int counter = 0;
	HumRegex hre;
	while (current) {
		if (current->isInterpretation()) {
			if (hre.search(current, "^\\*bstyle:")) {
				if (hre.search(current, "stop")) {
					activeQ = false;
					dashQ   = false;
					dotQ    = false;
					invisQ  = false;
					dash    = 0;
					dot     = 0;
					invis   = 0;
				} else {
					activeQ = true;
					if (hre.search(current, "^\\*bstyle:.*dash=(\\d+)")) {
						dashQ  = true;
						dotQ   = false;
						invisQ = false;
						dash   = hre.getMatchInt(1);
						counter = 0;
					} else if (hre.search(current, "^\\*bstyle:.*dot=(\\d+)")) {
						dashQ  = false;
						dotQ   = true;
						invisQ = false;
						dot    = hre.getMatchInt(1);
						counter = 0;
					} else if (hre.search(current, "^\\*bstyle:.*invis=(\\d+)")) {
						dashQ  = false;
						dotQ   = false;
						invisQ = true;
						invis  = hre.getMatchInt(1);
						counter = 0;
					}
				}
			}
			current = current->getNextToken();
			continue;
		}
		if (!current->isBarline()) {
			current = current->getNextToken();
			continue;
		}
		if (!activeQ) {
			current = current->getNextToken();
			continue;
		}

		int track = current->getTrack();
		HTp rcurrent = current;

		if (dashQ && (dash > 0)) {

			if (counter % dash != 0) {
				while (rcurrent) {
					if (track != rcurrent->getTrack()) {
						break;
					}
					string text = *rcurrent;
					if (text.find(":") == string::npos) {
						text += ":";
						rcurrent->setText(text);
					}
					rcurrent = rcurrent->getNextFieldToken();
				}
			}

		} else if (dotQ && (dot > 0)) {

			if (counter % dot != 0) {
				while (rcurrent) {
					if (track != rcurrent->getTrack()) {
						break;
					}
					string text = *rcurrent;
					if (text.find(".") == string::npos) {
						text += ".";
						rcurrent->setText(text);
					}
					rcurrent = rcurrent->getNextFieldToken();
				}
			}

		} else if (invisQ && (invis > 0)) {

			if (counter % invis != 0) {
				while (rcurrent) {
					if (track != rcurrent->getTrack()) {
						break;
					}
					string text = *rcurrent;
					if (text.find("-") == string::npos) {
						text += "-";
						rcurrent->setText(text);
					}
					rcurrent = rcurrent->getNextFieldToken();
				}
			}

		}
		counter++;
		current = current->getNextToken();
	}
}




/////////////////////////////////
//
// Tool_chantize::Tool_chantize -- Set the recognized options for the tool.
//

Tool_chantize::Tool_chantize(void) {
	define("R|no-reference-records=b",                "do not add reference records");
	define("r|only-add-reference-records=b",          "only add reference records");

	define("B|do-not-delete-breaks=b",                "do not delete system/page break markers");
	define("b|only-delete-breaks=b",                  "only delete breaks");

	define("A|do-not-fix-instrument-abbreviations=b", "do not fix instrument abbreviations");
	define("a|only-fix-instrument-abbreviations=b",   "only fix instrument abbreviations");

	define("E|do-not-fix-editorial-accidentals=b",    "do not fix instrument abbreviations");
	define("e|only-fix-editorial-accidentals=b",      "only fix editorial accidentals");

	define("N|do-not-remove-empty-transpositions=b",  "do not remove empty transposition instructions");
	define ("n|only-remove-empty-transpositions=b",   "only remove empty transpositions");
}



/////////////////////////////////
//
// Tool_chantize::run -- Primary interfaces to the tool.
//

bool Tool_chantize::run(HumdrumFileSet& infiles) {
	bool status = true;
	for (int i=0; i<infiles.getCount(); i++) {
		status &= run(infiles[i]);
	}
	return status;
}


bool Tool_chantize::run(const string& indata, ostream& out) {
	HumdrumFile infile(indata);
	bool status = run(infile);
	if (hasAnyText()) {
		getAllText(out);
	} else {
		out << infile;
	}
	return status;
}


bool Tool_chantize::run(HumdrumFile& infile, ostream& out) {
	bool status = run(infile);
	if (hasAnyText()) {
		getAllText(out);
	} else {
		out << infile;
	}
	return status;
}

//
// In-place processing of file:
//

bool Tool_chantize::run(HumdrumFile& infile) {
	processFile(infile);

	// Re-load the text for each line from their tokens.
	infile.createLinesFromTokens();
	outputFile(infile);
	return true;
}



//////////////////////////////
//
// Tool_chantize::outputFile --
//    * remove time signature,
//    * remove barlines, except double barlines
//    * convert rests to double barlines (except at the end of the
//         music).
//

void Tool_chantize::outputFile(HumdrumFile& infile) {
	vector<bool> terminalRest = getTerminalRestStates(infile);
	HTp token;
	bool restQ = false;
	for (int i=0; i<infile.getLineCount(); i++) {
		if (infile[i].isInterpretation()) {
			token = infile.token(i, 0);
			if (token->isTimeSignature()) {
				// suppress time signatures
				continue;
			}
		} else if (infile[i].isCommentGlobal()) {
			// Convert line breaks into invisible barlines.
			token = infile.token(i, 0);
			if (token->find("original") != string::npos) {
				int cols = 2;
				for (int j=0; j<cols; j++) {
					m_humdrum_text << "=-";
					if (j < cols - 1) {
						m_humdrum_text << "\t";
					}
				}
				m_humdrum_text << "\n";
				continue;
			}
		} else if (infile[i].isBarline()) {
			// suppress barlines
			token = infile.token(i, 0);
			// but do not suppress double barlines
			if (token->find("||") == string::npos) {
				continue;
			}
		} else if (infile[i].isData()) {
			token = infile.token(i, 0);
			if (token->isRest()) {
				if (terminalRest[i]) {
					continue;
				}
				if (!restQ) {
					restQ = true;
					// convert rest into double barline
					// but suppress rests at end of music
					//or just suppress
					// for (int j=0; j<infile[i].getFieldCount(); j++) {
					// 	m_humdrum_text  << "=-";
					// 	if (j < infile[i].getFieldCount() - 1) {
					// 		m_humdrum_text << "\t";
					// 	}
					// }
					// m_humdrum_text << "\n";
				}
				continue;
			} else {
				restQ = false;
			}
		}
		m_humdrum_text << infile[i] << "\n";
	}
	if (m_diamondQ) {
		m_humdrum_text << "!!!RDF**kern: j = diamond note, color=blue\n";
	}
	m_humdrum_text << "!!!RDF**kern: {} = ligature\n";
}



//////////////////////////////
//
// Tool_chantize::processFile --
//

void Tool_chantize::processFile(HumdrumFile& infile) {

	bool abbreviationsQ  = true;
	bool accidentalsQ    = true;
	bool referencesQ     = true;
	// bool breaksQ         = true;
	bool transpositionsQ = true;

	if (getBoolean("no-reference-records")) { referencesQ = false; }
	if (getBoolean("only-add-reference-records")) {
		abbreviationsQ  = false;
		accidentalsQ    = false;
		referencesQ     = true;
		// breaksQ         = false;
		transpositionsQ = false;
	}

	if (getBoolean("do-not-delete-breaks")) {
		//	breaksQ = false;
	}
	if (getBoolean("only-delete-breaks")) {
		abbreviationsQ  = false;
		accidentalsQ    = false;
		referencesQ     = false;
		// breaksQ         = true;
		transpositionsQ = false;
	}

	if (getBoolean("do-not-fix-instrument-abbreviations")) { abbreviationsQ = false; }
	if (getBoolean("only-fix-instrument-abbreviations")) {
		abbreviationsQ  = true;
		accidentalsQ    = false;
		referencesQ     = false;
		// breaksQ         = false;
		transpositionsQ = false;
	}

	if (getBoolean("do-not-fix-editorial-accidentals")) { accidentalsQ = false; }
	if (getBoolean("only-fix-editorial-accidentals")) {
		abbreviationsQ  = false;
		accidentalsQ    = true;
		referencesQ     = false;
		// breaksQ         = false;
		transpositionsQ = false;
	}

	if (getBoolean("do-not-remove-empty-transpositions")) { transpositionsQ = false; }
	if (getBoolean("only-remove-empty-transpositions")) {
		abbreviationsQ  = false;
		accidentalsQ    = false;
		referencesQ     = false;
		// breaksQ         = false;
		transpositionsQ = true;
	}

	m_diamondQ = hasDiamondNotes(infile);

	if (abbreviationsQ)  { fixInstrumentAbbreviations(infile); }
	if (accidentalsQ)    { fixEditorialAccidentals(infile); }
	if (referencesQ)     { addBibliographicRecords(infile); }
//	if (breaksQ)         { deleteBreaks(infile); }
	if (transpositionsQ) { deleteDummyTranspositions(infile); }

	// Remove rhythms from notes
	Tool_shed shed;
	vector<string> argv;
	argv.push_back("shed");
	argv.push_back("-x");     // only apply to **text spines
	argv.push_back("kern");
	argv.push_back("-e");
	// convert 8th notes into diamond notes
	// remove rhythms and beams
	argv.push_back("s/8/j/g; s/[0-9LJ]//g; s/\\(/{/g; s/\\)/}/g");
	shed.process(argv);
	shed.run(infile);
}



//////////////////////////////
//
// Tool_chantize::deleteDummyTranspositions -- Somehow empty
//    transpositions that go to the same pitch can appear in the
//    MusicXML data, so remove them here.  Example:
// 		*Trd0c0
//

void Tool_chantize::deleteDummyTranspositions(HumdrumFile& infile) {
	std::vector<int> ldel;
	for (int i=0; i<infile.getLineCount(); i++) {
		if (!infile[i].hasSpines()) {
			continue;
		}
		if (!infile[i].isInterpretation()) {
			continue;
		}
		bool empty = true;
		for (int j=0; j<infile[i].getFieldCount(); j++) {
			HTp token = infile.token(i, j);
			if (*token == "*") {
				continue;
			}
			if (!token->isKern()) {
				empty = false;
				continue;
			}
			if (*token == "*Trd0c0") {
				token->setText("*");
			} else {
				empty = false;
			}
		}
		if (empty) {
			ldel.push_back(i);
		}
	}

	if (ldel.size() == 1) {
		infile.deleteLine(ldel[0]);
	} else if (ldel.size() > 1) {
		cerr << "Warning: multiple transposition lines, not deleting them" << endl;
	}

}


//////////////////////////////
//
// Tool_chantize::fixEditorialAccidentals -- checkDataLine() does
//       all of the work for this function, which only manages
//       key signature and barline processing.
//    Rules for accidentals in Tasso in Music Project:
//    (1) Only note accidentals printed in the source editions
//        are displayed as regular accidentals.  These accidentals
//        are postfixed with an "X" in the **kern data.
//    (2) Editorial accidentals are given an "i" marker but not
//        a "X" marker in the **kern data.  This editorial accidental
//        is displayed above the note.
//    This algorithm makes adjustments to the input data because
//    Sibelius will drop editorial information after the frist
//    editorial accidental on that pitch in the measure.
//    (3) If a note is the same pitch as a previous note in the
//        measure and the previous note has an editorial accidental,
//        then make the note an editorial note.  However, if the
//        accidental state of the note matches the key-signature,
//        then do not add an editorial accidental, and there will be
//        no accidental displayed on the note.  In that case, add a "y"
//        after the accidental to indicate that it is interpreted
//        and not visible in the original score.
//

void Tool_chantize::fixEditorialAccidentals(HumdrumFile& infile) {
	m_pstates.resize(infile.getMaxTrack() + 1);
	m_estates.resize(infile.getMaxTrack() + 1);
	m_kstates.resize(infile.getMaxTrack() + 1);

	for (int i=0; i<(int)m_pstates.size(); i++) {
		m_pstates[i].resize(70);
		fill(m_pstates[i].begin(), m_pstates[i].end(), 0);
		m_kstates[i].resize(70);
		fill(m_kstates[i].begin(), m_kstates[i].end(), 0);
		m_estates[i].resize(70);
		fill(m_estates[i].begin(), m_estates[i].end(), false);
	}

	for (int i=0; i<infile.getLineCount(); i++) {
		if (infile[i].isInterpretation()) {
			updateKeySignatures(infile, i);
			continue;
		} else if (infile[i].isBarline()) {
			clearStates();
			continue;
		} else if (infile[i].isData()) {
			checkDataLine(infile, i);
		}
	}
}



//////////////////////////////
//
// Tool_chantize::fixInstrumentAbbreviations --
//

void Tool_chantize::fixInstrumentAbbreviations(HumdrumFile& infile) {
	int iline = -1;
	int aline = -1;

	std::vector<HTp> kerns = infile.getKernSpineStartList();
	if (kerns.empty()) {
		return;
	}

	HTp cur = kerns[0];
	while (cur) {
		if (cur->isData()) {
			break;
		}
		if (cur->compare(0, 3, "*I\"") == 0) {
			iline = cur->getLineIndex();
		} else if (cur->compare(0, 3, "*I'") == 0) {
			aline = cur->getLineIndex();
		}
		cur = cur->getNextToken();
	}

	if (iline < 0) {
		// no names to create abbreviations for
		return;
	}
	if (aline < 0) {
		// not creating a new abbreviation for now
		// (could add later).
		return;
	}
	if (infile[iline].getFieldCount() != infile[aline].getFieldCount()) {
		// no spine splitting between the two lines.
		return;
	}
	// Maybe also require them to be adjacent to each other.
	HumRegex hre;
	for (int j=0; j<(int)infile[iline].getFieldCount(); j++) {
		if (!infile.token(iline, j)->isKern()) {
			continue;
		}
		if (!hre.search(*infile.token(iline, j), "([A-Za-z][A-Za-z .0-9]+)")) {
			continue;
		}
		string name = hre.getMatch(1);
		string abbr = "*I'";
		if (name == "Basso Continuo") {
			abbr += "BC";
		} else if (name == "Basso continuo") {
			abbr += "BC";
		} else if (name == "basso continuo") {
			abbr += "BC";
		} else {
			abbr += toupper(name[0]);
		}
		// check for numbers after the end of the name and add to abbreviation
		infile.token(aline, j)->setText(abbr);
	}
}



//////////////////////////////
//
// Tool_chantize::deleteBreaks --
//

void Tool_chantize::deleteBreaks(HumdrumFile& infile) {
	HumRegex hre;
	for (int i=infile.getLineCount()-1; i>= 0; i--) {
		if (!infile[i].isGlobalComment()) {
			continue;
		}
		if (hre.search(*infile.token(i, 0), "linebreak\\s*:\\s*original")) {
			infile.deleteLine(i);
		}
		else if (hre.search(*infile.token(i, 0), "pagebreak\\s*:\\s*original")) {
			infile.deleteLine(i);
		}
	}
}



////////////////////////////////
//
// Tool_chantize::addBibliographicRecords --
//
// !!!folio:
// !!!system:
// !!!SMS:
//
// At end:
// !!!SMS-url:
// !!!AGN: Chant
// !!!ENC: [Encoder's name]
// !!!END: [Encoding date]
// !!!EEV: $DATE
//

void Tool_chantize::addBibliographicRecords(HumdrumFile& infile) {
	std::vector<HLp> refinfo = infile.getReferenceRecords();
	std::map<string, HLp> refs;
	for (int i=0; i<(int)refinfo.size(); i++) {
		string key = refinfo[i]->getReferenceKey();
		refs[key] = refinfo[i];
	}

	// header records

	if (refs.find("system") == refs.end()) {
		infile.insertLine(0, "!!!system:");
	}

	if (refs.find("folio") == refs.end()) {
		infile.insertLine(0, "!!!folio:");
	}

	if (refs.find("SMS") == refs.end()) {
		infile.insertLine(0, "!!!SMS:");
	}

	if (refs.find("OTL") == refs.end()) {
		if (infile.token(0, 0)->find("!!!OTL") != std::string::npos) {
			// do nothing
		} else {
			infile.insertLine(0, "!!!OTL:");
		}
	}

	// trailer records

	if (refs.find("SMS-url") == refs.end()) {
		infile.appendLine("!!!SMS-url:");
	}

	if (refs.find("AGN") == refs.end()) {
		infile.appendLine("!!!AGN: Chant");
	}

	for (int i=0; i<infile.getLineCount(); i++) {
		if (!infile[i].isReference()) {
			continue;
		}
		if (infile.token(i, 0)->find("!!!RDF**kern:") == std::string::npos) {
			continue;
		}
	}

	if (refs.find("ENC") == refs.end()) {
		infile.appendLine("!!!ENC: [Encoder's name]");
	}
	if (refs.find("END") == refs.end()) {
		string date = getDate();
		string line = "!!!EED: " + date;
		infile.appendLine(line);
	}
	if (refs.find("EEV") == refs.end()) {
		string date = getDate();
		string line = "!!!EEV: " + date;
		infile.appendLine(line);
	}

}



////////////////////////////////
//
// Tool_chantize::checkDataLine --
//

void Tool_chantize::checkDataLine(HumdrumFile& infile, int lineindex) {
	HumdrumLine& line = infile[lineindex];

	HumRegex hre;
	HTp token;
	bool haseditQ;
	int base7;
	int accid;
	int track;
	bool removeQ;
	for (int i=0; i<line.getFieldCount(); i++) {
		token = line.token(i);
		track = token->getTrack();
		if (!token->isKern()) {
			continue;
		}
		if (token->isNull()) {
			continue;
		}
		if (token->isRest()) {
			continue;
		}
		if (token->isSecondaryTiedNote()) {
			continue;
		}

		base7 = Convert::kernToBase7(token);
		accid = Convert::kernToAccidentalCount(token);
		haseditQ = false;
		removeQ = false;

		// Hard-wired to "i" as editorial accidental marker
		if (token->find("ni") != string::npos) {
			haseditQ = true;
		} else if (token->find("-i") != string::npos) {
			haseditQ = true;
		} else if (token->find("#i") != string::npos) {
			haseditQ = true;
		} else if (token->find("nXi") != string::npos) {
			haseditQ = true;
			removeQ = true;
		} else if (token->find("-Xi") != string::npos) {
			haseditQ = true;
			removeQ = true;
		} else if (token->find("#Xi") != string::npos) {
			haseditQ = true;
			removeQ = true;
		}

		if (removeQ) {
			string temp = *token;
			hre.replaceDestructive(temp, "", "X");
			token->setText(temp);
		}

		bool explicitQ = false;
		if (token->find("#X") != string::npos) {
			explicitQ = true;
		} else if (token->find("-X") != string::npos) {
			explicitQ = true;
		} else if (token->find("nX") != string::npos) {
			explicitQ = true;
		} else if (token->find("n") != string::npos) {
			// add an explicit accidental marker
			explicitQ = true;
			string text = *token;
			hre.replaceDestructive(text, "nX", "n");
			token->setText(text);
		}

		if (haseditQ) {
			// Store new editorial pitch state.
			m_estates.at(track).at(base7) = true;
			m_pstates.at(track).at(base7) = accid;
			continue;
		}

		if (explicitQ) {
			// No need to make editorial since it is visible.
			m_estates.at(track).at(base7) = false;
			m_pstates.at(track).at(base7) = accid;
			continue;
		}

		if (accid == m_kstates.at(track).at(base7)) {
			// 	!m_estates.at(track).at(base7)) {
			// add !m_estates.at(track).at(base) as a condition if
			// you want editorial accidentals to be added to return the
			// note to the accidental in the key.
			//
			// The accidental matches the key-signature state,
			// so it should not be made editorial eventhough
			// it is not visible.
			m_pstates.at(track).at(base7) = accid;

			// Add a "y" marker of there is an interpreted accidental
			// state (flat or sharp) that is part of the key signature.
			int hasaccid = false;
			if (token->find("#") != std::string::npos) {
				hasaccid = true;
			} else if (token->find("-") != std::string::npos) {
				hasaccid = true;
			}
			int hashide = false;
			if (token->find("-y") != std::string::npos) {
				hashide = true;
			}
			else if (token->find("#y") != std::string::npos) {
				hashide = true;
			}
			if (hasaccid && !hashide) {
				string text = *token;
				hre.replaceDestructive(text, "#y", "#");
				hre.replaceDestructive(text, "-y", "-");
				token->setText(text);
			}

			continue;
		}

		// At this point the previous note with this pitch class
		// had an editorial accidental, and this note also has the
		// same accidental, or there was a previous visual accidental
		// outside of the key signature that will cause this note to have
		// an editorial accidental mark applied (Sibelius will drop
		// secondary editorial accidentals in a measure when exporting,
		// MusicXML, which is why this function is needed).

		m_estates[track][base7] = true;
		m_pstates[track][base7] = accid;

		string text = token->getText();
		string output = "";
		bool foundQ = false;
		for (int j=0; j<(int)text.size(); j++) {
			if (text[j] == 'n') {
				output += "ni";
				foundQ = true;
			} else if (text[j] == '#') {
				output += "#i";
				foundQ = true;
			} else if (text[j] == '-') {
				output += "-i";
				foundQ = true;
			} else {
				output += text[j];
			}
		}

		if (foundQ) {
			token->setText(output);
			continue;
		}

		// The note is natural, but has no natural sign.
		// add the natural sign and editorial mark.
		for (int j=(int)output.size()-1; j>=0; j--) {
			if ((tolower(output[j]) >= 'a') && (tolower(output[j]) <= 'g')) {
				output.insert(j+1, "ni");
				break;
			}
		}
		token->setText(output);
	}
}



////////////////////////////////
//
// Tool_chantize::updateKeySignatures -- Fill in the accidental
//    states for each diatonic pitch.
//

void Tool_chantize::updateKeySignatures(HumdrumFile& infile, int lineindex) {
	HumdrumLine& line = infile[lineindex];
	int track;
	for (int i=0; i<line.getFieldCount(); i++) {
		if (!line.token(i)->isKeySignature()) {
			continue;
		}
		HTp token = line.token(i);
		track = token->getTrack();
		string text = token->getText();
		fill(m_kstates[track].begin(), m_kstates[track].end(), 0);
		for (int j=3; j<(int)text.size()-1; j++) {
			if (text[j] == ']') {
				break;
			}
			switch (text[j]) {
				case 'a': case 'A':
					switch (text[j+1]) {
						case '#': m_kstates[track][5] = +1;
						break;
						case '-': m_kstates[track][5] = -1;
						break;
					}
					break;

				case 'b': case 'B':
					switch (text[j+1]) {
						case '#': m_kstates[track][6] = +1;
						break;
						case '-': m_kstates[track][6] = -1;
						break;
					}
					break;

				case 'c': case 'C':
					switch (text[j+1]) {
						case '#': m_kstates[track][0] = +1;
						break;
						case '-': m_kstates[track][0] = -1;
						break;
					}
					break;

				case 'd': case 'D':
					switch (text[j+1]) {
						case '#': m_kstates[track][1] = +1;
						break;
						case '-': m_kstates[track][1] = -1;
						break;
					}
					break;

				case 'e': case 'E':
					switch (text[j+1]) {
						case '#': m_kstates[track][2] = +1;
						break;
						case '-': m_kstates[track][2] = -1;
						break;
					}
					break;

				case 'f': case 'F':
					switch (text[j+1]) {
						case '#': m_kstates[track][3] = +1;
						break;
						case '-': m_kstates[track][3] = -1;
						break;
					}
					break;

				case 'g': case 'G':
					switch (text[j+1]) {
						case '#': m_kstates[track][4] = +1;
						break;
						case '-': m_kstates[track][4] = -1;
						break;
					}
					break;
			}
			for (int j=0; j<7; j++) {
				if (m_kstates[track][j] == 0) {
					continue;
				}
				for (int k=1; k<10; k++) {
					m_kstates[track][j+k*7] = m_kstates[track][j];
				}
			}
		}
	}

	// initialize m_pstates with contents of m_kstates
	for (int i=0; i<(int)m_kstates.size(); i++) {
		for (int j=0; j<(int)m_kstates[i].size(); j++) {
			m_pstates[i][j] = m_kstates[i][j];
		}
	}

}



////////////////////////////////
//
// Tool_chantize::clearStates --
//

void Tool_chantize::clearStates(void) {
	for (int i=0; i<(int)m_pstates.size(); i++) {
		fill(m_pstates[i].begin(), m_pstates[i].end(), 0);
	}
	for (int i=0; i<(int)m_estates.size(); i++) {
		fill(m_estates[i].begin(), m_estates[i].end(), false);
	}
}


//////////////////////////////
//
// Tool_chantize::getDate --
//

string Tool_chantize::getDate(void) {
	time_t t = time(NULL);
	tm* timeptr = localtime(&t);
	stringstream ss;
	int year = timeptr->tm_year + 1900;
	int month = timeptr->tm_mon + 1;
	int day = timeptr->tm_mday;
	ss << year << "/";
	if (month < 10) {
		ss << "0";
	}
	ss << month << "/";
	if (day < 10) {
		ss << "0";
	}
	ss << day;
	return ss.str();
}


//////////////////////////////
//
// Tool_chantize::getTerminalRestStates -- return a vector of each line,
//    setting true if the line is a data line containing a rest at the end of the
//    music after the last note.
//

vector<bool> Tool_chantize::getTerminalRestStates(HumdrumFile& infile) {
	vector<bool> output(infile.getLineCount(), false);

	for (int i=infile.getLineCount() - 1; i>=0; i--) {
		if (!infile[i].isData()) {
			continue;
		}
		HTp token = infile.token(i, 0);
		if (token->isRest()) {
			output[i] = true;
		} else {
			break;
		}
	}

	return output;

}



//////////////////////////////
//
// Tool_chantize::hasDiamondNotes -- True if the duration of any line is less than a quarter note.
//

bool Tool_chantize::hasDiamondNotes(HumdrumFile& infile) {
	for (int i=0; i<infile.getLineCount(); i++) {
		HumNum duration = infile[i].getDuration();
		if (duration < 1) {
			return true;
		}
	}
	return false;
}



#define UNDEFINED_INTERVAL (-1000)
#define REST_INTERVAL (-1001)

/////////////////////////////////
//
// Tool_chint::Tool_chint -- Set the recognized options for the tool.
//

Tool_chint::Tool_chint(void) {
	define("b|bottom-part=i:1",      "bottom part number to colorize, 1-indexed");
	define("c|chromatic-coloring=b", "chromatic coloring");
	define("d|diatonic=b",           "diatonic intervals");
	define("m|middle=b",             "show diatonic intervals between staves");
	define("n|negative=b",           "show diatonic intervals for cross voices");
	define("t|top-part=i:2",         "top part number to colorize, 1-indexed");
	define("i|intervals=b",          "display interval names");
	define("B|no-color-bottom=b",    "do not color top analysis staff");
	define("T|no-color-top=b",       "do not color bottom analysis staff");
	define("8|preserve-octave=b",    "do not collapse P8 to P1");
}



/////////////////////////////////
//
// Tool_chint::run -- Do the main work of the tool.
//

bool Tool_chint::run(HumdrumFileSet& infiles) {
	bool status = true;
	for (int i=0; i<infiles.getCount(); i++) {
		status &= run(infiles[i]);
	}
	return status;
}


bool Tool_chint::run(const string& indata, ostream& out) {
	HumdrumFile infile(indata);
	bool status = run(infile);
	if (hasAnyText()) {
		getAllText(out);
	} else {
		out << infile;
	}
	return status;
}


bool Tool_chint::run(HumdrumFile& infile, ostream& out) {
	bool status = run(infile);
	if (hasAnyText()) {
		getAllText(out);
	} else {
		out << infile;
	}
	return status;
}


bool Tool_chint::run(HumdrumFile& infile) {
	processFile(infile);
	return true;
}



//////////////////////////////
//
// Tool_chint::initialize --  Initializations that only have to be done once
//    for all HumdrumFile segments.
//

void Tool_chint::initialize(void) {
	m_intervalQ = getBoolean("intervals");
	if (getBoolean("chromatic-coloring")) {
		chromaticColoring();
	} else {
		dissonanceColoring();
	}

	if (getBoolean("diatonic")) {
		fillIntervalNamesDiatonic();
	} else {
		fillIntervalNames();
	}

	m_noColorBotQ = getBoolean("no-color-bottom");
	m_noColorTopQ = getBoolean("no-color-top");
	m_negativeQ   = getBoolean("negative");
	m_octaveQ     = getBoolean("preserve-octave");
	m_middleQ     = getBoolean("middle");
}



//////////////////////////////
//
// Tool_chint::processFile --
//

void Tool_chint::processFile(HumdrumFile& infile) {
	initialize();

	vector<HTp> kernSpines;
	kernSpines = infile.getKernSpineStartList();

	int maxIndex = (int)kernSpines.size() - 1;
	int topIndex = -1;
	int botIndex = -1;

	if (getString("top-part") == "$") {
		topIndex = maxIndex;
	} else {
		topIndex = getInteger("top-part") - 1;
	}

	if (getString("bottom-part") == "$") {
		botIndex = maxIndex;
	} else {
		botIndex = getInteger("bottom-part") - 1;
	}

	if (topIndex < botIndex) {
		int temp = botIndex;
		botIndex = topIndex;
		topIndex = temp;
	}

	if ((topIndex < 0) | (botIndex < 0)) {
		return;
	}

	if ((topIndex > maxIndex) | (botIndex > maxIndex)) {
		return;
	}

	if (topIndex == botIndex) {
		return;
	}

	vector<int> topInterval;
	vector<int> botInterval;

	getPartIntervals(botInterval, topInterval, kernSpines[botIndex], kernSpines[topIndex], infile);

	int botTrack = kernSpines[botIndex]->getTrack();
	int topTrack = kernSpines[topIndex]->getTrack();
	insertPartColors(infile, botInterval, topInterval, botTrack, topTrack);
}



//////////////////////////////
//
// Tool_chint::getPartIntervals -- Assuming no *x
//

void Tool_chint::getPartIntervals(vector<int>& botInterval, vector<int>& topInterval,
		HTp botSpine, HTp topSpine, HumdrumFile& infile) {

	m_botPitch.resize(infile.getLineCount());
	m_topPitch.resize(infile.getLineCount());

	std::fill(m_botPitch.begin(), m_botPitch.end(), ".");
	std::fill(m_topPitch.begin(), m_topPitch.end(), ".");

	botInterval.resize(infile.getLineCount());
	topInterval.resize(infile.getLineCount());

	std::fill(botInterval.begin(), botInterval.end(), UNDEFINED_INTERVAL);
	std::fill(topInterval.begin(), topInterval.end(), UNDEFINED_INTERVAL);

	HumRegex hre;
	HTp current = botSpine->getNextToken();
	int ttrack = topSpine->getTrack();
	while (current) {
		if (!current->isData()) {
			current = current->getNextToken();
			continue;
		}

		HTp bot = current;
		int line = current->getLineIndex();
		HTp top = NULL;
		int botField = current->getFieldIndex();
		for (int i=botField+1; i<infile[line].getFieldCount(); i++) {
			HTp token = infile.token(line, i);
			int track = token->getTrack();
			if (track != ttrack) {
				continue;
			}
			top = token;
			break;
		}

		if (!top) {
			cerr << "TOP TOKEN IS NULL. BOTTOM TOKEN: " << bot << endl;
			return;
		}

		HTp botResolve = bot;
		HTp topResolve = top;
		if (botResolve->isNull()) {
			botResolve = botResolve->resolveNull();
		}
		if (topResolve->isNull()) {
			topResolve = topResolve->resolveNull();
		}
		if ((!botResolve) || botResolve->isNull()) {
			current = current->getNextToken();
			continue;
		}
		if ((!topResolve) || topResolve->isNull()) {
			current = current->getNextToken();
			continue;
		}

		if (botResolve->isRest()) {
			topInterval[line] = REST_INTERVAL;
			botInterval[line] = REST_INTERVAL;
			current = current->getNextToken();
			continue;
		}

		if (topResolve->isRest()) {
			botInterval[line] = REST_INTERVAL;
			topInterval[line] = REST_INTERVAL;
			current = current->getNextToken();
			continue;
		}

		m_botPitch[line] = hre.replaceDestructive(*botResolve, "", " .*");
		m_topPitch[line] = hre.replaceDestructive(*topResolve, "", " .*");

		int botB40 = abs(botResolve->getBase40Pitch());
		int topB40 = abs(topResolve->getBase40Pitch());

		int difference = topB40 - botB40;
		int negative = 1;
		if (difference < 0) {
			difference = -difference;
			negative = -1;
		}
		int difference2 = difference % 40;
		if (m_octaveQ && (difference2 == 0)) {
			if (difference != 0) {
				difference2 = 40;
			}
		}

		botInterval.at(line) = negative * difference2;
		topInterval.at(line) = negative * difference2;

		current = current->getNextToken();
	}
}



//////////////////////////////
//
// insertPartColors --
//

void Tool_chint::insertPartColors(HumdrumFile& infile, vector<int>& botInterval,
		vector<int>& topInterval, int botTrack, int topTrack) {

	for (int i=0; i<infile.getLineCount(); i++) {
		if (!infile[i].hasSpines()) {
			m_humdrum_text << infile[i] << endl;
			continue;
		}
		vector<string> output;
		output.clear();
		int fields = infile[i].getFieldCount();
		bool botUsed = false;
		bool topUsed = false;
		bool intervalUsed = false;
		for (int j = fields - 1; j >= 0; j--) {
			HTp token = infile.token(i, j);
			int track = token->getTrack();
			if (!botUsed && (track == botTrack)) {
				botUsed = true;
				if (!m_noColorBotQ) {
					if (token->isNull()) {
						output.push_back(".");
					} else {
						output.push_back(getColorToken(botInterval[i], infile, i, token));
					}
				}
			}
			if (!topUsed && (track == topTrack)) {
				topUsed = true;
				if (!m_noColorTopQ) {
					output.push_back(getColorToken(topInterval[i], infile, i, token));
				}
				if ((!intervalUsed) && m_intervalQ) {
					intervalUsed = true;
					if (token->isNull()) {
						output.push_back(".");
					} else {
						output.push_back(getIntervalToken(topInterval[i], infile, i));
					}
				}
			}
			output.push_back(*token);
		}
		for (int i=(int)output.size() - 1; i>=0; i--) {
			m_humdrum_text << output[i];
			if (i > 0) {
				m_humdrum_text << "\t";
			}
		}
		m_humdrum_text << endl;
	}
}



//////////////////////////////
//
// Tool_chint::getColorToken --
//

string Tool_chint::getColorToken(int interval, HumdrumFile& infile, int line, HTp token) {
	int absinterval = interval;
	if (absinterval > UNDEFINED_INTERVAL) {
		absinterval = abs(absinterval);
	}
	if (infile[line].isData()) {
		if (interval == REST_INTERVAL) {
			return "black";
		}
		if (interval == UNDEFINED_INTERVAL) {
			return ".";
		}
		if (token->isNull()) {
			return ".";
		}
		if (absinterval > 40) {
			return ".";
		} else {
			return m_color.at(absinterval);
		}
	}
	if (infile[line].isLocalComment()) {
		return "!";
	}
	HTp firstToken = infile.token(line, 0);
	if (firstToken->compare(0, 2, "**") == 0) {
		return "**color";
	}
	if (*firstToken == "*-") {
		return "*-";
	}
	if (firstToken->compare(0, 1, "*") == 0) {
		return "*";
	}
	if (firstToken->isBarline()) {
		return *firstToken;
	}
	return "ERROR";
}



//////////////////////////////
//
// Tool_chint::getIntervalToken --
//

string Tool_chint::getIntervalToken(int interval, HumdrumFile& infile, int line) {
	int absinterval = interval;
	if (interval > UNDEFINED_INTERVAL) {
		absinterval = abs(absinterval);
	}
	HumRegex hre;
	if (infile[line].isData()) {
		if (absinterval < 0) {
			return ".";
		}
		bool botTieQ = false;
		bool topTieQ = false;
		if (hre.search(m_botPitch[line], "[\\]_]")) {
			botTieQ = true;
		}
		if (hre.search(m_topPitch[line], "[\\]_]")) {
			topTieQ = true;
		}
		if (botTieQ && topTieQ) {
			return ".";
		}

		if (interval > 40) {
			// Above an octave is not handled.
			return ".";
		} else {
			if (m_negativeQ) {
				if (interval < 0) {
					return "-" + m_intervals.at(absinterval);
				} else {
					return m_intervals.at(absinterval);
				}
			} else {
				return m_intervals.at(absinterval);
			}
		}
	}
	if (infile[line].isLocalComment()) {
		return "!";
	}
	HTp firstToken = infile.token(line, 0);
	if (firstToken->compare(0, 2, "**") == 0) {
		if (!m_middleQ) {
			#ifdef __EMSCRIPTEN__
			return "**adata=hint";
			#else
			return "**hint";
			#endif
		} else {
			#ifdef __EMSCRIPTEN__
			return "**bdata=hint";
			#else
			return "**hint";
			#endif
		}
	}
	if (*firstToken == "*-") {
		return "*-";
	}
	if (firstToken->compare(0, 1, "*") == 0) {
		return "*";
	}
	if (firstToken->isBarline()) {
		return *firstToken;
	}
	return "ERROR";
}



//////////////////////////////
//
// Tool_chint::ChromaticColoring --
//

void Tool_chint::chromaticColoring(void) {

	m_color.resize(41);

	m_color[0]   = "gray";          // P1
	m_color[1]   = "lightgray";     // A1
	m_color[2]   = "gainsboro";     // AA1
	m_color[3]   = "white";         // unused
	m_color[4]   = "navy";          // d2
	m_color[5]   = "darkblue";      // m2
	m_color[6]   = "mediumblue";    // M2
	m_color[7]   = "royalblue";     // A2
	m_color[8]   = "steelblue";     // AA2
	m_color[9]   = "white";         // unused
	m_color[10]  = "darkgreen";     // dd3
	m_color[11]  = "green";         // d3
	m_color[12]  = "limegreen";     // m3
	m_color[13]  = "lawngreen";     // M3
	m_color[14]  = "lightgreen";    // A3
	m_color[15]  = "brown";         // dd4
	m_color[16]  = "darkorange";    // d4
	m_color[17]  = "orange";        // P4
	m_color[18]  = "gold";          // A4
	m_color[19]  = "yellow";        // AA4
	m_color[20]  = "white";         // unused
	m_color[21]  = "mistyrose";     // dd5
	m_color[22]  = "hotpink";       // d5
	m_color[23]  = "red";           // P5
	m_color[24]  = "crimson";       // A5
	m_color[25]  = "firebrick";     // AA5
	m_color[26]  = "white";         // unused
	m_color[27]  = "darkturquoise"; // d6
	m_color[28]  = "turquoise";     // m6
	m_color[29]  = "deepskyblue";   // M6
	m_color[30]  = "lightblue";     // A6
	m_color[31]  = "powderblue";    // AA6
	m_color[32]  = "white";         // unused
	m_color[33]  = "indigo";        // d7
	m_color[34]  = "purple";        // m7
	m_color[35]  = "darkmagenta";   // M7
	m_color[36]  = "mediumorchid";  // A7
	m_color[37]  = "mediumpurple";  // AA7
	m_color[38]  = "slategray";     // dd1
	m_color[39]  = "dimgray";       // d1
	m_color[40]  = "gray";          // P1

}



//////////////////////////////
//
// Tool_chint::dissonanceColoring --
//    gray          = P1 (unison)
//    indigo        = P5 (perfect intervals)
//    darkviolet    = P4 (other perfect intervals)
//    dodgerblue    = M3, M6 (major 3, 6)
//    darkturquoise = m3, m6 (minor 3, 6)
//    limegreen     = M2, m6 (weak dissonance)
//    limegreen     = M2, m6 (weak dissonance)
//    orange        = m2, M6 (strong dissonance)
//    gold          = A4 (strong dissonance)
//    crimson       = d5 (strong dissonance)
//    red           = other
//

void Tool_chint::dissonanceColoring(void) {

	m_color.resize(41);

	m_color[0]   = "gray";          // P1
	m_color[1]   = "red";           // A1
	m_color[2]   = "red";           // AA1
	m_color[3]   = "white";         // unused
	m_color[4]   = "red";           // dd2
	m_color[5]   = "orange";        // m2
	m_color[6]   = "lawngreen";     // M2
	m_color[7]   = "royalblue";     // A2
	m_color[8]   = "steelblue";     // AA2
	m_color[9]   = "white";         // AA1
	m_color[10]  = "red";           // d3
	m_color[11]  = "darkturquoise"; // m3
	m_color[12]  = "dodgerblue";    // M3
	m_color[13]  = "red";           // A3
	m_color[14]  = "red";           // AA3
	m_color[15]  = "red";           // dd4
	m_color[16]  = "red";           // d4
	m_color[17]  = "blueviolet";    // P4
	m_color[18]  = "gold";          // A4
	m_color[19]  = "red";           // AA4
	m_color[20]  = "white";         // unused
	m_color[21]  = "red";           // dd5
	m_color[22]  = "hotpink";       // d5
	m_color[23]  = "purple";        // P5
	m_color[24]  = "red";           // A5
	m_color[25]  = "red";           // AA5
	m_color[26]  = "white";         // unused
	m_color[27]  = "red";           // d6
	m_color[28]  = "darkturquoise"; // m6
	m_color[29]  = "dodgerblue";    // M6
	m_color[30]  = "red";           // A6
	m_color[31]  = "red";           // AA6
	m_color[32]  = "white";         // unused
	m_color[33]  = "chocolate";     // d7
	m_color[34]  = "limegreen";     // m7
	m_color[35]  = "darkorange";    // M7
	m_color[36]  = "red";           // A7
	m_color[37]  = "red";           // AA7
	m_color[38]  = "red";           // dd1
	m_color[39]  = "red";           // d1
	m_color[40]  = "gray";          // P1

}



//////////////////////////////
//
// Tool_chint::fillIntervalNames --
//

void Tool_chint::fillIntervalNames(void) {

	m_intervals.resize(41);

	m_intervals[0]   = "P1";      // C
	m_intervals[1]   = "A1";
	m_intervals[2]   = "AA1";
	m_intervals[3]   = "ERROR";
	m_intervals[4]   = "d2";
	m_intervals[5]   = "m2";
	m_intervals[6]   = "M2";      // D
	m_intervals[7]   = "A2";
	m_intervals[8]   = "AA2";
	m_intervals[9]   = "ERROR";
	m_intervals[10]  = "d3";
	m_intervals[11]  = "m3";
	m_intervals[12]  = "M3";      // E
	m_intervals[13]  = "A3";
	m_intervals[14]  = "AA3";
	m_intervals[15]  = "dd4";
	m_intervals[16]  = "d4";
	m_intervals[17]  = "P4";      // F
	m_intervals[18]  = "A4";
	m_intervals[19]  = "AA4";
	m_intervals[20]  = "ERROR";
	m_intervals[21]  = "dd5";
	m_intervals[22]  = "d5";
	m_intervals[23]  = "P5";      // G
	m_intervals[24]  = "A5";
	m_intervals[25]  = "AA5";
	m_intervals[26]  = "ERROR";
	m_intervals[27]  = "d6";
	m_intervals[28]  = "m6";
	m_intervals[29]  = "M6";      // A
	m_intervals[30]  = "A6";
	m_intervals[31]  = "AA6";
	m_intervals[32]  = "ERROR";
	m_intervals[33]  = "d7";
	m_intervals[34]  = "m7";
	m_intervals[35]  = "M7";      // B
	m_intervals[36]  = "A7";
	m_intervals[37]  = "AA7";
	m_intervals[38]  = "dd1";
	m_intervals[39]  = "d1";
	m_intervals[40]  = "P8";

}



//////////////////////////////
//
// Tool_chint::fillIntervalNamesDiatonic --
//

void Tool_chint::fillIntervalNamesDiatonic(void) {

	m_intervals.resize(41);

	m_intervals[0]   = "1";        // C
	m_intervals[1]   = "A1";
	m_intervals[2]   = "AA1";
	m_intervals[3]   = "ERROR";
	m_intervals[4]   = "d2";
	m_intervals[5]   = "2";
	m_intervals[6]   = "2";        // D
	m_intervals[7]   = "A2";
	m_intervals[8]   = "AA2";
	m_intervals[9]   = "ERROR";
	m_intervals[10]  = "d3";
	m_intervals[11]  = "3";
	m_intervals[12]  = "3";        // E
	m_intervals[13]  = "A3";
	m_intervals[14]  = "AA3";
	m_intervals[15]  = "dd4";
	m_intervals[16]  = "d4";
	m_intervals[17]  = "4";        // F
	m_intervals[18]  = "A4";
	m_intervals[19]  = "AA4";
	m_intervals[20]  = "ERROR";
	m_intervals[21]  = "dd5";
	m_intervals[22]  = "d5";
	m_intervals[23]  = "5";        // G
	m_intervals[24]  = "A5";
	m_intervals[25]  = "AA5";
	m_intervals[26]  = "ERROR";
	m_intervals[27]  = "d6";
	m_intervals[28]  = "6";
	m_intervals[29]  = "6";        // A
	m_intervals[30]  = "A6";
	m_intervals[31]  = "AA6";
	m_intervals[32]  = "ERROR";
	m_intervals[33]  = "d7";
	m_intervals[34]  = "7";
	m_intervals[35]  = "7";        // B
	m_intervals[36]  = "A7";
	m_intervals[37]  = "AA7";
	m_intervals[38]  = "dd1";
	m_intervals[39]  = "d1";
	m_intervals[40]  = "8";

}





/////////////////////////////////
//
// Tool_chooser::Tool_chooser -- Set the recognized options for the tool.
//

Tool_chooser::Tool_chooser(void) {
	define("s|n|segment=s",  "segments to pass to output");
}



/////////////////////////////////
//
// Tool_chooser::run -- Do the main work of the tool.
//

bool Tool_chooser::run(HumdrumFileSet& infiles) {
	processFiles(infiles);
	return true;
}


bool Tool_chooser::run(const string& indata) {
	initialize();
	HumdrumFileStream instream;
	instream.loadString(indata);
	HumdrumFileSet infiles;
	infiles.read(instream);
	processFiles(infiles);
	return true;
}



bool Tool_chooser::run(HumdrumFileStream& instream) {
	initialize();
	HumdrumFileSet infiles;
	infiles.read(instream);
	processFiles(infiles);
	return true;
}



//////////////////////////////
//
// Tool_chooser::initialize --
//

void Tool_chooser::initialize(void) {
	// do nothing
}



//////////////////////////////
//
// Tool_chooser::processFiles --
//

void Tool_chooser::processFiles(HumdrumFileSet& infiles) {
	int maximum = infiles.getCount();
	string expansion = getString("segment");
	vector<int> outlist = Convert::extractIntegerList(expansion, maximum);

	for (int i=0; i<(int)outlist.size(); i++) {
		m_humdrum_text << infiles[outlist[i]-1];
	}
}




/////////////////////////////////
//
// Tool_chord::Tool_chord -- Set the recognized options for the tool.
//

Tool_chord::Tool_chord(void) {
	// add options here
	define("u|sort-upwards=b",    "sort notes by lowest first in chord");
	define("d|sort-downwards=b",  "sort notes by highest first in chord");
	define("t|top-note=b",        "extract top note of chords");
	define("b|bottom-note=b",     "extract bottom note of chords");
	define("f|first-note=b",      "extract first note of chords");
	define("p|primary=b",         "place prefix/suffix/beams on first note in chord");
	define("l|last-note=b",       "extract last note of chords");
	define("s|spine=i:-1",        "spine to process (indexed from 1)");
	define("m|minimize=b",        "minimize chords");
	define("M|maximize=b",        "maximize chords");
}



/////////////////////////////////
//
// Tool_chord::run -- Do the main work of the tool.
//

bool Tool_chord::run(HumdrumFileSet& infiles) {
	bool status = true;
	for (int i=0; i<infiles.getCount(); i++) {
		status &= run(infiles[i]);
	}
	return status;
}


bool Tool_chord::run(const string& indata, ostream& out) {
	HumdrumFile infile(indata);
	bool status = run(infile);
	if (hasAnyText()) {
		getAllText(out);
	} else {
		out << infile;
	}
	return status;
}


bool Tool_chord::run(HumdrumFile& infile, ostream& out) {
	bool status = run(infile);
	if (hasAnyText()) {
		getAllText(out);
	} else {
		out << infile;
	}
	return status;
}


bool Tool_chord::run(HumdrumFile& infile) {
	initialize();
	processFile(infile, m_direction);
	infile.createLinesFromTokens();
	m_humdrum_text << infile;
	return true;
}



//////////////////////////////
//
// Tool_chord::initialize --
//

void Tool_chord::initialize(void) {
	m_direction = 1;
	if (getBoolean("sort-upwards")) {
		m_direction = -1;
	}
	if (getBoolean("sort-downwards")) {
		m_direction = +1;
	}
	m_spine = getInteger("spine");
	m_primary = getBoolean("primary");
	if (getBoolean("minimize")) {
		m_primary = true;
	}
}



//////////////////////////////
//
// Tool_chord::processFile --
//     direction:  1 = highest first
//     direction: -1 = lowest first
//

void Tool_chord::processFile(HumdrumFile& infile, int direction) {
	if (!(getBoolean("top-note") || getBoolean("bottom-note") ||
			getBoolean("sort-upwards") || getBoolean("sort-downwards") ||
			getBoolean("minimize") || getBoolean("maximize") ||
			getBoolean("first-note") || getBoolean("last-note"))) {
		// nothing to do
		return;
	}

	HumRegex hre;
	for (int i=0; i<infile.getStrandCount(); i++) {
		HTp stok = infile.getStrandStart(i);
		int track = stok->getTrack();
		if ((m_spine > 0) && (track != m_spine)) {
			continue;
		}
		if (!stok->isKern()) {
			continue;
		}
		HTp etok = infile.getStrandEnd(i);
		HTp tok = stok;
		while (tok && (tok != etok)) {
			if (!tok->isData()) {
				tok = tok->getNextToken();
				continue;
			}
			if (tok->isNull()) {
				tok = tok->getNextToken();
				continue;
			}
			if (!tok->isChord()) {
				tok = tok->getNextToken();
				continue;
			}
			processChord(tok, direction);
			tok = tok->getNextToken();
		}
	}
}



//////////////////////////////
//
// Tool_chord::processChord --
//

void Tool_chord::processChord(HTp tok, int direction) {
	vector<string> notes;
	int count = (int)tok->getSubtokenCount();
	for (int i=0; i<count; i++) {
		notes.emplace_back(tok->getSubtoken(i));
	}

	if (notes.size() <= 1) {
		// nothing to do
		return;
	}

	bool ismin = false;
	HumRegex hre;
	if (!hre.search(notes[1], "[0-9]")) {
		ismin = true;
	}

	vector<pair<int, int>> pitches(count);
	for (int i=0; i<(int)pitches.size(); i++) {
		pitches[i].first = Convert::kernToBase40(notes[i]);
		pitches[i].second = i;
	}

	if (ismin && (getBoolean("top-note") || getBoolean("bottom-note") || getBoolean("last-note"))) {
		maximizeChordPitches(notes, pitches);
	}

	if (getBoolean("top-note")) {
		direction = -1;
	}
	if (getBoolean("bottom-note")) {
		direction = -1;
	}
	if (getBoolean("first-note")) {
		direction = 0;
	}
	if (getBoolean("last-note")) {
		direction = 0;
	}

	if (direction > 0) {
		sort(pitches.begin(), pitches.end(),
			[](const pair<int, int>& a, const pair<int, int>& b) -> bool {
				return a.first > b.first;
			});
	} else if (direction < 0) {
		sort(pitches.begin(), pitches.end(),
			[](const pair<int, int>& a, const pair<int, int>& b) -> bool {
				return a.first < b.first;
			});
	}

	bool same = true;
	for (int i=1; i<(int)pitches.size(); i++) {
		if (pitches[i].second != pitches[i-1].second + 1) {
			same = false;
			break;
		}
	}
	if ((getBoolean("sort-upwards") || getBoolean("sort-downwards")) && same) {
		return;
	}

	string prefix;
	string suffix;

	if (hre.search(notes[0], "(&*\\([<>]?)")) {
		prefix = hre.getMatch(1);
		hre.replaceDestructive(notes[0], "", "(&*\\([<>]?)");
	}

	string beam;
	int beamindex = -1;

	for (int i=0; i<(int)notes.size(); i++) {
		if (hre.search(notes[i], "([LJkK]+[<>]?)")) {
			beamindex = i;
			beam = hre.getMatch(1);
			hre.replaceDestructive(notes[i], "", "([LJkK]+[<>]?)");
		}
	}

	if (getBoolean("maximize") && (beamindex >= 0)) {
		beamindex = (int)notes.size() - 1;
	}

	if (hre.search(notes.back(), "(&*\\)[<>]?)")) {
		suffix += hre.getMatch(1);
		hre.replaceDestructive(notes.back(), "", "(&*\\)[<>]?)");
	} else if (ismin || getBoolean("maximize")) {
		if (hre.search(notes[0], "(&*\\)[<>]?)")) {
			suffix += hre.getMatch(1);
			hre.replaceDestructive(notes[0], "", "(&*\\)[<>]?)");
		}
	}

	if (getBoolean("minimize")) {
		minimizeChordPitches(notes, pitches);
	} else if (getBoolean("maximize")) {
		maximizeChordPitches(notes, pitches);
	}

	string output = prefix;
	if (getBoolean("top-note")) {
		output += notes[pitches.back().second];
		output += beam;
	} else if (getBoolean("bottom-note")) {
		output += notes[pitches[0].second];
		output += beam;
	} else if (getBoolean("first-note")) {
		output += notes[pitches[0].second];
		output += beam;
	} else if (getBoolean("last-note")) {
		output += notes[pitches.back().second];
		output += beam;
	} else {
		for (int i=0; i<(int)pitches.size(); i++) {
			output += notes[pitches[i].second];
			if (m_primary && (i==0)) {
				output += beam;
				output += suffix;
			} else if ((!m_primary) && (beamindex == i)) {
				output += beam;
			}
			if (i < (int)pitches.size() - 1) {
				output += " ";
			}
		}
	}

	if (!m_primary) {
		output += suffix;
	}
	tok->setText(output);
}


//////////////////////////////
//
// Tool_chord::minimizeChordPitches -- remove durations, articulations
//   and stem directions for secondary notes in chord.
//		pitches[x].first = base40 pitch.
//		pitches[x].second = index for pitch in notes vector.
//

void Tool_chord::minimizeChordPitches(vector<string>& notes,
		vector<pair<int,int>>& pitches) {
	if (notes.empty()) {
		return;
	}
	HumRegex hre;
	string firstdur;
	string firstartic;
	string firststem;
	if (hre.search(notes[pitches[0].second], "([0-9%.]+)")) {
		firstdur = hre.getMatch(1);
	}
	if (hre.search(notes[pitches[0].second], "([\\\\/])")) {
		firststem = hre.getMatch(1);
		hre.replaceDestructive(firststem, "\\\\", "\\", "g");
	}

	for (int i=1; i<(int)pitches.size(); i++) {
		hre.replaceDestructive(notes[pitches[i].second], "", firstdur);
		hre.replaceDestructive(notes[pitches[i].second], "", firststem);

		// articulations:
		hre.replaceDestructive(notes[pitches[i].second], "", "'[<>]?");
		hre.replaceDestructive(notes[pitches[i].second], "", "~[<>]?");
		hre.replaceDestructive(notes[pitches[i].second], "", "\\^[<>]?");
	}
}



//////////////////////////////
//
// Tool_chord::maximizeChordPitches -- add durations, articulations
//   and stem directions to all secondary notes in chord.
//

void Tool_chord::maximizeChordPitches(vector<string>& notes,
		vector<pair<int,int>>& pitches) {
	if (notes.empty()) {
		return;
	}
	HumRegex hre;

	string prefix;
	string suffix;

	if (hre.search(notes[0], "(.*?)(?=[A-Ga-g])")) {
		prefix = hre.getMatch(1);
	}
	if (hre.search(notes[0], "([A-Ga-g]+[#n-]*[<>]?)(.*)")) {
		suffix = hre.getMatch(2);
	}

	for (int i=1; i<(int)notes.size(); i++) {
		notes[i] = prefix + notes[i] + suffix;
	}
}




#define EMPTY_ID ""
#define REST 0
#define RESTINT -1000000
#define RESTSTRING "R"
#define INTERVAL_HARMONIC 1
#define INTERVAL_MELODIC  2
#define MARKNOTES  1


/////////////////////////////////
//
// Tool_cint::Tool_cint -- Set the recognized options for the tool.
//

Tool_cint::Tool_cint(void) {
	define("base-40|base40|b40|40=b",             "display pitches/intervals in base-40");
	define("base-12|base12|b12|12=b",             "display pitches/intervals in base-12");
	define("base-7|base7|b7|7|diatonic=b",        "display pitches/intervals in base-7");
	define("g|grid|pitch|pitches=b",              "display pitch grid used to calculate modules");
	define("r|rhythm=b",                          "display rhythmic positions of notes");
	define("f|filename=b",                        "display filenames with --count");
	define("raw=b",                               "display only modules without formatting");
	define("raw2=b",                              "display only modules formatted for Vishesh");
	define("c|uncross=b",                         "uncross crossed voices when creating modules");
	define("k|koption=s:",                        "select only two spines to analyze");
	define("C|comma=b",                           "separate intervals by comma rather than space");
	define("retro|retrospective=b",               "retrospective module display in the score");
	define("suspension|suspensions=b",            "mark suspensions");
	define("rows|row=b",                          "display lattices in row form");
	define("dur|duration=b",                      "display durations appended to harmonic interval note attacks");
	define("id=b",                                "ids are echoed in module data");
	define("L|interleaved-lattice=b",             "display interleaved lattices");
	define("q|harmonic-parentheses=b",            "put square brackets around harmonic intervals");
	define("h|harmonic-marker=b",                 "put h character after harmonic intervals");
	define("m|melodic-marker=b",                  "put m character after melodic intervals");
	define("y|melodic-parentheses=b",             "put curly braces around melodic intervals");
	define("p|parentheses=b",                     "put parentheses around modules intervals");
	define("l|lattice=b",                         "calculate lattice");
	define("loc|location=b",                      "displayLocation");
	define("s|sustain=b",                         "display sustain/attack states of notes");
	define("o|octave=b",                          "reduce compound intervals to within an octave");
	define("H|no-harmonic=b",                     "don't display harmonic intervals");
	define("M|no-melodic=b",                      "don't display melodic intervals");
	define("t|top=b",                             "display top melodic interval of modules");
	define("T|top-only=b",                        "display only top melodic interval of modules");
	define("U|no-melodic-unisons=b",              "no melodic perfect unisons");
	define("attacks|attack=b",                    "start/stop module chains on pairs of note attacks");
	define("z|zero=b",                            "display diatonic intervals with 0 offset");
	define("N|note-marker=s:@",                   "pass-through note marking character");
	define("x|xoption=b",                         "display attack/sustain information on harmonic intervals only");
	define("n|chain=i:1",                         "number of sequential modules");
	define("R|no-rest|no-rests|norest|norests=b", "number of sequential modules");
	define("O|octave-all=b",                      "transpose all harmonic intervals to within an octave");
	define("chromatic=b",                         "display intervals as diatonic intervals with chromatic alterations");
	define("color=s:red",                         "color of marked notes");
	define("search=s:",                           "search string");
	define("mark=b",                              "mark matches notes from searches in data");
	define("count=b",                             "count matched modules from search query");
	define("debug=b",                             "determine bad input line number");
	define("author=b",                            "author of the program");
	define("version=b",                           "complation info");
	define("example=b",                           "example usages");
	define("help=b",                              "short description");
}



/////////////////////////////////
//
// Tool_cint::run -- Primary interfaces to the tool.
//

bool Tool_cint::run(HumdrumFileSet& infiles) {
	bool status = true;
	for (int i=0; i<infiles.getCount(); i++) {
		status &= run(infiles[i]);
	}
	return status;
}


bool Tool_cint::run(const string& indata, ostream& out) {
	HumdrumFile infile(indata);
	bool status = run(infile);
	if (hasAnyText()) {
		getAllText(out);
	} else {
		out << infile;
	}
	return status;
}


bool Tool_cint::run(HumdrumFile& infile, ostream& out) {
	bool status = run(infile);

	if (hasAnyText()) {
		getAllText(out);
	} else {
		out << infile;
	}

	return status;
}

//
// In-place processing of file:
//

bool Tool_cint::run(HumdrumFile& infile) {
	processFile(infile);


	if (hasAnyText()) {
		// getAllText(cout);
	} else {
		// Re-load the text for each line from their tokens.
		cout << infile;
	}

	return true;
}


///////////////////////////////////////////////////////////////////////////
//
// NoteNode class functions:
//

NoteNode::NoteNode(const NoteNode& anode) {
	b40        = anode.b40;
	line       = anode.line;
	spine      = anode.spine;
	measure    = anode.measure;
	serial     = anode.serial;
	mark       = anode.mark;
	notemarker = anode.notemarker;
	beatsize   = anode.beatsize;
	duration   = 0;
	protected_id = anode.protected_id;
}


NoteNode& NoteNode::operator=(NoteNode& anode) {
	if (this == &anode) {
		return *this;
	}
	b40        = anode.b40;
	line       = anode.line;
	spine      = anode.spine;
	measure    = anode.measure;
	serial     = anode.serial;
	mark       = anode.mark;
	notemarker = anode.notemarker;
	beatsize   = anode.beatsize;
	duration   = anode.duration;
	protected_id = anode.protected_id;
	return *this;
}


void NoteNode::setId(const string& anid) {
	protected_id = anid;
}


NoteNode::~NoteNode(void) {
	// do nothing
}


void NoteNode::clear(void) {
	mark = measure = serial = b40 = 0;
	beatsize = 0.0;
	notemarker = "";
	line = spine = -1;
	protected_id = "";
}


string NoteNode::getId(void) {
	return protected_id;
}


///////////////////////////////////////////////////////////////////////////
//
// Tool_cint functions:
//


//////////////////////////////
//
// Tool_cint::processFile -- Do requested analysis on a given file.
//

int Tool_cint::processFile(HumdrumFile& infile) {

   initialize();

	vector<vector<NoteNode> > notes;
	vector<string> names;
	vector<int>    ktracks;
	vector<HTp>    kstarts;
	vector<int>    reverselookup;

	infile.getSpineStartList(kstarts, "**kern");
	ktracks.resize(kstarts.size());
	for (int i=0; i<(int)kstarts.size(); i++) {
		ktracks[i] = kstarts[i]->getTrack();
	}

	if (koptionQ) {
		adjustKTracks(ktracks, getString("koption"));
	}
	notes.resize(ktracks.size());
	reverselookup.resize(infile.getTrackCount()+1);
	fill(reverselookup.begin(), reverselookup.end(), -1);

	vector<vector<string> > retrospective;
	if (retroQ) {
		initializeRetrospective(retrospective, infile, ktracks);
	}

//	if (locationQ || rhythmQ || durationQ) {
//		infile.analyzeRhythm();
//	}

	int i;
	for (i=0; i<(int)ktracks.size(); i++) {
		reverselookup[ktracks[i]] = i;
		// notes[i].reserve(infile.getLineCount());
		notes[i].resize(0);
	}

	getNames(names, reverselookup, infile);
	HumRegex pre;
	extractNoteArray(notes, infile, ktracks, reverselookup);

	if (pitchesQ) {
		printPitchGrid(notes, infile);
		exit(0);
	}

	int count = 0;
	if (latticeQ) {
		printLattice(notes, infile, ktracks, reverselookup, Chaincount);
	} else if (interleavedQ) {
		printLatticeInterleaved(notes, infile, ktracks, reverselookup,
			Chaincount);
	} else if (suspensionsQ) {
		count = printCombinationsSuspensions(notes, infile, ktracks,
				reverselookup, Chaincount, retrospective);
	} else {
		count = printCombinations(notes, infile, ktracks, reverselookup,
				Chaincount, retrospective, SearchString);
	}


	// handle search results here
	if (markQ) {
		if (count > 0) {
			addMarksToInputData(infile, notes, ktracks, reverselookup);
		}
		infile.createLinesFromTokens();
		m_humdrum_text << infile;
		m_humdrum_text << "!!!RDF**kern: " << NoteMarker << " = matched note, color=\"" << MarkColor << "\"\n";
	}

	if (debugQ) {
		int j;
		for (i=0; i<(int)retrospective[0].size(); i++) {
			for (j=0; j<(int)retrospective.size(); j++) {
				m_humdrum_text << retrospective[j][i];
				if (j < (int)retrospective.size() - 1) {
					m_humdrum_text << "\t";
				}
			}
			m_humdrum_text << "\n";
		}
	}

	return count;
}



//////////////////////////////
//
// Tool_cint::adjustKTracks -- Select only two spines to do analysis on.
//

void Tool_cint::adjustKTracks(vector<int>& ktracks, const string& koption) {
	HumRegex pre;
	if (!pre.search(koption, "(\\$|\\$?\\d*)[^\\$\\d]+(\\$|\\$?\\d*)")) {
		return;
	}
	int number1 = 0;
	int number2 = 0;
	HumRegex pre2;

	if (pre2.search(pre.getMatch(1), "\\d+")) {
		number1 = pre.getMatchInt(1);
		if (pre.getMatch(1).find('$') != string::npos) {
			number1 = (int)ktracks.size() - number1;
		}
	} else {
		number1 = (int)ktracks.size();
	}

	if (pre2.search(pre.getMatch(2), "\\d+")) {
		number2 = pre.getMatchInt(2);
		if (pre.getMatch(2).find('$') != string::npos) {
			number2 = (int)ktracks.size() - number2;
		}
	} else {
		number2 = (int)ktracks.size();
	}

	number1--;
	number2--;

	int track1 = ktracks[number1];
	int track2 = ktracks[number2];

	ktracks.resize(2);
	ktracks[0] = track1;
	ktracks[1] = track2;
}



//////////////////////////////
//
// Tool_cint::initializeRetrospective --
//

void Tool_cint::initializeRetrospective(vector<vector<string> >& retrospective,
		HumdrumFile& infile, vector<int>& ktracks) {

	int columns = (int)ktracks.size();
	columns = columns * (columns + 1) / 2; // triangle number of analysis cols.

	retrospective.resize(columns);
	int i, j;

	for (i=0; i<(int)retrospective.size(); i++) {
		retrospective[i].resize(infile.getLineCount());
	}

	string token;
	for (i=0; i<infile.getLineCount(); i++) {
		if (infile[i].isLocalComment()) {
			token = "!";
		} else if (infile[i].isGlobalComment()) {
			token = "!";
		} else if (infile[i].isReference()) {
			token = "!!";
		} else if (infile[i].isBarline()) {
			token = *infile.token(i, 0);
		} else if (infile[i].isData()) {
			token = ".";
		} else if (infile[i].isInterpretation()) {
			token = "*";
			if (infile[i].isExclusiveInterpretation()) {
				token = "**cint";
			}
		}

		for (j=0; j<(int)retrospective.size(); j++) {
			retrospective[j][i] = token;
		}
	}

	if (debugQ) {
		for (i=0; i<(int)retrospective[0].size(); i++) {
			for (j=0; j<(int)retrospective.size(); j++) {
				m_humdrum_text << retrospective[j][i];
				if (j < (int)retrospective.size() - 1) {
					m_humdrum_text << "\t";
				}
			}
			m_humdrum_text << "\n";
		}
	}
}



//////////////////////////////
//
// Tool_cint::printCombinationsSuspensions --
//
// have to do something with sbuffer
//

int  Tool_cint::printCombinationsSuspensions(vector<vector<NoteNode> >& notes,
		HumdrumFile& infile, vector<int>& ktracks, vector<int>& reverselookup,
		int n, vector<vector<string> >& retrospective) {

	string sbuffer;

	int oldcountQ = countQ;
	countQ = 1;             // mostly used to suppress intermediate output

	int countsum = 0;

	searchQ    = 1;               // turn on searching

	// Suspensions with length-2 modules
	n = 2;                        // -n 2
	xoptionQ   = 1;               // -x
	sbuffer = "";

	sbuffer += "^7xs 1 6sx -2 8xx$";
	sbuffer += "|^2sx -2 3xs 2 1xx$";
	sbuffer += "|^7xs 1 6sx 2 6xx$";
	sbuffer += "|^11xs 1 10sx -5 15xx$";
	sbuffer += "|^4xs 1 3sx -5 8xx$";
	sbuffer += "|^2sx -2 3xs 2 3xx$";

	// "9xs 1 8sx -2 10xx" archetype: Jos1405 m10 A&B
	sbuffer += "|^9xs 1 8sx -2 10xx$";
	// "4xs 1 3sx 5xx" archetype: Jos1713 m87-88 A&B
	sbuffer += "|^4xs 1 3sx -2 5xx$";
	// "11xs 1 10sx 4 8xx" archetype: Jos1402 m23-24 S&B
	sbuffer += "|^11xs 1 10sx 4 8xx$";

	countsum += printCombinations(notes, infile, ktracks, reverselookup, n,
			retrospective, sbuffer);

	// Suspensions with length-3 modules /////////////////////////////////
	n = 3;                        // -n 3
	xoptionQ   = 1;               // -x
	sbuffer = "";

	// "7xs 1 6sx 1 5sx 1 6sx" archetype: Jos2721 m27-78 S&T
	sbuffer += "^7xs 1 6sx 1 5sx 1 6sx$";
	// "7xs 1 6sx 1 6sx -2 8xx" archetype: Rue2018 m38-88 S&T
	sbuffer += "|^7xs 1 6sx 1 6sx -2 8xx$";
	// "11xs 1 10sx 1 10sx -5 15xx" archetype: Rue2018 m38-88 S&B
	sbuffer += "|^11xs 1 10sx 1 10sx -5 15xx$";

	countsum += printCombinations(notes, infile, ktracks, reverselookup, n,
							retrospective, sbuffer);

	// Suspensions with length-5 modules /////////////////////////////////
	n = 5;                        // -n 2
	xoptionQ   = 1;               // -x
	sbuffer = "";
	// "8xs 1 7sx 1 7sx 1 6sx 1 6sx 1 5sx -1 8xx" archetype: Duf3015a m94 S&T
	sbuffer += "^8xs 1 7sx 1 7sx 1 6sx 1 5sx -2 8xx$";

	countsum += printCombinations(notes, infile, ktracks, reverselookup, n,
							retrospective, sbuffer);

	// Suspensions with rests modules

	// done with multiple searches.  Mark the notes in the score if required.

	countQ = oldcountQ;

	return countsum;
}



//////////////////////////////
//
// Tool_cint::printCombinations --
//

int  Tool_cint::printCombinations(vector<vector<NoteNode> >& notes,
		HumdrumFile& infile, vector<int>& ktracks, vector<int>& reverselookup,
		int n, vector<vector<string> >& retrospective, const string& searchstring) {
	int i;
	int currentindex = 0;
	int matchcount   = 0;
	for (i=0; i<infile.getLineCount(); i++) {
		if (!infile[i].hasSpines()) {
			// print all lines here which do not contain spine
			// information.
			if (!(raw2Q || rawQ || markQ || retroQ || countQ)) {
				m_humdrum_text << infile[i] << "\n";
			}
			continue;
		}

		// At this point there are only four types of lines:
		//    (1) data lines
		//    (2) interpretation lines (lines starting with *)
		//    (3) local comment lines (lines starting with single !)
		//    (4) barlines

		if (infile[i].isInterpretation()) {
			string pattern = "*";
			if (infile.token(i, 0)->compare(0, 2, "**") == 0) {
				pattern = "**cint";
			} else if (*infile.token(i, 0) == "*-") {
				pattern = "*-";
			} else if (infile.token(i, 0)->compare(0, 2, "*>") == 0) {
				pattern = *infile.token(i, 0);
			}
			printAsCombination(infile, i, ktracks, reverselookup, pattern);
		} else if (infile[i].isLocalComment()) {
			printAsCombination(infile, i, ktracks, reverselookup, "!");
		} else if (infile[i].isBarline()) {
			printAsCombination(infile, i, ktracks, reverselookup, *infile.token(i, 0));
		} else {
			// print combination data
			currentindex = printModuleCombinations(infile, i, ktracks,
				reverselookup, n, currentindex, notes, matchcount, retrospective, searchstring);
		}
		if (!(raw2Q || rawQ || markQ || retroQ || countQ)) {
				m_humdrum_text << "\n";
		}
	}

	return matchcount;
}



//////////////////////////////
//
// Tool_cint::printModuleCombinations --
//

int Tool_cint::printModuleCombinations(HumdrumFile& infile, int line, vector<int>& ktracks,
		vector<int>& reverselookup, int n, int currentindex,
		vector<vector<NoteNode> >& notes, int& matchcount,
		vector<vector<string> >& retrospective, const string& searchstring) {

	int fileline = line;
	string filename = infile.getFilename();

	while ((currentindex < (int)notes[0].size())
			&& (fileline > notes[0][currentindex].line)) {
		currentindex++;
	}
	if (currentindex >= (int)notes[0].size()) {
		if (!(raw2Q || rawQ || markQ || retroQ || countQ)) {
			m_humdrum_text << ".";
			printAsCombination(infile, line, ktracks, reverselookup, ".");
		}
		return currentindex;
	}
	if (notes[0][currentindex].line != fileline) {
		// This section occurs when two voices are both sustaining
		// at the start of the module.  Print a "." to indicate that
		// the counterpoint module is continuing from a previous line.
		printAsCombination(infile, line, ktracks, reverselookup, ".");
		return currentindex;
	}

	// found the index into notes which matches to the current fileline.
	if (currentindex + n >= (int)notes[0].size()) {
		// asking for chain longer than rest of available data.
		printAsCombination(infile, line, ktracks, reverselookup, ".");
		return currentindex;
	}

	// printAsCombination(infile, line, ktracks, reverselookup, ".");
	// return currentindex;

	int tracknext;
	int track;
	int j, jj;
	int count = 0;
	for (j=0; j<infile[line].getFieldCount(); j++) {
		if (!infile.token(line, j)->isKern()) {
			if (!(raw2Q || rawQ || markQ || retroQ || countQ)) {
				m_humdrum_text << infile.token(line, j);
				if (j < infile[line].getFieldCount() - 1) {
					m_humdrum_text << "\t";
				}
			}
			continue;
		}
		track = infile.token(line, j)->getTrack();
		if (j < infile[line].getFieldCount() - 1) {
			tracknext = infile.token(line, j+1)->getTrack();
		} else {
			tracknext = -23525;
		}
		if (track == tracknext) {
			if (!(raw2Q || rawQ || markQ || retroQ || countQ)) {
				m_humdrum_text << infile.token(line, j);
				if (j < infile[line].getFieldCount() - 1) {
					m_humdrum_text << "\t";
				}
			}
			continue;
		}

		// print the **kern spine, then check to see if there
		// is some **cint data to print
		if (!(raw2Q || rawQ || markQ || retroQ || countQ)) {
				m_humdrum_text << infile.token(line, j);
		}
		if ((track != ktracks.back()) && (reverselookup[track] >= 0)) {
			count = (int)ktracks.size() - reverselookup[track] - 1;
			for (jj = 0; jj<count; jj++) {
				if (!(raw2Q || rawQ || markQ || retroQ || countQ)) {
					m_humdrum_text << "\t";
				}
				int part1 = reverselookup[track];
				int part2 = part1+1+jj;
				// m_humdrum_text << part1 << "," << part2;
				matchcount += printCombinationModulePrepare(m_humdrum_text, filename,
						notes, n, currentindex, part1, part2, retrospective, infile,
						searchstring);
			}
		}

		if (!(raw2Q || rawQ || markQ || retroQ || countQ)) {
			if (j < infile[line].getFieldCount() - 1) {
				m_humdrum_text << "\t";
			}
		}
	}

	return currentindex;
}



//////////////////////////////
//
// Tool_cint::printCombinationModulePrepare --
//

int Tool_cint::printCombinationModulePrepare(ostream& out, const string& filename,
		 vector<vector<NoteNode> >& notes, int n, int startline, int part1,
		 int part2, vector<vector<string> >& retrospective,
		HumdrumFile& infile, const string& searchstring) {
	int count = 0;
	HumRegex hre;
	stringstream tempstream;
	int match;
	string notemarker;
// ggg
	int status = printCombinationModule(tempstream, filename, notes,
			n, startline, part1, part2, retrospective, notemarker);
	if (status) {
		if (raw2Q || rawQ) {
			tempstream << "\n";
		}
		if ((!NoteMarker.empty()) && (notemarker == NoteMarker)) {
			out << NoteMarker;
		}
		if (searchQ) {
			// Check to see if the extracted module matches to the
			// search query.
			match = hre.search(tempstream.str(), searchstring);
			if (match) {
				count++;
				if (locationQ) {
					int line = notes[0][startline].line;
					double loc = infile[line].getDurationFromStart().getFloat() /
							infile[infile.getLineCount()-1].getDurationFromStart().getFloat();
					loc = int(100.0 * loc + 0.5)/100.0;
					m_humdrum_text << "!!LOCATION:"
							<< "\t"  << loc
							<< "\tm" << getMeasure(infile, line)
							<< "\tv" << ((int)notes.size() - part2)
							<< ":v"  << ((int)notes.size() - part1)
							<< "\t"  << infile.getFilename()
							<< endl;
				}
				if (raw2Q || rawQ) {
					out << tempstream.str();
					// newline already added somewhere previously.
					// m_humdrum_text << "\n";
				} else {
					// mark notes of the matched module(s) in the note array
					// for later marking in input score.
					status = printCombinationModule(tempstream, filename,
						 notes, n, startline, part1, part2, retrospective,
						 notemarker, MARKNOTES);
					if (status && (raw2Q || rawQ)) {
						tempstream << "\n";
					}
				}

			}
		} else {
			if (retroQ) {
				int column = getTriangleIndex((int)notes.size(), part1, part2);
				retrospective[column][status] = tempstream.str();
			} else {
				out << tempstream.str();
			}
		}
	} else {
		if (!(raw2Q || rawQ || markQ || retroQ || countQ || searchQ)) {
			out << ".";
		}
	}

	return count;
}



//////////////////////////////
//
// Tool_cint::getMeasure -- return the last measure number of the given line index.
//

int Tool_cint::getMeasure(HumdrumFile& infile, int line) {
	int measure = 0;
	HumRegex hre;

	for (int i=line; i>=0; i--) {
		if (!infile[i].isBarline()) {
			continue;
		}
		if (hre.search(*infile.token(i, 0), "=(\\d+)")) {
			measure = hre.getMatchInt(1);
			return measure;
		}
	}
	return 0;
}



//////////////////////////////
//
// Tool_cint::getTriangleIndex --
//

int Tool_cint::getTriangleIndex(int number, int num1, int num2) {
	// int triangle = number * (number + 1) / 2;
	// intermediate code, not active yet
	return 0;
}



//////////////////////////////
//
// Tool_cint::addMarksToInputData -- mark notes in the score which matched
//     to the search query.
//

void Tool_cint::addMarksToInputData(HumdrumFile& infile,
		vector<vector<NoteNode> >& notes, vector<int>& ktracks,
		vector<int>& reverselookup) {

	// first carry all marks from sustained portions of notes onto their
	// note attacks.
	int i, j;

	int mark = 0;
	int track = 0;
	int markpitch = -1;

	for (i=0; i<(int)notes.size(); i++) {
		mark = 0;
		for (j=(int)notes[i].size()-1; j>=0; j--) {
			if (mark && (-markpitch == notes[i][j].b40)) {
				// In the sustain region between a note
				// attack and the marked sustain. Mark the
				// sustained region as well (don't know
				// if this behavior might change in the
				// future.
				notes[i][j].mark = mark;
				continue;
			}
			if (mark && (markpitch == notes[i][j].b40)) {
				// At the start of a notes which was marked.
				// Mark the attack since only note attacks
				// will be marked in the score
				notes[i][j].mark = mark;
				mark = 0;
				continue;
			}
			if (mark && (markpitch != notes[i][j].b40)) {
				// something strange happened.  Probably
				// an open tie which was not started
				// properly, so just clear mark.
				mark = 0;
			}
			if (notes[i][j].mark) {
				mark = 1;
				markpitch = abs(notes[i][j].b40);
			} else {
				mark = 0;
			}

		}
	}

	// a forward loop here into notes array to continue
	// marks to end of sutained region of marked notes
	for (i=0; i<(int)notes.size(); i++)  {
		for (j=0; j<(int)notes[i].size(); j++) {
			if (notes[i][j].mark) {
				markpitch = -abs(notes[i][j].b40);
				continue;
			} else if (notes[i][j].b40 == markpitch) {
				notes[i][j].mark = 1;
				continue;
			} else {
				markpitch = -1;
			}
		}
	}

	// print mark information:
	// for (j=0; j<(int)notes[0].size(); j++) {
	//    for (i=0; i<(int)notes.size(); i++) {
	//       m_humdrum_text << notes[i][j].b40;
	//       if (notes[i][j].mark) {
	//          m_humdrum_text << "m";
	//       }
	//       m_humdrum_text << " ";
	//    }
	//    m_humdrum_text << "\n";
	// }


	// now go through the input score placing user-markers onto notes
	// which were marked in the note array.
	int currentindex = 0;
	for (i=0; i<infile.getLineCount(); i++) {
		if (!infile[i].isData()) {
			continue;
		}
		while ((currentindex < (int)notes[0].size())
				&& (i > notes[0][currentindex].line)) {
			currentindex++;
		}
		if (currentindex >= (int)notes[0].size()) {
			continue;
		}
		if (notes[0][currentindex].line != i) {
			continue;
		}

		for (j=0; j<infile[i].getFieldCount(); j++) {
			if (!infile.token(i, j)->isKern()) {
				continue;
			}
			if (*infile.token(i, j) == ".") {
				// Don't mark null tokens.
				continue;
			}
			track = infile.token(i, j)->getTrack();
			if (reverselookup[track] < 0) {
				continue;
			}
			if (notes[reverselookup[track]][currentindex].mark != 0) {
				markNote(infile, i, j);
			}
		}
	}
}



//////////////////////////////
//
// Tool_cint::markNote --
//

void Tool_cint::markNote(HumdrumFile& infile, int line, int col) {
	HTp token = infile.token(line, col);
	string text = *token;
	text += NoteMarker;
	token->setText(text);
}



//////////////////////////////
//
// Tool_cint::getOctaveAdjustForCombinationModule -- Find the minim harmonic interval in
//      the module chain.  If it is greater than an octave, then move it down
//      below an octave.  If the minimum is an octave, then don't do anything.
//      Not considering crossed voices.
//

int Tool_cint::getOctaveAdjustForCombinationModule(vector<vector<NoteNode> >& notes, int n,
		int startline, int part1, int part2) {

	// if the current two notes are both sustains, then skip
	if ((notes[part1][startline].b40 <= 0) &&
		 (notes[part2][startline].b40 <= 0)) {
		return 0;
	}

	if (norestsQ) {
		if (notes[part1][startline].b40 == 0) {
			return 0;
		}
		if (notes[part2][startline].b40 == 0) {
			return 0;
		}
	}

	int i;
	int count = 0;
	int attackcount = 0;
	int hint;

	vector<int> hintlist;
	hintlist.reserve(1000);

	for (i=startline; i<(int)notes[0].size(); i++) {
		if ((notes[part1][i].b40 <= 0) && (notes[part2][i].b40 <= 0)) {
			// skip notes if both are sustained
			continue;
		}

		if (attackQ && ((notes[part1][i].b40 <= 0) ||
							 (notes[part2][i].b40 <= 0))) {
			if (attackcount == 0) {
				// not at the start of a pair of attacks.
				return 0;
			}
		}

		// consider  harmonic interval
		if ((notes[part2][i].b40 != 0) && (notes[part1][i].b40 != 0)) {
			hint = abs(notes[part2][i].b40) - abs(notes[part1][i].b40);
			if (uncrossQ && (hint < 0)) {
				hint = -hint;
			}
			hintlist.push_back(hint);
		}

		// if count matches n, then exit loop
		if ((count == n) && !attackQ) {
			break;
		}
		count++;

		if ((notes[part1][i].b40 > 0) && (notes[part2][i].b40 > 0)) {
			// keep track of double attacks
			if (attackcount >= n) {
				break;
			} else {
				attackcount++;
			}
		}

	}

	int minimum = 100000;

	for (i=0; i<(int)hintlist.size(); i++) {
		if (hintlist[i] < minimum) {
			minimum = hintlist[i];
		}
	}

	if (minimum > 1000) {
	  // no intervals found to consider
	  return 0;
	}

	if ((minimum >= 0) && (minimum <= 40)) {
		// nothing to do
		return 0;
	}

	if (minimum > 40) {
		return -(minimum/40);
	} else if (minimum < 0) {
		// don't go positive, this will invert the interval.
		return (-minimum)/40;
	}

	//int octaveadjust = -(minimum / 40);

	//if (attackQ && (attackcount == n)) {
	//   return octaveadjust;
	//} else if (count == n) {
	//   return octaveadjust;
	//} else {
	//   // did not find the required number of modules.
	//   return 0;
	//}

	return 0;
}



//////////////////////////////
//
// Tool_cint::printCombinationModule -- Similar to printLatticeModule, but harmonic
//      intervals will not be triggered by a pair of sustained notes.
//      Print a counterpoint module or module chain given the start notes
//      and pair of parts to calculate the module (chains) from.  Will not
//      print anything if the chain length is longer than the note array.
//      The n parameter will be ignored if --attacks option is used
//      (--attacks will gnereate a variable length module chain).
//

int Tool_cint::printCombinationModule(ostream& out, const string& filename,
		vector<vector<NoteNode> >& notes, int n, int startline, int part1,
		int part2, vector<vector<string> >& retrospective, string& notemarker,
		int markstate) {

	notemarker = "";

	if (norestsQ) {
		if (notes[part1][startline].b40 == 0) {
			return 0;
		}
		if (notes[part2][startline].b40 == 0) {
			return 0;
		}
	}

	stringstream idstream;

	// int crossing =  0;
	//int oldcrossing =  0;

	int octaveadjust = 0;   // used for -o option
	if (octaveQ) {
		octaveadjust = getOctaveAdjustForCombinationModule(notes, n, startline,
				part1, part2);
	}

	ostream *outp = &out;
	// if (rawQ && !searchQ) {
	//    outp = &m_humdrum_text;
	// }

	if (n + startline >= (int)notes[0].size()) { // [20150202]
		// definitely nothing to do
		return 0;
	}

	if ((int)notes.size() == 0) {
		// nothing to do
		return 0;
	}

	// if the current two notes are both sustains, then skip
	if ((notes[part1][startline].b40 <= 0) &&
		 (notes[part2][startline].b40 <= 0)) {
		return 0;
	}

	if (raw2Q) {
		// print pitch of first bottom note
		if (filenameQ) {
			(*outp) << "file_" << filename;
			(*outp) << " ";
		}

		(*outp) << "v_" << part1 << " v_" << part2 << " ";

		if (base12Q) {
			(*outp) << "base12_";
			(*outp) << Convert::base40ToMidiNoteNumber(abs(notes[part1][startline].b40));
		} else if (base40Q) {
			(*outp) << "base40_";
			(*outp) << abs(notes[part1][startline].b40);
		} else {
			(*outp) << "base7_";
			(*outp) << Convert::base40ToDiatonic(abs(notes[part1][startline].b40));
		}
		(*outp) << " ";
	}

	if (parenQ) {
		(*outp) << "(";
	}

	int i;
	int count = 0;
	int countm = 0;
	int attackcount = 0;
	int idstart = 0;

	int lastindex = -1;
	int retroline = 0;

	for (i=startline; i<(int)notes[0].size(); i++) {
		if ((notes[part1][i].b40 <= 0) && (notes[part2][i].b40 <= 0)) {
			// skip notes if both are sustained
			continue;
		}

		if (norestsQ) {
			if (notes[part1][i].b40 == 0) {
				return 0;
			}
			if (notes[part2][i].b40 == 0) {
				return 0;
			}
		}

		if (attackQ && ((notes[part1][i].b40 <= 0) ||
							 (notes[part2][i].b40 <= 0))) {
			if (attackcount == 0) {
				// not at the start of a pair of attacks.
				return 0;
			}
		}

		// print the melodic intervals (if not the first item in chain)
		if ((count > 0) && !nomelodicQ) {
			if (mparenQ) {
				(*outp) << "{";
			}

			if (nounisonsQ) {
				// suppress modules which contain melodic perfect unisons:
				if ((notes[part1][i].b40 != 0) &&
					(abs(notes[part1][i].b40) == abs(notes[part1][lastindex].b40))) {
					return 0;
				}
				if ((notes[part2][i].b40 != 0) &&
					(abs(notes[part2][i].b40) == abs(notes[part2][lastindex].b40))) {
					return 0;
				}
			}
			// bottom melodic interval:
			if (!toponlyQ) {
				printInterval((*outp), notes[part1][lastindex],
						notes[part1][i], INTERVAL_MELODIC);
				if (mmarkerQ) {
					(*outp) << "m";
				}
			}

			// print top melodic interval here if requested
			if (topQ || toponlyQ) {
				if (!toponlyQ) {
					printSpacer((*outp));
				}
				// top melodic interval:
				printInterval((*outp), notes[part2][lastindex],
						notes[part2][i], INTERVAL_MELODIC);
				if (mmarkerQ) {
					(*outp) << "m";
				}
			}

			if (mparenQ) {
				(*outp) << "}";
			}
			printSpacer((*outp));
		}

		countm++;

		// print harmonic interval
		if (!noharmonicQ) {
			if (hparenQ) {
			  (*outp) << "[";
			}
			if (markstate) {
				notes[part1][i].mark = 1;
				notes[part2][i].mark = 1;
			} else {
				// oldcrossing = crossing;
				//crossing = printInterval((*outp), notes[part1][i],
				//      notes[part2][i], INTERVAL_HARMONIC, octaveadjust);
				printInterval((*outp), notes[part1][i],
						notes[part2][i], INTERVAL_HARMONIC, octaveadjust);
			}

			if (durationQ) {
				if (notes[part1][i].isAttack()) {
					(*outp) << "D" << notes[part1][i].duration;
				}
				if (notes[part2][i].isAttack()) {
					(*outp) << "d" << notes[part1][i].duration;
				}
			}

			if (hmarkerQ) {
				(*outp) << "h";
			}
			if (hparenQ) {
			  (*outp) << "]";
			}
		}

		// prepare the ids string if requested
		if (idQ) {
		//   if (count == 0) {
				// insert both first two notes, even if sustain.
				if (idstart != 0) { idstream << ':'; }
				idstart++;
				idstream << notes[part1][i].getId() << ':'
							<< notes[part2][i].getId();
		//   } else {
		//      // only insert IDs if an attack
		//      if (notes[part1][i].b40 > 0) {
		//         if (idstart != 0) { idstream << ':'; }
		//         idstart++;
		//         idstream << notes[part1][i].getId();
		//      }
		//      if (notes[part2][i].b40 > 0) {
		//         if (idstart != 0) { idstream << ':'; }
		//         idstart++;
		//         idstream << notes[part2][i].getId();
		//      }
		//   }
		}

		// keep track of notemarker state
		if (notes[part1][i].notemarker == NoteMarker) {
			notemarker = NoteMarker;
		}
		if (notes[part2][i].notemarker == NoteMarker) {
			notemarker = NoteMarker;
		}

		// if count matches n, then exit loop
		if ((count == n) && !attackQ) {
			retroline = i;
			break;
		} else {
			if (!noharmonicQ) {
				printSpacer((*outp));
			}
		}
		lastindex = i;
		count++;

		if ((notes[part1][i].b40 > 0) && (notes[part2][i].b40 > 0)) {
			// keep track of double attacks
			if (attackcount >= n) {
				retroline = i;
				break;
			} else {
				attackcount++;
			}
		}

	}

	if (parenQ) {
		(*outp) << ")";
	}

	if (idQ && idstart) {
		idstream << ends;
		(*outp) << " ID:" << idstream.str();
	}

	if (attackQ && (attackcount == n)) {
		return retroline;
	} else if ((countm>1) && (count == n)) {
		return retroline;
	} else if (n == 0) {
		return retroline;
	} else {
		// did not print the required number of modules.
		return 0;
	}

	return 0;
}



//////////////////////////////
//
// Tool_cint::printAsCombination --
//

void Tool_cint::printAsCombination(HumdrumFile& infile, int line, vector<int>& ktracks,
	 vector<int>& reverselookup, const string& interstring) {

	if (raw2Q || rawQ || markQ || retroQ || countQ) {
		return;
	}

	vector<int> done(ktracks.size(), 0);
	int track;
	int tracknext;
	int count;

	int j, jj;
	for (j=0; j<infile[line].getFieldCount(); j++) {
		if (!infile.token(line, j)->isKern()) {
			m_humdrum_text << infile.token(line, j);
			if (j < infile[line].getFieldCount() - 1) {
				m_humdrum_text << "\t";
			}
			continue;
		}
		track = infile.token(line, j)->getTrack();
		if (j < infile[line].getFieldCount() - 1) {
			tracknext = infile.token(line, j+1)->getTrack();
		} else {
			tracknext = -23525;
		}
		if (track == tracknext) {
			m_humdrum_text << infile.token(line, j);
			if (j < infile[line].getFieldCount() - 1) {
				m_humdrum_text << "\t";
			}
			continue;
		}

		// print the **kern spine, then check to see if there
		// is some **cint data to print
		// ggg
		m_humdrum_text << infile.token(line, j);

		if (reverselookup[track] >= 0) {
			count = (int)ktracks.size() - reverselookup[track] - 1;
			for (jj=0; jj<count; jj++) {
				m_humdrum_text << "\t" << interstring;
			}
		}

		if (j < infile[line].getFieldCount() - 1) {
			m_humdrum_text << "\t";
		}
	}
}



//////////////////////////////
//
// Tool_cint::printLatticeInterleaved --
//

void Tool_cint::printLatticeInterleaved(vector<vector<NoteNode> >& notes,
		HumdrumFile& infile, vector<int>& ktracks, vector<int>& reverselookup,
		int n) {
	int currentindex = 0;
	int i;
	for (i=0; i<infile.getLineCount(); i++) {
		if (!infile[i].hasSpines()) {
			// print all lines here which do not contain spine
			// information.
			if (!(rawQ || raw2Q)) {
				m_humdrum_text << infile[i] << "\n";
			}
			continue;
		}

		// At this point there are only four types of lines:
		//    (1) data lines
		//    (2) interpretation lines (lines starting with *)
		//    (3) local comment lines (lines starting with single !)
		//    (4) barlines

		if (infile[i].isInterpretation()) {
			string pattern = "*";
			if (infile.token(i, 0)->compare(0, 2, "**") == 0) {
				pattern = "**cint";
			} else if (infile.token(i, 0)->compare("*-") == 0) {
				pattern = "*-";
			} else if (infile.token(i, 0)->compare(0, 2, "*>") == 0) {
				pattern = *infile.token(i, 0);
			}
			printInterleaved(infile, i, ktracks, reverselookup, pattern);
		} else if (infile[i].isLocalComment()) {
			printInterleaved(infile, i, ktracks, reverselookup, "!");
		} else if (infile[i].isBarline()) {
			printInterleaved(infile, i, ktracks, reverselookup, *infile.token(i, 0));
		} else {
			// print interleaved data
			currentindex = printInterleavedLattice(infile, i, ktracks,
				reverselookup, n, currentindex, notes);
		}
		if (!(rawQ || raw2Q)) {
			m_humdrum_text << "\n";
		}
	}
}



//////////////////////////////
//
// Tool_cint::printInterleavedLattice --
//

int Tool_cint::printInterleavedLattice(HumdrumFile& infile, int line, vector<int>& ktracks,
		vector<int>& reverselookup, int n, int currentindex,
		vector<vector<NoteNode> >& notes) {

	int fileline = line;

	while ((currentindex < (int)notes[0].size())
			&& (fileline > notes[0][currentindex].line)) {
		currentindex++;
	}
	if (currentindex >= (int)notes[0].size()) {
		if (!(rawQ || raw2Q)) {
			m_humdrum_text << ".";
			printInterleaved(infile, line, ktracks, reverselookup, ".");
		}
		return currentindex;
	}
	if (notes[0][currentindex].line != fileline) {
		// should never get here.
		printInterleaved(infile, line, ktracks, reverselookup, "?");
		return currentindex;
	}

	// found the index into notes which matches to the current fileline.
	if (currentindex + n >= (int)notes[0].size()) {
		// asking for chain longer than rest of available data.
		printInterleaved(infile, line, ktracks, reverselookup, ".");
		return currentindex;
	}

	int tracknext;
	int track;
	int j;
	for (j=0; j<infile[line].getFieldCount(); j++) {
		if (!infile.token(line, j)->isKern()) {
			if (!(rawQ || raw2Q)) {
				m_humdrum_text << infile.token(line, j);
				if (j < infile[line].getFieldCount() - 1) {
					m_humdrum_text << "\t";
				}
			}
			continue;
		}
		track = infile.token(line, j)->getTrack();
		if (j < infile[line].getFieldCount() - 1) {
			tracknext = infile.token(line, j+1)->getTrack();
		} else {
			tracknext = -23525;
		}
		if (track == tracknext) {
			if (!(rawQ || raw2Q)) {
				m_humdrum_text << infile.token(line, j);
				if (j < infile[line].getFieldCount() - 1) {
					m_humdrum_text << "\t";
				}
			}
			continue;
		}

		// print the **kern spine, then check to see if there
		// is some **cint data to print
		if (!(rawQ || raw2Q)) {
			m_humdrum_text << infile.token(line, j);
		}
		if ((track != ktracks.back()) && (reverselookup[track] >= 0)) {
			if (!(rawQ || raw2Q)) {
				m_humdrum_text << "\t";
			}
			int part1 = reverselookup[track];
			int part2 = part1+1;
			// m_humdrum_text << part1 << "," << part2;
			printLatticeModule(m_humdrum_text, notes, n, currentindex, part1, part2);
		}

		if (!(rawQ || raw2Q)) {
			if (j < infile[line].getFieldCount() - 1) {
				m_humdrum_text << "\t";
			}
		}
	}

	return currentindex;
}



//////////////////////////////
//
// Tool_cint::printInterleaved --
//

void Tool_cint::printInterleaved(HumdrumFile& infile, int line, vector<int>& ktracks,
	 vector<int>& reverselookup, const string& interstring) {

	vector<int> done(ktracks.size(), 0);
	int track;
	int tracknext;

	int j;
	for (j=0; j<infile[line].getFieldCount(); j++) {
		if (!infile.token(line, j)->isKern()) {
			if (!(rawQ || raw2Q)) {
				m_humdrum_text << infile.token(line, j);
				if (j < infile[line].getFieldCount() - 1) {
					m_humdrum_text << "\t";
				}
			}
			continue;
		}
		track = infile.token(line, j)->getTrack();
		if (j < infile[line].getFieldCount() - 1) {
			tracknext = infile.token(line, j+1)->getTrack();
		} else {
			tracknext = -23525;
		}
		if (track == tracknext) {
			if (!(rawQ || raw2Q)) {
				m_humdrum_text << infile.token(line, j);
				if (j < infile[line].getFieldCount() - 1) {
					m_humdrum_text << "\t";
				}
			}
			continue;
		}

		// print the **kern spine, then check to see if there
		// is some **cint data to print
		if (!(rawQ || raw2Q)) {
			m_humdrum_text << infile.token(line, j);

			if ((track != ktracks.back()) && (reverselookup[track] >= 0)) {
				m_humdrum_text << "\t" << interstring;
			}

			if (j < infile[line].getFieldCount() - 1) {
				m_humdrum_text << "\t";
			}
		}
	}
}



//////////////////////////////
//
// Tool_cint::printLattice --
//

void Tool_cint::printLattice(vector<vector<NoteNode> >& notes, HumdrumFile& infile,
		vector<int>& ktracks, vector<int>& reverselookup, int n) {

	int i;
	int ii = 0;
	for (i=0; i<infile.getLineCount(); i++) {
		if (!(rawQ || raw2Q)) {
			m_humdrum_text << infile[i];
		}
		HTp ltok = infile.token(i, 0);
		if (ltok->compare(0, 2, "**") == 0) {
			if (!(rawQ || raw2Q)) {
				m_humdrum_text << "\t**cint\n";
			}
		} else if (*ltok == "*-") {
				m_humdrum_text << "\t*-\n";
		} else if (infile[i].isData()) {
			if (!(rawQ || raw2Q)) {
				m_humdrum_text << "\t";
			}
			if (rowsQ) {
				ii = printLatticeItemRows(notes, n, ii, i);
			} else {
				ii = printLatticeItem(notes, n, ii, i);
			}
			if (!(rawQ || raw2Q)) {
				m_humdrum_text << "\n";
			}
		} else if (infile[i].isBarline()) {
			if (!(rawQ || raw2Q)) {
				m_humdrum_text << "\t" << ltok << "\n";
			}
		} else if (infile[i].isInterpretation()) {
			if (!(rawQ || raw2Q)) {
				m_humdrum_text << "\t*\n";
			}
		} else if (infile[i].isLocalComment()) {
			if (!(rawQ || raw2Q)) {
				m_humdrum_text << "\t!\n";
			}
		} else {
			// null, global comment or reference record
			m_humdrum_text << "\n";
		}
	}

}


//////////////////////////////
//
// Tool_cint::printLatticeModule -- print a counterpoint module or module chain given
//      the start notes and pair of parts to calculate the module
//      (chains) from.  Will not print anything if the chain length
//      is longer than the note array.
//

int Tool_cint::printLatticeModule(ostream& out, vector<vector<NoteNode> >& notes, int n,
		int startline, int part1, int part2) {

	if (n + startline >= (int)notes[0].size()) {
		return 0;
	}

	if (parenQ) {
		out << "(";
	}

	int i;
	for (i=0; i<n; i++) {
		// print harmonic interval
		if (hparenQ) {
			out << "[";
		}
		printInterval(out, notes[part1][startline+i],
			notes[part2][startline+i], INTERVAL_HARMONIC);
		if (hmarkerQ) {
			out << "h";
		}
		if (hparenQ) {
			out << "]";
		}
		printSpacer(out);

		// print melodic interal(s)
		if (mparenQ) {
			out << "{";
		}
		// bottom melodic interval:
		if (!toponlyQ) {
			printInterval(out, notes[part1][startline+i],
							  notes[part1][startline+i+1], INTERVAL_MELODIC);
		}

		// print top melodic interval here if requested
		if (topQ || toponlyQ) {
			if (!toponlyQ) {
				printSpacer(out);
			}
			// top melodic interval:
			printInterval(out, notes[part2][startline+i],
							  notes[part2][startline+i+1], INTERVAL_MELODIC);
			if (mmarkerQ) {
				out << "m";
			}
		}

		if (mparenQ) {
			out << "}";
		}
		printSpacer(out);
	}

	// print last harmonic interval
	if (hparenQ) {
	  out << "[";
	}
	printInterval(out, notes[part1][startline+n],
			notes[part2][startline+n], INTERVAL_HARMONIC);
	if (hmarkerQ) {
		out << "h";
	}
	if (hparenQ) {
	  out << "]";
	}

	if (parenQ) {
		out << ")";
	}

	return 1;
}



//////////////////////////////
//
// Tool_cint::printLatticeItemRows -- Row form of the lattice.
//

int Tool_cint::printLatticeItemRows(vector<vector<NoteNode> >& notes, int n,
		int currentindex, int fileline) {

	while ((currentindex < (int)notes[0].size())
			&& (fileline > notes[0][currentindex].line)) {
		currentindex++;
	}
	if (currentindex >= (int)notes[0].size()) {
		if (!(rawQ || raw2Q)) {
			m_humdrum_text << ".";
		}
		return currentindex;
	}
	if (notes[0][currentindex].line != fileline) {
		// should never get here.
		if (!(rawQ || raw2Q)) {
			m_humdrum_text << "?";
		}
		return currentindex;
	}

	// found the index into notes which matches to the current fileline.
	if (currentindex + n >= (int)notes[0].size()) {
		// asking for chain longer than rest of available data.
		if (!(rawQ || raw2Q)) {
			m_humdrum_text << ".";
		}
		return currentindex;
	}

	stringstream tempstream;
	int j;
	int counter = 0;

	for (j=0; j<(int)notes.size()-1; j++) {
		// iterate through each part, printing the module
		// for adjacent parts.
		counter += printLatticeModule(tempstream, notes, n, currentindex, j, j+1);
		if (j < (int)notes.size()-2) {
			printSpacer(tempstream);
		}
	}

	if (!(rawQ || raw2Q)) {
		if (counter == 0) {
			m_humdrum_text << ".";
		} else {
			m_humdrum_text << tempstream.str();
		}
	}

	return currentindex;
}



//////////////////////////////
//
// Tool_cint::printLatticeItem --
//

int Tool_cint::printLatticeItem(vector<vector<NoteNode> >& notes, int n, int currentindex,
		int fileline) {
	while ((currentindex < (int)notes[0].size())
			&& (fileline > notes[0][currentindex].line)) {
		currentindex++;
	}
	if (currentindex >= (int)notes[0].size()) {
		if (!(rawQ || raw2Q)) {
			m_humdrum_text << ".";
		}
		return currentindex;
	}
	if (notes[0][currentindex].line != fileline) {
		// should never get here.
		if (!(rawQ || raw2Q)) {
			m_humdrum_text << "??";
		}
		return currentindex;
	}

	// found the index into notes which matches to the current fileline.
	if (currentindex + n >= (int)notes[0].size()) {
		// asking for chain longer than rest of available data.
		if (!(rawQ || raw2Q)) {
			m_humdrum_text << ".";
		}
		return currentindex;
	}

	int count;
	int melcount;
	int j;
	if (parenQ) {
		m_humdrum_text << "(";
	}
	for (count = 0; count < n; count++) {
		// print harmonic intervals
		if (hparenQ) {
			m_humdrum_text << "[";
		}
		for (j=0; j<(int)notes.size()-1; j++) {
			printInterval(m_humdrum_text, notes[j][currentindex+count],
					notes[j+1][currentindex+count], INTERVAL_HARMONIC);
			if (j < (int)notes.size()-2) {
				printSpacer(m_humdrum_text);
			}
		}
		if (hparenQ) {
			m_humdrum_text << "]";
		}
		printSpacer(m_humdrum_text);

		// print melodic intervals
		if (mparenQ) {
			m_humdrum_text << "{";
		}
		melcount = (int)notes.size()-1;
		if (topQ) {
			melcount++;
		}
		for (j=0; j<melcount; j++) {
			printInterval(m_humdrum_text, notes[j][currentindex+count],
					notes[j][currentindex+count+1], INTERVAL_MELODIC);
			if (j < melcount-1) {
				printSpacer(m_humdrum_text);
			}
		}
		if (mparenQ) {
			m_humdrum_text << "}";
		}
		printSpacer(m_humdrum_text);

	}
	// print last sequence of harmonic intervals
	if (hparenQ) {
		m_humdrum_text << "[";
	}
	for (j=0; j<(int)notes.size()-1; j++) {
		printInterval(m_humdrum_text, notes[j][currentindex+n],
				notes[j+1][currentindex+n], INTERVAL_HARMONIC);
		if (j < (int)notes.size()-2) {
			printSpacer(m_humdrum_text);
		}
	}
	if (hparenQ) {
		m_humdrum_text << "]";
	}
	if (parenQ) {
		m_humdrum_text << ")";
	}

	if ((rawQ || raw2Q)) {
		m_humdrum_text << "\n";
	}

	return currentindex;
}



//////////////////////////////
//
// Tool_cint::printInterval --
//

int Tool_cint::printInterval(ostream& out, NoteNode& note1, NoteNode& note2,
		int type, int octaveadjust) {
	if ((note1.b40 == REST) || (note2.b40 == REST)) {
		out << RESTSTRING;
		return 0;
	}
	int cross = 0;
	int pitch1 = abs(note1.b40);
	int pitch2 = abs(note2.b40);
	int interval = pitch2 - pitch1;

	if ((type == INTERVAL_HARMONIC) && (interval < 0)) {
		cross = 1;
		if (uncrossQ) {
			interval = -interval;
		}
	} else {
		interval = interval + octaveadjust  * 40;
	}

	if ((type == INTERVAL_HARMONIC) && (octaveallQ)) {
		if (interval <= -40) {
			interval = interval + 4000;
		}
		if (interval > 40) {
			if (interval % 40 == 0) {
				interval = 40;
			} else {
				interval = interval % 40;
			}
		} else if (interval < 0) {
			interval = interval + 40;
		}
	}
	if (base12Q && !chromaticQ) {
		interval = Convert::base40ToMidiNoteNumber(interval + 40*4 + 2) - 12*5;
		if ((type == INTERVAL_HARMONIC) && (octaveallQ)) {
			if (interval <= -12) {
				interval = interval + 1200;
			}
			if (interval > 12) {
				if (interval % 12 == 0) {
					interval = 12;
				} else {
					interval = interval % 12;
				}
			} else if (interval < 0) {
				interval = interval + 12;
			}
		}
		interval = interval + octaveadjust  * 12;
	} else if (base7Q && !chromaticQ) {
		interval = Convert::base40ToDiatonic(interval + 40*4 + 2) - 7*4;
		if ((type == INTERVAL_HARMONIC) && (octaveallQ)) {
			if (interval <= -7) {
				interval = interval + 700;
			}
			if (interval > 7) {
				if (interval % 7 == 0) {
					interval = 7;
				} else {
					interval = interval % 7;
				}
			} else if (interval < 0) {
				interval = interval + 7;
			}
		}
		interval = interval + octaveadjust  * 7;
	}


	if (chromaticQ) {
		out << Convert::base40ToIntervalAbbr(interval);
	} else {
		int negative = 1;
		if (interval < 0) {
			negative = -1;
			interval = -interval;
		}
		if (base7Q && !zeroQ) {
			out << negative * (interval+1);
		} else {
			out << negative * interval;
		}
	}

	if (sustainQ || ((type == INTERVAL_HARMONIC) && xoptionQ)) {
		// print sustain/attack information of intervals.
		if (note1.b40 < 0) {
			out << "s";
		} else {
			out << "x";
		}
		if (note2.b40 < 0) {
			out << "s";
		} else {
			out << "x";
		}
	}

	return cross;
}



//////////////////////////////
//
// Tool_cint::printSpacer -- space or comma...
//

void Tool_cint::printSpacer(ostream& out) {
	out << Spacer;
}



//////////////////////////////
//
// Tool_cint::printPitchGrid -- print the pitch grid from which all counterpoint
//      modules are calculated.
//

void Tool_cint::printPitchGrid(vector<vector<NoteNode> >& notes, HumdrumFile& infile) {
	int i = 0;
	int j = 0;
	int pitch;
	int abspitch;
	int newpitch;
	int partcount;
	int line;
	double beat;

	if (base40Q) {
		partcount = (int)notes.size();

		if (rhythmQ) {
			m_humdrum_text << "**absq\t";
			m_humdrum_text << "**bar\t";
			m_humdrum_text << "**beat\t";
		}
		for (i=0; i<partcount; i++) {
			m_humdrum_text << "**b40";
			if (i < partcount - 1) {
				m_humdrum_text << "\t";
			}
		}
		m_humdrum_text << endl;
		for (i=0; i<(int)notes[0].size(); i++) {
			if (rhythmQ) {
				line = notes[0][i].line;
				beat = infile[line].getDurationFromBarline().getFloat() * notes[0][i].beatsize + 1;
				m_humdrum_text << infile[line].getDurationFromStart().getFloat() << "\t";
				m_humdrum_text << notes[0][i].measure << "\t";
				m_humdrum_text << beat << "\t";
			}
			for (j=0; j<(int)notes.size(); j++) {
				if (!notes[j][i].notemarker.empty()) {
					m_humdrum_text << notes[j][i].notemarker;
				}
				m_humdrum_text << notes[j][i].b40;
				if (j < (int)notes.size()-1) {
					m_humdrum_text << "\t";
				}
			}
			m_humdrum_text << endl;
		}
		if (rhythmQ) {
			m_humdrum_text << "*-\t";
			m_humdrum_text << "*-\t";
			m_humdrum_text << "*-\t";
		}
		for (i=0; i<partcount; i++) {
			m_humdrum_text << "*-";
			if (i < partcount - 1) {
				m_humdrum_text << "\t";
			}
		}
		m_humdrum_text << endl;
	} else if (base7Q) {
		partcount = (int)notes.size();

		if (rhythmQ) {
			m_humdrum_text << "**absq\t";
			m_humdrum_text << "**bar\t";
			m_humdrum_text << "**beat\t";
		}
		for (i=0; i<partcount; i++) {
			m_humdrum_text << "**b7";
			if (i < partcount - 1) {
				m_humdrum_text << "\t";
			}
		}
		m_humdrum_text << endl;

		for (i=0; i<(int)notes[0].size(); i++) {
			if (rhythmQ) {
				line = notes[0][i].line;
				beat = infile[line].getDurationFromBarline().getFloat() * notes[0][i].beatsize + 1;
				m_humdrum_text << infile[line].getDurationFromStart().getFloat() << "\t";
				m_humdrum_text << notes[0][i].measure << "\t";
				m_humdrum_text << beat << "\t";
			}
			for (j=0; j<(int)notes.size(); j++) {
				if (!notes[j][i].notemarker.empty()) {
					m_humdrum_text << notes[j][i].notemarker;
				}
				pitch = notes[j][i].b40;
				abspitch = abs(pitch);
				if (pitch == 0) {
					// print rest
					m_humdrum_text << 0;
				} else {
					newpitch = Convert::base40ToDiatonic(abspitch);
					if (pitch < 0) {
						newpitch = -newpitch;
					}
					m_humdrum_text << newpitch;
				}
				if (j < (int)notes.size()-1) {
					m_humdrum_text << "\t";
				}
			}
			m_humdrum_text << endl;
		}
		if (rhythmQ) {
			m_humdrum_text << "*-\t";
			m_humdrum_text << "*-\t";
			m_humdrum_text << "*-\t";
		}
		for (i=0; i<partcount; i++) {
			m_humdrum_text << "*-";
			if (i < partcount - 1) {
				m_humdrum_text << "\t";
			}
		}
		m_humdrum_text << endl;
	} else if (base12Q) {
		partcount = (int)notes.size();

		if (rhythmQ) {
			m_humdrum_text << "**absq\t";
			m_humdrum_text << "**bar\t";
			m_humdrum_text << "**beat\t";
		}
		for (i=0; i<partcount; i++) {
			m_humdrum_text << "**b12";
			if (i < partcount - 1) {
				m_humdrum_text << "\t";
			}
		}
		m_humdrum_text << endl;

		for (i=0; i<(int)notes[0].size(); i++) {
			if (rhythmQ) {
				line = notes[0][i].line;
				beat = infile[line].getDurationFromBarline().getFloat() * notes[0][i].beatsize + 1;
				if (!notes[j][i].notemarker.empty()) {
					m_humdrum_text << notes[j][i].notemarker;
				}
				m_humdrum_text << infile[line].getDurationFromStart() << "\t";
				m_humdrum_text << notes[0][i].measure << "\t";
				m_humdrum_text << beat << "\t";
			}
			for (j=0; j<(int)notes.size(); j++) {
				if (!notes[j][i].notemarker.empty()) {
					m_humdrum_text << notes[j][i].notemarker;
				}
				pitch = notes[j][i].b40;
				if (pitch == 0) {
					// print rest
					m_humdrum_text << 0;
				} else {
					abspitch = abs(pitch);
					newpitch = Convert::base40ToMidiNoteNumber(abspitch);
					if (pitch < 0) {
						newpitch = -newpitch;
					}
					m_humdrum_text << newpitch;
				}
				if (j < (int)notes.size()-1) {
					m_humdrum_text << "\t";
				}
			}
			m_humdrum_text << endl;
		}
		if (rhythmQ) {
			m_humdrum_text << "*-\t";
			m_humdrum_text << "*-\t";
			m_humdrum_text << "*-\t";
		}
		for (i=0; i<partcount; i++) {
			m_humdrum_text << "*-";
			if (i < partcount - 1) {
				m_humdrum_text << "\t";
			}
		}
		m_humdrum_text << endl;
	} else {
		// print as Humdrum **kern data
		partcount = (int)notes.size();

		if (rhythmQ) {
			m_humdrum_text << "**absq\t";
			m_humdrum_text << "**bar\t";
			m_humdrum_text << "**beat\t";
		}
		for (i=0; i<partcount; i++) {
			m_humdrum_text << "**kern";
			if (i < partcount - 1) {
				m_humdrum_text << "\t";
			}
		}
		m_humdrum_text << endl;

		for (i=0; i<(int)notes[0].size(); i++) {
			if (rhythmQ) {
				line = notes[0][i].line;
				beat = infile[line].getDurationFromBarline().getFloat() * notes[0][i].beatsize + 1;
				if (!notes[j][i].notemarker.empty()) {
					m_humdrum_text << notes[j][i].notemarker;
				}
				m_humdrum_text << infile[line].getDurationFromStart() << "\t";
				m_humdrum_text << notes[0][i].measure << "\t";
				m_humdrum_text << beat << "\t";
			}
			for (j=0; j<(int)notes.size(); j++) {
				if (!notes[j][i].notemarker.empty()) {
					m_humdrum_text << notes[j][i].notemarker;
				}
				pitch = notes[j][i].b40;
				abspitch = abs(pitch);
				if (pitch == 0) {
					m_humdrum_text << "r";
				} else {
					if ((pitch > 0) && (i<(int)notes[j].size()-1) &&
						 (notes[j][i+1].b40 == -abspitch)) {
						// start of a note which continues into next
						// sonority.
						m_humdrum_text << "[";
					}
					m_humdrum_text << Convert::base40ToKern(abspitch);
					// print tie continue/termination as necessary.
					if (pitch < 0) {
						if ((i < (int)notes[j].size() - 1) &&
							 (notes[j][i+1].b40 == notes[j][i].b40)) {
						  // note sustains further
						  m_humdrum_text << "_";
						} else {
						  // note does not sustain any further.
						  m_humdrum_text << "]";
						}
					}
				}
				if (j < (int)notes.size()-1) {
					m_humdrum_text << "\t";
				}
			}
			m_humdrum_text << endl;
		}

		if (rhythmQ) {
			m_humdrum_text << "*-\t";
			m_humdrum_text << "*-\t";
			m_humdrum_text << "*-\t";
		}
		for (i=0; i<partcount; i++) {
			m_humdrum_text << "*-";
			if (i < partcount - 1) {
				m_humdrum_text << "\t";
			}
		}
		m_humdrum_text << endl;
	}
}



//////////////////////////////
//
// Tool_cint::extractNoteArray --
//

void Tool_cint::extractNoteArray(vector<vector<NoteNode> >& notes, HumdrumFile& infile,
		vector<int>& ktracks, vector<int>& reverselookup) {

	HumRegex hre;

	Ids.resize(infile.getTrackCount()+1);
	int i, j, ii, jj;
	for (i=0; i<(int)Ids.size(); i++) {
		Ids[i] = EMPTY_ID;
	}

	vector<NoteNode> current(ktracks.size());
	vector<double> beatsizes(infile.getTrackCount()+1, 1);

	int sign;
	int track = 0;
	int index;

	int snum = 0;
	int measurenumber = 0;
	int tempmeasurenum = 0;
	double beatsize = 1.0;
	int topnum, botnum;

	for (i=0; i<infile.getLineCount(); i++) {
		if (debugQ) {
			m_humdrum_text << "PROCESSING LINE: " << i << "\t" << infile[i] << endl;
		}
		if (infile[i].isBarline()) {
			tempmeasurenum = infile.getMeasureNumber(i);
			if (tempmeasurenum >= 0) {
				measurenumber = tempmeasurenum;
			}
		}
		for (j=0; j<(int)current.size(); j++) {
			current[j].clear();
			current[j].measure = measurenumber;
			current[j].line = i;
		}

		if (infile[i].isBarline() && (infile.token(i, 0)->find("||") != string::npos)) {
			// double barline (terminal for Josquin project), so add a row
			// of rests to prevent cint melodic interval identification between
			// adjacent notes in different sections.
			for (j=0; j<(int)notes.size(); j++) {
				notes[j].push_back(current[j]);
			}
		} else if (infile[i].isInterpretation()) {
			// search for time signatures from which to extract beat information.
			for (j=0; j<infile[i].getFieldCount(); j++) {
				track = infile.token(i, j)->getTrack();
				if (hre.search(*infile.token(i, j), "^\\*M(\\d+)/(\\d+)")) {
					// deal with 3%2 in denominator later...
					topnum = hre.getMatchInt(1);
					botnum = hre.getMatchInt(2);
					beatsize = botnum;
					if (((topnum % 3) == 0) && (topnum > 3) && (botnum > 1)) {
						// compound meter
						// fix later
						beatsize = botnum / 3;
					}
					beatsizes[track] = beatsize / 4.0;
				} else if (*infile.token(i, j) == "*met(C|)") {
					// MenCutC, use 2 as the "beat"
					beatsizes[track] = 2.0 / 4.0;
				}
			}
		} else if (idQ && infile[i].isLocalComment()) {
			for (j=0; j<infile[i].getFieldCount(); j++) {
				if (hre.search(*infile.token(i, j), "^!ID:\\s*([^\\s]*)")) {
					int track = infile.token(i, j)->getTrack();
					Ids[track] = hre.getMatch(1);
				}
			}
		}

		if (!infile[i].isData()) {
			continue;
		}

		for (j=0; j<infile[i].getFieldCount(); j++) {
			sign = 1;
			if (!infile.token(i, j)->isKern()) {
				continue;
			}
			track = infile.token(i, j)->getTrack();
			index = reverselookup[track];
			if (index < 0) {
				continue;
			}
			if (idQ) {
				current[index].getId() = Ids[track];
				Ids[track] = "";  // don't assign to next item;
			}
			current[index].line  = i;
			current[index].spine = j;
			current[index].beatsize = beatsizes[track];
			if (infile.token(i, j)->isNull()) {
				sign = -1;
				HTp nullx = infile.token(i, j)->resolveNull();
				if (nullx == NULL) {
					ii = jj = -1;
				} else {
					ii = nullx->getLineIndex();
					jj = nullx->getFieldIndex();
				}
			} else {
				ii = i;
				jj = j;
			}
			if (infile.token(ii, jj)->find(NoteMarker) != string::npos) {
				current[index].notemarker = NoteMarker;
			}
			if (infile.token(ii, jj)->find('r') != string::npos) {
				current[index].b40 = 0;
				current[index].serial = ++snum;
				continue;
			}
			if (*infile.token(ii, jj) == ".") {
				current[index].b40 = 0;
				current[index].serial = snum;
			}
			current[index].b40 = Convert::kernToBase40(*infile.token(ii, jj));
			if (infile.token(ii, jj)->find('_') != string::npos) {
				sign = -1;
				current[index].serial = snum;
			}
			if (infile.token(ii, jj)->find(']') != string::npos) {
				sign = -1;
				current[index].serial = snum;
			}
			current[index].b40 *= sign;
			if (sign > 0) {
				current[index].serial = ++snum;
				if (durationQ) {
					current[index].duration = infile.token(ii, jj)->getTiedDuration();
				}
			}
		}
		if (onlyRests(current) && onlyRests(notes.back())) {
			// don't store more than one row of rests in the data array.
			continue;
		}
		if (allSustained(current)) {
			// don't store sonorities which are purely sutained
			// (may need to be updated with a --sustain option implementation)
			continue;
		}
		for (j=0; j<(int)notes.size(); j++) {
			notes[j].push_back(current[j]);
		}
	}

	// attach ID tag to all sustain sections of notes
	if (idQ) {
		for (j=0; j<(int)notes.size(); j++) {
			for (i=1; i<(int)notes[j].size(); i++) {
				if (notes[j][i].isAttack()) {
					continue;
				}
				if ((int)notes[j][i].getId().size() > 0) {
					// allow for Ids on sustained notes which probably means
					// that there is a written tied note in the music.
					continue;
				}
				if (notes[j][i].getB40() == notes[j][i-1].getB40()) {
					notes[j][i].getId() = notes[j][i-1].getId();
				}
			}
		}
	}

}



//////////////////////////////
//
// Tool_cint::onlyRests -- returns true if all NoteNodes are for rests
//

int Tool_cint::onlyRests(vector<NoteNode>& data) {
	int i;
	for (i=0; i<(int)data.size(); i++) {
		if (!data[i].isRest()) {
			return 0;
		}
	}
	return 1;
}



//////////////////////////////
//
// Tool_cint::hasAttack -- returns true if at least one NoteNode has
//   has an attack.
//

int Tool_cint::hasAttack(vector<NoteNode>& data) {
	int i;
	for (i=0; i<(int)data.size(); i++) {
		if (data[i].isAttack()) {
			return 1;
		}
	}
	return 0;
}



//////////////////////////////
//
// Tool_cint::allSustained -- returns true if all NoteNodes are sustains
//    or rests (but not all rests).
//

int Tool_cint::allSustained(vector<NoteNode>& data) {
	int i;
	int hasnote = 0;
	for (i=0; i<(int)data.size(); i++) {
		if (data[i].b40 != 0) {
			hasnote = 1;
		}
		if (data[i].isAttack()) {
			return 0;
		}
	}
	if (hasnote == 0) {
		return 0;
	}
	return 1;
}



//////////////////////////////
//
// Tool_cint::getAbbreviations --
//

void Tool_cint::getAbbreviations(vector<string>& abbreviations,
		vector<string>& names) {
	abbreviations.resize(names.size());
	for (int i=0; i<(int)names.size(); i++) {
		getAbbreviation(abbreviations[i], names[i]);
	}
}



//////////////////////////////
//
// Tool_cint::getAbbreviation --
//

void Tool_cint::getAbbreviation(string& abbr, string& name) {
	HumRegex hre;
	hre.replaceDestructive(abbr, "(?<=[a-zA-Z])[a-zA-Z]*", "");
	hre.tr(abbr, "123456789", "abcdefghi");
}



//////////////////////////////
//
// Tool_cint::getKernTracks -- return a list of track number for **kern spines.
//

void Tool_cint::getKernTracks(vector<int>& ktracks, HumdrumFile& infile) {
	int i, j;
	ktracks.reserve(infile.getTrackCount()+1);
	ktracks.resize(0);
	int track;
	for (i=0; i<infile.getLineCount(); i++) {
		if (!infile[i].isInterpretation()) {
			continue;
		}
		for (j=0; j<infile[i].getFieldCount(); j++) {
			if (infile.token(i, j)->isKern()) {
				track = infile.token(i, j)->getTrack();
				ktracks.push_back(track);
			}
		}
		break;
	}
}



//////////////////////////////
//
// Tool_cint::getNames -- get the names of each column if they have one.
//

void Tool_cint::getNames(vector<string>& names, vector<int>& reverselookup,
		HumdrumFile& infile) {

	names.resize((int)reverselookup.size()-1);
	char buffer[1024] = {0};
	int value;
	HumRegex pre;
	int i;
	int j;
	int track;

	for (i=0; i<(int)names.size(); i++) {
		value = (int)reverselookup.size() - i;
		snprintf(buffer, 1024, "%d", value);
		names[i] = buffer;
	}

	for (i=0; i<infile.getLineCount(); i++) {
		if (infile[i].isData()) {
			// stop looking for instrument name after the first data line
			break;
		}
		if (!infile[i].isInterpretation()) {
			continue;
		}
		for (j=0; j<infile[i].getFieldCount(); j++) {
			if (reverselookup[infile.token(i, j)->getTrack()] < 0) {
				continue;
			}
			if (!infile.token(i, j)->isKern()) {
				continue;
			}
			if (pre.search(*infile.token(i, j), "^\\*I\"(.*)")) {
				track = infile.token(i, j)->getTrack();
				names[reverselookup[track]] = pre.getMatch(1);
			}
		}
	}

	if (debugQ) {
		for (i=0; i<(int)names.size(); i++) {
			m_humdrum_text << i << ":\t" <<  names[i] << endl;
		}
	}

}



//////////////////////////////
//
// Tool_cint::initialize -- validate and process command-line options.
//

void Tool_cint::initialize(void) {

	// handle basic options:
	if (getBoolean("author")) {
		m_humdrum_text << "Written by Craig Stuart Sapp, "
			  << "craig@ccrma.stanford.edu, September 2013" << endl;
		exit(0);
	} else if (getBoolean("version")) {
		m_humdrum_text << getCommand() << ", version: 16 March 2022" << endl;
		m_humdrum_text << "compiled: " << __DATE__ << endl;
		exit(0);
	} else if (getBoolean("help")) {
		usage(getCommand());
		exit(0);
	} else if (getBoolean("example")) {
		example();
		exit(0);
	}

	koptionQ = getBoolean("koption");

	if (getBoolean("comma")) {
		Spacer = ",";
	} else {
		Spacer = " ";
	}

	// dispay as base-7 by default
	base7Q = 1;

	base40Q    = getBoolean("base-40");
	base12Q    = getBoolean("base-12");
	chromaticQ = getBoolean("chromatic");
	zeroQ      = getBoolean("zero");

	if (base40Q) {
		base12Q = 0;
		base7Q = 0;
		zeroQ = 0;
	}

	if (base12Q) {
		base40Q = 0;
		base7Q = 0;
		zeroQ = 0;
	}

	pitchesQ     = getBoolean("pitches");
	debugQ       = getBoolean("debug");
	rhythmQ      = getBoolean("rhythm");
	durationQ    = getBoolean("duration");
	latticeQ     = getBoolean("lattice");
	sustainQ     = getBoolean("sustain");
	topQ         = getBoolean("top");
	toponlyQ     = getBoolean("top-only");
	hparenQ      = getBoolean("harmonic-parentheses");
	mparenQ      = getBoolean("melodic-parentheses");
	parenQ       = getBoolean("parentheses");
	rowsQ        = getBoolean("rows");
	hmarkerQ     = getBoolean("harmonic-marker");
	interleavedQ = getBoolean("interleaved-lattice");
	mmarkerQ     = getBoolean("melodic-marker");
	attackQ      = getBoolean("attacks");
	rawQ         = getBoolean("raw");
	raw2Q        = getBoolean("raw2");
	xoptionQ     = getBoolean("x");
	octaveallQ   = getBoolean("octave-all");
	octaveQ      = getBoolean("octave");
	noharmonicQ  = getBoolean("no-harmonic");
	nomelodicQ   = getBoolean("no-melodic");
	norestsQ     = getBoolean("no-rests");
	nounisonsQ   = getBoolean("no-melodic-unisons");
	Chaincount   = getInteger("n");
	searchQ      = getBoolean("search");
	markQ        = getBoolean("mark");
	idQ          = getBoolean("id");
	countQ       = getBoolean("count");
	filenameQ    = getBoolean("filename");
	suspensionsQ = getBoolean("suspensions");
	uncrossQ     = getBoolean("uncross");
	locationQ    = getBoolean("location");
	retroQ       = getBoolean("retrospective");
	MarkColor    = getString("color");
	NoteMarker   = "";
	if (getBoolean("note-marker")) {
		NoteMarker = getString("note-marker");
	}
	if (searchQ) {
		NoteMarker = getString("note-marker");
	}
	if (Chaincount < 0) {
		Chaincount = 0;
	}

	if (searchQ) {
		// Automatically assume marking of --search is used
		// (may change in the future).
		markQ = 1;
	}
	if (countQ) {
		searchQ = 1;
		markQ   = 0;
	}

	if (raw2Q) {
		norestsQ = 1;
	}

	if (searchQ) {
		SearchString = getString("search");
	}

}



//////////////////////////////
//
// Tool_cint::example -- example usage of the quality program
//

void Tool_cint::example(void) {
	m_humdrum_text <<
	"                                                                         \n"
	<< endl;
}



//////////////////////////////
//
// Tool_cint::usage -- gives the usage statement for the meter program
//

void Tool_cint::usage(const string& command) {
	m_humdrum_text <<
	"                                                                         \n"
	<< endl;
}



double cmr_note_info::m_syncopationWeight = 1.0;
double cmr_note_info::m_leapWeight = 0.5;


///////////////////////////////////////////////////////////////////////////
//
// cmr_note_info -- Helper class describing a conspicuous repetition note.
//    The cmr_group_info contains a vector of cmr_note_info objects.
//

//////////////////////////////
//
// cmr_note_info::cmr_note_info -- Constructor.
//

cmr_note_info::cmr_note_info(void) {
	clear();
}



/////////////////////////////
//
// cmr_note_info::clear -- Clear or initialized the contents.
//

void cmr_note_info::clear(void) {
	m_tokens.clear();
	m_measureBegin   = -1;
	m_measureEnd     = -1;
	m_hasSyncopation = -1;
	m_hasLeapBefore  = -1;
}



/////////////////////////////
//
// cmr_note_info::getMeasureBegin -- Get the measure number that
//    the note starts in.
//

int cmr_note_info::getMeasureBegin(void) {
	return m_measureBegin;
}



/////////////////////////////
//
// cmr_note_info::getMeasureEnd -- Get the measure number that the
//    note ends in (including consideration of the duration of the
//    note or tied groups that follow the notes).
//

int cmr_note_info::getMeasureEnd(void) {
	return m_measureEnd;
}



/////////////////////////////
//
// cmr_note_info::setMeasureBegin -- Get the measure number that
//    the note starts in.
//

void cmr_note_info::setMeasureBegin(int measure) {
	m_measureBegin = measure;
}



/////////////////////////////
//
// cmr_note_info::setMeasureEnd -- Set the measure number that the
//    note ends in (including consideration of the duration of the note
//    or tied group of notes that follow it).
//

void cmr_note_info::setMeasureEnd(int measure) {
	m_measureEnd = measure;
}



/////////////////////////////
//
// cmr_note_info::getStartTime -- Return the starting time of the note
//    in units of quarter notes since the start of the score.
//

HumNum cmr_note_info::getStartTime(void) {
	if (m_tokens.empty()) {
		return -1;
	} else {
		return m_tokens[0]->getDurationFromStart();
	}
}



/////////////////////////////
//
// cmr_note_info::getLineIndex --
//

int cmr_note_info::getLineIndex(void) {
	HTp token = getToken();
	if (token) {
		return token->getLineIndex();
	} else {
		return -1;
	}
}



/////////////////////////////
//
// cmr_note_info::getEndTime -- Return the ending time of the note in
//    units of quarter notes since the start of the score.  Note that this
//    is the ending duration of the last note in the tied group.
//

HumNum cmr_note_info::getEndTime(void) {
	if (m_tokens.empty()) {
		return -1;
	} else {
		HumNum noteDur = m_tokens.back()->getTiedDuration();
		return m_tokens.back()->getDurationFromStart() + noteDur;
	}
}



/////////////////////////////
//
// cmr_note_info::getMidiPitch -- Return the MIDI pitch of the note or -1 if invalid.
//
int cmr_note_info::getMidiPitch(void) {
	if (m_tokens.empty()) {
		return -1;
	} else {
		return m_tokens[0]->getMidiPitch();
	}
}



/////////////////////////////
//
// cmr_note_info::hasSyncopation -- True if the note is syncopated.
//

bool cmr_note_info::hasSyncopation(void) {
	if (m_hasSyncopation >= 0) {
		return (bool)m_hasSyncopation;
	} else if (m_tokens.empty()) {
		m_hasSyncopation = 0;
		return false;
	} else {
		m_hasSyncopation = (int)cmr_note_info::isSyncopated(m_tokens[0]);
		return (bool)m_hasSyncopation;
	}
}



//////////////////////////////
//
// cmr_note_info::isSyncopated -- Return true if the note is syncopated,which
//   means that the note is longer than the metric position that it is one; however,
//   this analysis is chopped off for notes longer than a beat (whole note).
//   Static function.
//

bool cmr_note_info::isSyncopated(HTp token) {
	if (token == NULL) {
		return false;
	}
	HumNum dur = token->getTiedDuration();
	double logDur = log2(dur.getFloat());
	int metlev = cmr_note_info::getMetricLevel(token);
	if (metlev >= 2) { // no syncopations occuring on whole-note level or higher
		return false;
	} else if (logDur > metlev) {
		return true;
	} else {
		return false;
	}
}



/////////////////////////////
//
// cmr_note_info::hasLeapBefore -- True if there is a melodic leap before the note.
//

bool cmr_note_info::hasLeapBefore(void) {
	if (m_hasLeapBefore >= 0) {
		return (bool)m_hasLeapBefore;
	} else if (m_tokens.empty()) {
		m_hasLeapBefore = 0;
		return false;
	} else {
		m_hasLeapBefore = cmr_note_info::isLeapBefore(m_tokens[0]);
		return (bool)m_hasLeapBefore;
	}
}



//////////////////////////////
//
// cmr_note_info::isLeapBefore -- Return true if the note is syncopated, which
//   means that the note is longer than the metric position that it is one; however,
//   this analysis is chopped off for notes longer than a beat (whole note).
//   Static function.
//

bool cmr_note_info::isLeapBefore(HTp token) {
	HTp current = token->getPreviousToken();
	int startNote = token->getMidiPitch();
	while (current) {
		if (!current->isData()) {
			current = current->getPreviousToken();
			continue;
		}
		if (current->isNull()) {
			current = current->getPreviousToken();
			continue;
		}
		if (current->isRest()) {
			return false;
			// current = current->getPreviousToken();
			// continue;
		}
		int testNote = current->getMidiPitch();
		int interval = startNote - testNote;
		return interval > 2;
	}
	return false;
}



//////////////////////////////
//
// cmr_note_info::getMetricLevel -- Return the metric level of the note:
//    whole note:   2
//    half note:    1
//    quarter note: 0
//    less than quarter note: -1
//

double cmr_note_info::getMetricLevel(HTp token) {
	HumNum beat = token->getDurationFromBarline();
	if (!beat.isInteger()) { // anything less than quarter note level
		return -1;
	} else if (beat.getNumerator() % 4 == 0) { // whole note level
		return 2;
	} else if (beat.getNumerator() % 2 == 0) { // half note level
		return 1;
	} else { // quarter note level
		return 0;
	}
}



/////////////////////////////
//
// cmr_note_info::getNoteStrength -- Calculate a strength value for the
//     note.  This is based on if the note is syncopated and/or if it
//     is preceded by a melodic leap.
//

double cmr_note_info::getNoteStrength(void) {
	double output = 1.0;
	if (hasSyncopation()) {
		output += m_syncopationWeight;
	}
	if (hasLeapBefore()) {
		output += m_leapWeight;
	}
	return output;
}



//////////////////////////////
//
// cmr_note_info::markNote -- Only print the first note in a series
//     of notes with the same pitch.
//

void cmr_note_info::markNote(const string& marker) {
	for (int i=0; i<(int)m_tokens.size(); i++) {
		HTp token = m_tokens[i];
		if (i > 0) {
			// don't color repeated secondary notes.
			if (token->isNoteAttack()) {
				break;
			}
		}
		string text = *token;
		if (text.find(marker) != string::npos) {
			continue;
		}
		text += marker;
		token->setText(text);
	}
}



//////////////////////////////
//
// cmr_note_info::printNote --
//    Default values:
//       output = std::cerr
//       marker = ""
// If marker is not empty, then the marker string will be removed from the
// output.
//

ostream& cmr_note_info::printNote(ostream& output, const string& marker) {
	string newmarker = marker;
	if (marker == "+") {
		newmarker = "\\+";
	} else if (marker == "|") {
		newmarker = "\\|";
	} else if (marker == "?") {
		newmarker = "\\?";
	}

	string contents;
	for (int i=0; i<(int)m_tokens.size(); i++) {
		contents +=  *m_tokens[i];
		if (i < (int)m_tokens.size() - 1) {
			contents += ",";
		}
	}
	contents += "(";
	if (hasSyncopation()) {
		contents += "S";
	}
	if (hasLeapBefore()) {
		contents += "L";
	}
	contents += to_string(m_tokens[0]->getLineNumber());
	contents += ")";
	if (!newmarker.empty()) {
		HumRegex hre;
		hre.replaceDestructive(contents, "", newmarker, "g");
	}
	output << contents;
	return output;
}



//////////////////////////////
//
// cmr_note_info::getPitch -- Return scientific pitch name.
//

string cmr_note_info::getPitch(void) {
	if (m_tokens.empty()) {
		return "R";
	} else {
		int octave = Convert::kernToOctaveNumber(m_tokens.at(0));
		int accidentals = Convert::kernToAccidentalCount(m_tokens.at(0));
		int dpc = Convert::kernToDiatonicPC(m_tokens.at(0));
		string output;
		switch (dpc) {
			case 0: output += "C"; break;
			case 1: output += "D"; break;
			case 2: output += "E"; break;
			case 3: output += "F"; break;
			case 4: output += "G"; break;
			case 5: output += "A"; break;
			case 6: output += "B"; break;
		}
		for (int i=0; i<abs(accidentals); i++) {
			if (accidentals < 0) {
				output += "b";
			} else {
				output += "#";
			}
		}
		output += to_string(octave);
		return output;
	}
}



///////////////////////////////
//
// cmr_note_info::getToken --
//

HTp cmr_note_info::getToken(void) {
	if (m_tokens.empty()) {
		return NULL;
	} else {
		return m_tokens.at(0);
	}
}



///////////////////////////////////////////////////////////////////////////
//
// cmr_group_info -- helper class describing a conspicuous repetition groups.
//    This class is used to store a conspicuous melodic repetition (which will
//    be merged with adjacent CMRs later if they overlap.
//

//////////////////////////////
//
// cmr_group_info::cmr_group_info -- Constructor.
//

cmr_group_info::cmr_group_info(void) {
	clear();
}



//////////////////////////////
//
// cmr_group_info::clear -- Clear or initialize object.
//

void cmr_group_info::clear(void) {
	m_serial    = -1;
	m_direction =  0;
	m_notes.clear();
}



//////////////////////////////
//
// cmr_group_info::getNoteCount -- Return the number of notes in the group.
//

int cmr_group_info::getNoteCount(void) {
	if (m_serial < 0) {
		return 0;
	} else {
		return (int)m_notes.size();
	}
}



//////////////////////////////
//
// cmr_group_info::getGroupDuration -- Return the duration of the group, which
//     is the duration beetween the first and last note attack.
//

HumNum cmr_group_info::getGroupDuration(void) {
	if (m_notes.empty()) {
		return -1;
	} else {
		HumNum startPos = m_notes[0].m_tokens[0]->getDurationFromStart();
		HumNum endPos   = m_notes.back().m_tokens[0]->getDurationFromStart();
		return endPos - startPos;
	}
}



//////////////////////////////
//
// cmr_group_info::addNote -- Add a note to the list. The input is a list of
//     notes tied together.
//

void cmr_group_info::addNote(vector<HTp>& tiednotes, vector<int>& barnums) {
	if (tiednotes.empty()) {
		cerr << "Strange problem in cmr_group_info::addNote" << endl;
	}
	m_notes.resize(m_notes.size() + 1);
	m_notes.back().m_tokens = tiednotes;

	int line = m_notes.back().m_tokens.at(0)->getLineIndex();
	int mstart = barnums.at(line);
	m_notes.back().setMeasureBegin(mstart);

	line = m_notes.back().m_tokens.back()->getLineIndex();
	int mend = barnums.at(line);
	m_notes.back().setMeasureEnd(mend);
}



//////////////////////////////
//
// cmr_group_info::getMeasureBegin --
//

int cmr_group_info::getMeasureBegin(void) {
	if (m_notes.empty()) {
		return -1;
	} else {
		return m_notes[0].getMeasureBegin();
	}
}



//////////////////////////////
//
// cmr_group_info::getMeasureEnd -- Return the starting measure of the last note in the group.
//

int cmr_group_info::getMeasureEnd(void) {
	if (m_notes.empty()) {
		return -1;
	}
	return m_notes.back().getMeasureEnd();
}



//////////////////////////////
//
// cmr_group_info::getStartTime -- Return the starting time of the first
//     note in the CMR in units of quarter notes since the start of the music.
//

HumNum cmr_group_info::getStartTime(void) {
	if (m_notes.empty()) {
		return -1;
	} else {
		return m_notes[0].getStartTime();
	}
}



//////////////////////////////
//
// cmr_group_info::getEndTime -- Return the starting time of the last note
//     in the CMR group in units of quarter notes since the start of the music.
//

HumNum cmr_group_info::getEndTime(void) {
	if (m_notes.empty()) {
		return -1;
	} else {
		return m_notes.back().getStartTime();
	}
}



//////////////////////////////
//
// cmr_group_info::getMidiPitch -- Return the MIDI pitch of the group's note.
//

int cmr_group_info::getMidiPitch(void) {
	if (m_notes.empty()) {
		return -1;
	} else {
		return m_notes[0].getMidiPitch();
	}
}



//////////////////////////////
//
// cmr_group_info::getSerial --
//

int cmr_group_info::getSerial(void) {
	return m_serial;
}



//////////////////////////////
//
// cmr_group_info::setSerial --
//

void cmr_group_info::setSerial(int serial) {
	m_serial = serial;
}



//////////////////////////////
//
// cmr_group_info::getDirection --
//

int cmr_group_info::getDirection(void) {
	return m_direction;
}



//////////////////////////////
//
// cmr_group_info::setDirectionUp --
//

void cmr_group_info::setDirectionUp(void) {
	m_direction = +1;
}



//////////////////////////////
//
// cmr_group_info::setDirectionDown --
//

void cmr_group_info::setDirectionDown(void) {
	m_direction = -1;
}



//////////////////////////////
//
// cmr_group_info::getLeapCount --
//

int cmr_group_info::getLeapCount(void) {
	int output = 0;
	for (int i=0; i<(int)m_notes.size(); i++) {
		if (m_notes.at(i).hasLeapBefore()) {
			output++;
		}
	}
	return output;
}



//////////////////////////////
//
// cmr_group_info::getSyncopationCount --
//

int cmr_group_info::getSyncopationCount(void) {
	int output = 0;
	for (int i=0; i<(int)m_notes.size(); i++) {
		if (m_notes.at(i).hasSyncopation()) {
			output++;
		}
	}
	return output;
}



//////////////////////////////
//
// cmr_group_info::makeInvalid --
//

void cmr_group_info::makeInvalid(void) {
	if (m_serial > 0) {
		m_serial *= -1;
	}
}



//////////////////////////////
//
// cmr_group_info::getTrack --
//

int cmr_group_info::getTrack(void) {
	if (getNoteCount() <= 0) {
		return -1;
	} else {
		HTp token = getNote(0);
		if (token) {
			return token->getTrack();
		} else {
			return -1;
		}
	}
}



//////////////////////////////
//
// cmr_group_info::getStartLineNumber --
//

int cmr_group_info::getStartLineNumber(void) {
	if (m_notes.empty()) {
		return -1;
	} else {
		return m_notes[0].m_tokens[0]->getLineNumber();
	}
}



//////////////////////////////
//
// cmr_group_info::getStartLineNumber --
//

int cmr_group_info::getStartFieldNumber(void) {
	if (m_notes.empty()) {
		return -1;
	} else {
		return m_notes[0].m_tokens[0]->getFieldNumber();
	}
}



//////////////////////////////
//
// cmr_group_info::getGroupStrength -- Return the strength value for the CMR.
//

double cmr_group_info::getGroupStrength(void) {
	double output = 0.0;
	for (int i=0; i<(int)m_notes.size(); i++) {
		output += m_notes[i].getNoteStrength();
	}
	return output;
}



//////////////////////////////
//
// cmr_group_info::getNote -- return the note attack at the given index.
//    Return NULL if out of range.
//

HTp cmr_group_info::getNote(int index) {
	if (index < 0) {
		return NULL;
	} else if (index >= getNoteCount()) {
		return NULL;
	} else {
		return m_notes[index].m_tokens[0];
	}
}



//////////////////////////////
//
// cmr_group_info::getStartNote -- return attack note of first note in group, or
//    return NULL if out of range.
//

HTp cmr_group_info::getFirstToken(void) {
	if (m_notes.empty()) {
		return NULL;
	} else {
		return m_notes[0].getToken();
	}
}



//////////////////////////////
//
// cmr_group_info::isValid -- Group that is not merged
//

bool cmr_group_info::isValid(void) {
	if (m_serial >= 0) {
		return true;
	} else {
		return false;
	}
}



//////////////////////////////
//
// cmr_group_info::markNotes --
//

void cmr_group_info::markNotes(const string& marker) {
	for (int i=0; i<(int)m_notes.size(); i++) {
		m_notes[i].markNote(marker);
	}
}



//////////////////////////////
//
// cmr_group_info::mergeGroup -- Merges overlapping groups into a single
//     larger group and invalidates the second group after merging.
//

bool cmr_group_info::mergeGroup(cmr_group_info& group) {
	if (this == &group) {
		// Same group.
		return false;
	}
	if (!this->isValid()) {
		return false;
	}
	if (!group.isValid()) {
		return false;
	}

	int dir1 = getDirection();
	int dir2 = group.getDirection();
	if (dir1 != dir2) {
		return false;
	}
	if (dir1 == 0) {
		cerr << "Error: unassigned direction for groups" << endl;
		return false;
	}

	HumNum start1 = this->getStartTime();
	HumNum start2 = group.getStartTime();
	HumNum end1   = this->getEndTime();
	HumNum end2   = group.getEndTime();

	if (start2 > end1) {
		// nothing to merge
		return false;
	}

	int duplicates = 0;

	// Identify duplicate notes shared between groups:
	vector<bool> duplicateNote(group.getNoteCount(), false);
	for (int i=0; i<group.getNoteCount(); i++) {
		for (int j=0; j<(int)getNoteCount(); j++) {
			HTp token1 = getNote(j);
			HTp token2 = group.getNote(i);
			if (duplicateNote[i]) {
				continue;
			}
			if (token1 == token2) {
				duplicateNote[i] = true;
				duplicates++;
			}
		}
	}

	if (duplicates == group.getNoteCount()) {
		// group is a subset of this.
		group.makeInvalid();
		return "true";
	}

	// Copy unshared notes into first group:
	for (int i=0; i<group.getNoteCount(); i++) {
		if (duplicateNote[i]) {
			continue;
		}
		m_notes.push_back(group.m_notes[i]);
	}

	// Deactivate group being merged:
	if (group.getSerial() > 0) {
		group.makeInvalid();
	} else {
		cerr << "Strange problem merging group" << endl;
		return false;
	}

	return true;
}



//////////////////////////////
//
// cmr_group_info::printNotes --
//    Default values:
//       output = std::cerr
//       marker = ""
// If marker is not empty, then the marker string will be removed from the
// output.
//

ostream& cmr_group_info::printNotes(ostream& output, const string& marker) {
	for (int i=0; i<(int)m_notes.size(); i++) {
		m_notes[i].printNote(output, marker);
		if (i < (int)m_notes.size() - 1) {
			output << " ";
		}
	}
	return output;
}


//////////////////////////////
//
// cmr_group_info::getPitch -- Return scientific pitch name.
//

string cmr_group_info::getPitch(void) {
	if (m_notes.empty()) {
		return "R";
	} else {
		return m_notes.at(0).getPitch();
	}
}


///////////////////////////////////////////////////////////////////////////

/////////////////////////////////
//
// Tool_cmr::Tool_cmr -- Constructor: set the recognized options for the tool.
//

Tool_cmr::Tool_cmr(void) {
	define("data|raw|raw-data=b",         "print analysis data");
	define("m|mark-up|marker-up=s:+",     "symbol to mark peak cmr notes");
	define("M|mark-down|marker-down=s:|", "symbol to mark anti-peak cmr notes");
	define("c|color|color-up=s:red",      "color of CMR peak notes");
	define("C|color-down=s:orange",       "color of CMR anti-peak notes");
	define("r|ignore-rest=d:1.0",         "ignore rests smaller than given value (in whole notes)");
	define("n|number=i:3",                "number of high notes in a row");
	define("N|cmr-number=b",              "show enumeration number of CMR above/below starting note");
	define("d|dur|duration=d:6.0",        "maximum duration between cmr note attacks in whole notes");
	define("i|info=b",                    "print cmr info");
	define("p|peaks=b",                   "detect only positive cmrs");
	define("t|troughs=b",                 "detect only negative cmrs");
	define("A|not-accented=b",            "counts only cmrs that do not have melodic accentation");
	define("s|syncopation-weight=d:1.0",  "weight for syncopated notes");
	define("leap|leap-weight=d:0.5",      "weight for leapng notes");
	define("l|local-peaks=b",             "mark local peaks");
	define("L|only-local-peaks=b",        "mark local peaks only");
	define("merge|merged|show-merged=b",  "print merged groups");
	define("S|summary=b",                 "summarize CMRs for multiple inputs");
	define("v|vega=b",                    "output default Vega-lite plot");
	define("V|no-html=b",                 "output Vega-lite plot without HTML");
	define("countplot=b",                 "output Vega-lite plot for CMR count");
	define("strengthplot=b",              "output Vega-lite plot with strength scores");
	define("h|half=b",                    "durations given in half notes (mimims)");
	define("D|debug=b",                   "print debug information");
}



/////////////////////////////////
//
// Tool_cmr::run -- Do the main work of the tool.
//

bool Tool_cmr::run(HumdrumFileSet& infiles) {
	bool status = true;
	for (int i=0; i<infiles.getCount(); i++) {
		status &= run(infiles[i]);
	}
	return status;
}


bool Tool_cmr::run(const string& indata, ostream& out) {
	HumdrumFile infile(indata);
	bool status = run(infile);
	if (hasAnyText()) {
		getAllText(out);
	} else {
		out << infile;
	}
	return status;
}


bool Tool_cmr::run(HumdrumFile& infile, ostream& out) {
	bool status = run(infile);
	if (hasAnyText()) {
		getAllText(out);
	} else {
		out << infile;
	}
	return status;
}


bool Tool_cmr::run(HumdrumFile& infile) {
	initialize();
	processFile(infile);
	return true;
}



//////////////////////////////
//
// Tool_cmr::initialize --  Initializations that only have to be done once
//    for all HumdrumFile segments.
//

void Tool_cmr::initialize(void) {
	m_rawQ          = getBoolean("raw-data");
	m_peaksQ        = getBoolean("peaks");
	m_npeaksQ       = getBoolean("troughs");
	m_naccentedQ    = getBoolean("not-accented");
	m_localQ        = getBoolean("local-peaks");
	m_localOnlyQ    = getBoolean("only-local-peaks");
	m_showMergedQ   = getBoolean("show-merged");
	m_summaryQ      = getBoolean("summary");
	m_vegaQ         = getBoolean("vega");
	m_htmlQ         = getBoolean("no-html");
	m_vegaCountQ    = getBoolean("countplot");
	m_vegaStrengthQ = getBoolean("strengthplot");
	m_numberQ       = getBoolean("cmr-number");
	m_debugQ        = getBoolean("debug");
	if (m_localOnlyQ) {
		m_localQ = true;
	}

	m_markerUp     = getString("marker-up");
	m_markerDown   = getString("marker-down");
	m_colorUp      = getString("color-up");
	m_colorDown    = getString("color-down");

	m_smallRest    = getDouble("ignore-rest") * 4.0;  // convert from whole notes to quarter notes
	m_cmrNum       = getInteger("number");
	m_cmrDur       = getInteger("duration") * 4.0;    // convert from whole notes to quarter notes
	m_infoQ        = getBoolean("info");
	m_halfQ        = getBoolean("half");

	cmr_note_info::m_syncopationWeight = getDouble("syncopation-weight");
	cmr_note_info::m_leapWeight        = getDouble("leap-weight");

	m_noteGroups.clear();
}



//////////////////////////////
//
// Tool_cmr::processFile -- Do CMR analysis on score.
//

void Tool_cmr::processFile(HumdrumFile& infile) {
	vector<HTp> starts = infile.getKernSpineStartList();
	m_minPitch.clear();
	m_maxPitch.clear();
	m_minPitch.resize(infile.getMaxTrack() + 1);
	m_maxPitch.resize(infile.getMaxTrack() + 1);

	m_local_count = 0;

	m_barNum = infile.getMeasureNumbers();

	// print information about each note group:
	getPartNames(m_partNames, infile);

	// Analyze CMR for each part, starting with the highest part:
	for (int i=(int)starts.size()-1; i>=0; i--) {
		getNoteList(m_notelist, starts.at(i));
		getVocalRange(m_minPitch, m_maxPitch, m_notelist);
		if (m_peaksQ) {
			processSpine(starts[i], infile);
		} else if (m_npeaksQ) {
			processSpineFlipped(starts[i], infile);
		} else {
			processSpine(starts[i], infile);
			processSpineFlipped(starts[i], infile);
		}
	}

	mergeOverlappingPeaks();

	if (m_vegaQ || m_vegaStrengthQ || m_vegaCountQ) {
		m_free_text << " " << endl;
	}

	if (!(m_rawQ || m_summaryQ || m_vegaQ || m_vegaStrengthQ || m_vegaCountQ)) {
		markNotesInScore();
	}

	if (!(m_rawQ || m_summaryQ || m_vegaQ || m_vegaStrengthQ || m_vegaCountQ)) {
		if (m_numberQ) {
			addGroupNumbersToScore(infile);
		}
		infile.createLinesFromTokens();
		m_humdrum_text << infile;

		if (!m_localOnlyQ) {

			if (hasGroupUp()) {
				m_humdrum_text << "!!!RDF**kern: ";
				m_humdrum_text << m_markerUp;
				m_humdrum_text << " = marked note (up), color=";
				m_humdrum_text << m_colorUp;
				m_humdrum_text << endl;
			}

			if (hasGroupDown()) {
				m_humdrum_text << "!!!RDF**kern: ";
				m_humdrum_text << m_markerDown;
				m_humdrum_text << " = marked note (down), color=";
				m_humdrum_text << m_colorDown;
				m_humdrum_text << endl;
			}

		} else {
			if (m_local_count_n > 0) {
				m_humdrum_text << "!!!RDF**kern: ";
				m_humdrum_text << m_local_marker_n;
				m_humdrum_text << " = marked note, color=";
				m_humdrum_text << m_local_color_n;
				m_humdrum_text << endl;
			}
		}

	}

	if (!m_localOnlyQ) {
		if (m_summaryQ) {
			printSummaryStatistics(infile);
		} else if (m_vegaQ || m_vegaCountQ || m_vegaStrengthQ) {
			storeVegaData(infile);
		} else {
			printStatistics(infile);
		}
	}

	if (m_infoQ && !m_summaryQ) {
		prepareHtmlReport();
	}

}



//////////////////////////////
//
// Tool_cmr::processSpine -- Process one part.  Only the first voice/layer
//    on the staff will be processed (so only the top part if there is a divisi).
//

void Tool_cmr::processSpine(HTp startok, HumdrumFile&  infile) {
	// notelist is a two dimensional array of notes.   The
	// first dimension is a list of the note attacks in time
	// (plus rests), and the second dimension is for a list of the
	// tied notes after the first one (this is so that we can
	// highlight both the starting note and any tied notes to that
	// starting note later).
	if (m_notelist.empty()) {
		m_midinums.clear();
		m_metlevs.clear();
		m_syncopation.clear();
		m_leapbefore.clear();
		return;
	}

	m_track = m_notelist.at(0).at(0)->getTrack();
	getMidiNumbers(m_midinums, m_notelist);
	identifyLocalPeaks(m_localpeaks, m_midinums);
	getMetlev(m_metlevs, m_notelist);
	getSyncopation(m_syncopation, m_notelist);
	getLeapBefore(m_leapbefore, m_midinums);

	if (m_localQ) {
		markNotes(m_notelist, m_localpeaks, m_local_marker);
	}
	if (m_localOnlyQ) {
		return;
	}

	for (int i=0; i<(int)m_notelist.size(); i++) {
		checkForCmr(i, 1, infile);
	}

	if (m_rawQ) {
		printAnalysisData();
	}
}



//////////////////////////////
//
// Tool_cmr::processSpineFlipped -- Similar to processSpine(), but
//    searches for minima CMRs rather than maxima CMRs.
//

void Tool_cmr::processSpineFlipped(HTp startok, HumdrumFile& infile) {
	// notelist is a two dimensional array of notes.   The
	// first dimension is a list of the note attacks in time
	// (plus rests), and the second dimension is for a list of the
	// tied notes after the first one (this is so that we can
	// highlight both the starting note and any tied notes to that
	// starting note later).
	if (m_notelist.empty()) {
		m_midinums.clear();
		m_metlevs.clear();
		m_syncopation.clear();
		m_leapbefore.clear();
		return;
	}

	m_track = m_notelist.at(0).at(0)->getTrack();
	getMidiNumbers(m_midinums, m_notelist);
	flipMidiNumbers(m_midinums);
	identifyLocalPeaks(m_localpeaks, m_midinums);
	getMetlev(m_metlevs, m_notelist);
	getSyncopation(m_syncopation, m_notelist);
	getLeapBefore(m_leapbefore, m_midinums);


	if (m_rawQ) {
		printAnalysisData();
	}

	if (m_localQ) {
		markNotes(m_notelist, m_localpeaks, m_local_marker_n);
	}
	if (m_localOnlyQ) {
		return;
	}

	for (int i=0; i<(int)m_notelist.size(); i++) {
		checkForCmr(i, -1, infile);
	}
}



//////////////////////////////
//
// checkForCmr -- store CMR if identified.
//
// * Must have at least one sycopation or three leaps.
// * Other CMR notes are on strong beats.
// * Need three CMRs within window (6 whole notes) to count.
// * Cannot have a note higher than a major/minor second between CMR notes.
// * Can have a note a major/minor second above CMR notes, but only
//   if the higher note is not on a strong beat.
//

void Tool_cmr::checkForCmr(int index, int direction, HumdrumFile& infile) {
	// Change next condition: trigger on a syncopation, or three leaps
	// before the same pitch.  Then later there will be a check for higher
	// notes between the candidate CMR notes:
	// Local peak must be present to trigger CMR
	//if (!m_localpeaks.at(index)) {
	//	return;
	//}

	// The local peak note must have a leap before it (or a rest)
	// and/or a syncopation to trigger a CMR search:
	if (!(m_syncopation.at(index) || m_leapbefore.at(index))) {
		return;
	}

	int pitch = m_midinums.at(index);

	// Create list of notes with same pitch within target duration after target note.
	vector<int> candidates;
	candidates.push_back(index);

	int i = index+1;
	HumNum duration = 0;
	if (i < (int)m_notelist.size()) {
		duration = m_notelist.at(i).at(0)->getDurationFromStart() - m_notelist.at(index).at(0)->getDurationFromStart();
	}

	// Check for matching peaks after target note:
	while ((i < (int)m_notelist.size()) && (duration.getFloat() <= m_cmrDur)) {
		if (m_midinums.at(i) > pitch + 2) {
			// Cannot exceed a major second above peak note
			// maybe check if adjacent to a peak note
			break;
		}
		if (m_midinums.at(i) == pitch) {
			double metlev = m_metlevs.at(i);
			if (metlev > 1) { // has to be on whole-note level (metlev == 2)
				candidates.push_back(i);
			} else if (isMelodicallyAccented(i)) {
				candidates.push_back(i);
			}
		}
		i++;
		if (i < (int)m_notelist.size()) {
			duration = m_notelist.at(i).at(0)->getDurationFromStart() - m_notelist.at(index).at(0)->getDurationFromStart();
		}
	}

	i = index-1;
	duration = 0;
	if (i < (int)m_notelist.size()) {
		duration = m_notelist.at(index).at(0)->getDurationFromStart() - m_notelist.at(i).at(0)->getDurationFromStart();
	}

	// Check for matching peaks before target note:
	while ((i >= 0) && (duration.getFloat() <= m_cmrDur)) {
		if (m_midinums.at(i) > pitch + 2) {
			// Cannot exceed a major second above peak note
			// maybe check if adjacent to a peak note
			break;
		}
		if (m_midinums.at(i) == pitch) {
			double metlev = m_metlevs.at(i);
			if (metlev > 1) { // has to be on whole-note level (metlev == 2)
				candidates.insert(candidates.begin(), i);
			} else if (isMelodicallyAccented(i)) {
				candidates.insert(candidates.begin(), i);
			}
		}
		i--;
		if (i >= 0) {
			duration = m_notelist.at(index).at(0)->getDurationFromStart() - m_notelist.at(i).at(0)->getDurationFromStart();
		}
	}

	if ((int)candidates.size() < m_cmrNum) {
		// Not enough notes to consider a CMR.
		return;
	}

	for (int i=0; i<=(int)candidates.size() - m_cmrNum; i++) {
		int index1 = candidates.at(i);
		int index2 = candidates.at(i+m_cmrNum-1);
		HumNum dur1 = m_notelist.at(index1).at(0)->getDurationFromStart();
		HumNum dur2 = m_notelist.at(index2).at(0)->getDurationFromStart();
		HumNum duration = dur2 - dur1;
		if (duration > m_cmrDur) {
			continue;
		}
		if (hasHigher(pitch, 2, m_midinums, m_notelist, index1, index2)) {
			continue;
		}

		// Found a CMR (or piece of longer one that will be merged later)
		// so store it at the end of m_noteGroups:
		m_noteGroups.resize(m_noteGroups.size() + 1);
		for (int j=0; j<m_cmrNum; j++) {
			int tindex = candidates.at(i+j);
			m_noteGroups.back().addNote(m_notelist.at(tindex), m_barNum);
		}
		m_noteGroups.back().setSerial((int)m_noteGroups.size() + 1);

		if (direction < 0) {
			m_noteGroups.back().setDirectionDown();
		} else {
			m_noteGroups.back().setDirectionUp();
		}
	}

	int leapcount = m_noteGroups.back().getLeapCount();
	int syncocount  = m_noteGroups.back().getSyncopationCount();
	if (syncocount == 0) {
		if (leapcount < 3) {
			// Delete groups since it has less than three leaps and no syncopations
			if (m_noteGroups.size() > 0) {
				m_noteGroups.resize(m_noteGroups.size() - 1);
			}
		}
	}

	// Remove negative groups if they share a note with a positive group.
	vector<bool> positive(infile.getLineCount(), false);
	for (int i=0; i<(int)m_noteGroups.size(); i++) {
		int dir = m_noteGroups.at(i).getDirection();
		if (dir > 0) {
			for (int j=0; j<m_noteGroups.at(i).getNoteCount(); j++) {
				int line = m_noteGroups.at(i).getToken(j)->getLineIndex();
				positive.at(line) = true;
			}
		} else if (dir < 0) {
			bool valid = true;
			for (int j=0; j<m_noteGroups.at(i).getNoteCount(); j++) {
				int line = m_noteGroups.at(i).getToken(j)->getLineIndex();
				if (positive.at(line)) {
					valid = false;
					break;
				}
			}
			if (!valid) {
				m_noteGroups.at(i).makeInvalid();
			}
		}
	}
}



//////////////////////////////
//
// Tool_cmr::hasHigher -- There may be a note a step higher
//     (or lower) between any two notes of the CMR provided
//     that that higher note is not on a strong beat.
//     True = invalid CMR case.
//

bool Tool_cmr::hasHigher(int pitch, int tolerance, vector<int>& midinums, vector<vector<HTp>>& notelist, int index1, int index2) {

	for (int i=index1; i<=index2; i++) {
		// Only a step above (major or minor second) is allowed.
		// Input tolerance is 2.
		if (midinums.at(i) > pitch + tolerance) {
			return true;
		}

		if (midinums.at(i) > pitch) {
			// If the higher note is accented, then invalidate the cmr.
			if (isOnStrongBeat(notelist.at(i).at(0))) {
				return true;
			}
		}

	}

	return false;
}



//////////////////////////////
//
// Tool_cmr::hasGroupUp -- True if there is at least one CMR that contains a peak note.
//

bool Tool_cmr::hasGroupUp(void) {
	for (int i=0; i<(int)m_noteGroups.size(); i++) {
		if (m_noteGroups.at(i).isValid()) {
			if (m_noteGroups.at(i).getDirection() == +1) {
				return true;
			}
		}
	}
	return false;
}



//////////////////////////////
//
// Tool_cmr::hasGroupDown -- True if there is at least one CMR that contains an antipeak note.
//

bool Tool_cmr::hasGroupDown(void) {
	for (int i=0; i<(int)m_noteGroups.size(); i++) {
		if (m_noteGroups.at(i).isValid()) {
			if (m_noteGroups.at(i).getDirection() == -1) {
				return true;
			}
		}
	}
	return false;
}



//////////////////////////////
//
// Tool_cmr::getComposer-- Use regex to extract composer information from score filename
//

string Tool_cmr::getComposer(HumdrumFile& infile) {
   HumRegex hre;

	 string filename = infile.getFilename();
	 // Remove any directory prefix:
	 string code = "unknown";
	 // Search for a capital letter followed by two small letters followed by four digits:
	 if (hre.search(filename, "([A-Z][a-z][a-z])")) {
			code = hre.getMatch(1);
	 }
	return code;
}



//////////////////////////////
//
// Tool_cmr::printSummaryStatistics --
//

void Tool_cmr::printSummaryStatistics(HumdrumFile& infile) {

	m_free_text << getGroupCount() << "	" << ((double)getGroupNoteCount() / countNotesInScore(infile)) * 1000.0 << "	" << infile.getFilename() << endl;

	m_cmrCount.push_back(getGroupCount());
	m_cmrNoteCount.push_back(getGroupNoteCount());
	m_scoreNoteCount.push_back(countNotesInScore(infile));
}



//////////////////////////////
//
// Tool_cmr::storeVegaData --
//

void Tool_cmr::storeVegaData(HumdrumFile& infile) {

	string composer = getComposer(infile);
	m_vegaData << "{" << endl;
	m_vegaData << "	\"Composers\": " << '"' << composer << '"' << "," << endl;
	m_vegaData << "	\"Score\": \"" << infile.getFilename() << "\"," << endl;
	m_vegaData << "	\"CMR note density permil\": " << ((double)getGroupNoteCount() / countNotesInScore(infile)) * 1000 << "," << endl;
	if (getGroupCount() == 0) {
		m_vegaData << "	\"Average CMR strength\": " << "0" << "," << endl;
	} else {
		m_vegaData << "	\"Average CMR strength\": " << ((double)getStrengthScore() / getGroupCount()) << "," << endl;
	}
	m_vegaData << "	\"CMR count\": " << getGroupCount() << endl;
	m_vegaData << "}," << endl;

}



////////////////////////////
//
// Tool_cmr::printVegaPlot --
//

void Tool_cmr::printVegaPlot(void) {
	string vegaDataHeader = R"(
		{
		  "$schema": "https://vega.github.io/schema/vega-lite/v5.json",
		  "description": "A vertical box plot showing median, min, and max CMR count in Josquin.",
		  "data": {
		    "values": [)";
		cout << vegaDataHeader << endl;

		cout << m_vegaData.str() << endl;

		if (m_vegaCountQ) {
			string vegaDataFooter = R"(
			]},
			 "mark": {
				 "type": "boxplot",
				 "extent": "min-max"
			 },
			 "encoding": {
				 "x": {"field": "Composers", "type": "nominal"},
				 "color": {"field": "Composers", "type": "nominal", "legend": null},
				 "y": {
					 "field": "CMR count",
					 "type": "quantitative",
					 "scale": {"zero": false}
				 }
			 }
		 })";

		 cout << vegaDataFooter << endl;
	 } else	if (m_vegaStrengthQ) {
	 			string vegaDataFooter = R"(
	 			]},
	 			 "mark": {
	 				 "type": "boxplot",
	 				 "extent": "min-max"
	 			 },
	 			 "encoding": {
	 				 "x": {"field": "Composers", "type": "nominal"},
	 				 "color": {"field": "Composers", "type": "nominal", "legend": null},
	 				 "y": {
	 					 "field": "Average CMR strength",
	 					 "type": "quantitative",
	 					 "scale": {"zero": false}
	 				 }
	 			 }
	 		 })";

		cout << vegaDataFooter << endl;
	 	} else {
			string vegaDataFooter = R"(
		 ]},
			"mark": {
				"type": "boxplot",
				"extent": "min-max"
			},
			"encoding": {
				"x": {"field": "Composers", "type": "nominal"},
				"color": {"field": "Composers", "type": "nominal", "legend": null},
				"y": {
					"field": "CMR note density permil",
					"type": "quantitative",
					"scale": {"zero": false}
				}
			}
		})";
			cout << vegaDataFooter << endl;
	 }
}



////////////////////////////
//
// Tool_cmr::printHtmlPlot --
//

void Tool_cmr::printHtmlPlot(void) {

	string header = R"(<!DOCTYPE html>
<html>
<head>
<title>Sortcount output</title>
<script src="https://cdn.jsdelivr.net/npm/vega@4.4.0"></script>
<script src="https://cdn.jsdelivr.net/npm/vega-lite@3.0.0-rc12"></script>
<script src="https://cdn.jsdelivr.net/npm/vega-embed@3.29.1"></script>
</head>
<body>
<div id="plotarea"></div>
<script type="text/javascript">
var mydata =)";
	cout << header << endl;

	printVegaPlot();

	string footer = R"(vegaEmbed('#plotarea', mydata);
</script>
</body>
</html>)";
	cout << footer << endl;
}



//////////////////////////////
//
// Tool_cmr::finally --
//

void Tool_cmr::finally(void) {
	//cerr << "\nFUNCTION RUN AFTER ALL INPUT FILES HAVE BEEN PROCESSED" << endl;
	if ((m_vegaQ || m_vegaCountQ || m_vegaStrengthQ) && (m_htmlQ)) {
		printVegaPlot();
	} else if ((m_vegaQ || m_vegaCountQ || m_vegaStrengthQ) && !(m_htmlQ)) {
		printHtmlPlot();
	} else if (m_cmrCount.size() > 1) {
		double meanCmrCount = Convert::mean(m_cmrCount);
		double stdDevCmrCount = Convert::standardDeviation(m_cmrCount);

		vector<double> cmrNoteDensities(m_cmrNoteCount.size());
		for (int i=0; i < (int)cmrNoteDensities.size(); i++) {
			cmrNoteDensities[i] = (double)m_cmrNoteCount[i] / m_scoreNoteCount[i];
		}

		double meanCmrNoteDen = Convert::mean(cmrNoteDensities);
		double stdDevCmrNoteDen = Convert::standardDeviation(cmrNoteDensities);

		cout << "CMR count mean: " << meanCmrCount << endl;
		cout << "CMR count standard deviation: " << stdDevCmrCount << endl;
		cout << "CMR note density mean: " << meanCmrNoteDen * 1000 << " permil " << endl;
		cout << "CMR note density standard deviation: " << stdDevCmrNoteDen * 1000 << " permil " << endl;
	}

}



//////////////////////////////
//
// Tool_cmr::printStatistics --
//

void Tool_cmr::printStatistics(HumdrumFile& infile) {
	int all_note_count = countNotesInScore(infile);

	m_humdrum_text << "!!!!!!!!!! CMR INFO !!!!!!!!!!!!!!!!!!!!!!!!!!!!!"  << endl;

	m_humdrum_text << "!!!cmr_groups: "  << getGroupCount()     << endl;
	m_humdrum_text << "!!!cmr_notes: "   << getGroupNoteCount() << endl;
	m_humdrum_text << "!!!score_notes: " << all_note_count      << endl;

	double groupDensity = ((double)getGroupCount() / all_note_count);
	double groupNoteDensity = ((double)getGroupNoteCount() / all_note_count);

	m_humdrum_text << "!!!cmr_group_density: " << groupDensity * 1000.0 << " permil" << endl;
	m_humdrum_text << "!!!cmr_note_density: "  << groupNoteDensity * 1000.0 << " permil" << endl;

	printGroupStatistics(infile);

	m_humdrum_text << "!!!!!!!!!! END CMR INFO !!!!!!!!!!!!!!!!!!!!!!!!!" << endl;
}



/////////////////////////////
//
// printGroupStatistics -- Print information about individual CMR groups.
//

void Tool_cmr::printGroupStatistics(HumdrumFile& infile) {
	int pcounter = 1;
	int ncounter = -1;
	for (int i=0; i<(int)m_noteGroups.size(); i++) {
		if (!m_showMergedQ) {
			if (!m_noteGroups[i].isValid()) {
				continue;
			}
		}
		double groupDuration = m_noteGroups[i].getGroupDuration().getFloat() / 4.0;
		if (m_halfQ) {
			groupDuration *= 2;
		}
		int track = m_noteGroups[i].getTrack();

		m_humdrum_text << "!!!!!!!!!! CMR GROUP INFO !!!!!!!!!!!!!!!!!!!!!!!"               << endl;
		if (m_noteGroups.at(i).isValid()) {
			m_humdrum_text << "!!!cmr_group_num: "  << pcounter++                            << endl;
		} else {
			m_humdrum_text << "!!!cmr_merge_num: "  << ncounter--                            << endl;
		}
		if (track > 0) {
			m_humdrum_text << "!!!cmr_track: "      << track                                 << endl;
			m_humdrum_text << "!!!cmr_part: "       << m_partNames.at(track)                 << endl;
			m_humdrum_text << "!!!voice_pitch_min: "<< m_minPitch.at(track)                  << endl;
			m_humdrum_text << "!!!voice_pitch_max: "<< m_maxPitch.at(track)                  << endl;
		}
		m_humdrum_text << "!!!cmr_start_line: "    << m_noteGroups[i].getStartLineNumber()  << endl;
		m_humdrum_text << "!!!cmr_start_field: "   << m_noteGroups[i].getStartFieldNumber() << endl;
		m_humdrum_text << "!!!cmr_start_measure: " << m_noteGroups[i].getMeasureBegin()     << endl;
		m_humdrum_text << "!!!cmr_end_measure: "   << m_noteGroups[i].getMeasureEnd()       << endl;
		// Durations are in units of whole notes (semibreves):
		m_humdrum_text << "!!!cmr_duration: "      << groupDuration << m_durUnit            << endl;
		m_humdrum_text << "!!!cmr_strength: "      << m_noteGroups[i].getGroupStrength()    << endl;
		m_humdrum_text << "!!!cmr_direction: "     << m_noteGroups[i].getDirection()        << endl;
		m_humdrum_text << "!!!cmr_note_count: "    << m_noteGroups[i].getNoteCount()        << endl;
		m_humdrum_text << "!!!cmr_pitch: "         << m_noteGroups[i].getPitch()            << endl;
		m_humdrum_text << "!!!cmr_pitches: ";
		if (m_noteGroups.at(i).getDirection() == -1) {
			m_noteGroups.at(i).printNotes(m_humdrum_text, m_markerDown);
		} else {
			m_noteGroups.at(i).printNotes(m_humdrum_text, m_markerUp);
		}
		m_humdrum_text << endl;
	}
}



//////////////////////////////
//
// Tool_cmr::getPartNames -- partNames is indexed by track.
//

void Tool_cmr::getPartNames(vector<string>& partNames, HumdrumFile& infile) {
	partNames.clear();
	partNames.resize(infile.getMaxTrack() + 1);

	for (int i=0; i<infile.getLineCount(); i++) {
		if (infile[i].isData()) {
			break;
		}
		if (!infile[i].isInterpretation()) {
			continue;
		}
		for (int j=0; j<infile[i].getFieldCount(); j++) {
			HTp token = infile.token(i, j);
			int track = token->getTrack();
			if (!partNames[track].empty()) {
				continue;
			}
			if (token->isInstrumentAbbreviation()) {
				if (token->size() > 3) {
					partNames[track] = token->substr(3);
				}
			}
		}
	}

	for (int i=0; i<infile.getLineCount(); i++) {
		if (infile[i].isData()) {
			break;
		}
		if (!infile[i].isInterpretation()) {
			continue;
		}
		for (int j=0; j<infile[i].getFieldCount(); j++) {
			HTp token = infile.token(i, j);
			int track = token->getTrack();
			if (!partNames[track].empty()) {
				continue;
			}
			if (token->isInstrumentName()) {
				if (token->size() > 3) {
					partNames[track] = token->substr(3);
				}
			}
		}
	}

}



//////////////////////////////
//
// Tool_cmr::prepareHtmlReport -- Create HTML template for displaying CMR analysis data.
//

void Tool_cmr::prepareHtmlReport(void) {
	string htmlReport = R"(!!@@BEGIN: PREHTML
!!@CONTENT:
!!<h1 class='cmr'>Conspicuous Melodic Repetition</h1>
!!<table class='gcmr'>
!!   <tr><td>Group density</td><td>@{cmr_group_density}</td></tr>
!!   <tr><td>Group note density</td><td>@{cmr_note_density}</td></tr>
!!</table>
!! <br/>
!! @{printTable:}
!!<style>
!!   h1.cmr { font-size: 24px; }
!!   table.cmr tr:not(:first-child):hover { background: #ff000011; }
!!   table.cmr { max-width: 500px; }
!!   table.gcmr td:nth-child(2) { width:100%; }
!!   table.gcmr td:first-child {white-space: pre; text-align: right; padding-right: 10px; font-weight: bold; }
!!   table.gcmr td:first-child::after { content: ':'; }
!!</style>
!!@JAVASCRIPT:
!!function printTable(refs1, refs2, language) {
!!   let numbers = refs2.cmr_group_num;
!!   let durations = refs2.cmr_duration;
!!   let pitches = refs2.cmr_pitch;
!!   let minpitch = refs2.voice_pitch_min;
!!   let maxpitch = refs2.voice_pitch_max;
!!   let strengths = refs2.cmr_strength;
!!   let count = refs2.cmr_note_count;
!!   let directions = refs2.cmr_direction;
!!   let parts = refs2.cmr_part;
!!   let smeasure = refs2.cmr_start_measure;
!!   let emeasure = refs2.cmr_end_measure;
!!   let output = '';
!!   output += `<table class='cmr'>`;
!!   output += '<tr>';
!!   output += '<th>CMR</th>';
!!   output += '<th>Notes</th>';
!!   output += '<th>Pitch</th>';
!!   output += '<th>Range</th>';
!!   output += '<th>Duration</th>';
!!   output += '<th>Strength</th>';
!!   output += '<th>Measure(s)</th>';
!!   output += '</tr>';
!!   for (let i=0; i<numbers.length; i++) {
!!      let direction = parseInt(directions[i].value) == 1 ? "+" : "-";;
!!      output += '<tr>';
!!      output += `<td>${numbers[i].value}</td>`;
!!      output += `<td>${count[i].value}</td>`;
!!      output += `<td>${direction}${pitches[i].value}</td>`;
!!      output += `<td>${minpitch[i].value}&ndash;${maxpitch[i].value}</td>`;
!!      output += `<td>${durations[i].value}</td>`;
!!      output += `<td>${strengths[i].value}</td>`;
!!      let location = '';
!!      let part = parts[i].value;
!!      let startm = parseInt(smeasure[i].value);
!!      let endm   = parseInt(emeasure[i].value);
!!      if (startm !== endm) {
!!         location = `${startm}&ndash;${endm}`;
!!      } else {
!!         location = `${startm}`;
!!      }
!!      if (part) {
!!         location = `${part}: ${location}`;
!!      }
!!      output += `<td>${location}</td>`;
!!      output += '</tr>';
!!   }
!!   output += '</table>';
!!   return output;
!!}
!!@@END: PREHTML)";

	m_humdrum_text << htmlReport << endl;
}



//////////////////////////////
//
// Tool_cmr::mergeOverlappingPeaks -- Merge overlapping cmr groups.
//    Groups that need to be merged:
//    * Have the same track number (same staff)
//    * Have the same MIDI pitch
//    * Have and starttime for one group that starts before or on the
//      endtime of another group.
//    Merged groups are indicated as inactive if their index is set to
//    a negative value.
//

void Tool_cmr::mergeOverlappingPeaks(void) {
	// This algorithm does not handle multiple groups that
	// need merging, so redo the overlap identification
	// several more times to enture multiple groups are
	// merged (that is what the for-loop is for, which
	// keeps merging until there are no mergers left).

	for (int k=0; k<100; k++) {
		bool mergers = false;
		for (int i=0; i<(int)m_noteGroups.size(); i++) {
			for (int j=i+1; j<(int)m_noteGroups.size(); j++) {
				mergers |= checkGroupPairForMerger(m_noteGroups.at(i), m_noteGroups.at(j));
			}
		}
		if (!mergers) {
			break;
		}
	}

	adjustGroupSerials();
}



//////////////////////////////
//
// adjustGroupSerials -- Renumber groups after mergers.
//

void Tool_cmr::adjustGroupSerials(void) {
	int pcounter = 1;
	int ncounter = -1;
	for (int i=0; i<(int)m_noteGroups.size(); i++) {
		if (m_noteGroups.at(i).getSerial() > 0) {
			m_noteGroups.at(i).setSerial(pcounter++);
		} else {
			m_noteGroups.at(i).setSerial(ncounter--);
		}
	}
}



//////////////////////////////
//
// Tool_cmr::checkGroupPairForMerger --
//    * Have and starttime for one group that starts before or on the
//      endtime of another group.
//    Merged groups are indicated as inactive if their index is set to
//    a negative value.
// Return value is true if there was a merger; otherwise, returns false.
//

bool Tool_cmr::checkGroupPairForMerger(cmr_group_info& group1, cmr_group_info& group2) {
	if (!group1.isValid()) {
		return false;
	}
	if (!group2.isValid()) {
		return false;
	}
	if (&group1 == &group2) {
		// Same group.
		return false;
	}

	// Groups must have the same track number (i.e., the same staff/part):
	if (group1.getTrack() != group2.getTrack()) {
		return false;
	}

	// Groups must have the same MIDI pitch:
	int midi1 = group1.getMidiPitch();
	int midi2 = group2.getMidiPitch();
	if (midi1 != midi2) {
		return false;
	}

	// Check if they overlap:
	HumNum start1 = group1.getStartTime();
	HumNum start2 = group2.getStartTime();
	// HumNum end1   = group1.getEndTime();
	// HumNum end2   = group2.getEndTime();

	if (start1 == start2) {
		if (group1.getNoteCount() > group2.getNoteCount()) {
			return group1.mergeGroup(group2);
		} else {
			return group2.mergeGroup(group1);
		}
	} else if (start1 < start2) {
		return group1.mergeGroup(group2);
	} else {
		return group2.mergeGroup(group1);
	}
}



//////////////////////////////
//
// Tool_cmr::printAnalysisData -- For debugging.
//

void Tool_cmr::printAnalysisData(void) {
	string partname = m_partNames.at(m_track);
	cerr << "NOTELIST FOR " << partname << " ===================================" << endl;
	cerr << "BAR\tMIDI\tLPEAK\tMETLEV\tSYNC\tLEAP\tNOTES\n";
	for (int i=0; i<(int)m_notelist.size(); i++) {
		cerr << m_barNum.at(m_notelist.at(i).at(0)->getLineIndex());
		cerr << "\t";
		cerr << m_midinums.at(i);
		cerr << "\t";
		cerr << m_localpeaks.at(i);
		cerr << "\t";
		cerr << m_metlevs.at(i);
		cerr << "\t";
		cerr << m_syncopation.at(i);
		cerr << "\t";
		cerr << m_leapbefore.at(i);
		cerr << "\t";
		for (int j=0; j<(int)m_notelist.at(i).size(); j++) {
			cerr << m_notelist.at(i).at(j) << " ";
		}
		cerr << endl;
	}
	cerr << "==================================================" << endl;
}



//////////////////////////////
//
// Tool_cmr::markNotes -- mark notes in list that are true
//     with given marker.
//

void Tool_cmr::markNotes(vector<vector<HTp>>& notelist, vector<bool> localpeaks, const string& marker) {
	bool negative = false;
	if (marker == m_local_marker_n) {
		negative = true;
	}
	for (int i=0; i<(int)localpeaks.size(); i++) {
		if (!localpeaks[i]) {
			continue;
		}
		for (int j=0; j<(int)notelist.at(i).size(); j++) {
			string text = *notelist[i][j];
			if (text.find(marker) == string::npos) {
				text += marker;
				notelist[i][j]->setText(text);
				if (negative) {
					m_local_count_n++;
				} else {
					m_local_count++;
				}
			}
		}
	}
}



//////////////////////////////
//
// Tool_cmr::flipMidiNumbers -- Invert midi numbers to search for minima rather than maxima.
//

void Tool_cmr::flipMidiNumbers(vector<int>& midinums) {
	for (int i=0; i<(int)midinums.size(); i++) {
		if (midinums[i] == 0) {
			continue;
		}
		int flippedMidiNum = (midinums[i] * -1) + 128;
		midinums[i] = flippedMidiNum;
	}
}



//////////////////////////////
//
// Tool_cmr::markNotesInScore -- Mark all valid CMR notes.
//

void Tool_cmr::markNotesInScore(void) {
	for (int i=0; i<(int)m_noteGroups.size(); i++) {
		if (m_noteGroups.at(i).isValid()) {
			if (m_noteGroups.at(i).getDirection() == -1) {
				m_noteGroups.at(i).markNotes(m_markerDown);
			} else {
				m_noteGroups.at(i).markNotes(m_markerUp);
			}
		}
	}
}



//////////////////////////////
//
// Tool_cmr::getLocalPeakNotes -- Throw away notes/rests that are not cmrs.
//

void Tool_cmr::getLocalPeakNotes(vector<vector<HTp>>& newnotelist,
		vector<vector<HTp>>& oldnotelist, vector<bool>& localpeaks) {

	// durations == duration of notes in quarter-note units
	vector<double> durations;
	getDurations(durations, oldnotelist);

	// strongbeat == true if on a beat (whole-note metric postion).
	vector<bool> strongbeat;
	getBeat(strongbeat, oldnotelist);


	////////////////////////////
	//
	// Refinement to add to following loop: If the note has
	// a duration less than or equal two 2 (half note), and
	// the note is not on a beat then do not add it to the
	// newnotelist vector.
	//
	////////////////////////////

	newnotelist.clear();
	for (int i=0; i<(int)localpeaks.size(); i++) {
		if ((durations[i] <= 2) && (strongbeat[i] == false)) {
			continue;
		}
		if (localpeaks[i]) {
			newnotelist.push_back(oldnotelist[i]);
		}
	}

}



//////////////////////////////
//
// Tool_cmr::identifyLocalPeaks -- Identify notes that are higher than their
//    adjacent neighbors.  The midinums are MIDI note numbers (integers)
//    for the pitch, with higher number meaning higher pitches.  Rests are
//    the value 0.  Do not assign a note as a cmr note if one of the
//    adjacent notes is a rest. (This could be refined later, such as ignoring
//    short rests).
//

void Tool_cmr::identifyLocalPeaks(vector<bool>& localpeaks, vector<int>& midinums) {
	localpeaks.resize(midinums.size());
	fill(localpeaks.begin(), localpeaks.end(), false);

	if (midinums.size() < 3) {
		// Avoid silly cases.
		return;
	}

	for (int i=0; i<(int)midinums.size() - 1; i++) {
		if ((i > 0) && (midinums.at(i-1) <= 0) && (midinums.at(i+1) <= 0)) {
			// Ignore notes that have rests on both sides.
			continue;
		} else if (midinums.at(i) <= 0) {
			// Ignore rests.
			continue;
		}
		if ((i == 0) && (midinums.at(i) > 0) && (midinums.at(i) > midinums.at(i+1))) {
			// Allow for peak at start of music
			localpeaks.at(i) = true;
		} else if ((i == (int)midinums.size() - 1) && (midinums.back() > 0) && (midinums.back() > midinums.at((int)midinums.size() - 2))) {
			// Allow for peak at end of music
			localpeaks.at(i) = true;
		} else if ((i > 0) && (midinums.at(i) > midinums.at(i-1)) && (midinums.at(i+1) == 0)) {
			// Allow rest after peak note.
			localpeaks.at(i) = true;
		} else if ((i > 0) && (midinums.at(i-1) == 0) && (midinums.at(i) > midinums.at(i+1))) {
			// Allow rest before peak note.
			localpeaks.at(i) = true;
		} else if ((i > 0) && (midinums.at(i) > midinums.at(i-1)) && (midinums.at(i) > midinums.at(i+1))) {
			// Check neighboring notes.
			localpeaks.at(i) = true;
		}
	}
}



//////////////////////////////
//
// Tool_cmr::identifyPeakSequence -- Identify a sequence of local cmrs that can form
//       a cmr sequence
//
// Input variables:
//      globalcmrnotes -- output data that is set to true if the note is part of a
//                         global cmr note sequence.  This vector has the same size
//                         as the cmrmidinums vector and the values are initially set
//                         to false.  This function will set notes that are part of a sequence
//                         of local cmr notes (with length m_cmrNum) to true.
//      cmrmidinums    -- MIDI number vector for all local cmr notes.
//      notes           -- double vector of Humdrum notes (needed to get timestamps).
//
// Member variables that are needed in the Algorithm:
//    int    m_cmrNum: Number of consecutive local cmrnotes needed to mark global cmr.
//    double m_cmrDur: Maximum duration of the first/last cmr note in global cmr sequence.
//
//

void Tool_cmr::identifyPeakSequence(vector<bool>& globalcmrnotes, vector<int>& cmrmidinums,
		vector<vector<HTp>>& notes) {

	// Set initial positions of globalcmrnotes to false:
	globalcmrnotes.resize(cmrmidinums.size());
	fill(globalcmrnotes.begin(), globalcmrnotes.end(), false);

	// Get the timestamps of all local peak notes:
	vector<double> timestamps(notes.size(), 0.0);
	for (int i=0; i<(int)notes.size(); i++) {
		timestamps[i] = notes[i][0]->getDurationFromStart().getFloat();
	}

	// The code below (under Algorithms) will set notes identified as a "global cmr" to true
	// in this vector.

	///////////////////////////////////////////
	//
	// Algorithm:
	//
	//    * Loop through each element in the cmrmidinums vector checking if it is a part of
	//      a longer cmr sequence of the same MIDI note number.  The loop will start at
	//      index 0 and go until but not including cmrmidinums.size() - m_cmrNum.
	//
	//    * A "global cmr" means that there are m_cmrNum MIDI note numbers in a row in cmrmidinums.
	//      (all of the cmr notes have to be the same MIDI note number).
	//
	//    *  In addition, the time difference between the starting and ending note of the cmr
	//       sequence has to be equal to or less than m_cmrDur (6 whole notes by default).
	//       Subtract the timestamp of the last and first notes and see if the duration between
	//       these notes is equal or less than m_cmrDur.
	//
	//////////////////////////////////////////

	for (int i=0; i<(int)cmrmidinums.size() - m_cmrNum; i++) {
		bool match = true;
		bool accented = isMelodicallyAccented(i);
		for (int j=1; j<m_cmrNum; j++) {
			accented |= isMelodicallyAccented(i+j);
			if (cmrmidinums.at(i+j) != cmrmidinums.at(i+j-1)) {
				match = false;
				break;
			}
		}
		if (!match) {
			continue;
		}
		if ((!m_naccentedQ) && (!accented)){
			continue;
		}
		if ((m_naccentedQ) && (accented)) {
			continue;
		}

		HumNum duration = timestamps.at(i + m_cmrNum - 1) - timestamps.at(i);
		if (duration.getFloat() > m_cmrDur) {
			continue;
		}

		// Store the (potential) CMR:
		m_noteGroups.resize(m_noteGroups.size() + 1);
		for (int j=0; j<m_cmrNum; j++) {
			m_noteGroups.back().addNote(notes.at(i+j), m_barNum);
		}

		// variables to do cmr group mergers later:
		m_noteGroups.back().setSerial(i+1);

		for (int j=0; j<m_cmrNum; j++) {
			globalcmrnotes[i+j] = true;
		}
	}
}



//////////////////////////////
//
// Tool_cmr::getMidiNumbers -- convert note tokens into MIDI note numbers.
//    60 = middle C (C4), 62 = D4, 72 = C5, 48 = C3.
//

void Tool_cmr::getMidiNumbers(vector<int>& midinums, vector<vector<HTp>>& notelist) {

	midinums.resize(notelist.size());

	fill(midinums.begin(), midinums.end(), 0); // fill with rests by default
	for (int i=0; i<(int)notelist.size(); i++) {
		midinums.at(i) = Convert::kernToMidiNoteNumber(notelist.at(i).at(0));
		if (midinums.at(i) < 0) {
			// Set rests to be 0
			midinums.at(i) = 0;
		}
	}
}



//////////////////////////////
//
// Tool_cmr::getSyncopation -- Identify if notes are syncopated or not.
//

void Tool_cmr::getSyncopation(std::vector<bool>& synco, std::vector<std::vector<HTp>>& notelist) {
	synco.resize(notelist.size());
	for (int i=0; i<(int)synco.size(); i++) {
		synco.at(i) = isSyncopated(notelist.at(i).at(0));
	}
}



//////////////////////////////
//
// Tool_cmr::getLeapBefore -- Identify if notes have melodic leaps up before them.
//

void Tool_cmr::getLeapBefore(std::vector<bool>& leap, std::vector<int>& midinums) {
	leap.resize(midinums.size());
	fill(leap.begin(), leap.end(), false);
	for (int i=1; i<(int)leap.size(); i++) {
		int note1 = midinums.at(i);
		if (note1 <= 0) {
			continue;
		}
		int note2 = midinums.at(i-1);
		// Consider duration of rests here.
		if ((note2 <= 0) && (i >= 2)) {
			note2 = midinums.at(i-2);
		}
		if (note2 <= 0) {
			continue;
		}
		int interval = note1 - note2;
		if (interval > 2) {
			leap.at(i) = true;
		}
	}
}



//////////////////////////////
//
// Tool_cmr::getMetlev -- convert note tokens into Metric levels.
//

void Tool_cmr::getMetlev(std::vector<double>& metlevs, std::vector<std::vector<HTp>>& notelist) {
	metlevs.resize(notelist.size());
	for (int i=0; i<(int)metlevs.size(); i++) {
		metlevs.at(i) = cmr_note_info::getMetricLevel(notelist.at(i).at(0));
	}
}



//////////////////////////////
//
// Tool_cmr::getNoteList -- Return a list of the notes and rests for
//     a part, with the input being the starting token of the part from
//     which the note list should be extracted.  The output is a two-
//     dimensional vector.  The first dimension is for the list of notes,
//     and the second dimension is used to store any subsequent tied notes
//     so that they can be marked and highlighted in the score.
//

void Tool_cmr::getNoteList(vector<vector<HTp>>& notelist, HTp starting) {
	notelist.clear();
	notelist.reserve(2000);

	int lastpitch = -1;

	// HTp previous = NULL;
	HTp current = starting;
	while (current) {
		if (!current->isData()) {
			current = current->getNextToken();
			continue;
		}
		if (current->isNull()) {
			current = current->getNextToken();
			continue;
		}
		if (current->isNoteSustain()) {
			if (notelist.size() > 0) {
				notelist.back().push_back(current);
			}
			// previous = current;
			current = current->getNextToken();
			continue;
		}
		if (current->isRest()) {
			if ((!notelist.empty()) && notelist.back().at(0)->isRest()) {
				notelist.back().push_back(current);
				// previous = current;
				current = current->getNextToken();
				lastpitch = -1;
				continue;
			}
		}
		int pitch = current->getMidiPitch();
		if (pitch == lastpitch) {
			if (notelist.size() > 0) {
				notelist.back().push_back(current);
			}
			// previous = current;
			current = current->getNextToken();
			continue;
		}
		lastpitch = pitch;
		notelist.resize(notelist.size() + 1);
		notelist.back().push_back(current);
		if (!current->isRest()) {
			m_noteCount++;
		}
		// previous = current;
		current = current->getNextToken();
	}

	// Remove any rests that are shorter or equal to m_shortRest:
	vector<vector<HTp>> output;
	output.reserve(notelist.size());
	for (int i=0; i<(int)notelist.size() - 1; i++) {
		if (!notelist.at(i).at(0)->isRest()) {
			output.push_back(notelist.at(i));
			continue;
		}
		// get the duration of the (multi-rest):
		HumNum restStart = notelist.at(i).at(0)->getDurationFromStart();
		HumNum noteStart = notelist.at(i+1).at(0)->getDurationFromStart();
		HumNum duration = noteStart - restStart;
		if (duration.getFloat() > m_smallRest) {
			output.push_back(notelist.at(i));
		}
	}
}



//////////////////////////////
//
// getNoteDurations --
//

void  Tool_cmr::getDurations(vector<double>& durations, vector<vector<HTp>>& notelist) {
	durations.resize(notelist.size());
	for (int i=0; i<(int)notelist.size(); i++) {
		HumNum duration = notelist.at(i).at(0)->getTiedDuration();
		durations.at(i) = duration.getFloat();
	}
}



//////////////////////////////
//
// Tool_cmr::getBeat --
//

void Tool_cmr::getBeat(vector<bool>& metpos, vector<vector<HTp>>& notelist) {
	metpos.resize(notelist.size());
	for (int i=0; i<(int)notelist.size(); i++) {
		HumNum position = notelist.at(i).at(0)->getDurationFromBarline();
		if (position.getDenominator() != 1) {
			metpos.at(i) = false;
		} if (position.getNumerator() % 4 == 0) {
			metpos.at(i) = true;
		} else {
			metpos.at(i) = false;
		}

	}
}



//////////////////////////////
//
// Tool_cmr::isOnStrongBeat --
//

bool Tool_cmr::isOnStrongBeat(HTp token) {
	HumNum position = token->getDurationFromBarline();
	if (position.getDenominator() != 1) {
		return false;
	} if (position.getNumerator() % 4 == 0) {
		return true;
	} else {
		return false;
	}
}



//////////////////////////////
//
// Tool_cmr::isMelodicallyAccented --
//

bool Tool_cmr::isMelodicallyAccented(int index) {
	return m_leapbefore.at(index) || m_syncopation.at(index);
}



//////////////////////////////
//
// Tool_cmr::isSyncopated --
//

bool Tool_cmr::isSyncopated(HTp token) {
	return cmr_note_info::isSyncopated(token);
}



//////////////////////////////
//
// Tool_cmr::countNotesInScore -- Count the number of notes in a score,
//     ignoring tied notes that are not attacks, and treating chords as a single note.
//

int Tool_cmr::countNotesInScore(HumdrumFile& infile) {
	int counter = 0;
	for (int i=0; i<infile.getLineCount(); i++) {
		if (!infile[i].isData()) {
			continue;
		}
		for (int j=0; j<infile[i].getFieldCount(); j++) {
			HTp token = infile.token(i, j);
			if (!token->isKern()) {
				continue;
			}
			if (token->isNull()) {
				continue;
			}
			if (token->isRest()) {
				continue;
			}
			if (token->isSecondaryTiedNote()) {
				continue;
			}
			counter++;

		}
	}
	return counter;
}



//////////////////////////////
//
// Tool_cmr::getGroupCount -- Return the number of groups.
//

int Tool_cmr::getGroupCount(void) {
	int output = 0;
	for (int i=0; i<(int)m_noteGroups.size(); i++) {
		if (m_noteGroups[i].isValid()) {
			output++;
		}
	}
	return output;
}



//////////////////////////////
//
// Tool_cmr::getGroupNoteCount -- Return the number notes in all groups.
//

int Tool_cmr::getGroupNoteCount(void) {
	int output = 0;
	for (int i=0; i<(int)m_noteGroups.size(); i++) {
		if (m_noteGroups[i].isValid()) {
			output += m_noteGroups[i].getNoteCount();
		}
	}
	return output;
}



//////////////////////////////
//
// Tool_cmr::getStrengthScore -- Return the total strength score for all CMRs.
//

int Tool_cmr::getStrengthScore(void) {
	int output = 0;
	for (int i=0; i<(int)m_noteGroups.size(); i++) {
		if (m_noteGroups[i].isValid()) {
			output += m_noteGroups[i].getGroupStrength();
		}
	}
	return output;
}



/////////////////////////////
//
// Tool_cmr::getVocalRange --
//

void Tool_cmr::getVocalRange(vector<string>& minpitch, vector<string>& maxpitch, vector<vector<HTp>>& notelist) {
	if (notelist.empty()) {
		return;
	}
	int track = notelist.at(0).at(0)->getTrack();
	HTp mintok = NULL;
	HTp maxtok = NULL;
	int minb40 = -1;
	int maxb40 = -1;

	for (int i=0; i<(int)notelist.size(); i++) {
		if (notelist.at(i).at(0)->isRest()) {
			continue;
		}

		if (mintok == NULL) {
			mintok = notelist.at(i).at(0);
			minb40 = Convert::kernToBase40(mintok);
		} else {
			int b40 = Convert::kernToBase40(notelist.at(i).at(0));
			if (b40 < minb40) {
				minb40 = b40;
				mintok = notelist.at(i).at(0);
			}
		}

		if (maxtok == NULL) {
			maxtok = notelist.at(i).at(0);
			maxb40 = Convert::kernToBase40(maxtok);
		} else {
			int b40 = Convert::kernToBase40(notelist.at(i).at(0));
			if (b40 > maxb40) {
				maxb40 = b40;
				maxtok = notelist.at(i).at(0);
			}
		}
	}

	minpitch.at(track) = getPitch(mintok);
	maxpitch.at(track) = getPitch(maxtok);
}



//////////////////////////////
//
// Tool_cmr::getPitch -- Return scientific pitch name.
//

string Tool_cmr::getPitch(HTp token) {
	if (!token) {
		return "";
	}
	if (token->isRest()) {
		return "R";
	} else {
		int octave = Convert::kernToOctaveNumber(token);
		int accidentals = Convert::kernToAccidentalCount(token);
		int dpc = Convert::kernToDiatonicPC(token);
		string output;
		switch (dpc) {
			case 0: output += "C"; break;
			case 1: output += "D"; break;
			case 2: output += "E"; break;
			case 3: output += "F"; break;
			case 4: output += "G"; break;
			case 5: output += "A"; break;
			case 6: output += "B"; break;
		}
		for (int i=0; i<abs(accidentals); i++) {
			if (accidentals < 0) {
				output += "b";
			} else {
				output += "#";
			}
		}
		output += to_string(octave);
		return output;
	}
}



//////////////////////////////
//
// Tool_cmr::addGroupNumbersToScore --
//

void Tool_cmr::addGroupNumbersToScore(HumdrumFile& infile) {
	for (int i=0; i<(int)m_noteGroups.size(); i++) {
		if (m_noteGroups.at(i).getSerial() <= 0) {
			continue;
		}
		HTp token = m_noteGroups.at(i).getFirstToken();
		if (!token) {
			continue;
		}
		int serial = m_noteGroups.at(i).getSerial();
		int direction = m_noteGroups.at(i).getDirection();
		addGroupNumberToScore(infile, token, serial, direction);
	}
}



//////////////////////////////
//
// Tool_cmr::addGroupNumberToScore
//

void Tool_cmr::addGroupNumberToScore(HumdrumFile& infile, HTp note, int number, int dir) {
	if (!note) {
		return;
	}

	int index = note->getLineIndex();
	int field = note->getFieldIndex();

	if (index > 0) {
		if (infile[index-1].isCommentLocal()) {
			HTp token = infile.token(index-1, field);
			if (*token == "!") {
				string output = getLocalLabelToken(number, dir);
				token->setText(output);
				return;
			}
		} else {
			string newline;
			for (int i=0; i<infile[index].getFieldCount(); i++) {
				if (i == field) {
					string newtoken = getLocalLabelToken(number, dir);
					newline += newtoken;
				} else {
					newline += "!";
				}
				if (i < infile[index].getFieldCount() - 1) {
					newline += "\t";
				}
			}
			infile.insertLine(index, newline);
		}
	}
}



//////////////////////////////
//
// Tool_cmr::getLocalLabelToken --
//

string Tool_cmr::getLocalLabelToken(int number, int dir) {
	string output = "!LO:TX:";
	if (dir > 0) {
		output += "a:color=";
		output += m_colorUp;
	} else {
		output += "b:color=";
		output += m_colorDown;
	}
	output += ":t=";
	output += to_string(number);
	return output;
}





/////////////////////////////////
//
// Tool_colorgroups::Tool_colorgroups -- Set the recognized options for the tool.
//

Tool_colorgroups::Tool_colorgroups(void) {
	define("A=s:crimson",    "color for group A");
	define("B=s:dodgerblue", "color for group B");
	define("C=s:purple",     "color for group C");
	define("command=b",      "print shed command only");
}



/////////////////////////////////
//
// Tool_colorgroups::run -- Do the main work of the tool.
//

bool Tool_colorgroups::run(HumdrumFileSet& infiles) {
	bool status = true;
	for (int i=0; i<infiles.getCount(); i++) {
		status &= run(infiles[i]);
	}
	return status;
}


bool Tool_colorgroups::run(const string& indata, ostream& out) {
	HumdrumFile infile(indata);
	bool status = run(infile);
	if (hasAnyText()) {
		getAllText(out);
	} else {
		out << infile;
	}
	return status;
}


bool Tool_colorgroups::run(HumdrumFile& infile, ostream& out) {
	bool status = run(infile);
	if (hasAnyText()) {
		getAllText(out);
	} else {
		out << infile;
	}
	return status;
}


bool Tool_colorgroups::run(HumdrumFile& infile) {
	initialize();
	processFile(infile);
	return true;
}



//////////////////////////////
//
// Tool_colorgroups::initialize --  Initializations that only have to be done once
//    for all HumdrumFile segments.
//

void Tool_colorgroups::initialize(void) {
	// do nothing
}



//////////////////////////////
//
// Tool_colorgroups::processFile --
//

void Tool_colorgroups::processFile(HumdrumFile& infile) {
	Tool_shed shed;
	vector<string> argv;

	string command = "s/grp:A/color:";
	command += getString("A");
	command += "/I; ";
	command += "s/grp:B/color:";
	command += getString("B");
	command += "/I; ";
	command += "s/grp:C/color:";
	command += getString("C");
	command += "/I";

	if (getBoolean("command")) {
		m_free_text << command << endl;
		return;
	}

	argv.clear();
	argv.push_back("shed"); // name of program (placeholder)
	argv.push_back("-e");
	argv.push_back(command);

	shed.process(argv);
	shed.run(infile);
}





/////////////////////////////////
//
// Tool_colortriads::Tool_colortriads -- Set the recognized options for the tool.
//

Tool_colortriads::Tool_colortriads(void) {
	define("A=b",            "do not color triads with diatonic A root");
	define("B=b",            "do not color triads with diatonic B root");
	define("C=b",            "do not color triads with diatonic C root");
	define("D=b",            "do not color triads with diatonic D root");
	define("E=b",            "do not color triads with diatonic E root");
	define("F=b",            "do not color triads with diatonic F root");
	define("G=b",            "do not color triads with diatonic G root");
	define("a=s:darkviolet", "color for A triads");
	define("b=s:darkorange", "color for B triads");
	define("c=s:limegreen",  "color for C triads");
	define("d=s:royalblue",  "color for D triads");
	define("e=s:crimson",    "color for E triads");
	define("f=s:gold",       "color for F triads");
	define("g=s:skyblue",    "color for G triads");
	define("r|relative=b",   "functional coloring (green = key tonic)");
	define("k|key=s",        "key to transpose coloring to");
	define("commands=b",     "print msearch commands only");
	define("filters=b",      "print filter commands only");
}



/////////////////////////////////
//
// Tool_colortriads::run -- Do the main work of the tool.
//

bool Tool_colortriads::run(HumdrumFileSet& infiles) {
	bool status = true;
	for (int i=0; i<infiles.getCount(); i++) {
		status &= run(infiles[i]);
	}
	return status;
}


bool Tool_colortriads::run(const string& indata, ostream& out) {
	HumdrumFile infile(indata);
	bool status = run(infile);
	if (hasAnyText()) {
		getAllText(out);
	} else {
		out << infile;
	}
	return status;
}


bool Tool_colortriads::run(HumdrumFile& infile, ostream& out) {
	bool status = run(infile);
	if (hasAnyText()) {
		getAllText(out);
	} else {
		out << infile;
	}
	return status;
}


bool Tool_colortriads::run(HumdrumFile& infile) {
	initialize();
	processFile(infile);
	return true;
}



//////////////////////////////
//
// Tool_colortriads::initialize --  Initializations that only have to be done once
//    for all HumdrumFile segments.
//

void Tool_colortriads::initialize(void) {
	m_colorState.resize(7);
	fill(m_colorState.begin(), m_colorState.end(), true);
	if (getBoolean("A")) { m_colorState[0] = false; }
	if (getBoolean("B")) { m_colorState[1] = false; }
	if (getBoolean("C")) { m_colorState[2] = false; }
	if (getBoolean("D")) { m_colorState[3] = false; }
	if (getBoolean("E")) { m_colorState[4] = false; }
	if (getBoolean("F")) { m_colorState[5] = false; }
	if (getBoolean("G")) { m_colorState[6] = false; }

	m_color.resize(7);
	m_color[0] = getString("a");
	m_color[1] = getString("b");
	m_color[2] = getString("c");
	m_color[3] = getString("d");
	m_color[4] = getString("e");
	m_color[5] = getString("f");
	m_color[6] = getString("g");

	m_searches.resize(7);
	m_searches[0] = "(=ace)";
	m_searches[1] = "(=bdf)";
	m_searches[2] = "(=ceg)";
	m_searches[3] = "(=dfa)";
	m_searches[4] = "(=egb)";
	m_searches[5] = "(=fac)";
	m_searches[6] = "(=gbd)";

	m_marks.resize(7);
	m_marks[0] = "V";
	m_marks[1] = "Z";
	m_marks[2] = "@";
	m_marks[3] = "|";
	m_marks[4] = "j";
	m_marks[5] = "+";
	m_marks[6] = "N";


	m_filtersQ  = getBoolean("filters");
	m_commandsQ = getBoolean("commands");
	m_relativeQ = getBoolean("relative");
	m_key       = getString("key");
}


//////////////////////////////
//
// Tool_colortriads::getDiatonicTransposition -- Transpose
//    amount to allow for funcional colors: green = tonic,
//    light blue = dominant, yellow = subdominant, etc.
//    Only the first key designation will be considered, and
//    it must come before any data lines in the score.
//

int Tool_colortriads::getDiatonicTransposition(HumdrumFile& infile) {
	int key;
	char ch;
	int output = 0;
	if (!m_key.empty()) {
		ch = m_key[0];
		if (isupper(ch)) {
			key = m_key.at(0) - 'A';
		} else {
			key = m_key.at(0) - 'a';
		}
		output = 2 - key; // C index is at 2
		if (abs(output) >= 7) {
			output = 0;
		}
		return output;
	}

	for (int i=0; i<infile.getLineCount(); i++) {
		if (infile[i].isData()) {
			break;
		}
		if (!infile[i].isInterpretation()) {
			continue;
		}
		for (int j=0; j<infile[i].getFieldCount(); j++) {
			HTp token = infile.token(i, j);
			if (!token->isKern()) {
				continue;
			}
			if (!token->isKeyDesignation()) {
				continue;
			}
			if (token->size() < 2) {
				continue;
			}
			char ch = token->at(1);
			if (isupper(ch)) {
				key = token->at(1) - 'A';
			} else {
				key = token->at(1) - 'a';
			}
			output = 2 - key; // C index is at 2
			if (abs(output) >= 7) {
				output = 0;
			}
			break;
		}
	}
	return output;
}



//////////////////////////////
//
// Tool_colortriads::processFile --
//

void Tool_colortriads::processFile(HumdrumFile& infile) {
	Tool_msearch msearch;
	vector<string> argv;

	int dtranspose = 0;
	if (m_relativeQ) {
		dtranspose = getDiatonicTransposition(infile);
	}

	int index;
	for (int i=0; i<7; i++) {
		if (dtranspose) {
			index = (i + dtranspose + 70) % 7;
		} else {
			index = i;
		}
		if (m_colorState[index]) {
			argv.clear();
			argv.push_back("msearch"); // name of program (placeholder)
			argv.push_back("-p");
			argv.push_back(m_searches[i]);
			argv.push_back("-m");
			argv.push_back(m_marks[index]);
			argv.push_back("--color");
			argv.push_back(m_color[index]);
			if (m_commandsQ) {
				m_free_text << argv[0];
				for (int j=1; j<(int)argv.size(); j++) {
					if (argv[j] == "|") {
						m_free_text << " '|'";
					} else {
						m_free_text << " " << argv[j];
					}
				}
				m_free_text << endl;
			} else if (m_filtersQ) {
				m_free_text << "!!!filter: " << argv[0];
				for (int j=1; j<(int)argv.size(); j++) {
					if (argv[j] == "|") {
						m_free_text << " '|'";
					} else {
						m_free_text << " " << argv[j];
					}
				}
				m_free_text << endl;
			} else {
				msearch.process(argv);
				msearch.run(infile);
			}
		}
	}
}





// Note state variables for grouping:
#define TYPE_UNDEFINED           9 /* for inactive groupings */
#define TYPE_NoteSustainAttack   3
#define TYPE_NoteAttack          2
#define TYPE_RestAttack          1
#define TYPE_NONE                0
#define TYPE_RestSustain        -1
#define TYPE_NoteSustain        -2
#define TYPE_NoteSustainSustain -3

#define COMPOSITE_TREMOLO_MARKER "||"


/////////////////////////////////
//
// Tool_composite::Tool_composite -- Set the recognized options for the tool.
//

Tool_composite::Tool_composite(void) {
	define("debug=b",                  "print debug statements");
	define("a|append=b",               "append data to end of line (top of system)");
	define("x|extract=b",              "only output composite rhythm spines");
	define("grace=b",                  "include grace notes in composite rhythms");
	define("u|up-stem=b",              "force notes to be up-stem");
	define("C|color-full-composite=b", "color full composite rhythm if score has groups");
	define("l|score-size=d:100.0",     "set staff size of input score (percent)");
	define("L|analysis-size=d:100.0",  "set staff size of analysis staves (percent)");
	define("o|only=s",                 "output notes of given group (A or B)");
	define("r|rhythms=b",              "convert input score to rhythms only.");
	define("e|events=b",               "show event counts on analysis staves.");
	define("F|no-full-composite=b",    "do not do full composite rhythm analysis");
	define("c|coincidence=b",          "do coincidence rhythm analysis");
	define("g|group|groups=b",         "do group rhythm analysis");
	define("m|mark=b",                 "mark coincidences in group analysis and input score");
	define("M|mark-input=b",           "mark coincidences in input score");

	// Numeric analysis options:
	define("A|analysis|analyses=s",    "list of numeric analysis features to extract");

	// Styling for numeric analyses;
	define("Z|no-zeros|no-zeroes=b",   "do not show zeros in analyses.");
}



//////////////////////////////
//
// Tool_composite::initialize -- Prepare interface variables.
//

void Tool_composite::initialize(HumdrumFile& infile) {
	m_debugQ   = getBoolean("debug");
	m_appendQ  = getBoolean("append");
	m_extractQ = getBoolean("extract");
	if (m_extractQ) {
		m_appendQ = false;
		m_prependQ = false;
	}
	m_graceQ   = getBoolean("grace");

	m_hasGroupsQ = hasGroupInterpretations(infile);

	m_colorFullCompositeQ =  getBoolean("color-full-composite");
	m_fullCompositeQ      = !getBoolean("no-full-composite");
	m_coincidenceQ        =  getBoolean("coincidence");
	m_groupsQ             =  getBoolean("groups");
	m_upstemQ             =  getBoolean("up-stem");
	m_rhythmQ             =  getBoolean("rhythms");
	m_eventQ              =  getBoolean("events");

	// There must be at least one analysis being done (excluding -o options):
	if (!m_groupsQ && !m_coincidenceQ) {
		m_fullCompositeQ = true;
	}

	// Extract music in a specific group:
	m_onlyQ          = getBoolean("only");
	m_only           = getString("only");

	m_scoreSize      = getDouble("score-size");
	m_analysisSize   = getDouble("analysis-size");

	if (m_fullCompositeQ) {
		m_fullComposite.resize(infile.getLineCount());
	}

	m_groups.resize(2);
	m_groups[0].resize(infile.getLineCount());
	m_groups[1].resize(infile.getLineCount());

	m_analysisOnsetsQ    = false;
	m_analysisAccentsQ   = false;
	m_analysisOrnamentsQ = false;
	m_analysisSlursQ     = false;
	m_analysisTotalQ     = false;

	if (getBoolean("analyses")) {
		string argument = getString("analyses");
		if (argument == "all") {
			m_analysisOnsetsQ    = true;
			m_analysisAccentsQ   = true;
			m_analysisOrnamentsQ = true;
			m_analysisSlursQ     = true;
			m_analysisTotalQ     = true;
		} else {
			if (argument.find("n") != string::npos) {
				m_analysisOnsetsQ = true;
			}
			if (argument.find("a") != string::npos) {
				m_analysisAccentsQ = true;
			}
			if (argument.find("o") != string::npos) {
				m_analysisOrnamentsQ = true;
			}
			if (argument.find("s") != string::npos) {
				m_analysisSlursQ = true;
			}
			if (argument.find("t") != string::npos) {
				m_analysisTotalQ = true;
			}
		}
	}

	m_analysisIndex.resize(5);
	m_analysisIndex[0] = m_analysisOnsetsQ;
	m_analysisIndex[1] = m_analysisAccentsQ;
	m_analysisIndex[2] = m_analysisOrnamentsQ;
	m_analysisIndex[3] = m_analysisSlursQ;
	m_analysisIndex[4] = m_analysisTotalQ;

	m_nozerosQ         = getBoolean("no-zeros");
	m_numericAnalysisSpineCount = 0;
	m_analysesQ = false;
	for (int i=0; i<(int)m_analysisIndex.size(); i++) {
		if (m_analysisIndex[i]) {
			m_analysesQ = true;
			m_numericAnalysisSpineCount++;
		}
	}

	initializeNumericAnalyses(infile);
	m_assignedQ = false;
	m_coinMarkQ = getBoolean("mark");
	if (getBoolean("mark-input")) {
		m_coinMarkQ = true;
		m_extractInputQ = true;
	}
}



//////////////////////////////
//
// Tool_composite::initializeNumericAnalyses --
//

void Tool_composite::initializeNumericAnalyses(HumdrumFile& infile) {
	double initValue = 0;
	m_analyses.clear();
	m_analyses.resize(m_ANALYSES_DIM1);  // first index is composite rhythm type
	for (int i=0; i<(int)m_analyses.size(); i++) {
		m_analyses.at(i).resize(m_ANALYSES_DIM2);  // second index is analysis type
		if (m_analysisOnsetsQ) {
			m_analyses.at(i).at(m_ONSET).resize(infile.getLineCount());
		}
		if (m_analysisAccentsQ) {
			m_analyses.at(i).at(m_ACCENT).resize(infile.getLineCount());
		}
		if (m_analysisOrnamentsQ) {
			m_analyses.at(i).at(m_ORNAMENT).resize(infile.getLineCount());
		}
		if (m_analysisSlursQ) {
			m_analyses.at(i).at(m_SLUR).resize(infile.getLineCount());
		}
		if (m_analysisTotalQ) {
			m_analyses.at(i).at(m_TOTAL).resize(infile.getLineCount());
		}
		// third index is the line number in input file:
		for (int j=0; j<(int)m_analysisIndex.size(); j++) {
			if (m_analysisIndex[j]) {
				m_analyses.at(i).at(j).resize(infile.getLineCount());
				fill(m_analyses.at(i).at(j).begin(), m_analyses.at(i).at(j).end(), initValue);
			} else {
				m_analyses.at(i).at(j).clear();
			}
		}
	}

}



/////////////////////////////////
//
// Tool_composite::run -- Do the main work of the tool.
//

bool Tool_composite::run(HumdrumFileSet& infiles) {
	bool status = true;
	for (int i=0; i<infiles.getCount(); i++) {
		status &= run(infiles[i]);
	}
	return status;
}


bool Tool_composite::run(const string& indata, ostream& out) {
	HumdrumFile infile;
	infile.readStringNoRhythm(indata);
	bool status = run(infile);
	if (hasAnyText()) {
		getAllText(out);
	} else {
		out << infile;
	}
	return status;
}


bool Tool_composite::run(HumdrumFile& infile, ostream& out) {
	bool status = run(infile);
	if (hasAnyText()) {
		getAllText(out);
	} else {
		out << infile;
	}
	return status;
}


bool Tool_composite::run(HumdrumFile& infile) {
	processFile(infile);
	return true;
}



//////////////////////////////
//
// Tool_composite::processFile --
//

void Tool_composite::processFile(HumdrumFile& infile) {
	initialize(infile);
	if (m_onlyQ) {
		if (!m_assignedQ) {
			assignGroups(infile);
		}
		analyzeLineGroups(infile);
		extractGroup(infile, m_only);
		return;
	}

	if (m_groupsQ) {
		checkForAutomaticGrouping(infile);
	}

	if (m_coincidenceQ) {
		analyzeCoincidenceRhythms(infile);
	}
	if (m_fullCompositeQ) {
		analyzeFullCompositeRhythm(infile);
	}
	analyzeGroupCompositeRhythms(infile);
	if (m_analysesQ) {
		doNumericAnalyses(infile);
	}
	prepareOutput(infile);
}



//////////////////////////////
//
// checkForAutomaticGrouping --
//

void Tool_composite::checkForAutomaticGrouping(HumdrumFile& infile) {

	bool hasGroups = false;
	int interpline = -1;
	int manipline = -1;
	int dataline = -1;
	int barline = -1;

	for (int i=0; i<infile.getLineCount(); i++) {
		if (!infile[i].hasSpines()) {
			continue;
		}
		if ((barline < 0) && infile[i].isBarline()) {
			barline = i;
		}
		if ((dataline < 0) && infile[i].isData()) {
			dataline = i;
		}
		if ((manipline < 0) && infile[i].isManipulator()) {
			HTp token = infile.token(i, 0);
			if ((!token->isExclusiveInterpretation()) && (!token->isTerminator())) {
				manipline = i;
			}
			continue;
		}
		if (!infile[i].isInterpretation()) {
			continue;
		}
		if ((dataline < 0) && (barline < 0) && (manipline < 0)) {
			// Put below last interp line
			HTp current = infile.token(i, 0);
			current = current->getNextToken();
			if (current) {
				interpline = current->getLineIndex();
			} else {
				interpline = i;
			}
		}
		for (int j=0; j<infile[i].getFieldCount(); j++) {
			HTp token = infile.token(i, j);
			if (!token->isKern()) {
				continue;
			}
			if (token->compare(0, 5, "*grp:") == 0) {
				hasGroups = 1;
				break;
			}
		}
		if (hasGroups) {
			break;
		}
	}
	if (hasGroups) {
		return;
	}

	// No Groupings found in score, so add *grp:A to the first kern spine
	// and *grp:B to the second kern spine (from left-to-right).

	vector<HTp> sstarts;
	infile.getSpineStartList(sstarts, "**kern");
	if (sstarts.size() != 2) {
		return;
	}

	// Add a new interpretation line before the first data line, but also before
	// the first barline and also before the first manipline.
	int addline = dataline;
	if (addline < 0) {
		// no data?
		return;
	}
	if ((addline > barline) && (barline > 0)) {
		addline = barline;
	}
	if ((addline > manipline) && (manipline > 0)) {
		addline = manipline;
	}
	if ((addline > interpline) && (interpline > 0)) {
		addline = interpline;
	}

	if (addline < 0) {
		// something strange
		return;
	}

	infile.insertNullInterpretationLineAboveIndex(addline);
	for (int i=0; i< (int)sstarts.size(); i++) {
		int track = sstarts[i]->getTrack();
		for (int j=0; j<infile[addline].getFieldCount(); j++) {
			HTp token = infile.token(addline, j);
			int jtrack = token->getTrack();
			if (track != jtrack) {
				continue;
			}
			if (i == 0) {
				token->setText("*grp:A");
			} else if (i == 1) {
				token->setText("*grp:B");
			}
			break;
		}
	}
	infile[addline].createLineFromTokens();
}



//////////////////////////////
//
// Tool_composite::hasGroupInterpretations --
//

bool Tool_composite::hasGroupInterpretations(HumdrumFile& infile) {
	for (int i=0; i<infile.getLineCount(); i++) {
		if (!infile[i].isInterpretation()) {
			continue;
		}
		for (int j=0; j<infile[i].getFieldCount(); j++) {
			HTp token = infile.token(i, j);
			if (!token->isKern()) {
				continue;
			}
			if (token->compare(0, 5, "*grp:") == 0) {
				return true;
			}
		}
	}
	return false;
}



//////////////////////////////
//
// Tool_composite::addCoincidenceMarks --
//

void Tool_composite::addCoincidenceMarks(HumdrumFile& infile) {
	for (int i=0; i<infile.getLineCount(); i++) {
		if (!infile[i].isData()) {
			continue;
		}
		bool domark = needsCoincidenceMarker(i);
		if (!domark) {
			continue;
		}
		for (int j=0; j<infile[i].getFieldCount(); j++) {
			HTp token = infile.token(i, j);
			if (!token->isKern()) {
				continue;
			}
			if (token->isNull()) {
				continue;
			}
			for (int i=0; i<token->getSubtokenCount(); i++) {
				string subtok = token->getSubtoken(i);
				if (subtok.find("r") != string::npos) {
					continue;
				}
				subtok += m_coinMark;
				token->replaceSubtoken(i, subtok);
				// Maybe highlight only if note attack?
			}
		}
	}
}



//////////////////////////////
//
// Tool_composite::prepareOutput --
//

void Tool_composite::prepareOutput(HumdrumFile& infile) {
	analyzeOutputVariables(infile);

	if (m_coinMarkQ) {
		addCoincidenceMarks(infile);
		infile.generateLinesFromTokens();
	}

	if (m_rhythmQ) {
		convertNotesToRhythms(infile);
	}

	stringstream analysis;

	// Prepare the output data:
	for (int i=0; i<infile.getLineCount(); i++) {
		if (!infile[i].hasSpines()) {
			analysis << infile[i];
		} else {
			getAnalysisOutputLine(analysis, infile, i);
		}
		analysis << endl;
	}

	HumdrumFile output;
	output.readString(analysis.str());
	stringstream tempout;

	addStaffInfo(output, infile);
	addTimeSignatureChanges(output, infile);
	addMeterSignatureChanges(output, infile);
	adjustBadCoincidenceRests(output, infile);
	for (int i=0; i<output.getLineCount(); i++) {
		output[i].createLineFromTokens();
	}

	HumRegex hre;

	for (int i=0; i<infile.getLineCount(); i++) {
		if (m_verseLabelIndex && (m_verseLabelIndex == -i)) {
			string labelLine = generateVerseLabelLine(output, infile, i);
			if (!labelLine.empty()) {
				if (!hre.search(labelLine, "^[*\t]+$")) {
					tempout << labelLine;
					tempout << endl;
				}
			}
		}

		if (m_striaIndex && (m_striaIndex == -i)) {
			string striaLine =  generateStriaLine(output, infile, i);
			if (!striaLine.empty()) {
				tempout << striaLine;
				tempout << endl;
			}
		}

		if (((m_scoreSize != 100.0) || (m_analysisSize != 100.0)) && m_sizeIndex && (m_sizeIndex == -i)) {
			string sizeLine = generateSizeLine(output, infile, i);
			if (!sizeLine.empty()) {
				tempout << sizeLine;
				tempout << endl;
			}
		}

		if (!infile[i].hasSpines()) {
			tempout << output[i];
		} else if (m_appendQ) {
			// analysis data at end of line
			if (m_extractInputQ || !m_extractQ) {
				tempout << infile[i];
			}
			if (!(m_extractQ || m_extractInputQ)) {
				tempout << "\t";
			}
			if (m_extractQ || !m_extractInputQ) {
				tempout << output[i];
			}
		} else if (m_prependQ) {
			// analysis data at start of line (default)
			if (!m_extractInputQ || m_extractQ) {
				tempout << output[i];
			}
			if (!(m_extractQ || m_extractInputQ)) {
				tempout << "\t";
			}
			if (!m_extractQ || m_extractInputQ) {
				tempout << infile[i];
			}
		} else {
			// output data only
			tempout << output[i];
		}
		tempout << endl;
	}

	if (m_beamQ) {
	/*
		string spinelist = "1";
		int otracks = output.getMaxTrack();
		if (otracks > 1) {
			spinelist += "-" + to_string(otracks);
		}
		string soption = "-s " + spinelist;
		vector<string> argv;
		argv.push_back(soption);    // only beam analysis spines.
		argv.push_back("-g");       // beam adjacent grace notes

		Tool_autobeam autobeam;
		autobeam.process(argv);
		HumdrumFile finaloutput;
		finaloutput.readString(tempout.str());
		autobeam.run(finaloutput);
		for (int i=0; i<finaloutput.getLineCount(); i++) {
			finaloutput[i].createLineFromTokens();
		}
		m_humdrum_text << finaloutput;
	*/

		HumdrumFile finaloutput;
		finaloutput.readString(tempout.str());
		Tool_autobeam autobeam;
		autobeam.run(finaloutput);
		m_humdrum_text << finaloutput;

	} else {
		m_humdrum_text << tempout.str();
	}

	if (m_coinMarkQ) {
		m_humdrum_text << "!!!RDF**kern: " << m_coinMark;
		m_humdrum_text << " = marked note, coincidence note, color=\"";
		m_humdrum_text << m_coinMarkColor << "\"" << endl;
	}
	if (m_colorFullCompositeQ) {
		m_humdrum_text << "!!!RDF**kern: " << m_AMark;
      m_humdrum_text << " = marked note, polyrhythm group A, color=\"";
		m_humdrum_text << m_AMarkColor << "\"" << endl;
		m_humdrum_text << "!!!RDF**kern: " << m_BMark;
      m_humdrum_text << " = marked note, polyrhythm group B, color=\"";
		m_humdrum_text << m_BMarkColor << "\"" << endl;
		if (!m_coinMarkQ) {
			m_humdrum_text << "!!!RDF**kern: " << m_coinMark;
			m_humdrum_text << " = marked note, coincidence note, color=\"";
			m_humdrum_text << m_coinMarkColor << "\"" << endl;
		}
	}

	if (m_groupBEventCount >= 0) {
		m_humdrum_text << "!!!group-b-event-count: " << m_groupBEventCount << endl;
	}
	if (m_groupAEventCount >= 0) {
		m_humdrum_text << "!!!group-a-event-count: " << m_groupAEventCount << endl;
	}
	if (m_fullCompositeEventCount >= 0) {
		m_humdrum_text << "!!!composite-event-count: " << m_fullCompositeEventCount << endl;
	}
	if (m_coincidenceEventCount >= 0) {
		m_humdrum_text << "!!!coincidence-event-count: " << m_coincidenceEventCount << endl;
	}
}



//////////////////////////////
//
// Tool_composite::generateVerseLabelLine --
//

string Tool_composite::generateVerseLabelLine(HumdrumFile& output, HumdrumFile& input, int line) {

	if (m_extractInputQ) {
		return "";
	}

	string outstring;
	string inputBlanks;
	if (!m_extractQ) {
		for (int i=0; i<input[line].getFieldCount(); i++) {
			inputBlanks += "*";
			if (i < input[line].getFieldCount() - 1) {
				inputBlanks += "\t";
			}
		}
	}
	if (!(m_extractQ || m_extractInputQ)) {
		if (m_appendQ) {
			outstring += inputBlanks;
			outstring += "\t";
		}
	}
	string outputLabels;
	if (!m_extractInputQ) {
		for (int i=0; i<output[line].getFieldCount(); i++) {
			HTp token = output.token(line, i);
			string exinterp = token->getExInterp();
			if (exinterp.compare(0, 8, "**vdata-") != 0) {
				outputLabels += "*";
				if (i < output[line].getFieldCount() - 1) {
					outputLabels += "\t";
				}
				continue;
			}
			string label = exinterp.substr(8);
			outputLabels += "*v:";
			outputLabels += label;
			outputLabels += ":";
			if (i < output[line].getFieldCount() - 1) {
				outputLabels += "\t";
			}
		}
	}
	outstring += outputLabels;
	if (m_prependQ || m_extractQ) {
		if (!(m_extractQ || m_extractInputQ)) {
			outstring += "\t";
		}
		outstring += inputBlanks;
	}

	return outstring;
}



//////////////////////////////
//
// Tool_composite::generateStriaLine --
// m_extractQ      == output only
// m_extractInputQ == input only
//

string Tool_composite::generateStriaLine(HumdrumFile& output, HumdrumFile& input, int line) {

	if (m_extractInputQ) {
		return "";
	}

	string outstring;
	string inputBlanks;
	if (!m_extractQ) {
		for (int i=0; i<input[line].getFieldCount(); i++) {
			inputBlanks += "*";
			if (i < input[line].getFieldCount() - 1) {
				inputBlanks += "\t";
			}
		}
		if (m_appendQ) {
			outstring += inputBlanks;
			if (!m_extractInputQ) {
				outstring += "\t";
			}
		}
	}

	string outputStria;
	if (!m_extractInputQ) {
		for (int i=0; i<output[line].getFieldCount(); i++) {
			HTp token = output.token(line, i);
			string exinterp = token->getExInterp();
			if (exinterp.compare(0, 6, "**kern") != 0) {
				outputStria += "*";
				if (i < output[line].getFieldCount() - 1) {
					outputStria += "\t";
				}
				continue;
			}
			outputStria += "*stria1";
			if (i < output[line].getFieldCount() - 1) {
				outputStria += "\t";
			}
		}
	}

	outstring += outputStria;
	if (m_prependQ || m_extractQ) {
		if (!(m_extractQ || m_extractInputQ)) {
			outstring += "\t";
		}
		outstring += inputBlanks;
	}

	return outstring;
}



//////////////////////////////
//
// Tool_composite::generateSizeLine --
// m_extractQ      == output only
// m_extractInputQ == input only
//

string Tool_composite::generateSizeLine(HumdrumFile& output, HumdrumFile& input, int line) {

	if (m_extractInputQ) {
		return "";
	}

	string outstring;
	string inputBlanks;
	if (!m_extractQ) {
		for (int i=0; i<input[line].getFieldCount(); i++) {
			HTp token = input.token(line, i);
			inputBlanks += "*";
			if (token->isKernLike() && (m_scoreSize != 100.0)) {
				stringstream value;
				value.str("");
				value << m_scoreSize;
				inputBlanks += "size:";
				inputBlanks += value.str();
				inputBlanks += "%";
			}
			if (i < input[line].getFieldCount() - 1) {
				inputBlanks += "\t";
			}
		}
		if (m_appendQ) {
			outstring += inputBlanks;
			if (!m_extractInputQ) {
				outstring += "\t";
			}
		}
	}

	string outputSize;
	if (!m_extractInputQ) {
		for (int i=0; i<output[line].getFieldCount(); i++) {
			HTp token = output.token(line, i);
			string exinterp = token->getExInterp();
			if (exinterp.compare(0, 6, "**kern") != 0) {
				outputSize += "*";
				if (output[line].getFieldCount()) {
					outputSize += "\t";
				}
				continue;
			}
			outputSize += "*";
			if (m_analysisSize != 100.0) {
				outputSize += "size:";
				stringstream value;
				value.str("");
				value << m_analysisSize;
				outputSize += value.str();
				outputSize += "%";
			}
			if (i < output[line].getFieldCount() - 1) {
				outputSize += "\t";
			}
		}
	}

	outstring += outputSize;
	if (m_prependQ || m_extractQ) {
		if (!(m_extractQ || m_extractInputQ)) {
			outstring += "\t";
		}
		outstring += inputBlanks;
	}

	return outstring;
}



//////////////////////////////
//
// Tool_composite::getAnalysisOutputLine --
//

void Tool_composite::getAnalysisOutputLine(ostream& output, HumdrumFile& infile, int line) {
	if (!infile[line].hasSpines()) {
		return;
	}
	bool processedQ = false;

	stringstream tempout;

	if (m_coincidenceQ) {
		string value = getCoincidenceToken(infile, line);
		tempout << value;
		if (m_upstemQ) {
			if (value.find("R") != string::npos) {
				tempout << "/";
			}
		}
		if (m_coinMarkQ) {
			if (value.find("R") != string::npos) {
				tempout << m_coinMark;
			}
		}
		if (processedQ) {
			tempout << "\t";
		}
		processedQ = true;
		// print coincidence spine data here
		if (m_numericAnalysisSpineCount) {
			addNumericAnalyses(tempout, infile, line, m_analyses[m_COINCIDENCE]);
		}
	}

	if (m_fullCompositeQ) {
		if (processedQ) {
			tempout << "\t";
		}
		processedQ = true;
		tempout << getFullCompositeToken(infile, line);
		if (m_numericAnalysisSpineCount) {
			addNumericAnalyses(tempout, infile, line, m_analyses[m_COMPOSITE_FULL]);
		}
	}

	if (m_groupsQ) {
		if (processedQ) {
			tempout << "\t";
		}
		processedQ = true;
		tempout << getGroupCompositeToken(infile, line, 0);
		if (m_numericAnalysisSpineCount) {
			addNumericAnalyses(tempout, infile, line, m_analyses[m_COMPOSITE_A]);
		}
		tempout << "\t";
		tempout << getGroupCompositeToken(infile, line, 1);
		if (m_numericAnalysisSpineCount) {
			addNumericAnalyses(tempout, infile, line, m_analyses[m_COMPOSITE_B]);
		}
	}
	output << tempout.str();
}



//////////////////////////////
//
// Tool_composite::addNumericAnalyses -- For a composite rhythm spine, add numeric
//     analyses spines after it as necessary.
//

void Tool_composite::addNumericAnalyses(ostream& output, HumdrumFile& infile, int line,
		vector<vector<double>>& numericAnalyses) {
	if (!infile[line].hasSpines()) {
		return;
	}
	if (infile[line].isCommentLocal()) {
		for (int i=0; i<(int)m_analysisIndex.size(); i++) {
			if (m_analysisIndex[i]) {
				output << "\t" << "!";
			}
		}
	} else if (infile[line].isBarline()) {
		HTp token = infile.token(line, 0);
		for (int i=0; i<(int)m_analysisIndex.size(); i++) {
			if (m_analysisIndex[i]) {
				output << "\t" << token;
			}
		}
	} else if (infile[line].isInterpretation()) {
		HTp token = infile.token(line, 0);
		for (int i=0; i<(int)m_analysisIndex.size(); i++) {
			if (m_analysisIndex[i]) {
				output << "\t";
				if (*token == "*-") {
					output << token;
				} else if (token->compare(0, 2, "**") == 0) {
					switch (i) {
						case 0: output << "**vdata-onsets";    break;
						case 1: output << "**vdata-accents";   break;
						case 2: output << "**vdata-ornaments"; break;
						case 3: output << "**vdata-slurs";     break;
						case 4: output << "**vdata-total";     break;
					}
				} else {
					output << "*";
				}
			}
		}
	} else if (infile[line].isData()) {
		for (int i=0; i<(int)m_analysisIndex.size(); i++) {
			if (m_analysisIndex[i]) {
				double value = numericAnalyses.at(i).at(line);
				output << "\t";
				if (value < 0) {
					output << ".";
				} else {
					output << value;
				}
			}
		}
	} else {
		for (int i=0; i<(int)m_analysisIndex.size(); i++) {
			if (m_analysisIndex[i]) {
				output << "\t" << "PROBLEM";
			}
		}
	}
}



//////////////////////////////
//
// Tool_composite::getCoincidenceToken --
//

string Tool_composite::getCoincidenceToken(HumdrumFile& infile, int line) {
	if (infile[line].isData()) {
		if (m_coincidence[line] != "") {
			return m_coincidence[line];
		} else {
			return ".";
		}
	} else if (infile[line].isInterpretation()) {
		HTp token = infile.token(line, 0);
		if (*token == "*-") {
			return "*-";
		} else if (token->compare(0, 2, "**") == 0) {
			return "**kern-coin";
		} else if (line == m_clefIndex) {
			return "*clefX";
		} else if (line == m_instrumentNameIndex) {
			string output = "*I\"Coincidence";
			if (m_eventQ) {
				m_coincidenceEventCount = getEventCount(m_coincidence);
				stringstream value;
				value.str("");
				value << "\\n(" << m_coincidenceEventCount << " event";
				if (m_coincidenceEventCount != 1) {
					value << "s";
				}
				value << ")";
				output += value.str();
			}
			return output;
		} else if (line == m_instrumentAbbrIndex) {
			return "*I'Coin.";
		} else if (line == m_timeSignatureIndex) {
			return getTimeSignature(infile, m_timeSignatureIndex, "");
		} else if (line == m_meterSymbolIndex) {
			return getMetricSymbol(infile, m_meterSymbolIndex, "");
		} else {
			return "*";
		}
	} else if (infile[line].isCommentLocal()) {
		return "!";
	} else if (infile[line].isBarline()) {
		HTp token = infile.token(line, 0);
		return *token;
	} else {
		return "PROBLEM";
	}
}



//////////////////////////////
//
// Tool_composite::getFullCompositeToken --
//

string Tool_composite::getFullCompositeToken(HumdrumFile& infile, int line) {
	if (infile[line].isData()) {
		if (m_fullComposite[line] != "") {
			bool domark = needsCoincidenceMarker(line);
			string output = m_fullComposite[line];
			if (domark) {
				output += m_coinMark;
			} else if (m_colorFullCompositeQ) {
				output += getFullCompositeMarker(line);
			}
			return output;
		} else {
			return ".";
		}
	} else if (infile[line].isInterpretation()) {
		HTp token = infile.token(line, 0);
		if (*token == "*-") {
			return "*-";
		} else if (token->compare(0, 2, "**") == 0) {
			return "**kern-comp";
		} else if (line == m_clefIndex) {
			return "*clefX";
		} else if (line == m_instrumentNameIndex) {
			string output = "*I\"Composite";
			if (m_eventQ) {
				m_fullCompositeEventCount = getEventCount(m_fullComposite);
				stringstream value;
				value.str("");
				value << "\\n(" << m_fullCompositeEventCount << " event";
				if (m_fullCompositeEventCount != 1) {
					value << "s";
				}
				value << ")";
				output += value.str();
			}
			return output;
		} else if (line == m_instrumentAbbrIndex) {
			return "*I'Comp.";
		} else if (line == m_timeSignatureIndex) {
			return getTimeSignature(infile, m_timeSignatureIndex, "");
		} else if (line == m_meterSymbolIndex) {
			return getMetricSymbol(infile, m_meterSymbolIndex, "");
		} else {
			return "*";
		}
	} else if (infile[line].isCommentLocal()) {
		return "!";
	} else if (infile[line].isBarline()) {
		HTp token = infile.token(line, 0);
		return *token;
	} else {
		return "PROBLEM";
	}
}



//////////////////////////////
//
// Tool_composite::getFullCompositeMarker --
//

string Tool_composite::getFullCompositeMarker(int line) {

	bool domark = needsCoincidenceMarker(line, true);
	if (domark) {
		return m_coinMark;
	}

	string Avalue = m_groups.at(0).at(line);
	string Bvalue = m_groups.at(1).at(line);

	if ((Avalue == ".") && (Bvalue == ".")) {
		return "";
	}

	// bool Arest = Avalue.find("r") != string::npos;
	// bool Brest = Bvalue.find("r") != string::npos;
	bool Anote = Avalue.find("R") != string::npos;
	bool Bnote = Bvalue.find("R") != string::npos;
	// bool Anull = Avalue == ".";
	// bool Bnull = Bvalue == ".";

	// deal with tied note sustains?
	if (Anote) {
		return m_AMark;
	}
	if (Bnote) {
		return m_BMark;
	}

	return "";
}



//////////////////////////////
//
// Tool_composite::needsCoincidenceMarker -- return composite marker if there
//     is a coincidence between two groups on the given line (from group analysis data).
//

bool Tool_composite::needsCoincidenceMarker(int line, bool forceQ) {
	string group1 = m_groups.at(0).at(line);
	string group2 = m_groups.at(1).at(line);

	if (!m_coinMarkQ) {
		if (!forceQ) {
			return false;
		}
	}

	// Coincidence if there are no ties or rests, or null tokens involved.
	bool domark = true;
	if (group1 == "") {
		domark = false;
	} else if (group2 == "") {
		domark = false;
	} else if (group1.find("r") != string::npos) {
		domark = false;
	} else if (group2.find("r") != string::npos) {
		domark = false;
	} else if (group1.find("_") != string::npos) {
		domark = false;
	} else if (group2.find("_") != string::npos) {
		domark = false;
	} else if (group1.find("]") != string::npos) {
		domark = false;
	} else if (group2.find("]") != string::npos) {
		domark = false;
	} else if (group1 == ".") {
		domark = false;
	} else if (group2 == ".") {
		domark = false;
	}

	return domark;
}



//////////////////////////////
//
// Tool_composite::getGroupCompositeToken --
//    group 0 == "A"
//    group 1 == "B"
//

string Tool_composite::getGroupCompositeToken(HumdrumFile& infile, int line, int group) {
	string tgroup = (group == 0) ? "A" : "B";

	if (infile[line].isData()) {
		if (m_groups.at(group).at(line) != "") {
			string output = m_groups.at(group).at(line);
			bool domark = needsCoincidenceMarker(line);
			if (domark) {
				output += m_coinMark;
			}
			return output;
		} else {
			return ".";
		}
	} else if (infile[line].isInterpretation()) {
		HTp token = infile.token(line, 0);
		if (*token == "*-") {
			return "*-";
		} else if (token->compare(0, 2, "**") == 0) {
			if (group == 0) {
				return "**kern-grpA";
			} else {
				return "**kern-grpB";
			}
		} else if (line == m_clefIndex) {
			return "*clefX";
		} else if (line == m_instrumentNameIndex) {
			if (group == 0) {
				string output = "*I\"Group A";
				if (m_eventQ) {
					m_groupAEventCount = getEventCount(m_groups.at(0));
					stringstream value;
					value.str("");
					value << "\\n(" << m_groupAEventCount << " event";
					if (m_groupAEventCount != 1) {
						value << "s";
					}
					value << ")";
					output += value.str();
				}
				return output;
			} else {
				string output = "*I\"Group B";
				if (m_eventQ) {
					m_groupBEventCount = getEventCount(m_groups.at(1));
					stringstream value;
					value.str("");
					value << "\\n(" << m_groupBEventCount << " event";
					if (m_groupBEventCount != 1) {
						value << "s";
					}
					value << ")";
					output += value.str();
				}
				return output;
			}
		} else if (line == m_instrumentAbbrIndex) {
			if (group == 0) {
				return "*I'Grp. A.";
			} else {
				return "*I'Grp. B.";
			}
		} else if (line == m_groupAssignmentIndex) {
			string grp = "*grp:";
			if (group == 0) {
				grp += "A";
			} else if (group == 1) {
				grp += "B";
			}
			return grp;
		} else if (line == m_timeSignatureIndex) {
			return getTimeSignature(infile, m_timeSignatureIndex, tgroup);
		} else if (line == m_meterSymbolIndex) {
			return getMetricSymbol(infile, m_meterSymbolIndex, tgroup);
		} else {
			return "*";
		}
	} else if (infile[line].isCommentLocal()) {
		return "!";
	} else if (infile[line].isBarline()) {
		HTp token = infile.token(line, 0);
		return *token;
	} else {
		return "PROBLEM";
	}

}



//////////////////////////////
//
// Tool_composite::getTimeSignature --
//

string Tool_composite::getTimeSignature(HumdrumFile& infile, int line, const string& group) {
	if (!infile[line].isInterpretation()) {
		return "*";
	}
	HTp lastToken = NULL;
	for (int j=0; j<infile[line].getFieldCount(); j++) {
		HTp token = infile.token(line, j);
		if (!token->isTimeSignature()) {
			continue;
		}
		lastToken = token;
		if (group != "") {
			string sgroup = token->getValue("auto", "group");
			if (sgroup != group) {
				continue;
			}
		}
		return *token;
	}
	if (lastToken) {
		return *lastToken;
	}
	return "*";
}



//////////////////////////////
//
// Tool_composite::getMetricSymbol --
//

string Tool_composite::getMetricSymbol(HumdrumFile& infile, int line, const string& group) {
	if (!infile[line].isInterpretation()) {
		return "*";
	}
	HTp lastToken = NULL;
	for (int j=0; j<infile[line].getFieldCount(); j++) {
		HTp token = infile.token(line, j);
		if (!token->isMetricSymbol()) {
			continue;
		}
		lastToken = token;
		if (group != "") {
			string sgroup = token->getValue("auto", "group");
			if (sgroup != group) {
				continue;
			}
		}
		return *token;
	}
	if (lastToken) {
		return *lastToken;
	}
	return "*";
}



//////////////////////////////
//
// Tool_composite::analyzeFullCompositeRhythm --
//

void Tool_composite::analyzeFullCompositeRhythm(HumdrumFile& infile) {

	vector<HumNum> durations(infile.getLineCount());
	for (int i=0; i<infile.getLineCount(); i++) {
		durations[i] = infile[i].getDuration();
	}

	vector<int> isRest(infile.getLineCount(), false);
	vector<int> isNull(infile.getLineCount(), false);
	vector<int> isSustain(infile.getLineCount(), false);

	for (int i=0; i<infile.getLineCount(); i++) {
		if (durations[i] == 0) {
			continue;
		}
		bool allnull = true;
		bool allrest = true;
		bool allsustain = true;
		for (int j=0; j<infile[i].getFieldCount(); j++) {
			HTp tok = infile.token(i, j);
			if (tok->isNull()) {
				continue;
			}
			allnull = false;
			if (!tok->isKern()) {
				continue;
			}
			if (tok->isNote()) {
				allrest = false;
				allnull = false;
				if ((tok->find("_") == string::npos) &&
				    (tok->find("]") == string::npos)) {
					allsustain = false;
				} else {
				// 	cerr << "NOTE THAT IS SUSTAIN: " << tok << endl;
				}
			} else {
				// cerr << "TOKEN IS NOT NOTE " << tok << endl;
			}
			if (tok->isRest()) {
				allnull = false;
			}
		}
		isRest[i]    = allrest    ? true : false;
		isNull[i]    = allnull    ? true : false;
		isSustain[i] = allsustain ? true : false;
	}

	string pstring = m_pitch;
	if (m_upstemQ) {
		pstring += "/";
	}

	HumRegex hre;
	for (int i=0; i<infile.getLineCount(); i++) {
		if (!infile[i].isData()) {
			continue;
		}

		if (infile[i].getDuration() == 0) {
			// Grace note data line (most likely)
			if (!m_graceQ) {
				continue;
			}
			// otherwise, borrow the view of the first grace note found on the line
			// (beaming, visual duration) and apply the target pitch to the grace note.
			for (int j=0; j<infile[i].getFieldCount(); j++) {
				HTp tok = infile.token(i, j);
				if (!tok->isKern()) {
					continue;
				}
				if (tok->isNull()) {
					continue;
				}
				if (tok->isGrace()) {
					string q;
					string beam;
					string recip;
					if (hre.search(tok, "(\\d+%?\\d*\\.*)")) {
						recip = hre.getMatch(1);
					}
					if (hre.search(tok, "([LJk]+)")) {
						beam = hre.getMatch(1);
					}
					if (hre.search(tok, "(q+)")) {
						q = hre.getMatch(1);
					}
					string full;
					full += recip;
					full += q;
					full += pstring;
					full += beam;
					m_fullComposite[i] = full;
					break;
				}
			}
			continue;
		}

		HumNum duration = getLineDuration(infile, i, isNull);
		string output;
		if (isNull[i]) {
			m_fullComposite[i] = ".";
			continue;
		} else {
			output = Convert::durationToRecip(duration);
		}

//		if (onlyAuxTremoloNotes(infile, i)) {
//			// mark auxiliary notes so that they can be merged
//			// with a preceding note later.
//			output += COMPOSITE_TREMOLO_MARKER;
//		}

		if (isRest[i]) {
			output += "r";
		} else {
			output += pstring;
			if (isSustain.at(i)) {
				output += "]";
			}
		}
		m_fullComposite[i] = output;
		// cerr << output << "\tS:" << isSustain[i] << "\tR:" << isRest[i] << "\tN:" << isNull[i] << endl;
	}


	fixTiedNotes(m_fullComposite, infile);

//	removeAuxTremolosFromCompositeRhythm(infile);

}



//////////////////////////////
//
// Tool_composite::fixTiedNotes --
//

void Tool_composite::fixTiedNotes(vector<string>& data, HumdrumFile& infile) {
	bool intie = false;
	HumRegex hre;
	for (int i=(int)data.size() - 1; i>=0; i--) {
		if (!infile[i].isData()) {
			continue;
		}
		if (infile[i].isGraceLine()) {
			// ignore grace-note lines
			continue;
		}
		if (data.at(i) == ".") {
			continue;
		}
		if (data.at(i) == "") {
			continue;
		}
		if (intie) {
			if (data.at(i).find("[") != string::npos) {
				intie = false;
			} else if (data.at(i).find("]") != string::npos) {
				hre.replaceDestructive(data.at(i), "_", "[]]");
			} else if (data.at(i).find("_") != string::npos) {
				// do nothing
			} else {
				data.at(i) = "[" + data.at(i);
				intie = false;
			}
		} else {
			if (data.at(i).find("]") != string::npos) {
				intie = true;
			} else if (data.at(i).find("_") != string::npos) {
				intie = true;
			}
		}
	}
}



//////////////////////////////
//
// Tool_composite::analyzeCoincidenceRhythms --
//

void Tool_composite::analyzeCoincidenceRhythms(HumdrumFile& infile) {
	if (!m_assignedQ) {
		assignGroups(infile);
	}
	vector<int> groupAstates;
	vector<int> groupBstates;

	// -2 = sustain tied note -1 = sustain, 0 = rest (or non-data), +1 = note attack
	getNumericGroupStates(groupAstates, infile, "A");
	getNumericGroupStates(groupBstates, infile, "B");

	vector<HumNum> timestamps(infile.getLineCount(), 0);
	for (int i=0; i<infile.getLineCount(); i++) {
		timestamps[i] = infile[i].getDurationFromStart();
	}

	vector<int> merged(infile.getLineCount(), 0);
	for (int i=0; i<infile.getLineCount(); i++) {
		if (groupAstates.at(i) == groupBstates.at(i)) {
			merged[i] = groupAstates.at(i);
		} else if (groupAstates[i] == 0 || (groupBstates[i] == 0)) {
			merged[i] = 0;
		} else if ((groupAstates[i] > 0) && (groupBstates[i] < 0)) {
			merged[i] = groupBstates[i];
		} else if ((groupAstates[i] < 0) && (groupBstates[i] > 0)) {
			merged[i] = groupAstates[i];
		} else {
			merged[i] = -1;
		}
	}

	bool foundFirstAttackA = false;
	bool foundFirstAttackB = false;

	m_coincidence.resize(infile.getLineCount());
	for (int i=0; i<infile.getLineCount(); i++) {
		m_coincidence[i] = "";
	}

	vector<int> noteAttack(infile.getLineCount(), 0);
	int currValue = -1000;
	int lastValue = -1000;
	for (int i=0; i<infile.getLineCount(); i++) {
		if (!infile[i].isData()) {
			continue;
		}
		lastValue = currValue;
		if (groupAstates[i] > 0) {
			foundFirstAttackA = true;
		}
		if (groupBstates[i] > 0) {
			foundFirstAttackB = true;
		}
		int value = merged[i];
		if (!foundFirstAttackA) {
			value = 0;
		}
		if (!foundFirstAttackB) {
			value = 0;
		}
		currValue = value;

		if (currValue == 1) {
			noteAttack[i] = 1;
		} else if (currValue == -2) {
			// sustained note attack
			noteAttack[i] = -2;
		} else if ((currValue == 0) && (lastValue != 0)) {
			noteAttack[i] = 2; // 2 means a "rest" note.
		}
	}

	// Need to split rests across barlines (at least non-invisible ones).
	// Also split rests if they generate an unprintable rhythm...

	vector<int> nextAttackIndex(infile.getLineCount(), -1);
	vector<int> prevAttackIndex(infile.getLineCount(), -1);
	int lasti = 0;
	for (int i=0; i<infile.getLineCount(); i++) {
		if (noteAttack[i] != 0) {
			prevAttackIndex[i] = lasti;
			lasti = i;
		}
	}
	int nexti = infile.getLineCount() - 1;
	for (int i=infile.getLineCount()-1; i>=0; i--) {
		if (noteAttack[i] != 0) {
			nextAttackIndex[i] = nexti;
			nexti = i;
		}
	}

	string lastnote = "";
	HumNum remainder;
	bool barline = false;
	for (int i=0; i<infile.getLineCount(); i++) {
		if (infile[i].isBarline()) {
			barline = true;
		}
		if (barline && infile[i].isData() && !infile[i].isGraceLine()) {
			barline = false;
			if (remainder > 0) {
				// add remainder rest at start of new barline
				HumNum durtobar = infile[i].getDurationToBarline();
				if (remainder > durtobar) {
					// Presuming a need for a rest at the start of the measure that
					// is an overflow from the previous measure.
					remainder = remainder - durtobar;
					string recip = Convert::durationToRecip(durtobar);
					string text = recip + "r";
					lastnote = text;
					m_coincidence[i] = text;
				} else {
					// Remainder rest stops by the next bar.
					string recip = Convert::durationToRecip(remainder);
					remainder = 0;
					string text = recip + "r";
					lastnote = text;
					m_coincidence[i] = text;
				}
			}
		}
		if (noteAttack[i]) {
			HumNum duration = infile[nextAttackIndex[i]].getDurationFromStart() -
				infile[i].getDurationFromStart();
			HumNum durtobar = infile[i].getDurationToBarline();
			if (duration > durtobar) {
				// clip the duration to the duration of the rest of the measure
				remainder = duration - durtobar;
				duration = durtobar;
			}

			string recip = Convert::durationToRecip(duration);
			string text;
			if (noteAttack[i] == -2) {
				text = recip + "eR";
				// this is either a middle tie if next note is also a tie
				// or is a tie end if the next note is an attack (1) or rest (2)
				if (noteAttack[nextAttackIndex[i]] > 0) {
					// tie end
					if ((lastnote.find("[") == string::npos) && (lastnote.find("_") == string::npos)) {
						m_coincidence[i] = recip + "r";
						// previous note is not a tie, so switch this note to a rest from
						// a sustained tied note

					} else {
						m_coincidence[i] = text;
						m_coincidence[i] += "]";
					}
					lastnote = m_coincidence[i];
				} else {
					// tie middle
					if ((lastnote.find("[") == string::npos) && (lastnote.find("_") == string::npos)) {
						m_coincidence[i] = text + "[";
					} else {
						m_coincidence[i] = text + "_";
					}
					lastnote = m_coincidence[i];
				}
			} else if (noteAttack[i] == 1) {
				text = recip + "eR";
				// Start a tie if the next note is a tied note
				if (noteAttack[nextAttackIndex[i]] == -2) {
					m_coincidence[i] = "[" + text;
					lastnote = m_coincidence[i];
				} else {
					m_coincidence[i] = text;
					lastnote = m_coincidence[i];
				}
			} else if (noteAttack[i] == 2) {
				// rest
				text = recip + "r";
				m_coincidence[i] = text;
				lastnote = m_coincidence[i];
			}
		}
	}

	// fixTiedNotes(m_coincidence, infile);

	if (m_debugQ) {
		cerr << "MERGED Coincidence states:" << endl;
		cerr << "TS\tA\tB\tMerged\tAttack\tIndex\tNext\tPrev\tCoin\tInput\n";
		for (int i=0; i<(int)merged.size(); i++) {
			cerr << timestamps[i] << "\t";
			cerr << groupAstates[i] << "\t" << groupBstates[i];
			cerr << "\t" << merged[i] << "\t" << noteAttack[i];
			cerr << "\t" << i;
			cerr << "\t" << nextAttackIndex[i] << "\t" << prevAttackIndex[i];
			cerr << "\t" << m_coincidence[i];
			cerr << "\t" << infile[i] << endl;
		}
		cerr << "==================================" << endl;
	}

}



//////////////////////////////
//
// Tool_composite::getNumericGroupStates -- return +1 if there is a note
//     attack in a given group, -1 if there is a sustained note in the group,
//     or 0 if there is a rest in the group.
//

void Tool_composite::getNumericGroupStates(vector<int>& states,
		HumdrumFile& infile, const string& tgroup) {
	states.resize(infile.getLineCount());
	fill(states.begin(), states.end(), 0);
	bool nullSustain = false;
	bool tieNote = false;
	for (int i=0; i<infile.getLineCount(); i++) {
		if (!infile[i].isData()) {
			continue;
		}
		vector<int> linestates;
		linestates.clear();
		for (int j=0; j<infile[i].getFieldCount(); j++) {
			HTp token = infile.token(i, j);
			if (!token->isKern()) {
				continue;
			}
			string group = token->getValue("auto", "group");
			if (group != tgroup) {
				// need to check if null tokens have groups
				continue;
			}
			nullSustain = false;
			if (token->isNull()) {
				nullSustain = true;
				token = token->resolveNull();
				if (!token) {
					continue;
				}
			}
			if (token->isRest()) {
				// resting is the default state.
				continue;
			}
			vector<string> subtoks = token->getSubtokens();
			bool suschord = true;
			bool attackchord = false;
			for (int k=0; k<(int)subtoks.size(); k++) {
				if (subtoks[k].find("r") != string::npos) {
					continue;
				}
				if (subtoks[k] == ".") {
					// Strange null subtoken.
					continue;
				}
				if ((subtoks[k].find("]") == string::npos) &&
						(subtoks[k].find("_") == string::npos)) {
					suschord = false;
					attackchord = true;
				}
				if (!nullSustain) {
					if ((subtoks[k].find("]") != string::npos) ||
						 	(subtoks[k].find("_") != string::npos)) {
						tieNote = true;
					} else {
						tieNote = false;
					}
				} else {
					tieNote = false;
				}
			} // end of chord analysis
			if (attackchord) {
				suschord = false;
			}
			if (suschord && nullSustain) {
				linestates.push_back(-1);
			} else if (nullSustain) {
				linestates.push_back(-1);
			} else if (suschord) {
				if (tieNote) {
					linestates.push_back(-2);
				} else {
					linestates.push_back(-1);
				}
			} else {
				linestates.push_back(1);
			}
		} // end of line analysis

		bool allZero = true;
		for (int m=0; m<(int)linestates.size(); m++) {
			if (linestates[m] != 0) {
				allZero = false;
				break;
			}
		}
		bool hasAttack = false;
		for (int m=0; m<(int)linestates.size(); m++) {
			if (linestates[m] > 0) {
				hasAttack = true;
				break;
			}
		}
		if (hasAttack) {
			states[i] = 1;
		} else if (allZero) {
			states[i] = 0;
		} else {
			states[i] = -1;
		}
	}
}



//////////////////////////////
//
// Tool_composite::analyzeGroupCompositeRhythms --
//

void Tool_composite::analyzeGroupCompositeRhythms(HumdrumFile& infile) {
	vector<HumNum> durations(infile.getLineCount());
	for (int i=0; i<infile.getLineCount(); i++) {
		durations[i] = infile[i].getDuration();
	}

	vector<int> isRest(infile.getLineCount(), false);
	vector<int> isNull(infile.getLineCount(), false);

	for (int i=0; i<infile.getLineCount(); i++) {
		if (durations[i] == 0) {
			continue;
		}
		bool allnull = true;
		bool allrest = true;
		for (int j=0; j<infile[i].getFieldCount(); j++) {
			HTp tok = infile.token(i, j);
			string value = tok->getValue("auto", "ignoreTremoloNote");
			if (value == "1") {
				continue;
			}
			if (tok->isNull()) {
				continue;
			}
			allnull = false;
			if (!tok->isKern()) {
				continue;
			}
			if (tok->isNote()) {
				allrest = false;
				break;
			}
			if (tok->isRest()) {
				allnull = false;
			}
		}
		isRest[i] = allrest ? true : false;
		isNull[i] = allnull ? true : false;
	}

	string pstring = m_pitch;
	if (m_upstemQ) {
		pstring += "/";
	}

	HumRegex hre;

	if (!m_assignedQ) {
		assignGroups(infile);
	}
	analyzeLineGroups(infile);
	if (m_debugQ) {
		printGroupAssignments(infile);
	}

	vector<vector<int>> groupstates;
	getGroupStates(groupstates, infile);

	vector<vector<HumNum>> groupdurs;
	getGroupDurations(groupdurs, groupstates, infile);

	vector<vector<string>> rhythms;
	getGroupRhythms(rhythms, groupdurs, groupstates, infile);

	string curtimesigA;
	string curtimesigB;

	for (int i=0; i<infile.getLineCount(); i++) {
		if (!infile[i].isData()) {
			continue;
		}
		if (infile[i].getDuration() == 0) {
			// Grace note data line (most likely)
			if (!m_graceQ) {
				continue;
			}
			// otherwise, borrow the view of the first grace note found on the line
			// (beaming, visual duration) and apply the target pitch to the grace note.
			for (int j=0; j<infile[i].getFieldCount(); j++) {
				HTp tok = infile.token(i, j);
				if (!tok->isKern()) {
					continue;
				}
				if (tok->isNull()) {
					continue;
				}
				if (tok->isGrace()) {
					string q;
					string beam;
					string recip;
					if (hre.search(tok, "(\\d+%?\\d*\\.*)")) {
						recip = hre.getMatch(1);
					}
					if (hre.search(tok, "([LJk]+)")) {
						beam = hre.getMatch(1);
					}
					if (hre.search(tok, "(q+)")) {
						q = hre.getMatch(1);
					}
					string full;
					full += recip;
					full += q;
					full += pstring;
					full += beam;
					// HTp targettok = NULL;
					// HTp targettok2 = NULL;
					//if (m_appendQ) {
					//	targettok = infile.token(i, infile[i].getFieldCount()-2);
					//	targettok2 = infile.token(i, infile[i].getFieldCount()-1);
					//} else {
					//	if (m_coincidenceQ) {
					//		targettok = infile.token(i, 1);
					//		targettok2 = infile.token(i, 2);
					//	} else {
					//		targettok = infile.token(i, 0);
					//		targettok2 = infile.token(i, 1);
					//	}
					//}

					string group = infile.token(i, j)->getValue("auto", "group");
					if (group == "A") {
						m_groups[0][i] = full;
					} else if (group == "B") {
						m_groups[1][i] = full;
					}
					break;
				}
			}
			continue;
		}

		// dealing with a non-zero data line:
		HumNum duration = getLineDuration(infile, i, isNull);
		string recip = rhythms[0][i];
		string recip2 = rhythms[1][i];
		if (recip.empty()) {
			recip = ".";
		} else {
			if (groupstates[0][i] == TYPE_RestAttack) {
				recip += "rR";
			}
			else if (groupstates[0][i] == TYPE_UNDEFINED) {
				// make invisible rest (rest not part of group)
				recip += "ryy";
			} else {
				recip += pstring;
			}
		}
		if (recip2.empty()) {
			// null group: add invisible rest to rhythm
			// HumNum linedur = infile[i].getDuration();
			// recip2 = Convert::durationToRecip(linedur);
			recip2 += ".";
		} else {
			if (groupstates[1][i] == TYPE_RestAttack) {
				recip2 += "rR";
			}
			else if (groupstates[1][i] == TYPE_UNDEFINED) {
				// make invisible rest (rest not part of group)
				recip2 += "ryy";
			} else {
				recip2 += pstring;
			}
		}
		m_groups[0][i] = recip;
		m_groups[1][i] = recip2;
	}

//	if (!m_together.empty()) {
//		if (m_appendQ) {
//			markTogether(infile, -2);
//		} else {
//			markTogether(infile, 2);
//		}
//	}
}



//////////////////////////////
//
// Tool_compare2::getGrouprhythms --
//

void Tool_composite::getGroupRhythms(vector<vector<string>>& rhythms,
		vector<vector<HumNum>>& groupdurs, vector<vector<int>>& groupstates,
		HumdrumFile& infile) {
	rhythms.resize(groupdurs.size());
	for (int i=0; i<(int)rhythms.size(); i++) {
		getGroupRhythms(rhythms[i], groupdurs[i], groupstates[i], infile);
	}
}

void Tool_composite::getGroupRhythms(vector<string>& rhythms, vector<HumNum>& durs,
		vector<int>& states, HumdrumFile& infile) {
	rhythms.clear();
	rhythms.resize(durs.size());
	int lastnotei = -1;
	for (int i=0; i<(int)rhythms.size(); i++) {
		if (states[i] <= 0) {
			continue;
		}
		string prefix = "";
		string postfix = "";
		for (int j=i+1; j<(int)rhythms.size(); j++) {
			if ((states[j]) > 0 && (states[j] < 5)) {
				if ((states[i] == TYPE_NoteAttack) && (states[j] == TYPE_NoteSustainAttack)) {
					prefix = "[";
				} else if ((states[i] == TYPE_NoteSustainAttack) && (states[j] == TYPE_NoteSustainAttack)) {
					postfix = "_";
				} else if ((states[i] == TYPE_NoteSustainAttack) && (states[j] == TYPE_NoteAttack)) {
					postfix = "]";
				} else if ((states[i] == TYPE_NoteSustainAttack) && (states[j] == TYPE_RestAttack)) {
					postfix = "]";
				}
				lastnotei = j;
				break;
			}
		}
		string value = Convert::durationToRecip(durs[i]);
		rhythms[i] = prefix + value + postfix;
	}
	if (lastnotei >= 0) {
		if (states[lastnotei] == TYPE_NoteSustainAttack) {
			rhythms[lastnotei] = rhythms[lastnotei] + "]";
		}
	}

	if (m_debugQ) {
		cerr << "=========================================" << endl;
		cerr << "RECIP FOR GROUP: " << endl;
		for (int i=0; i<(int)rhythms.size(); i++) {
			cerr << rhythms[i] << "\t" << durs[i] << "\t" << states[i] << "\t" << infile[i] << endl;
		}
		cerr << "^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^" << endl;
	}
}



//////////////////////////////
//
// Tool_composite::getGroupDurations --
//

void Tool_composite::getGroupDurations(vector<vector<HumNum>>& groupdurs,
		vector<vector<int>>& groupstates, HumdrumFile& infile) {
	groupdurs.resize(groupstates.size());
	for (int i=0; i<(int)groupstates.size(); i++) {
		getGroupDurations(groupdurs[i], groupstates[i], infile);
	}
}

void Tool_composite::getGroupDurations(vector<HumNum>& groupdurs,
		vector<int>& groupstates, HumdrumFile& infile) {
	HumNum enddur = infile.getScoreDuration();
	groupdurs.resize(groupstates.size());
	fill(groupdurs.begin(), groupdurs.end(), -1);
	int eventi = -1;
	HumNum lasttime = 0;
	for (int i=0; i<(int)groupdurs.size(); i++) {
		if (groupstates[i] > 0) {
			if (eventi >= 0) {
				HumNum eventtime = infile[i].getDurationFromStart();
				HumNum duration = eventtime - lasttime;
				groupdurs[eventi] = duration;
				lasttime = eventtime;
				eventi = i;
				continue;
			} else {
				eventi = i;
			}
		}
	}
	if (eventi >= 0) {
		HumNum duration = enddur - lasttime;
		groupdurs[eventi] = duration;
	}
}



/////////////////////////////
//
// Tool_composite::getGroupStates -- Pull out the group note states for each
//    composite rhytm stream.
//
//    group:A:type = "note"   if there is at least one note attack in group A on the line.
//    group:A:type = "ncont"  if there is no attack but at least one note sustain in group A.
//    group:A:type = "snote"  there is a printed note which is part of a tie group sustain note.
//    group:A:type = "scont"  continuation of a tie group sutain note.
//    group:A:type = "rest"   if there is no note attack or sustain but there is a rest start.
//    group:A:type = "rcont"  if there is a rest continuing in group A on the line.
//    group:A:type = "none"   if there is no activity for group A on the line.
//
//    Numeric equivalents:
//     9 = TYPE_UNDEFINED           = "undefined"
//     3 = TYPE_NoteSustainAttack   = "snote"
//     2 = TYPE_NoteAttack          = "note"
//     1 = TYPE_RestAttack          = "rest"
//     0 = TYPE_NONE                = "none"
//    -1 = TYPE_RestSustain         = "rcont"
//    -2 = TYPE_NoteSustain         = "ncont"
//    -3 = TYPE_NoteSustainSustain  = "scont"
//

void Tool_composite::getGroupStates(vector<vector<int>>& groupstates, HumdrumFile& infile) {
	groupstates.resize(2);
	groupstates[0].resize(infile.getLineCount());
	groupstates[1].resize(infile.getLineCount());
	fill(groupstates[0].begin(), groupstates[0].end(), 0);
	fill(groupstates[1].begin(), groupstates[1].end(), 0);

	for (int i=0; i<infile.getLineCount(); i++) {
		for (int j=0; j<(int)groupstates.size(); j++) {
			char groupname = 'A' + j;
			string name;
			name.clear();
			name += groupname;
			string state = infile[i].getValue("group", name, "type");
			int typenum = typeStringToInt(state);
			groupstates[j][i] = typenum;
		}
	}
}



//////////////////////////////
//
// Tool_composite::getLineDuration -- Return the duration of the line, but return
//    0 if the line only contains nulls.  Also add the duration of any subsequent
//    lines that are null lines before any data content lines.

HumNum Tool_composite::getLineDuration(HumdrumFile& infile, int index, vector<int>& isNull) {
	if (isNull[index]) {
		return 0;
	}
	if (!infile[index].isData()) {
		return 0;
	}
	HumNum output = infile[index].getDuration();
	for (int i=index+1; i<infile.getLineCount(); i++) {
		if (!infile[i].isData()) {
			continue;
		}
		if (isNull[i]) {
			output += infile[i].getDuration();
		} else {
			break;
		}
	}
	return output;
}



//////////////////////////////
//
// Tool_composite::assignGroups -- Add a parameter
//   auto:grouping = "A" or "B" depending on the group.  This
//   can be generalized later to more letters, or arbitrary
//   strings perhaps.  This comes from an interpretation such
//   as *grp:A or *grp:B in the data.  If *grp: is found without
//   a letter, than that group will be null group.


void Tool_composite::assignGroups(HumdrumFile& infile) {
	m_assignedGroups = true;

	int maxtrack = infile.getMaxTrack();
	vector<vector<string>> curgroup;
	curgroup.resize(maxtrack + 1);
	for (int i=0; i<(int)curgroup.size(); i++) {
		curgroup[i].resize(100);
	}

	for (int i=0; i<infile.getLineCount(); i++) {
		if (!infile[i].hasSpines()) {
			continue;
		}
		for (int j=0; j<infile[i].getFieldCount(); j++) {
			// checking all spines (not just **kern data).
			HTp token = infile.token(i, j);
			int track = token->getTrack();
			int subtrack = token->getSubtrack();
			if (subtrack > 99) {
				cerr << "Too many subspines!" << endl;
				continue;
			}

			if (*token == "*grp:A") {
				curgroup.at(track).at(subtrack) = "A";
				if (subtrack == 0) {
					for (int k=1; k<(int)curgroup.at(track).size(); k++) {
						curgroup.at(track).at(k) = "A";
					}
				}
				backfillGroup(curgroup, infile, i, track, subtrack, "A");
			}
			if (*token == "*grp:B") {
				curgroup.at(track).at(subtrack) = "B";
				if (subtrack == 0) {
					for (int k=1; k<(int)curgroup.at(track).size(); k++) {
						curgroup.at(track).at(k) = "B";
					}
				}
				backfillGroup(curgroup, infile, i, track, subtrack, "B");
			}
			// could expanded to other groups here
			if (*token == "*grp:") {
				// clear a group:
				curgroup.at(track).at(subtrack) = "";
				if (subtrack == 0) {
					for (int k=1; k<(int)curgroup.at(track).size(); k++) {
						curgroup.at(track).at(k) = "";
					}
				}
				backfillGroup(curgroup, infile, i, track, subtrack, "");
			}

			string group = curgroup.at(track).at(subtrack);
			token->setValue("auto", "group", group);
		}
	}
	m_assignedQ = true;
}



//////////////////////////////
//
// Tool_composite::backfillGroup -- Go back and reassign a group to all lines
//   before *grp:A or *grp:B so that time signatures and the like are used as
//   desired even if they come before a new group definition.
//

void Tool_composite::backfillGroup(vector<vector<string>>& curgroup, HumdrumFile& infile,
		int line, int track, int subtrack, const string& group) {
	int lastline = -1;
	for (int i=line-1; i>=0; i--) {
		if (infile[i].isData()) {
			lastline = i+1;
			break;
		}
		curgroup.at(track).at(subtrack) = group;
		if (subtrack == 0) {
			for (int k=1; k<(int)curgroup.at(track).size(); k++) {
				curgroup.at(track).at(k) = group;
			}
		}
	}
	if (lastline < 0) {
		lastline = 0;
	}
	for (int i=lastline; i<line; i++) {
		if (infile[i].isData()) {
			break;
		}
		for (int j=0; j<infile[i].getFieldCount(); j++) {
			HTp token = infile.token(i, j);
			int ttrack = token->getTrack();
			if (ttrack != track) {
				continue;
			}
			int tsubtrack = token->getSubtrack();
			if (tsubtrack != subtrack) {
				continue;
			}
			string group = curgroup.at(track).at(subtrack);
			token->setValue("auto", "group", group);
		}
	}
}



//////////////////////////////
//
// Tool_composite::analyzeLineGroups -- Look at each line for Group A and B and determine if
//    And one of five activity types are possible for the line:
//        group:A:type = "note"   if there is at least one note attack in group A on the line.
//        group:A:type = "ncont"  if there is no attack but at least one note sustain in group A.
//        group:A:type = "rest"   if there is no note attack or sustain but there is a rest start.
//        group:A:type = "rcont"  if there is a rest continuing in group A on the line.
//        group:A:type = "empty"  if there is no activity for group A on the line.
//

void Tool_composite::analyzeLineGroups(HumdrumFile& infile) {
	for (int i=0; i<infile.getLineCount(); i++) {
		if (!infile[i].hasSpines()) {
			continue;
		}
		if (!infile[i].data()) {
			continue;
		}
		analyzeLineGroup(infile, i, "A");
		analyzeLineGroup(infile, i, "B");
	}
}



//////////////////////////////
//
// Tool_composite::analyzeLineGroup --
//
//     9 = TYPE_UNDEFINED           = "undefined"
//     3 = TYPE_NoteSustainAttack   = "snote"
//     2 = TYPE_NoteAttack          = "note"
//     1 = TYPE_RestAttack          = "rest"
//     0 = TYPE_NONE                = "none"
//    -1 = TYPE_RestSustain         = "rcont"
//    -2 = TYPE_NoteSustain         = "ncont"
//    -3 = TYPE_NoteSustainSustain  = "scont"
//

void Tool_composite::analyzeLineGroup(HumdrumFile& infile, int line, const string& target) {
	int groupstate = getGroupNoteType(infile, line, target);
	switch (groupstate) {
		case TYPE_NoteSustainAttack:
			infile[line].setValue("group", target, "type", "snote");
			break;
		case TYPE_NoteAttack:
			infile[line].setValue("group", target, "type", "note");
			break;
		case TYPE_RestAttack:
			infile[line].setValue("group", target, "type", "rest");
			break;
		case TYPE_RestSustain:
			infile[line].setValue("group", target, "type", "rcont");
			break;
		case TYPE_NoteSustain:
			infile[line].setValue("group", target, "type", "ncont");
			break;
		case TYPE_NoteSustainSustain:
			infile[line].setValue("group", target, "type", "scont");
			break;
		case TYPE_NONE:
			infile[line].setValue("group", target, "type", "none");
			break;
		default:
			infile[line].setValue("group", target, "type", "undefined");
			break;
	}
}



//////////////////////////////
//
// Tool_composite::extractGroup --
//

void Tool_composite::extractGroup(HumdrumFile& infile, const string &target) {
	for (int i=0; i<infile.getLineCount(); i++) {
		if (!infile[i].isData()) {
			m_humdrum_text << infile[i] << endl;
			continue;
		}
		for (int j=0; j<infile[i].getFieldCount(); j++) {
			HTp token = infile[i].token(j);
			if ((!token->isData()) || token->isNull()) {
				m_humdrum_text << token;
				if (j < infile[i].getFieldCount() - 1) {
					m_humdrum_text << "\t";
				}
				continue;
			}
			string group = token->getValue("auto", "group");
			if (group == target) {
				m_humdrum_text << token;
			} else {
				if (token->isRest()) {
					m_humdrum_text << token << "yy";
				} else {
					HumRegex hre;
					string rhythm = "4";
					if (hre.search(token, "(\\d+%?\\d*\\.*)")) {
						rhythm = hre.getMatch(1);
					}
					m_humdrum_text << rhythm << "ryy";
				}
			}
			if (j < infile[i].getFieldCount() - 1) {
				m_humdrum_text << "\t";
			}
		}
		m_humdrum_text << endl;
	}
}



//////////////////////////////
//
// Tool_composite::getGroupNoteType --
//
//  9 = TYPE_UNDEFINED
//  3 = TYPE_NoteSustainAttack
//  2 = TYPE_NoteAttack
//  1 = TYPE_RestAttack
//  0 = TYPE_NONE
// -1 = TYPE_RestSustain
// -2 = TYPE_NoteSustain
// -3 = TYPE_NoteSustainSustain
//

int Tool_composite::getGroupNoteType(HumdrumFile& infile, int line, const string& group) {
	if (!infile[line].isData()) {
		return TYPE_NONE;
	}

	vector<HTp> grouptokens;
	for (int i=0; i<infile[line].getFieldCount(); i++) {
		HTp token = infile.token(line, i);
		if (!token->isKern()) {
			continue;
		}
		string tgroup = token->getValue("auto", "group");
		if (group == tgroup) {
			grouptokens.push_back(token);
		}
	}

	if (grouptokens.empty()) {
		return TYPE_UNDEFINED;
	}

	bool hasRestAttack    = false;
	bool hasRestSustain   = false;
	bool hasNoteAttack    = false;
	bool hasNoteSustain   = false;
	bool hasNoteSAttack   = false;
	bool hasNoteSSustain  = false;

	for (int i=0; i<(int)grouptokens.size(); i++) {
		HTp token = grouptokens[i];
		string value = token->getValue("auto", "ignoreTremoloNote");
		if (value == "1") {
			hasNoteSustain = true;
			// need to check for tie on head note...
			continue;
		}
		if (token->isNull()) {
			HTp resolved = token->resolveNull();
			if (resolved && !resolved->isNull()) {
				if (resolved->isRest()) {
					hasRestSustain = true;
				} else {
					if (resolved->isNoteAttack()) {
						hasNoteSustain = true;
					} else if (resolved->isNoteSustain()) {
						hasNoteSSustain = true;
					}
				}
			}
			continue;
		}
		if (token->isRest()) {
			hasRestAttack = true;
			continue;
		}
		if (token->isNoteAttack()) {
			string value = token->getValue("auto", "ignoreTremoloNote");
			if (value != "1") {
				hasNoteAttack = true;
			}
			continue;
		}
		if (token->isNoteSustain()) {
			hasNoteSAttack = true;
		}
	}

	//  3 = TYPE_NoteSustainAttack
	//  2 = TYPE_NoteAttack
	//  1 = TYPE_RestAttack
	//  0 = TYPE_NONE
	// -1 = TYPE_RestSustain
	// -2 = TYPE_NoteSustain
	// -3 = TYPE_NoteSustainSustain

	if (hasNoteAttack) {
		return TYPE_NoteAttack;
	}
	if (hasNoteSAttack) {
		return TYPE_NoteSustainAttack;
	}
	if (hasNoteSustain) {
		return TYPE_NoteSustain;
	}
	if (hasNoteSSustain) {
		return TYPE_NoteSustainSustain;
	}
	if (hasRestAttack) {
		return TYPE_RestAttack;
	}
	if (hasRestSustain) {
		return TYPE_RestSustain;
	}

	cerr << "Warning: no category for line " << infile[line] << endl;

	return 0;
}



//////////////////////////////
//
// Tool_composite::printGroupAssignments -- for debugging of group assignments.
//

void Tool_composite::printGroupAssignments(HumdrumFile& infile) {
	for (int i=0; i<infile.getLineCount(); i++) {
		if (!infile[i].hasSpines()) {
			cerr << infile[i] << endl;
			continue;
		}
		for (int j=0; j<infile[i].getFieldCount(); j++) {
			HTp token = infile.token(i, j);
			string value = token->getValue("auto", "group");
			cerr << token;
			if (!value.empty()) {
				cerr << "{" << value << "}";
			}
			if (j < infile[i].getFieldCount() - 1) {
				cerr << "\t";
			}
		}
		cerr << endl;
	}
}



//////////////////////////////
//
// Tool_composite::typeStringToInt -- Convert between numeric and string state forms.
//

int Tool_composite::typeStringToInt(const string& value) {
	if (value == "snote") { return TYPE_NoteSustainAttack;  }
	if (value == "note")  { return TYPE_NoteAttack;         }
	if (value == "rest")  { return TYPE_RestAttack;         }
	if (value == "none")  { return TYPE_NONE;               }
	if (value == "rcont") { return TYPE_RestSustain;        }
	if (value == "ncont") { return TYPE_NoteSustain;        }
	if (value == "scont") { return TYPE_NoteSustainSustain; }
	return TYPE_UNDEFINED;
}


//////////////////////////////
//
// Tool_composite::doNumericAnalyses --
//

void Tool_composite::doNumericAnalyses(HumdrumFile& infile) {
	if (m_analysisOnsetsQ) {
		doOnsetAnalyses(infile);
	}
	if (m_analysisAccentsQ) {
		doAccentAnalyses(infile);
	}
	if (m_analysisOrnamentsQ) {
		doOrnamentAnalyses(infile);
	}
	if (m_analysisAccentsQ) {
		doSlurAnalyses(infile);
	}
	if (m_analysisTotalQ) {
		doTotalAnalyses(infile);
	}
}



//////////////////////////////
//
// Tool_composite::doOnsetAnalyses -- targetGroup == "" means full composite onsets.
//     First index of m_analyses is rhythm type, and second index is analysis type,
//     with "0" being onsets.
//

void Tool_composite::doOnsetAnalyses(HumdrumFile& infile) {
	if (m_analyses.at(m_COMPOSITE_FULL).at(m_ONSET).size() > 0) {
		doOnsetAnalysis(m_analyses.at(m_COMPOSITE_FULL).at(m_ONSET), infile, "");
	}
	if (m_analyses.at(m_COMPOSITE_A).at(m_ONSET).size() > 0) {
		doOnsetAnalysis(m_analyses.at(m_COMPOSITE_A).at(m_ONSET), infile, "A");
	}
	if (m_analyses.at(m_COMPOSITE_A).at(m_ONSET).size() > 0) {
		doOnsetAnalysis(m_analyses.at(m_COMPOSITE_B).at(m_ONSET), infile, "B");
	}

	// Coincidence onset analysis must come after other onset analyses since it
	// relies on group A + group B.
	if (m_analyses.at(m_COINCIDENCE).at(m_ONSET).size() > 0) {
		doOnsetAnalysisCoincidence(m_analyses.at(m_COINCIDENCE).at(m_ONSET),
		                           m_analyses.at(m_COMPOSITE_A).at(m_ONSET),
		                           m_analyses.at(m_COMPOSITE_B).at(m_ONSET));
	}
}



//////////////////////////////
//
// Tool_composite::doOnsetAnalysisCoincidence -- Note onset analysis for coincidence analysis.
//

void Tool_composite::doOnsetAnalysisCoincidence(vector<double>& output,
		vector<double>& inputA, vector<double>& inputB) {
	fill(output.begin(), output.end(), 0);
	for (int i=0; i<(int)inputA.size(); i++) {
		if ((inputA[i] > 0) && (inputB[i] > 0)) {
			output[i] = inputA[i] + inputB[i];
		}
	}
}



//////////////////////////////
//
// Tool_composite::doOnsetAnalysis -- Count all of the notes on each line
//   (that are in **kern spines, ignoring other kern-like spines).
//

void Tool_composite::doOnsetAnalysis(vector<double>& analysis, HumdrumFile& infile,
		const string& targetGroup) {
	// analysis should already be sized to the number of lines in infile,
	// and initialized to zero.

	bool fullQ = (targetGroup == "") ? true : false;
	string group;

	for (int i=0; i<(int)infile.getLineCount(); i++) {
		if (!infile[i].isData()) {
			continue;
		}
		int csum = 0;
		for (int j=0; j<infile[i].getFieldCount(); j++) {
			HTp token = infile.token(i, j);
			if (!token->isKern()) {
				continue;
			}
			if (!fullQ) {
				string group = token->getValue("auto", "group");
				if (group != targetGroup) {
					continue;
				}
			}
			csum += countNoteOnsets(token);
		}
		analysis.at(i) = csum;
	}
}



//////////////////////////////
//
// Tool_composite::countNoteOnsets --
//

int Tool_composite::countNoteOnsets(HTp token) {
   vector<string> subtoks;
   subtoks = token->getSubtokens();
   int sum = 0;
   if (*token == ".") {
      return sum;
   }
   for (int i=0; i<(int)subtoks.size(); i++) {
      if (subtoks[i].find('r') != string::npos) {
         continue;
      }
      if (subtoks[i].find('_') != string::npos) {
         continue;
      }
      if (subtoks[i].find(']') != string::npos) {
         continue;
      }
      sum++;
   }
   return sum;
}



//////////////////////////////
//
// Tool_composite::doAccentAnalyses --
//

void Tool_composite::doAccentAnalyses(HumdrumFile& infile) {
	cerr << "DOING ACCENT ANALYSES" << endl;
}



//////////////////////////////
//
// Tool_composite::doOrnamentAnalyses --
//

void Tool_composite::doOrnamentAnalyses(HumdrumFile& infile) {
	cerr << "DOING ORNAMENT ANALYSES" << endl;
}



//////////////////////////////
//
// Tool_composite::doSlurAnalyses --
//

void Tool_composite::doSlurAnalyses(HumdrumFile& infile) {
	cerr << "DOING SLUR ANALYSES" << endl;
}



//////////////////////////////
//
// Tool_composite::doTotalAnalyses -- The totals analysis is the last
//    index in the m_analysis array.  It is the sum of all other analyses
//    (provided that they are non-negative).
//

void Tool_composite::doTotalAnalyses(HumdrumFile& infile) {
	if (m_numericAnalysisSpineCount <= 1) {
		// nothing to do.
		return;
	}

	// first index is rhythm analysis type
	// second index is numeric analysis type
	// third index is row in data (if analysis type is active)

	for (int i=0; i<(int)m_analyses.size(); i++) {
		for (int k=0; k<infile.getLineCount(); k++) {
			double sum = 0.0;
			for (int j=0; j<(int)m_analyses.at(i).size()-1; j++) {
				if (k < (int)m_analyses.at(i).at(j).size()) {
					sum += m_analyses.at(i).at(j).at(k);
				}
			}
			m_analyses.at(i).back().at(k) = sum;
		}
	}
}



//////////////////////////////
//
// Tool_composite::analyzeOutputVariables --
//

void Tool_composite::analyzeOutputVariables(HumdrumFile& infile) {

	m_instrumentNameIndex  = 0;
	m_instrumentAbbrIndex  = 0;
	m_timeSignatureIndex   = 0;
	m_meterSymbolIndex     = 0;
	m_groupAssignmentIndex = 0;
	m_firstDataIndex       = 0;
	m_verseLabelIndex      = 0;
	m_striaIndex           = 0;  // just before clef line in score
	m_sizeIndex            = 0;
	m_clefIndex            = 0;

	int barlineIndex = 0;
	int lastInterpBeforeBarline = 0;

	for (int i=0; i<infile.getLineCount(); i++) {
		if (!infile[i].hasSpines()) {
			continue;
		}
		if (infile[i].isData()) {
			m_firstDataIndex = i;
			break;
		}
		if (infile[i].isBarline()) {
			barlineIndex = i;
		}
		if (!infile[i].isInterpretation()) {
			continue;
		}
		if (infile[i].isManipulator()) {
			continue;
		}
		if (!barlineIndex) {
			lastInterpBeforeBarline = i;
		}
		for (int j=0; j<infile[i].getFieldCount(); j++) {
			HTp token = infile.token(i, j);
			if (!token->isKernLike()) {
				continue;
			}
			if (token->isClef()) {
				m_clefIndex = i;
			}
			if (token->isInstrumentName()) {
				m_instrumentNameIndex = i;
			}
			if (token->isInstrumentAbbreviation()) {
				m_instrumentAbbrIndex = i;
			}
			if (token->isTimeSignature()) {
				m_timeSignatureIndex = i;
			}
			if (token->isMeterSymbol()) {
				m_meterSymbolIndex = i;
			}
			if (token->isStria()) {
				m_striaIndex = i;
			}
			if (token->compare(0, 6, "*size:") == 0) {
				m_sizeIndex = i;
			}
			if (token->compare(0, 5, "*grp:") == 0) {
				if (m_groupAssignmentIndex <= 0) {
					m_groupAssignmentIndex = i;
				}
			}
			if (token->compare(0, 4, "*v:") == 0) {
				if (!barlineIndex) {
					// do not add after a barline (but that would probably be OK).
					if (m_verseLabelIndex <= 0) {
						m_verseLabelIndex = i;
					}
				}
			}
		}
	}

	// If any variable is 0, then decide on a location to insert.  Maybe
	// store as a negative value to indicate that the line needs to be added.

	if (!m_verseLabelIndex) {
		// In rare cases there could be a LO parameter for the last interpretation
		// line (such as a layout parameter for a clef).  Currently ignore this problem.
		if (lastInterpBeforeBarline > 0) {
			m_verseLabelIndex = -lastInterpBeforeBarline;
		}
	}

	if (!m_striaIndex) {
		// In rare cases there could be a LO parameter for the last interpretation
		// line (such as a layout parameter for a clef).  Currently ignore this problem.
		if (m_clefIndex > 0) {
			m_striaIndex = -m_clefIndex;
		}
	}

	if (!m_sizeIndex) {
		// In rare cases there could be a LO parameter for the last interpretation
		// line (such as a layout parameter for a clef).  Currently ignore this problem.
		if (m_clefIndex > 0) {
			m_sizeIndex = -m_clefIndex;
		}
	}

}



//////////////////////////////
//
// Tool_composite::addStaffInfo -- Find staff number line and add staff line to output
//     if found.
//

void Tool_composite::addStaffInfo(HumdrumFile& output, HumdrumFile& infile) {
	int staffindex = -1;
	HumRegex hre;
	int lastStaff = -1;
	for (int i=0; i<infile.getLineCount(); i++) {
		if (infile[i].isData()) {
			break;
		}
		if (!infile[i].isInterpretation()) {
			continue;
		}
		for (int j=0; j<infile[i].getFieldCount(); j++) {
			HTp token = infile.token(i, j);
			if (!token->isKernLike()) {
				continue;
			}
			if (hre.search(token, "^\\*staff(\\d+)")) {
				lastStaff = hre.getMatchInt(1);
				staffindex = i;
				break;
			}
		}

		if (staffindex > 0) {
			break;
		}
	}
	if (staffindex < 0) {
		return;
	}
	if (lastStaff < 0) {
		return;
	}

	int currentStaff = lastStaff;
	for (int j=output[staffindex].getFieldCount() - 1; j>=0; j--) {
		HTp token = output.token(staffindex, j);
		string staffnum = "*staff" + to_string(++currentStaff);
		token->setText(staffnum);
	}

	output[staffindex].createLineFromTokens();

	int beginStaff = lastStaff + 1;
	int endStaff = lastStaff + output[staffindex].getFieldCount();
	int staffcount = output[staffindex].getFieldCount();
	string decoadd;
	if (staffcount > 1) {
		decoadd = "[(";
		for (int i=beginStaff; i<=endStaff; i++) {
			decoadd += "s" + to_string(i);
		}
		decoadd += ")]";
	} else {
		decoadd = "s" + to_string(beginStaff);
	}

	for (int i=output.getLineCount() - 1; i>=0; i--) {
		if (!output[i].isGlobalReference()) {
			continue;
		}
		HTp token = output.token(i, 0);
		if (hre.search(token, "!!!system-decoration:(\\s*)(.*)(\\s*)$")) {
			string prevalue = hre.getMatch(1);
			string original = hre.getMatch(2);
			string postvalue = hre.getMatch(3);
			string text = "!!!system-decoration:"+ prevalue + original + decoadd + postvalue;
			token->setText(text);
			output[i].createLineFromTokens();
			break;
		}
	}

}



///////////////////////////////
//
// Tool_composite::addTimeSignatureChanges --
//

void Tool_composite::addTimeSignatureChanges(HumdrumFile& output, HumdrumFile& infile) {
	string timesig;
	string groupAsig;
	string groupBsig;

	for (int i=0; i<infile.getLineCount(); i++) {
		if (!infile[i].isInterpretation()) {
			continue;
		}
		timesig    = "*";
		groupAsig  = "*";
		groupBsig  = "*";

		bool foundtime = false;

		for (int j=0; j<infile[i].getFieldCount(); j++) {
			HTp token = infile.token(i, j);
			if (token->isTimeSignature()) {
				string group = token->getValue("auto", "group");
				if (group == "") {
					timesig = *token;
					foundtime = true;
				} else if (group == "A") {
					groupAsig = *token;
					foundtime = true;
				} else if (group == "B") {
					groupBsig = *token;
					foundtime = true;
				} else {
					timesig = *token;
					foundtime = true;
				}
			}
		}

		if (!foundtime) {
			continue;
		}

		for (int j=0; j<output[i].getFieldCount(); j++) {
			HTp token = output.token(i, j);
			string spinetype = token->getDataType();
			if ((spinetype == "**kern-coin") || (spinetype == "**kern-comp")) {
				if (timesig != "") {
					token->setText(timesig);
				} else if (groupAsig != "") {
					token->setText(groupAsig);
				} else if (groupBsig != "") {
					token->setText(groupAsig);
				}
			} else if (spinetype == "**kern-grpA") {
				if (groupAsig != "") {
					token->setText(groupAsig);
				}
			} else if (spinetype == "**kern-grpB") {
				if (groupBsig != "") {
					token->setText(groupBsig);
				}
			}
		}
		output[i].createLineFromTokens();

	}
}



///////////////////////////////
//
// Tool_composite::addMeterSignatureChanges --
//

void Tool_composite::addMeterSignatureChanges(HumdrumFile& output, HumdrumFile& infile) {
	string metersig;
	string groupAsig;
	string groupBsig;

	for (int i=0; i<infile.getLineCount(); i++) {
		if (!infile[i].isInterpretation()) {
			continue;
		}
		metersig   = "";
		groupAsig  = "";
		groupBsig  = "";

		bool foundtime = false;

		for (int j=0; j<infile[i].getFieldCount(); j++) {
			HTp token = infile.token(i, j);
			if (token->isMetricSymbol()) {
				string group = token->getValue("auto", "group");
				if (group == "") {
					metersig = *token;
					foundtime = true;
				} else if (group == "A") {
					groupAsig = *token;
					foundtime = true;
				} else if (group == "B") {
					groupBsig = *token;
					foundtime = true;
				} else {
					metersig = *token;
					foundtime = true;
				}
			}
		}

		if (!foundtime) {
			continue;
		}

		for (int j=0; j<output[i].getFieldCount(); j++) {
			HTp token = output.token(i, j);
			string spinetype = token->getDataType();
			if ((spinetype == "**kern-coin") || (spinetype == "**kern-comp")) {
				if (metersig != "") {
					token->setText(metersig);
				} else if (groupAsig != "") {
					token->setText(groupAsig);
				} else if (groupBsig != "") {
					token->setText(groupAsig);
				}
			} else if (spinetype == "**kern-grpA") {
				if (groupAsig != "") {
					token->setText(groupAsig);
				}
			} else if (spinetype == "**kern-grpB") {
				if (groupBsig != "") {
					token->setText(groupBsig);
				}
			}
		}
		output[i].createLineFromTokens();

	}
}



//////////////////////////////
//
// adjustBadCoincidenceRests -- Sometimes coincidence rests are not so great, particularly
//    when they are long and there is a small note that will add to it to fill in a measure
//    (such as a 5 eighth-note rest in 6/8).  Try to simplify such case in this function
//    (more can be added on a case-by-case basis).
//
//    3... => 5 eighth notes (split according to meter).
//

void Tool_composite::adjustBadCoincidenceRests(HumdrumFile& output, HumdrumFile& infile) {
	vector<HTp> sstarts;
	output.getSpineStartList(sstarts, "**kern-coin");
	if (sstarts.empty()) {
		// no coincidence spine to process
		return;
	}

	HumRegex hre;
	vector<HumNum> timesigtop(output.getLineCount(), 4);
	vector<HumNum> timesigbot(output.getLineCount(), 4);
	HumNum tts = 4;
	HumNum bts = 4;

	for (int i=0; i<output.getLineCount(); i++) {
		if (!output[i].isInterpretation()) {
			timesigtop[i] = tts;
			timesigbot[i] = bts;
			continue;
		}
		for (int j=0; j<output[i].getFieldCount(); j++) {
			HTp token = output.token(i, j);
			if (token->getDataType() != "**kern-coin") {
				continue;
			}
			if (token->isTimeSignature()) {
				if (hre.search(token, "^\\*M(\\d+)/(\\d+)")) {
					tts = hre.getMatch(1);
					bts = hre.getMatch(2);
				}
			}
			break;
		}
		timesigtop[i] = tts;
		timesigbot[i] = bts;
	}

	HTp current = sstarts.at(0);
	string rhythm;
	while (current) {
		if (!current->isData()) {
			current = current->getNextToken();
			continue;
		}
		if (current->isNull()) {
			current = current->getNextToken();
			continue;
		}
		if (!current->isRest()) {
			// should check pitches as well
			current = current->getNextToken();
			continue;
		}
		if (hre.search(current, "(\\d+%?\\d*\\.*)")) {
			rhythm = hre.getMatch(1);
			if (rhythm == "3...") {
				int lindex = current->getLineIndex();
				current = fixBadRestRhythm(current, rhythm, timesigtop[lindex], timesigbot[lindex]);
			}
		}
		current = current->getNextToken();
	}

}


//////////////////////////////
//
// Tool_composite::fixBadRestRhythm --
//

HTp Tool_composite::fixBadRestRhythm(HTp token, string& rhythm, HumNum tstop, HumNum tsbot) {
	HumNum duration = Convert::recipToDuration(rhythm);
	if (rhythm == "3...") {
		duration = 5;
		duration /= 8;
		duration *= 4;
	}
	HumRegex hre;
	vector<HTp> tokens;
	HTp current = token;
	bool compound = false;
	HumNum testval = tstop / 3;
	if ((testval > 1) && testval.isInteger()) {
		compound = true;
	}
	tokens.push_back(token);
	current = current->getNextToken();
	while (current) {
		if (current->isBarline()) {
			tokens.push_back(current);
			break;
		}
		if (current->isData()) {
			tokens.push_back(current);
			if (!current->isNull()) {
				break;
			}
		}
		current = current->getNextToken();
	}

	vector<HumNum> beatfrac;
	for (int i=0; i<(int)tokens.size(); i++) {
		HumNum value = tokens[i]->getDurationFromBarline();
		if (compound) {
			value /= 3;
		}
		value *= tsbot;
		value /= 4;
		double dval = value.getFloat();
		int intval = (int)dval;
		value -= intval;
		beatfrac.push_back(value);
	}

	for (int i=1; i<(int)tokens.size() - 1; i++) {
		if (beatfrac[i] == 0) {
			// split rest at a beat boundary
			HumNum pos1 = token->getDurationFromStart();
			HumNum pos2 = tokens[i]->getDurationFromStart();
			HumNum predur = pos2 - pos1;
			HumNum postdur = duration - predur;
			string newrhy = Convert::durationToRecip(predur);
			string toktext = *token;
			string text2 = *token;
			hre.replaceDestructive(toktext, newrhy, rhythm);
			token->setText(toktext);
			newrhy = Convert::durationToRecip(postdur);
			hre.replaceDestructive(text2, newrhy, rhythm);
			tokens[i]->setText(text2);
			// doing only once for now
			break;
		}
	}

	if (tokens.back()->isBarline()) {
		return tokens.back();
	}
	if (tokens.size() == 1) {
		return tokens.back();
	}
	if (tokens.size() > 1) {
		return tokens.at((int)tokens.size() - 2);
	}
	// shouldn't get here
	return NULL;
}



//////////////////////////////
//
// Tool_composite::convertNotesToRhythms --
//

void Tool_composite::convertNotesToRhythms(HumdrumFile& infile) {
	HumRegex hre;
	for (int i=0; i<infile.getLineCount(); i++) {
		if (!infile[i].isData()) {
			continue;
		}
		for (int j=0; j<infile[i].getFieldCount(); j++) {
			HTp token = infile.token(i, j);
			if (!token->isKern()) {
				continue;
			}
			if (token->isRest()) {
				continue;
			}
			if (token->isNull()) {
				continue;
			}
			string text = *token;
			hre.replaceDestructive(text, "eR", "[A-Ga-g]+[#n-]*", "g");
			token->setText(text);
		}
		infile[i].createLineFromTokens();
	}
}



//////////////////////////////
//
// getEventCount -- Return the number of note attacks.
//

int Tool_composite::getEventCount(vector<string>& data) {
	int output = 0;
	for (int i=0; i<(int)data.size(); i++) {
		if (data[i] == "") {
			continue;
		}
		if (data[i] == ".") {
			continue;
		}
		if (data[i].find("*") != string::npos) {
			continue;
		}
		if (data[i].find("!") != string::npos) {
			continue;
		}
		if (data[i].find("r") != string::npos) {
			continue;
		}
		if (data[i].find("_") != string::npos) {
			continue;
		}
		if (data[i].find("]") != string::npos) {
			continue;
		}
		output++;
	}
	return output;
}





// Note state variables for grouping:
#define TYPE_UNDEFINED           9 /* for inactive groupings */
#define TYPE_NoteSustainAttack   3
#define TYPE_NoteAttack          2
#define TYPE_RestAttack          1
#define TYPE_NONE                0
#define TYPE_RestSustain        -1
#define TYPE_NoteSustain        -2
#define TYPE_NoteSustainSustain -3

#define COMPOSITE_TREMOLO_MARKER "||"


/////////////////////////////////
//
// Tool_compositeold::Tool_compositeold -- Set the recognized options for the tool.
//

Tool_compositeold::Tool_compositeold(void) {
	define("a|append=b",                      "append data to end of line (top of system)");

	define("P|analysis-onsets=b",             "count number of note (pitch) onsets in feature");
	define("A|analysis-accents=b",            "count number of accents in feature");
	define("O|analysis-ornaments=b",          "count number of ornaments in feature");
	define("S|analysis-slurs=b",              "count number of slur beginnings/ending in feature");
	define("T|analysis-total=b",              "count total number of analysis features for each note");
	define("all|all-analyses=b",              "do all analyses");

	define("grace=b",                         "include grace notes in composite rhythm");
	define("u|stem-up=b",                     "stem-up for composite rhythm parts");
	define("x|extract=b",                     "only output composite rhythm spines");
	define("o|only=s",                        "output notes of given group");
	define("t|tremolo=b",                     "preserve tremolos");
	define("B|no-beam=b",                     "do not apply automatic beaming");
	define("G|only-groups=b",                 "only split composite rhythm into separate streams by group markers");
	define("g|add-groups=b",                  "also split composite rhythm into separate streams by group markers");
	define("c|coincidence-rhythm=b",          "add coincidence rhythm for groups");
	define("m|match|together=s:limegreen",    "mark alignments in group composite analyses");
	define("M=b",                             "equivalent to -m limegreen");
	define("n|together-in-score=s:limegreen", "mark alignments in group in SCORE (not analyses)");
	define("N=b",                             "equivalent to -n limegreen");
	define("Z|no-zeros|no-zeroes=b",          "do not show zeros in analyses.");
	define("pitch=s:eR",                      "pitch to display for composite rhythm");
	define("debug=b",                         "print debugging information");
}



/////////////////////////////////
//
// Tool_compositeold::run -- Do the main work of the tool.
//

bool Tool_compositeold::run(HumdrumFileSet& infiles) {
	bool status = true;
	for (int i=0; i<infiles.getCount(); i++) {
		status &= run(infiles[i]);
	}
	return status;
}


bool Tool_compositeold::run(const string& indata, ostream& out) {
	HumdrumFile infile;
	infile.readStringNoRhythm(indata);
	bool status = run(infile);
	if (hasAnyText()) {
		getAllText(out);
	} else {
		out << infile;
	}
	return status;
}


bool Tool_compositeold::run(HumdrumFile& infile, ostream& out) {
	bool status = run(infile);
	if (hasAnyText()) {
		getAllText(out);
	} else {
		out << infile;
	}
	return status;
}


bool Tool_compositeold::run(HumdrumFile& infile) {
	initialize();
	processFile(infile);
	if (m_analysisQ) {
		analyzeComposite(infile);
	}
	addLabelsAndStria(infile);
	if (!m_onlyQ) {
		infile.createLinesFromTokens();
		// need to convert to text for now:
		m_humdrum_text << infile;
	}
	return true;
}



//////////////////////////////
//
// Tool_compositeold::analyzeComposite -- Do numerical analyses of features from notes
//     in composite rhythms (full, group A, group B, coincidence) and insert
//     analyses as lyrics for each composite rhythm staff.
//

void Tool_compositeold::analyzeComposite(HumdrumFile& infile) {
	infile.analyzeStructureNoRhythm();

	initializeAnalysisArrays(infile);

	// groups is a list of the spine starts for composite rhythms.
	// the value is null if there is no analysis of that type, or
	// if there always has been a -AROS analysis already for that type.
	// groups[0] = full composite rhythm spine start
	// groups[1] = group A composite rhythm spine start
	// groups[2] = group B composite rhythm spine start
	// groups[3] = coincidence rhythm spine start
	vector<HTp> groups;
	getCompositeSpineStarts(groups, infile);

	// Invalidate composite spines that already have analyses:
	// **text spine after composite spine:
	for (int i=0; i<(int)groups.size(); i++) {
		if (!groups[i]) {
			continue;
		}
		int track = groups[i]->getTrack();
		HTp current = groups[i];
		current = current->getNextField();
		while (current) {
			int track2 = current->getTrack();
			if (track2 == track) {
				current = current->getNextField();
				continue;
			}
			if (current->isDataType("**text")) {
				groups[i] = NULL;
			}
			break;
		}
	}

	// tracks :: A boolean for composite analysis staves.  These staves
	// will have extra **text spines added after them with the analyses
	// inserted into them.  The tracks vector also is used to prevent
	// composite rhythms from being includeded in the numerical analyses
	// for note onsets, accents, ornaments and slurs.
	vector<bool> tracks(infile.getMaxTrack() + 1, false);;
	for (int i=0; i<(int)groups.size(); i++) {
		if (groups[i] == NULL) {
			continue;
		}
		int track = groups[i]->getTrack();
		tracks[track] = true;
	}

	// Presuming analysis tracks are already in sorted order
// probably get rid of analysisTracks if no longer used?
	vector<int> analysisTracks(groups.size());
	int addition = 0;
	// Coincidence spine added at end of array, but calculate
	// for beginning here:
	if (groups[3]) {
		addition++;
		analysisTracks[3] = groups[3]->getTrack() + addition;
	}
	for (int i=0; i<3; i++) {
		if (!groups[i]) {
			continue;
		}
		addition++;
		analysisTracks[i] = groups[i]->getTrack() + addition;
	}

	// Do analyses and keep track of which features to add to inserted **text spines.
	vector<string> spines;
	if (m_analysisOnsetsQ) {
		spines.push_back("onsets");
		analyzeCompositeOnsets(infile, groups, tracks);
	}
	if (m_analysisAccentsQ) {
		spines.push_back("accents");
		analyzeCompositeAccents(infile, groups, tracks);
	}
	if (m_analysisOrnamentsQ) {
		spines.push_back("ornaments");
		analyzeCompositeOrnaments(infile, groups, tracks);
	}
	if (m_analysisSlursQ) {
		spines.push_back("slurs");
		analyzeCompositeSlurs(infile, groups, tracks);
	}

	if ((spines.size() > 1) && m_analysisTotalQ) {
		spines.push_back("total");
		analyzeCompositeTotal(infile, groups, tracks);
	}

	if (spines.size() == 0) {
		// Strange: nothing to do...
		return;
	}

	// Fill in the analyses in the desired spine:
	vector<int> expansionList = getExpansionList(tracks,
			infile.getMaxTrack(), (int)spines.size());
	string expansion = makeExpansionString(expansionList);
	Tool_extract extract;
	stringstream edata;
	infile.createLinesFromTokens();
	edata << infile;
	HumdrumFile einput;
	einput.readString(edata.str());
	extract.setModified("s", expansion);
	extract.setModified("n", "vdata");
	extract.run(einput);
	HumdrumFile outfile;
	outfile.readString(extract.getAllText());

	// Now go back an insert analyses into outfile.
	insertAnalysesIntoFile(outfile, spines, expansionList, tracks);

	// Replace contents of infile with the analysis:
	bool done = 1;
	if (done) {
		stringstream temp;
		outfile.createLinesFromTokens();
		temp << outfile;
		infile.readString(temp.str());
	}
}



//////////////////////////////
//
// Tool_compositeold::insertAnalysesIntoFile --
//     spines   -- List of string for analysis types requested.
//
//     spineMap -- Expansion list that was applied to the output file to add
//                 extra **text spines for storing analyses.  Index according
//                 to the new track numberes and the value is the old track
//                 value or 0 if it is a new analysis spine that was added.
//
//     tracks   -- A boolean for composite analysis staves.  These tracks
//                 will have extra **text spines added after them
//                 with the analyses inserted into the added spines.  The tracks
//                 vector also is used to prevent composite rhythms
//                 from being included in the numerical analyses
//                 for note onsets, accents, ornaments and
//                 slurs. The track values are the old track assignments
//                 before extra analysis spines are added (see spineMap which
//                 is similar

void Tool_compositeold::insertAnalysesIntoFile(HumdrumFile& outfile, vector<string>& spines,
		vector<int>& spineMap, vector<bool>& tracks) {

	int count = (int)spines.size();
	if (count <= 0) {
		return;
	}

	vector<int> trackMap; // spine map but indexed by track by adding a dummy first element.
	trackMap.resize(spineMap.size() + 1);
	trackMap[0] = -1;
	for (int i=0; i<(int)spineMap.size(); i++) {
		trackMap[i+1] = spineMap[i];
	}

	// dataByTrack -- a lookup table where the index is the new track number, and the
	// value is a pointer to the vector containing the analysis to place in that
	// track.
	vector<vector<double>*> dataByTrack;
	assignAnalysesToVdataTracks(dataByTrack, spines, outfile);

	stringstream ss;

	for (int i=0; i<outfile.getLineCount(); i++) {
		if (!outfile[i].isData()) {
			continue;
		}
		for (int j=0; j<outfile[i].getFieldCount(); j++) {
			HTp token = outfile.token(i, j);
			int track = token->getTrack();
			if (dataByTrack.at(track) == NULL) {
				continue;
			}
			double value = dataByTrack.at(track)->at(i);
			if (m_nozerosQ) {
				if (value > 0) {
					ss.str("");
					ss << value;
					string newvalue = ss.str();
					token->setText(newvalue);
				}
			} else {
				ss.str("");
				ss << value;
				string newvalue = ss.str();
				token->setText(newvalue);
			}
		}
	}
}



//////////////////////////////
//
// Tool_compositeold::assignAnalysesToVdataTracks --
//    * spines is a list of the analysis/analyses that are done. Values are:
//         onsets    = note onsets
//         accents   = note accents
//         ornaments = note ornaments
//         slurs     = slurs
//         total     = total score
//    * tracks is a boolean to indicate that there is a **vdata spine
//      at that position in the file (zero-indexed spine position)
//      that should be filled in with an analysis.
//    * dataByTrack is a list of analysis data (pointers) that should be filling
//      in a particular spine (also zero-indexed).
//
//
//

void Tool_compositeold::assignAnalysesToVdataTracks(vector<vector<double>*>& dataByTrack,
		vector<string>& spines, HumdrumFile& outfile) {

	vector<HTp> spinestarts;
	outfile.getSpineStartList(spinestarts);

	// spineMap is a 0-index track map.
	dataByTrack.resize(spinestarts.size()+1);
	for (int i=0; i<(int)dataByTrack.size(); i++) {
		dataByTrack[i] = NULL;
	}

	for (int i=0; i<(int)spinestarts.size(); i++) {
		HTp token = spinestarts[i];
		if (!((*token == "**kern-grpA") || (*token == "**kern-grpB") ||
		      (*token == "**kern-comp") || (*token == "**kern-coin"))) {
			continue;
		}
		for (int j=0; j<(int)spines.size(); j++) {
			if (i + j + 1 > (int)spinestarts.size() - 1) {
				break;
			}
			HTp vtoken = spinestarts.at(i+j+1);
			if (*vtoken != "**vdata") {
				continue;
			}
			string text = "**vdata-";
			text += spines[j];
			vtoken->setText(text);
			int track = vtoken->getTrack();
			if (spines[j] == "onsets") {
				if (*token == "**kern-grpA") {
					dataByTrack[track] = &m_analysisOnsets.at(1);
				} else if (*token == "**kern-grpB") {
					dataByTrack[track] = &m_analysisOnsets.at(2);
				} else if (*token == "**kern-comp") {
					dataByTrack[track] = &m_analysisOnsets.at(0);
				} else if (*token == "**kern-coin") {
					dataByTrack[track] = &m_analysisOnsets.at(3);
				}
			} else if (spines[j] == "accents") {
				if (*token == "**kern-grpA") {
					dataByTrack[track] = &m_analysisAccents.at(1);
				} else if (*token == "**kern-grpB") {
					dataByTrack[track] = &m_analysisAccents.at(2);
				} else if (*token == "**kern-comp") {
					dataByTrack[track] = &m_analysisAccents.at(0);
				} else if (*token == "**kern-coin") {
					dataByTrack[track] = &m_analysisAccents.at(3);
				}
			} else if (spines[j] == "ornaments") {
				if (*token == "**kern-grpA") {
					dataByTrack[track] = &m_analysisOrnaments.at(1);
				} else if (*token == "**kern-grpB") {
					dataByTrack[track] = &m_analysisOrnaments.at(2);
				} else if (*token == "**kern-comp") {
					dataByTrack[track] = &m_analysisOrnaments.at(0);
				} else if (*token == "**kern-coin") {
					dataByTrack[track] = &m_analysisOrnaments.at(3);
				}
			} else if (spines[j] == "slurs") {
				if (*token == "**kern-grpA") {
					dataByTrack[track] = &m_analysisSlurs.at(1);
				} else if (*token == "**kern-grpB") {
					dataByTrack[track] = &m_analysisSlurs.at(2);
				} else if (*token == "**kern-comp") {
					dataByTrack[track] = &m_analysisSlurs.at(0);
				} else if (*token == "**kern-coin") {
					dataByTrack[track] = &m_analysisSlurs.at(3);
				}
			} else if (spines[j] == "total") {
				if (*token == "**kern-grpA") {
					dataByTrack[track] = &m_analysisTotal.at(1);
				} else if (*token == "**kern-grpB") {
					dataByTrack[track] = &m_analysisTotal.at(2);
				} else if (*token == "**kern-comp") {
					dataByTrack[track] = &m_analysisTotal.at(0);
				} else if (*token == "**kern-coin") {
					dataByTrack[track] = &m_analysisTotal.at(3);
				}
			}
		}
		i += (int)spines.size();
	}
}



//////////////////////////////
//
// Tool_compositeold::analyzeCompositeOnsets --
//

void Tool_compositeold::analyzeCompositeOnsets(HumdrumFile& infile,
		vector<HTp>& groups, vector<bool>& tracks) {

	if (groups[0]) {
		doTotalOnsetAnalysis(m_analysisOnsets[0], infile, groups[0]->getTrack(), tracks);
	}

	if ((groups[1] && groups[2]) || groups[3]) {
		doGroupOnsetAnalyses(m_analysisOnsets.at(1), m_analysisOnsets.at(2), infile);
	}

	if (groups[3]) {
		doCoincidenceOnsetAnalysis(m_analysisOnsets);
	}

	if (m_debugQ) {
		for (int i=0; i<(int)m_analysisOnsets[0].size(); i++) {
			for (int j=0; j<4; j++) {
				cout << m_analysisOnsets[j][i] << "\t";
			}
			cout << endl;
		}
	}

}



//////////////////////////////
//
// Tool_compositeold::doCoincidenceOnsetAnalyses --
//

void Tool_compositeold::doCoincidenceOnsetAnalysis(vector<vector<double>>& analysis) {
	if (analysis.size() < 4) {
		cerr << "ERROR: Expecting at least 4 analysis slots." << endl;
	}
	fill(analysis[3].begin(), analysis[3].end(), -1);

	bool found = false;
	for (int i=0; i<(int)analysis[1].size(); i++) {
		if ((analysis[1].at(i) > 0) && (analysis[2].at(i) > 0)) {
			analysis[3].at(i) = analysis[1].at(i) + analysis[2].at(i);
			found = true;
		}
	}
	if (found) {
		return;
	}
}



//////////////////////////////
//
// Tool_compositeold::doGroupOnsetAnalyses --
//

void Tool_compositeold::doGroupOnsetAnalyses(vector<double>& analysisA,
      vector<double>& analysisB, HumdrumFile& infile) {

	int asum = 0;
	int bsum = 0;
	for (int i=0; i<(int)infile.getLineCount(); i++) {
		if (!infile[i].isData()) {
			continue;
		}
		asum = 0;
		bsum = 0;
		for (int j=0; j<infile[i].getFieldCount(); j++) {
			HTp token = infile.token(i, j);
			if (!token->isKern()) {
				continue;
			}
			string group = token->getValue("auto", "group");
			if (group == "A") {
				asum += countNoteOnsets(token);
			} else if (group == "B") {
				bsum += countNoteOnsets(token);
			}
		}
		if (asum > 0) {
			// Don't report 0 note onsets for tied notes.
			analysisA[i] = asum;
		}
		if (bsum > 0) {
			// Don't report 0 note onsets for tied notes.
			analysisB[i] = bsum;
		}
	}
}



//////////////////////////////
//
// Tool_compositeold::doTotalOnsetAnalysis -- Count all of the notes on each line
//   (that are not any potential previous analsysis spine).
//

void Tool_compositeold::doTotalOnsetAnalysis(vector<double>& analysis, HumdrumFile& infile,
		int track, vector<bool>& tracks) {

	analysis.resize(infile.getLineCount());
	fill(analysis.begin(), analysis.end(), -1);

	// Identify previous composite analysis spines that should be ignored:
	vector<HTp> composite;
	vector<bool> ignore(infile.getMaxTrack() + 1, false);
	getCompositeSpineStarts(composite, infile);
	for (int i=0; i<(int)composite.size(); i++) {
		if (!composite[i]) {
			continue;
		}
		int track = composite[i]->getTrack();
		ignore[track] = true;
	}

	int csum = 0;
	for (int i=0; i<(int)infile.getLineCount(); i++) {
		if (!infile[i].isData()) {
			continue;
		}
		csum = 0;
		for (int j=0; j<infile[i].getFieldCount(); j++) {
			HTp token = infile.token(i, j);
			if (!token->isKern()) {
				continue;
			}
			int track = token->getTrack();
			if (ignore[track]) {
				continue;
			}
			csum += countNoteOnsets(token);
		}
		if (csum > 0) {
			// don't report 0 counts on tied notes
			analysis[i] = csum;
		}
	}
}



//////////////////////////////
//
// Tool_compositeold::initializeAnalysisArrays --
//

void Tool_compositeold::initializeAnalysisArrays(HumdrumFile& infile) {
	m_analysisOnsets.resize(4);
	for (int i=0; i<(int)m_analysisOnsets.size(); i++) {
		m_analysisOnsets[i].resize(infile.getLineCount());
		fill(m_analysisOnsets[i].begin(), m_analysisOnsets[i].end(), 0.0);
	}

	m_analysisAccents.resize(4);
	for (int i=0; i<(int)m_analysisAccents.size(); i++) {
		m_analysisAccents[i].resize(infile.getLineCount());
		fill(m_analysisAccents[i].begin(), m_analysisAccents[i].end(), 0.0);
	}

	m_analysisOrnaments.resize(4);
	for (int i=0; i<(int)m_analysisOrnaments.size(); i++) {
		m_analysisOrnaments[i].resize(infile.getLineCount());
		fill(m_analysisOrnaments[i].begin(), m_analysisOrnaments[i].end(), 0.0);
	}

	m_analysisSlurs.resize(4);
	for (int i=0; i<(int)m_analysisSlurs.size(); i++) {
		m_analysisSlurs[i].resize(infile.getLineCount());
		fill(m_analysisSlurs[i].begin(), m_analysisSlurs[i].end(), 0.0);
	}

	m_analysisTotal.resize(4);
	for (int i=0; i<(int)m_analysisTotal.size(); i++) {
		m_analysisTotal[i].resize(infile.getLineCount());
		fill(m_analysisTotal[i].begin(), m_analysisTotal[i].end(), 0.0);
	}
}



//////////////////////////////
//
// Tool_compositeold::analyzeCompositeAccents --
//

void Tool_compositeold::analyzeCompositeAccents(HumdrumFile& infile, vector<HTp>& groups,
		vector<bool>& tracks) {

	for (int i=0; i<infile.getLineCount(); i++) {
		if (!infile[i].isData()) {
			continue;
		}
		for (int j=0; j<infile[i].getFieldCount(); j++) {
			HTp token = infile.token(i, j);
			if (!token->isKern()) {
				continue;
			}
			if (token->isNull()) {
				continue;
			}
			if (token->isRest()) {
				continue;
			}
			vector<string> subtoks = token->getSubtokens();
			int sum = 0;
			for (int k=0; k<(int)subtoks.size(); k++) {
				int staccato      = 0;
				int staccatissimo = 0;
				int tenuto        = 0;
				int marcato       = 0;
				int sforzando     = 0;
				for (int m=0; m<(int)subtoks[k].size(); m++) {
					int value = subtoks.at(k).at(m);
					if (value == '\'') { // staccato or staccatissimo
						staccato++;
					} else if (value == '`') { // staccatissimo
						staccatissimo++;
					} else if (value == '^') { // accent or heavy accent
						marcato++;
					} else if (value == '~') { // tenuto
						tenuto++;
					} else if (value == 'z') { // sforzando
						// also check in **dynam spines?
						sforzando++;
					}
				}
				if (staccato)      { sum++; }
				if (staccatissimo) { sum++; }
				if (tenuto)        { sum++; }
				if (marcato)       { sum++; }
				if (sforzando)     { sum++; }
			}
			string group = token->getValue("auto", "group");
			m_analysisAccents.at(0).at(i) += sum;
			if (group == "A") {
				m_analysisAccents.at(1).at(i) += sum;
			}
			if (group == "B") {
				m_analysisAccents.at(2).at(i) += sum;
			}
		}
	}

	// Calculate coincidence accents:
	for (int i=0; i<(int)m_analysisAccents[0].size(); i++) {
		if ((m_analysisAccents[1][i] > 0) && (m_analysisAccents[2][i] > 0)) {
			m_analysisAccents[3][i] += m_analysisAccents[1][i];
			m_analysisAccents[3][i] += m_analysisAccents[2][i];
		}
	}
}



//////////////////////////////
//
// Tool_compositeold::analyzeCompositeOrnaments --
//

void Tool_compositeold::analyzeCompositeOrnaments(HumdrumFile& infile, vector<HTp>& groups,
		vector<bool>& tracks) {

	for (int i=0; i<infile.getLineCount(); i++) {
		if (!infile[i].isData()) {
			continue;
		}
		for (int j=0; j<infile[i].getFieldCount(); j++) {
			HTp token = infile.token(i, j);
			if (!token->isKern()) {
				continue;
			}
			if (token->isNull()) {
				continue;
			}
			if (token->isRest()) {
				continue;
			}
			vector<string> subtoks = token->getSubtokens();
			int sum = 0;
			for (int k=0; k<(int)subtoks.size(); k++) {
				int trill     = 0;
				int mordent   = 0;
				int turn      = 0;
				for (int m=0; m<(int)subtoks[k].size(); m++) {
					int value = subtoks.at(k).at(m);
					if (value == 'T') { // major-second trill
						trill++;
					} else if (value == 't') { // minor-second trill
						trill++;
					} else if (value == 'M') { // mordent
						mordent++;
					} else if (value == 'm') { // mordent
						mordent++;
					} else if (value == 'W') { // mordent
						mordent++;
					} else if (value == 'w') { // mordent
						mordent++;
					} else if (value == 'S') { // turn
						turn++;
					} else if (value == 's') { // turn
						turn++;
					} else if (value == '$') { // inverted turn
						turn++;
					}
				}
				if (trill)      { sum++; }
				if (turn)       { sum++; }
				if (mordent)    { sum++; }
			}
			string group = token->getValue("auto", "group");
			m_analysisOrnaments.at(0).at(i) += sum;
			if (group == "A") {
				m_analysisOrnaments.at(1).at(i) += sum;
			}
			if (group == "B") {
				m_analysisOrnaments.at(2).at(i) += sum;
			}
		}
	}

	// Calculate coincidence accents:
	for (int i=0; i<(int)m_analysisOrnaments[0].size(); i++) {
		if ((m_analysisOrnaments[1][i] > 0) && (m_analysisOrnaments[2][i] > 0)) {
			m_analysisOrnaments[3][i] += m_analysisOrnaments[1][i];
			m_analysisOrnaments[3][i] += m_analysisOrnaments[2][i];
		}
	}
}



//////////////////////////////
//
// Tool_compositeold::analyzeCompositeSlurs --
//

void Tool_compositeold::analyzeCompositeSlurs(HumdrumFile& infile, vector<HTp>& groups,
		vector<bool>& tracks) {

	m_analysisSlurs.resize(4);

	for (int i=0; i<(int)m_analysisSlurs.size(); i++) {
		m_analysisSlurs[i].resize(infile.getLineCount());
		fill(m_analysisSlurs[i].begin(), m_analysisSlurs[i].end(), 0.0);
	}

	for (int i=0; i<infile.getLineCount(); i++) {
		if (!infile[i].isData()) {
			continue;
		}
		for (int j=0; j<infile[i].getFieldCount(); j++) {
			HTp token = infile.token(i, j);
			if (!token->isKern()) {
				continue;
			}
			if (token->isNull()) {
				continue;
			}
			if (token->isRest()) {
				continue;
			}
			vector<string> subtoks = token->getSubtokens();
			int sum = 0;
			for (int k=0; k<(int)subtoks.size(); k++) {
				int slurstart = 0;
				int slurend   = 0;
				for (int m=0; m<(int)subtoks[k].size(); m++) {
					int value = subtoks.at(k).at(m);
					if (value == '(') { // slur start
						slurstart++;
					} else if (value == ')') { // slur end
						slurend++;
					}
				}
				if (slurstart) { sum++; }
				if (slurend)   { sum++; }
			}
			string group = token->getValue("auto", "group");
			m_analysisSlurs.at(0).at(i) += sum;
			if (group == "A") {
				m_analysisSlurs.at(1).at(i) += sum;
			}
			if (group == "B") {
				m_analysisSlurs.at(2).at(i) += sum;
			}
		}
	}

	// Calculate coincidence accents:
	for (int i=0; i<(int)m_analysisSlurs[0].size(); i++) {
		if ((m_analysisSlurs[1][i] > 0) && (m_analysisSlurs[2][i] > 0)) {
			m_analysisSlurs[3][i] += m_analysisSlurs[1][i];
			m_analysisSlurs[3][i] += m_analysisSlurs[2][i];
		}
	}
}



//////////////////////////////
//
// Tool_compositeold::analyzeCompositeTotal --
//

void Tool_compositeold::analyzeCompositeTotal(HumdrumFile& infile, vector<HTp>& groups, vector<bool>& tracks) {

	m_analysisTotal.resize(4);

	for (int i=0; i<(int)m_analysisTotal.size(); i++) {
		m_analysisTotal[i].resize(infile.getLineCount());
		fill(m_analysisTotal[i].begin(), m_analysisTotal[i].end(), 0.0);
	}

	for (int i=0; i<(int)m_analysisTotal[0].size(); i++) {
		for (int j=0; j<(int)m_analysisTotal.size(); j++) {
			if (m_analysisOnsets[j][i]    > 0) { m_analysisTotal[j][i]  += m_analysisOnsets[j][i];    }
			if (m_analysisAccents[j][i]   > 0) { m_analysisTotal[j][i]  += m_analysisAccents[j][i];   }
			if (m_analysisOrnaments[j][i] > 0) { m_analysisTotal[j][i]  += m_analysisOrnaments[j][i]; }
			if (m_analysisSlurs[j][i]     > 0) { m_analysisTotal[j][i]  += m_analysisSlurs[j][i];     }
		}
	}

}



//////////////////////////////
//
// Tool_compositeold::doCoincidenceAnalysis --
//

void Tool_compositeold::doCoincidenceAnalysis(HumdrumFile& outfile, HumdrumFile& infile, int ctrack, HTp coincidenceStart) {

	int ignoreTrack = coincidenceStart->getTrack();

	vector<HTp> composite;
	vector<bool> ignore(infile.getMaxTrack() + 1, false);

	getCompositeSpineStarts(composite, infile);
	for (int i=0; i<(int)composite.size(); i++) {
		if (!composite[i]) {
			continue;
		}
		int track = composite[i]->getTrack();
		ignore[track] = true;
	}

	HTp ctok = NULL;
	int csum = 0;
	for (int i=0; i<(int)outfile.getLineCount(); i++) {
		if (!outfile[i].isData()) {
			continue;
		}
		ctok = NULL;
		for (int j=0; j<outfile[i].getFieldCount(); j++) {
			HTp token = outfile.token(i, j);
			int track = token->getTrack();
			if (track == ignoreTrack) {
				continue;
			}
			if (track == ctrack) {
				ctok = token;
				break;
			}
		}

		csum = 0;
		for (int j=0; j<infile[i].getFieldCount(); j++) {
			HTp token = infile.token(i, j);
			if (!token->isKern()) {
				continue;
			}
			int track = token->getTrack();
			if (track == ignoreTrack) {
				if (*token == ".") {
					// For coincidence analysis, where the coincidence
					// spine is currently assumed to be first on the line.
					// Don't count notes if there is no coincidence rhytym.
					csum = 0;
					break;
				}
			}
			if (ignore[track]) {
				continue;
			}
			csum += countNoteOnsets(token);
		}
		if (csum > 0) {
			// don't report 0 counts on tied notes
			ctok->setText(to_string(csum));
		}
	}
}


//////////////////////////////
//
// Tool_compositeold::countNoteOnsets --
//

int Tool_compositeold::countNoteOnsets(HTp token) {
	vector<string> subtoks;
	subtoks = token->getSubtokens();
	int sum = 0;
	if (*token == ".") {
		return sum;
	}
	for (int i=0; i<(int)subtoks.size(); i++) {
		if (subtoks[i].find('r') != string::npos) {
			continue;
		}
		if (subtoks[i].find('_') != string::npos) {
			continue;
		}
		if (subtoks[i].find(']') != string::npos) {
			continue;
		}
		sum++;
	}
	return sum;
}



//////////////////////////////
//
// Tool_compositeold::getexpansionList --  Add an extra spine after
//      every track in the input list, up to maxtrack.  Adding
//      analysis spines is also handleded in this funtion.
//

vector<int> Tool_compositeold::getExpansionList(vector<bool>& tracks, int maxtrack, int count) {
	vector<int> extraspine(maxtrack, false);

	for (int i=1; i<(int)tracks.size(); i++) {
		if (tracks.at(i)) {
			extraspine.at(i-1) = count;
		}
	}
	vector<int> output;
	for (int i=0; i<(int)extraspine.size(); i++) {
		output.push_back(i+1);
		for (int j=0; j<extraspine.at(i); j++) {
			output.push_back(0);
		}
	}
	return output;
}



//////////////////////////////
//
// Tool_compositeold::makeExpansionString -- converts output of
//      getExpansionList into a string.  New spines are given
//      a track value of 0.
//


string Tool_compositeold::makeExpansionString(vector<int>& tracks) {
	string output;
	for (int i=0; i<(int)tracks.size(); i++) {
		output += to_string(tracks[i]);
		if (i < (int)tracks.size() - 1) {
			output += ",";
		}
	}
	return output;
}



//////////////////////////////
//
// Tool_compositeold::getCompositeSpineStarts --
//    **kern-grpA == Group A composite analysis
//    **kern-grpB == Group B composite analysis
//    **kern-comp == (Total group) Composite analysis
//    **kern-coin == Group A + B coincidence analysis
//

void Tool_compositeold::getCompositeSpineStarts(vector<HTp>& groups, HumdrumFile& infile) {
	groups.resize(4);
	// 0: full composite rhythm analysis (union of Group A and Group B)
	// 1: Group A
	// 2: Group B
	// 3: Coincidence (intersection of Group A and Group B)
	for (int i=0; i<(int)groups.size(); i++) {
		groups[i] = NULL;
	}

	vector<HTp> spines;
	infile.getKernLikeSpineStartList(spines);
	for (int i=0; i<(int)spines.size(); i++) {
		string dtype = spines[i]->getDataType();
		if (dtype == "**kern-comp") {
			groups[0] = spines[i];
		}
		if (dtype == "**kern-grpA") {
			groups[1] = spines[i];
		}
		if (dtype == "**kern-grpB") {
			groups[2] = spines[i];
		}
		if (dtype == "**kern-coin") {
			groups[3] = spines[i];
		}
	}
}



//////////////////////////////
//
// Tool_compositeold::initialize --
//

void Tool_compositeold::initialize(void) {
	m_pitch       = getString("pitch");
	m_extractQ    = getBoolean("extract");
	m_onlygroupsQ = getBoolean("only-groups");
	m_addgroupsQ  = getBoolean("add-groups");
	m_nogroupsQ   = !(m_onlygroupsQ || m_addgroupsQ);
	m_graceQ      = getBoolean("grace");
	m_tremoloQ    = getBoolean("tremolo");
	m_upQ         = getBoolean("stem-up");
	m_appendQ     = getBoolean("append");
	m_debugQ      = getBoolean("debug");
	m_onlyQ       = getBoolean("only");
	m_nozerosQ    = getBoolean("no-zeros");

	m_analysisOnsetsQ    = getBoolean("analysis-onsets");
	m_analysisAccentsQ   = getBoolean("analysis-accents");
	m_analysisOrnamentsQ = getBoolean("analysis-ornaments");
	m_analysisSlursQ     = getBoolean("analysis-slurs");
	m_analysisTotalQ    = getBoolean("analysis-total");

	if (getBoolean("all-analyses")) {
		m_analysisOnsetsQ    = true;
		m_analysisAccentsQ   = true;
		m_analysisOrnamentsQ = true;
		m_analysisSlursQ     = true;
		m_analysisTotalQ    = true;
	}

	m_analysisQ = m_analysisOnsetsQ;
	m_analysisQ |= m_analysisAccentsQ;
	m_analysisQ |= m_analysisOrnamentsQ;
	m_analysisQ |= m_analysisSlursQ;
	m_only      = getString("only");
	m_coincidenceQ = getBoolean("coincidence-rhythm");

	if (getBoolean("together-in-score")) {
		m_togetherInScore = getString("together-in-score");
	}
	if (getBoolean("N")) {
		m_togetherInScore = "limegreen";
	}

	if (getBoolean("together")) {
		m_together = getString("together");
	}
	if (getBoolean("M")) {
		m_together = "limegreen";
	}

	m_coincideDisplayQ = false;
	if (!m_together.empty()) {
		m_coincideDisplayQ = true;
	}
	if (!m_togetherInScore.empty()) {
		m_coincideDisplayQ = true;
	}

	if (m_extractQ) {
		m_appendQ = false;
	}
	if (m_upQ) {
		m_pitch += "/";
	}
	m_hasGroupsQ = false;
	m_assignedGroups = false;

	m_nestQ = true;

	if (m_coincidenceQ) {
		if (m_together.empty() && m_togetherInScore.empty()) {
			m_suppressCMarkQ = true;
		}
	}
}



//////////////////////////////
//
// Tool_compositeold::processFile --
//

void Tool_compositeold::processFile(HumdrumFile& infile) {
	if (!m_tremoloQ) {
		reduceTremolos(infile);
	}

	m_hasGroupsQ = hasGroupInterpretations(infile);

	if (m_onlyQ) {
		assignGroups(infile);
		analyzeLineGroups(infile);
		extractGroup(infile, m_only);
		return;
	}

	if (m_hasGroupsQ && !m_nogroupsQ) {
		prepareMultipleGroups(infile);
	}
	if (!m_onlygroupsQ) {
		prepareSingleGroup(infile);
	}

	if (m_hasGroupsQ && !m_togetherInScore.empty()) {
		markCoincidencesMusic(infile);
	} else if (m_hasGroupsQ && m_coincidenceQ) {
		markCoincidencesMusic(infile);
	}

	if ((!m_together.empty()) || (!m_togetherInScore.empty())) {
		if (!hasPipeRdf(infile)) {
			string text = "!!!RDF**kern: | = marked note, color=\"";
			if (!m_together.empty()) {
				text += m_together;
			} else {
				text += m_togetherInScore;
			}
			text += "\"";
			infile.appendLine(text);
		}
	}

	if (m_nestQ) {
		extractNestingData(infile);
	}

}



//////////////////////////////
//
// Tool_compositeold::extractGroup --
//

void Tool_compositeold::extractGroup(HumdrumFile& infile, const string &target) {
	for (int i=0; i<infile.getLineCount(); i++) {
		if (!infile[i].isData()) {
			m_humdrum_text << infile[i] << endl;
			continue;
		}
		for (int j=0; j<infile[i].getFieldCount(); j++) {
			HTp token = infile[i].token(j);
			if ((!token->isData()) || token->isNull()) {
				m_humdrum_text << token;
				if (j < infile[i].getFieldCount() - 1) {
					m_humdrum_text << "\t";
				}
				continue;
			}
			string group = token->getValue("auto", "group");
			if (group == target) {
				m_humdrum_text << token;
			} else {
				if (token->isRest()) {
					m_humdrum_text << token << "yy";
				} else {
					HumRegex hre;
					string rhythm = "4";
					if (hre.search(token, "(\\d+%?\\d*\\.*)")) {
						rhythm = hre.getMatch(1);
					}
					m_humdrum_text << rhythm << "ryy";
				}
			}
			if (j < infile[i].getFieldCount() - 1) {
				m_humdrum_text << "\t";
			}
		}
		m_humdrum_text << endl;
	}
}



//////////////////////////////
//
// Tool_compositeold::hasPipeRdf -- True if already has:
//     !!!RDF**kern: | = marked note, color=\"";
//

bool Tool_compositeold::hasPipeRdf(HumdrumFile& infile) {
	for (int i=0; i<infile.getLineCount(); i++) {
		if (infile[i].hasSpines()) {
			continue;
		}
		HTp token = infile.token(i, 0);
		if (token->find("!!!RDF**kern: | = marked note") != string::npos) {
			return true;
		}
	}
	return false;
}



//////////////////////////////
//
// Tool_compositeold::extractNestingData -- Count the number of notes in the compositeold
//

void Tool_compositeold::extractNestingData(HumdrumFile& infile) {
	if (m_hasGroupsQ && !m_nogroupsQ) {
		if (m_appendQ) {
			analyzeNestingDataGroups(infile, -2);
		} else {
			analyzeNestingDataGroups(infile, +2);
		}
	} else {
		if (m_appendQ) {
			analyzeNestingDataAll(infile, -1);
		} else {
			analyzeNestingDataAll(infile, +1);
		}
	}
}



//////////////////////////////
//
// Tool_compositeold::analyzeNestingDataGroups --
//

void Tool_compositeold::analyzeNestingDataGroups(HumdrumFile& infile, int direction) {
	vector<HTp> sstarts;
	infile.getSpineStartList(sstarts);
	if (sstarts.empty()) {
		// strange problem
		return;
	}
	if (sstarts.size() < 2) {
		// strange problem
		return;
	}
	HTp spineA = NULL;
	HTp spineB = NULL;
	if (direction == 2) {
		if (m_coincidenceQ) {
			spineA = sstarts[1];
			spineB = sstarts[2];
		} else {
			spineA = sstarts[0];
			spineB = sstarts[1];
		}
	} else if (direction == -2) {
		spineA = sstarts.at(sstarts.size() - 2);
		spineB = sstarts.back();
	} else {
		// strange problem
		return;
	}
	if (!spineA) {
		// strange problem
		return;
	}
	if (!spineB) {
		// strange problem
		return;
	}
	int totalA = 0;
	int coincideA = 0;
	int totalB = 0;
	int coincideB = 0;

	getNestData(spineA, totalA, coincideA);
	getNestData(spineB, totalB, coincideB);

	string output1a = "!!!group-A-total-notes: ";
	output1a += to_string(totalA);
	infile.appendLine(output1a);

	if (m_coincideDisplayQ) {
		string output2a = "!!!group-A-coincide-notes: ";
		output2a += to_string(coincideA);
		infile.appendLine(output2a);
	}

	string output1b = "!!!group-B-total-notes: ";
	output1b += to_string(totalB);
	infile.appendLine(output1b);

	if (m_coincideDisplayQ) {
		string output2b = "!!!group-B-coincide-notes: ";
		output2b += to_string(coincideB);
		infile.appendLine(output2b);
	}
}




//////////////////////////////
//
// Tool_compositeold::analyzeNestingDataGroups --
//

void Tool_compositeold::analyzeNestingDataAll(HumdrumFile& infile, int direction) {
	vector<HTp> sstarts;
	infile.getSpineStartList(sstarts);
	if (sstarts.empty()) {
		// strange problem
		return;
	}
	HTp spine = NULL;
	if (direction == -1) {
		spine = sstarts.back();
	} else if (direction == 1) {
		if (m_coincidenceQ) {
			spine = sstarts[1];
		} else {
			spine = sstarts[0];
		}
	}
	if (spine == NULL) {
		// strange problem
		return;
	}
	int total = 0;
	int coincide = 0;

	getNestData(spine, total, coincide);
	string output1 = "!!!composite-total-notes: ";
	output1 += to_string(total);
	infile.appendLine(output1);

	if (!m_together.empty()) {
		string output2 = "!!!composite-coincide-notes: ";
		output2 += to_string(coincide);
		infile.appendLine(output2);
	}
}



//////////////////////////////
//
// Tool_compositeold::getNestData -- return total number of mononphonic note
//   onsets in primary spine as well as those marked with "|" (for coincidences).
//

void Tool_compositeold::getNestData(HTp spine, int& total, int& coincide) {
	total = 0;
	coincide = 0;
	HTp current = spine;
	while (current) {
		if (!current->isData()) {
			current = current->getNextToken();
			continue;
		}
		if (current->isNull()) {
			current = current->getNextToken();
			continue;
		}
		if (!current->isNoteAttack()) {
			current = current->getNextToken();
			continue;
		}
		total++;
		if (current->find("|") != string::npos) {
			coincide++;
		}
		current = current->getNextToken();
	}
}



//////////////////////////////
//
// Tool_compositeold::hasGroupInterpretations --
//

bool Tool_compositeold::hasGroupInterpretations(HumdrumFile& infile) {
	for (int i=0; i<infile.getLineCount(); i++) {
		if (!infile[i].isInterpretation()) {
			continue;
		}
		for (int j=0; j<infile[i].getFieldCount(); j++) {
			HTp token = infile.token(i, j);
			if (!token->isKern()) {
				continue;
			}
			if (token->compare(0, 5, "*grp:") == 0) {
				return true;
			}
		}
	}
	return false;
}



//////////////////////////////
//
// Tool_compositeold::prepareMultipleGroups --
//

void Tool_compositeold::prepareMultipleGroups(HumdrumFile& infile) {

	Tool_extract extract;

	// add two columns, one for each rhythm stream:
	if (m_coincidenceQ) {
		if (!m_appendQ) {
			extract.setModified("s", "0,0,0,1-$");
		} else {
			extract.setModified("s", "1-$,0,0,0");
		}
	} else {
		if (!m_appendQ) {
			extract.setModified("s", "0,0,1-$");
		} else {
			extract.setModified("s", "1-$,0,0");
		}
	}

	vector<HumNum> durations(infile.getLineCount());
	for (int i=0; i<infile.getLineCount(); i++) {
		durations[i] = infile[i].getDuration();
	}

	vector<bool> isRest(infile.getLineCount(), false);
	vector<bool> isNull(infile.getLineCount(), false);

	for (int i=0; i<infile.getLineCount(); i++) {
		if (durations[i] == 0) {
			continue;
		}
		bool allnull = true;
		bool allrest = true;
		for (int j=0; j<infile[i].getFieldCount(); j++) {
			HTp tok = infile.token(i, j);
			string value = tok->getValue("auto", "ignoreTremoloNote");
			if (value == "1") {
				continue;
			}
			if (tok->isNull()) {
				continue;
			}
			allnull = false;
			if (!tok->isKern()) {
				continue;
			}
			if (tok->isNote()) {
				allrest = false;
				break;
			}
			if (tok->isRest()) {
				allnull = false;
			}
		}
		if (allrest) {
			isRest[i] = true;
		}
		if (allnull) {
			isNull[i] = true;
		}
	}

	string pstring = m_pitch;

	HumRegex hre;

	bool wroteA = false;
	bool wroteB = false;

	stringstream sstream;
	sstream << infile;
	HumdrumFile originalfile;
	originalfile.readString(sstream.str());
	extract.run(originalfile);
	infile.readString(extract.getAllText());
	// need to redo tremolo analyses...
	if (!m_tremoloQ) {
		reduceTremolos(infile);
	}

	assignGroups(infile);
	analyzeLineGroups(infile);
	if (m_debugQ) {
		printGroupAssignments(infile);
	}
	vector<vector<int>> groupstates;
	getGroupStates(groupstates, infile);
	vector<vector<HumNum>> groupdurs;
	getGroupDurations(groupdurs, groupstates, infile);
	vector<vector<string>> rhythms;
	getGroupRhythms(rhythms, groupdurs, groupstates, infile);

	string curtimesigA;
	string curtimesigB;

	HTp token = NULL;
	HTp token2 = NULL;
	for (int i=0; i<infile.getLineCount(); i++) {
		token = NULL;
		token2 = NULL;
		if (infile[i].isInterpretation()) {
			if (m_appendQ) {
				token = infile.token(i, infile[i].getFieldCount() - 2);
				token2 = infile.token(i, infile[i].getFieldCount() - 1);
			} else {
				if (m_coincidenceQ) {
					token = infile.token(i, 1);
					token2 = infile.token(i, 2);
				} else {
					token = infile.token(i, 0);
					token2 = infile.token(i, 1);
				}
			}
			if (token && token->compare("**blank") == 0) {
				token->setText("**kern-grpA");
			}
			if (token2 && token2->compare("**blank") == 0) {
				token2->setText("**kern-grpB");
			}
			// continue;

			// copy time signature and tempos
			for (int j=2; j<infile[i].getFieldCount(); j++) {
				HTp stok = infile.token(i, j);
				string tokgroup = stok->getValue("auto", "group");
				if (stok->isTempo()) {
					token->setText(*stok);
					token2->setText(*stok);
				} else if (stok->isTimeSignature()) {
					if (tokgroup == "A") {
						if (curtimesigA != *stok) {
							token->setText(*stok);
							curtimesigA = *stok;
						}
					} else if (tokgroup == "B") {
						if (curtimesigB != *stok) {
							token2->setText(*stok);
							curtimesigB = *stok;
						}
					}
				} else if (stok->isMensurationSymbol()) {
					if (tokgroup == "A") {
						token->setText(*stok);
					} else if (tokgroup == "B") {
						token2->setText(*stok);
					}
				} else if (stok->isKeySignature()) {
					// Don't transfer key signature, but maybe add as an option.
					// token->setText(*stok);
					// token2->setText(*stok);
				} else if (stok->isClef()) {
					token->setText("*clefX");
					token2->setText("*clefX");
				} else if (stok->compare(0, 5, "*grp:") == 0) {
					if (!wroteA) {
						token->setText("*grp:A");
						wroteA = true;
					}
					if (!wroteB) {
						token2->setText("*grp:B");
						wroteB = true;
					}
				}
			}
		}
		if (!infile[i].isData()) {
			continue;
		}
		if (infile[i].getDuration() == 0) {
			// Grace note data line (most likely)
			if (!m_graceQ) {
				continue;
			}
			// otherwise, borrow the view of the first grace note found on the line
			// (beaming, visual duration) and apply the target pitch to the grace note.
			for (int j=0; j<infile[i].getFieldCount(); j++) {
				HTp tok = infile.token(i, j);
				if (!tok->isKern()) {
					continue;
				}
				if (tok->isNull()) {
					continue;
				}
				if (tok->isGrace()) {
					string q;
					string beam;
					string recip;
					if (hre.search(tok, "(\\d+%?\\d*\\.*)")) {
						recip = hre.getMatch(1);
					}
					if (hre.search(tok, "([LJk]+)")) {
						beam = hre.getMatch(1);
					}
					if (hre.search(tok, "(q+)")) {
						q = hre.getMatch(1);
					}
					string full;
					full += recip;
					full += q;
					full += pstring;
					full += beam;
					HTp targettok = NULL;
					HTp targettok2 = NULL;
					if (m_appendQ) {
						targettok = infile.token(i, infile[i].getFieldCount()-2);
						targettok2 = infile.token(i, infile[i].getFieldCount()-1);
					} else {
						if (m_coincidenceQ) {
							targettok = infile.token(i, 1);
							targettok2 = infile.token(i, 2);
						} else {
							targettok = infile.token(i, 0);
							targettok2 = infile.token(i, 1);
						}
					}

					string group = infile.token(i, j)->getValue("auto", "group");
					if (group == "A") {
						targettok->setText(full);
					} else if (group == "B") {
						targettok2->setText(full);
					}
					break;
				}
			}
			continue;
		}

		HumNum duration = getLineDuration(infile, i, isNull);
		string recip = rhythms[0][i];
		string recip2 = rhythms[1][i];
		if (recip.empty()) {
			recip = ".";
		} else {
			if (groupstates[0][i] == TYPE_RestAttack) {
				recip += "rR";
			}
			else if (groupstates[0][i] == TYPE_UNDEFINED) {
				// make invisible rest (rest not part of group)
				recip += "ryy";
			} else {
				recip += m_pitch;
			}
		}
		if (recip2.empty()) {
			// null group: add invisible rest to rhythm
			// HumNum linedur = infile[i].getDuration();
			// recip2 = Convert::durationToRecip(linedur);
			recip2 += ".";
		} else {
			if (groupstates[1][i] == TYPE_RestAttack) {
				recip2 += "rR";
			}
			else if (groupstates[1][i] == TYPE_UNDEFINED) {
				// make invisible rest (rest not part of group)
				recip2 += "ryy";
			} else {
				recip2 += m_pitch;
			}
		}

		HTp token2 = NULL;
		if (m_appendQ) {
			token = infile.token(i, infile[i].getFieldCount()-2);
			token2 = infile.token(i, infile[i].getFieldCount()-1);
		} else {
			if (m_coincidenceQ) {
				token = infile.token(i, 1);
				token2 = infile.token(i, 2);
			} else {
				token = infile.token(i, 0);
				token2 = infile.token(i, 1);
			}
		}

		token->setText(recip);
		token2->setText(recip2);
	}

	if (m_extractQ) {
		Tool_extract extract2;
		extract2.setModified("s", "1-2");
		extract2.run(infile);
		infile.readString(extract2.getAllText());
	}

	if (!getBoolean("no-beam")) {
		Tool_autobeam autobeam;
		if (m_appendQ) {
			int trackcount =  infile.getTrackCount();
			string tstring;
			if (m_coincidenceQ) {
				tstring = to_string(trackcount-2);
				tstring += "-";
				tstring += to_string(trackcount);
			} else {
				tstring = to_string(trackcount-1);
				tstring += ",";
				tstring += to_string(trackcount);
			}
			autobeam.setModified("t", tstring);
		} else {
			if (m_coincidenceQ) {
				autobeam.setModified("t", "1-3");
			} else {
				autobeam.setModified("t", "1,2");
			}
		}

		// need to analyze structure for some reason:
		// infile.analyzeStrands();
		infile.analyzeStructure();
		autobeam.run(infile);

	}

	if (!m_together.empty()) {
		if (m_appendQ) {
			markTogether(infile, -2);
		} else {
			markTogether(infile, 2);
		}
	}
}



//////////////////////////////
//
// Tool_compositeold::markTogether --
//

void Tool_compositeold::markTogether(HumdrumFile& infile, int direction) {
	if (m_together.empty()) {
		return;
	}

	HTp groupA = NULL;
	HTp groupB = NULL;
	for (int i=0; i<infile.getLineCount(); i++) {
		if (!infile[i].hasSpines()) {
			continue;
		}
		if (!infile[i].isData()) {
			continue;
		}
		if (direction == 2) {
			if (m_coincidenceQ) {
				groupA = infile.token(i, 2);
				groupB = infile.token(i, 1);
			} else {
				groupA = infile.token(i, 1);
				groupB = infile.token(i, 0);
			}
		} else if (direction == -2) {
			groupA = infile.token(i, infile[i].getFieldCount() - 1);
			groupB = infile.token(i, infile[i].getFieldCount() - 2);
		} else {
			cerr << "Unknown direction " << direction << " in Tool_compare::markTogether" << endl;
			return;
		}
		if ((groupA == NULL) || (groupB == NULL)) {
			cerr << "STRANGE problem here in Tool_compare::markTogether" << endl;
			continue;
		}
		if (groupA->isNull()) {
			continue;
		}
		if (groupB->isNull()) {
			continue;
		}
		if (groupA->isRest()) {
			continue;
		}
		if (groupB->isRest()) {
			continue;
		}
		if (groupA->isSustainedNote()) {
			continue;
		}
		if (groupB->isSustainedNote()) {
			continue;
		}
		// the two notes are attacking at the same time to add marker
		string text = groupA->getText();
		text += "|";
		groupA->setText(text);
		text = groupB->getText();
		text += "|";
		groupB->setText(text);
	}

}



//////////////////////////////
//
// Tool_compositeold::getGrouprhythms --
//

void Tool_compositeold::getGroupRhythms(vector<vector<string>>& rhythms,
		vector<vector<HumNum>>& groupdurs, vector<vector<int>>& groupstates,
		HumdrumFile& infile) {
	rhythms.resize(groupdurs.size());
	for (int i=0; i<(int)rhythms.size(); i++) {
		getGroupRhythms(rhythms[i], groupdurs[i], groupstates[i], infile);
	}
}


void Tool_compositeold::getGroupRhythms(vector<string>& rhythms, vector<HumNum>& durs,
		vector<int>& states, HumdrumFile& infile) {
	rhythms.clear();
	rhythms.resize(durs.size());
	int lastnotei = -1;
	for (int i=0; i<(int)rhythms.size(); i++) {
		if (states[i] <= 0) {
			continue;
		}
		string prefix = "";
		string postfix = "";
		for (int j=i+1; j<(int)rhythms.size(); j++) {
			if ((states[j]) > 0 && (states[j] < 5)) {
				if ((states[i] == TYPE_NoteAttack) && (states[j] == TYPE_NoteSustainAttack)) {
					prefix = "[";
				} else if ((states[i] == TYPE_NoteSustainAttack) && (states[j] == TYPE_NoteSustainAttack)) {
					postfix = "_";
				} else if ((states[i] == TYPE_NoteSustainAttack) && (states[j] == TYPE_NoteAttack)) {
					postfix = "]";
				} else if ((states[i] == TYPE_NoteSustainAttack) && (states[j] == TYPE_RestAttack)) {
					postfix = "]";
				}
				lastnotei = j;
				break;
			}
		}
		string value = Convert::durationToRecip(durs[i]);
		rhythms[i] = prefix + value + postfix;
	}
	if (lastnotei >= 0) {
		if (states[lastnotei] == TYPE_NoteSustainAttack) {
			rhythms[lastnotei] = rhythms[lastnotei] + "]";
		}
	}

	if (m_debugQ) {
		cerr << "=========================================" << endl;
		cerr << "RECIP FOR GROUP: " << endl;
		for (int i=0; i<(int)rhythms.size(); i++) {
			cerr << rhythms[i] << "\t" << durs[i] << "\t" << states[i] << "\t" << infile[i] << endl;
		}
		cerr << "^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^" << endl;
	}

}



//////////////////////////////
//
// Tool_compositeold::getGroupDurations --
//

void Tool_compositeold::getGroupDurations(vector<vector<HumNum>>& groupdurs,
		vector<vector<int>>& groupstates, HumdrumFile& infile) {
	groupdurs.resize(groupstates.size());
	for (int i=0; i<(int)groupstates.size(); i++) {
		getGroupDurations(groupdurs[i], groupstates[i], infile);
	}
}

void Tool_compositeold::getGroupDurations(vector<HumNum>& groupdurs,
		vector<int>& groupstates, HumdrumFile& infile) {
	HumNum enddur = infile.getScoreDuration();
	groupdurs.resize(groupstates.size());
	fill(groupdurs.begin(), groupdurs.end(), -1);
	int eventi = -1;
	HumNum lasttime = 0;
	for (int i=0; i<(int)groupdurs.size(); i++) {
		if (groupstates[i] > 0) {
			if (eventi >= 0) {
				HumNum eventtime = infile[i].getDurationFromStart();
				HumNum duration = eventtime - lasttime;
				groupdurs[eventi] = duration;
				lasttime = eventtime;
				eventi = i;
				continue;
			} else {
				eventi = i;
			}
		}
	}
	if (eventi >= 0) {
		HumNum duration = enddur - lasttime;
		groupdurs[eventi] = duration;
	}
}



/////////////////////////////
//
// Tool_compositeold::getGroupStates -- Pull out the group note states for each
//    cmoposite rhytm stream.
//
//    group:A:type = "note"   if there is at least one note attack in group A on the line.
//    group:A:type = "ncont"  if there is no attack but at least one note sustain in group A.
//    group:A:type = "snote"  there is a printed note which is part of a tie group sustain note.
//    group:A:type = "scont"  continuation of a tie group sutain note.
//    group:A:type = "rest"   if there is no note attack or sustain but there is a rest start.
//    group:A:type = "rcont"  if there is a rest continuing in group A on the line.
//    group:A:type = "none"   if there is no activity for group A on the line.
//
//    Numeric equivalents:
//     9 = TYPE_UNDEFINED           = "undefined"
//     3 = TYPE_NoteSustainAttack   = "snote"
//     2 = TYPE_NoteAttack          = "note"
//     1 = TYPE_RestAttack          = "rest"
//     0 = TYPE_NONE                = "none"
//    -1 = TYPE_RestSustain         = "rcont"
//    -2 = TYPE_NoteSustain         = "ncont"
//    -3 = TYPE_NoteSustainSustain  = "scont"
//

void Tool_compositeold::getGroupStates(vector<vector<int>>& groupstates, HumdrumFile& infile) {
	groupstates.resize(2);
	groupstates[0].resize(infile.getLineCount());
	groupstates[1].resize(infile.getLineCount());
	fill(groupstates[0].begin(), groupstates[0].end(), 0);
	fill(groupstates[1].begin(), groupstates[1].end(), 0);

	for (int i=0; i<infile.getLineCount(); i++) {
		for (int j=0; j<(int)groupstates.size(); j++) {
			char groupname = 'A' + j;
			string name;
			name.clear();
			name += groupname;
			string state = infile[i].getValue("group", name, "type");
			int typenum = typeStringToInt(state);
			groupstates[j][i] = typenum;
		}
	}
}



//////////////////////////////
//
// Tool_compositeold::typeStringToInt -- Convert between numeric and string state forms.
//

int Tool_compositeold::typeStringToInt(const string& value) {
	if (value == "snote") { return TYPE_NoteSustainAttack;  }
	if (value == "note")  { return TYPE_NoteAttack;         }
	if (value == "rest")  { return TYPE_RestAttack;         }
	if (value == "none")  { return TYPE_NONE;               }
	if (value == "rcont") { return TYPE_RestSustain;        }
	if (value == "ncont") { return TYPE_NoteSustain;        }
	if (value == "scont") { return TYPE_NoteSustainSustain; }
	return TYPE_UNDEFINED;
}



//////////////////////////////
//
// Tool_compositeold::prepareSingleGroup --
//

void Tool_compositeold::prepareSingleGroup(HumdrumFile& infile) {
	Tool_extract extract;

	if (m_coincidenceQ) {
		if (m_appendQ) {
			extract.setModified("s", "1-$,0,0");
		} else {
			extract.setModified("s", "0,0,1-$");
		}
	} else {
		if (m_appendQ) {
			extract.setModified("s", "1-$,0");
		} else {
			extract.setModified("s", "0,1-$");
		}
	}

	vector<HumNum> durations(infile.getLineCount());
	for (int i=0; i<infile.getLineCount(); i++) {
		durations[i] = infile[i].getDuration();
	}

	vector<bool> isRest(infile.getLineCount(), false);
	vector<bool> isNull(infile.getLineCount(), false);
	for (int i=0; i<infile.getLineCount(); i++) {
		if (durations[i] == 0) {
			continue;
		}
		bool allnull = true;
		bool allrest = true;
		for (int j=0; j<infile[i].getFieldCount(); j++) {
			HTp tok = infile.token(i, j);
			if (tok->isNull()) {
				continue;
			}
			allnull = false;
			if (!tok->isKern()) {
				continue;
			}
			if (tok->isNote()) {
				allrest = false;
				break;
			}
			if (tok->isRest()) {
				allnull = false;
			}
		}
		if (allrest) {
			isRest[i] = true;
		} else {
			isRest[i] = false;
		}
		if (allnull) {
			isNull[i] = true;
		} else {
			isNull[i] = false;
		}
	}

	string pstring = m_pitch;

	HumRegex hre;

	extract.run(infile);
	infile.readString(extract.getAllText());
	// need to redo tremolo analyses...
	if (!m_tremoloQ) {
		reduceTremolos(infile);
	}

	HTp token;
	for (int i=0; i<infile.getLineCount(); i++) {
		if (infile[i].isInterpretation()) {
			if (m_appendQ) {
				token = infile.token(i, infile[i].getFieldCount() - 1);
			} else {
				if (m_coincidenceQ) {
					token = infile.token(i, 1);
				} else {
					token = infile.token(i, 0);
				}
			}
			if (token->compare("**blank") == 0) {
				token->setText("**kern-comp");
				continue;
			}
			// copy time signature and tempos
			for (int j=1; j<infile[i].getFieldCount(); j++) {
				HTp stok = infile.token(i, j);
				if (stok->isTempo()) {
					token->setText(*stok);
				} else if (stok->isTimeSignature()) {
					token->setText(*stok);
				} else if (stok->isMensurationSymbol()) {
					token->setText(*stok);
				} else if (stok->isKeySignature()) {
					// token->setText(*stok);
				} else if (stok->isClef()) {
					token->setText("*clefX");
				} else if (stok->isKeyDesignation()) {
					// token->setText(*stok);
				} else if (stok->compare(0, 5, "*grp:") == 0) {
					// Don't transfer any group tags to the cmoposite rhythm.
					token->setText("*");
				}
			}
		}
		if (!infile[i].isData()) {
			continue;
		}
		if (infile[i].getDuration() == 0) {
			// Grace note data line (most likely)
			if (!m_graceQ) {
				continue;
			}
			// otherwise, borrow the view of the first grace note found on the line
			// (beaming, visual duration) and apply the target pitch to the grace note.
			for (int j=0; j<infile[i].getFieldCount(); j++) {
				HTp tok = infile.token(i, j);
				if (!tok->isKern()) {
					continue;
				}
				if (tok->isNull()) {
					continue;
				}
				if (tok->isGrace()) {
					string q;
					string beam;
					string recip;
					if (hre.search(tok, "(\\d+%?\\d*\\.*)")) {
						recip = hre.getMatch(1);
					}
					if (hre.search(tok, "([LJk]+)")) {
						beam = hre.getMatch(1);
					}
					if (hre.search(tok, "(q+)")) {
						q = hre.getMatch(1);
					}
					string full;
					full += recip;
					full += q;
					full += pstring;
					full += beam;
					HTp targettok = infile.token(i, 0);
					if (m_appendQ) {
						targettok = infile.token(i, infile[i].getFieldCount() - 1);
					} else if (m_coincidenceQ) {
						targettok = infile.token(i, 1);
					}
					targettok->setText(full);
					break;
				}
			}
			continue;
		}
		HumNum duration = getLineDuration(infile, i, isNull);
		string recip;
		if (isNull[i]) {
			recip = ".";
		} else {
			recip = Convert::durationToRecip(duration);
		}

		if (onlyAuxTremoloNotes(infile, i)) {
			// mark auxiliary notes so that they can be merged
			// with a preceding note later.
			recip += COMPOSITE_TREMOLO_MARKER;
		}

		if (m_appendQ) {
			token = infile.token(i, infile[i].getFieldCount() - 1);
		} else {
			if (m_coincidenceQ) {
				token = infile.token(i, 1);
			} else {
				token = infile.token(i, 0);
			}
		}
		if (isRest[i]) {
			if (!isNull[i]) {
				recip += "r";
			}
		} else {
			recip += pstring;
		}
		token->setText(recip);
	}

	if (m_extractQ) {
		Tool_extract extract2;
		extract2.setModified("s", "1");
		extract2.run(infile);
		infile.readString(extract2.getAllText());
	}

	if (!getBoolean("no-beam")) {
		Tool_autobeam autobeam;

		if (m_coincidenceQ) {
			if (m_appendQ) {
				int trackcount =  infile.getTrackCount();
				string tstring = to_string(trackcount - 1);
				tstring += ",";
				tstring = to_string(trackcount);
				autobeam.setModified("t", tstring);
			} else {
				autobeam.setModified("t", "1,2");
			}
		} else {
			if (m_appendQ) {
				int trackcount =  infile.getTrackCount();
				string tstring = to_string(trackcount);
				autobeam.setModified("t", tstring);
			} else {
				autobeam.setModified("t", "1");
			}
		}

		// need to analyze structure for some reason:
		// infile.analyzeStrands();
		infile.analyzeStructure();
		autobeam.run(infile);
	}

	removeAuxTremolosFromCompositeRhythm(infile);

	if ((!m_together.empty()) && m_hasGroupsQ) {
		if (m_appendQ) {
			markCoincidences(infile, -1);
		} else {
			markCoincidences(infile, +1);
		}
	}
}


//////////////////////////////
//
// Tool_compositeold::markCoincidencesMusic -- Mark notes that are attacked
//   at the same time as notes in the other cmoposite group (only two groups
//   considered).
//

void Tool_compositeold::markCoincidencesMusic(HumdrumFile& infile) {
	if (!m_assignedGroups) {
		assignGroups(infile);
	}
	HumRegex hre;

	bool suppress = false;
	if (m_suppressCMarkQ) {
		suppress = true;
	}
	if (m_togetherInScore.empty()) {
		suppress = true;
	}
	vector<int> coincidences(infile.getLineCount(), 0);
	for (int i=0; i<infile.getLineCount(); i++) {
		if (!infile[i].isData()) {
			continue;
		}
		bool both = isOnsetInBothGroups(infile, i);
		if (!both) {
			continue;
		}
		for (int j=0; j<infile[i].getFieldCount(); j++) {
			HTp token = infile.token(i, j);
			if (!token->isKern()) {
				continue;
			}
			if (token->isNull()) {
				continue;
			}
			if (token->isRest()) {
				continue;
			}
			if (!token->isNoteAttack()) {
				continue;
			}
			if (token->find("|") != string::npos) {
				continue;
			}
			string group = token->getValue("auto", "group");
			if (group.empty()) {
				continue;
			}
			if (!suppress) {
				string text = token->getText();
				hre.replaceDestructive(text, "| ", " ", "g");
				text += "|";
				token->setText(text);
			}
			coincidences[i] = 1;
		}
	}

	if (m_coincidenceQ) {
		int direction = 2;
		if (m_appendQ) {
			direction = -2;
		}
		fillInCoincidenceRhythm(coincidences, infile, direction);
	}
}




//////////////////////////////
//
// Tool_compositeold::markCoincidences -- Similar to markTogether() for
//    marking grouped cmoposite rhythms, but this one is for the single-
//    streamed cmoposite rhythm when there are groupings.
//

void Tool_compositeold::markCoincidences(HumdrumFile& infile, int direction) {
	if (!m_assignedGroups) {
		assignGroups(infile);
	}
	vector<HTp> sstarts;
	infile.getSpineStartList(sstarts);
	if (sstarts.empty()) {
		return;
	}

	vector<int> coincidences(infile.getLineCount(), 0);

	HTp cmoposite;
	if (direction > 0) {
		if (m_coincidenceQ) {
			cmoposite = sstarts[1];
		} else {
			cmoposite = sstarts[0];
		}
	} else {
		cmoposite = sstarts.back();
	}

	HumRegex hre;
	HTp current = cmoposite;
	while (current) {
		if (!current->isData()) {
			current = current->getNextToken();
			continue;
		}
		if (current->isNull()) {
			current = current->getNextToken();
			continue;
		}
		if (current->isRest()) {
			current = current->getNextToken();
			continue;
		}
		if (!current->isNoteAttack()) {
			current = current->getNextToken();
			continue;
		}
		// cmoposite rhythm note is an attack, so see if it
		// occurs in both groups (only two groups considered for now)
		int line = current->getLineIndex();
		bool bothGroups = isOnsetInBothGroups(infile, line);
		if (bothGroups) {
			string text = current->getText();
			// mark all notes in chords:
			hre.replaceDestructive(text, "| ", " ", "g");
			text += "|";
			current->setText(text);
			coincidences[current->getLineIndex()] = 1;
		}
		current = current->getNextToken();
	}

	if (m_coincidenceQ) {
		fillInCoincidenceRhythm(coincidences, infile, direction);
	}
}


//////////////////////////////
//
// Tool_compositeold::getCoincidenceRhythms --
//

void Tool_compositeold::getCoincidenceRhythms(vector<string>& rhythms, vector<int>& coincidences,
		HumdrumFile& infile) {
	rhythms.clear();
	rhythms.resize(infile.getLineCount());
	for (int i=0; i<infile.getLineCount(); i++) {
		if (coincidences.at(i)) {
			int startindex = i;
			int endindex = -1;
			for (int j=i+1; j<(int)coincidences.size(); j++) {
				if (infile[j].isBarline()) {
					endindex = j;
					break;
				} else if (coincidences[j]) {
					endindex = j;
					 break;
				}
			}
			if (endindex < 0) {
				endindex = infile.getLineCount() - 1;
			}
			HumNum duration = infile[endindex].getDurationFromStart() - infile[startindex].getDurationFromStart();
			string rhythm = Convert::durationToRecip(duration);
			// check here if contains "%" character.
			rhythms[startindex] = rhythm;
		}
	}

	// go back and insert rests at starts of measures.
	bool barline = false;
	bool founddata = false;
	for (int i=0; i<infile.getLineCount(); i++) {
		if (!infile[i].hasSpines()) {
			continue;
		}
		if (infile[i].isBarline()) {
			barline = true;
			continue;
		}
		if ((barline && infile[i].isData()) || (!founddata && infile[i].isData())) {
			founddata = true;
			barline = false;
			if (coincidences[i]) {
				continue;
			}
			// need to add a rests
			int startindex = i;
			int endindex = -1;
			for (int j=i+1; j<(int)coincidences.size(); j++) {
				if (infile[j].isBarline()) {
					endindex = j;
					break;
				} else if (coincidences[j]) {
					endindex = j;
					 break;
				}
			}
			if (endindex < 0) {
				endindex = infile.getLineCount() - 1;
			}
			HumNum duration = infile[endindex].getDurationFromStart() - infile[startindex].getDurationFromStart();
			string rhythm = Convert::durationToRecip(duration) + "r";
			// check here if contains "%" character.
			rhythms[startindex] = rhythm;
		}
	}
}



//////////////////////////////
//
// Tool_compositeold::fillInCoincidenceRhythm --
//

void Tool_compositeold::fillInCoincidenceRhythm(vector<int>& coincidences,
		HumdrumFile& infile, int direction) {
	vector<string> rhythms;
	getCoincidenceRhythms(rhythms, coincidences, infile);

	vector<HTp> sstarts;
	infile.getSpineStartList(sstarts);
	HTp spine = NULL;
	switch (direction) {
		case +2:
			spine = sstarts.at(0);
			break;
		case +1:
			spine = sstarts.at(0);
			break;
		case -1:
			spine = sstarts.at((int)sstarts.size() - 2);
			break;
		case -2:
			spine = sstarts.at((int)sstarts.size() - 3);
			break;
		default:
			cerr << "ERROR IN FILLINCOINCIDENCERHYTHM" << endl;
			return;
	}
	if (!spine) {
		cerr << "PROBLEM IN FILLINCOINCIDENCERHYTHM" << endl;
		return;
	}
	if (*spine != "**blank") {
		cerr << "STRANGE PROBLEM IN FILLINCOINCIDENCERHYTHM" << endl;
		return;
	}

	HTp current = spine;
	while (current) {
		if (current->isInterpretation()) {
			processCoincidenceInterpretation(infile, current);
		}
		if (!current->isData()) {
			current = current->getNextToken();
			continue;
		}
		if (!rhythms[current->getLineIndex()].empty()) {
			string text = rhythms[current->getLineIndex()];
			text += m_pitch;
			current->setText(text);
		}
		current = current->getNextToken();
	}
}



//////////////////////////////
//
// Tool_compositeold::processCoincidenceInterpretation --
//

void Tool_compositeold::processCoincidenceInterpretation(HumdrumFile& infile, HTp token) {
	int line = token->getLineIndex();
	HTp timesig  = NULL;
	HTp exinterp  = NULL;
	HTp clef     = NULL;
	HTp metersig = NULL;
	HTp stria    = NULL;
	HTp iname    = NULL;
	HTp iabbr    = NULL;
	HTp tempo    = NULL;
	for (int i=0; i<infile[line].getFieldCount(); i++) {
		HTp current = infile.token(line, i);
		if (!current->isKern()) {
			continue;
		}
		if (current->isTimeSignature()) {
			timesig = current;
		} else if (current->isExInterp()) {
			exinterp = current;
		} else if (current->isTempo()) {
			tempo = current;
		} else if (current->isClef()) {
			clef = current;
		} else if (current->isInstrumentName()) {
			iname = current;
		} else if (current->isInstrumentAbbreviation()) {
			iabbr = current;
		} else if (current->isStria()) {
			stria = current;
		}
	}

	if (clef) {
		token->setText("*clefX");
	}
	if (timesig) {
		token->setText(*timesig);
	}
	if (metersig) {
		token->setText(*metersig);
	}
	if (tempo) {
		token->setText(*tempo);
	}
	if (stria) {
		token->setText("*stria1");
	}
	if (iname) {
		token->setText("*I\"Coincidence");
	}
	if (iabbr) {
		token->setText("*I'Coin.");
	}
	if (exinterp) {
		token->setText("**kern-coin");
	}

}



//////////////////////////////
//
// Tool_compositeold::isOnsetInBothGroups --
//

bool Tool_compositeold::isOnsetInBothGroups(HumdrumFile& infile, int line) {
	bool hasA = false;
	bool hasB = false;
	for (int i=0; i<infile[line].getFieldCount(); i++) {
		HTp token = infile.token(line, i);
		if (!token->isKern()) {
			continue;
		}
		if (token->isNull()) {
			continue;
		}
		if (token->isRest()) {
			continue;
		}
		if (!token->isNoteAttack()) {
			continue;
		}
		string value = token->getValue("auto", "ignoreTremoloNote");
		if (value == "1") {
			continue;
		}
		string group = token->getValue("auto", "group");
		if (group.empty()) {
			continue;
		}
		if (group == "A") {
			hasA = true;
		} else if (group == "B") {
			hasB = true;
		}
		if (hasA && hasB) {
			return true;
		}
	}

	return false;
}



//////////////////////////////
//
// Tool_compositeold::removeAuxTremolosFromCompositeRhythm --
//

void Tool_compositeold::removeAuxTremolosFromCompositeRhythm(HumdrumFile& infile) {
	vector<HTp> starts = infile.getKernSpineStartList();
	vector<HTp> stops;
	infile.getSpineStopList(stops);

	if (stops.empty()) {
		return;
	}
	HTp current = NULL;
	if (m_appendQ) {
		current = stops.back();
	} else {
		if (m_coincidenceQ) {
			current = stops[1];
		} else {
			current = stops[0];
		}
	}
	if (current == NULL) {
		return;
	}
	current = current->getPreviousToken();
	HumNum accumulator = 0;
	while (current) {
		if (!current->isData()) {
			current = current->getPreviousToken();
			continue;
		}
		if (*current == ".") {
			current = current->getPreviousToken();
			continue;
		}
		if (current->find(COMPOSITE_TREMOLO_MARKER) != string::npos) {
			string text = current->getText();
			accumulator += Convert::recipToDuration(text);
			current->setText(".");
		} else if (accumulator > 0) {
			string text = current->getText();
			HumNum totaldur = Convert::recipToDuration(text);
			totaldur += accumulator;
			accumulator = 0;
			string newrhy = Convert::durationToRecip(totaldur);
			HumRegex hre;
			hre.replaceDestructive(text, newrhy, "\\d+%?\\d*\\.*");
			current->setText(text);
		}
		current = current->getPreviousToken();
	}
}



//////////////////////////////
//
// Tool_compositeold::onlyAuxTremoloNotes -- True if note onsets on line are only for
//     auxiliary tremolo notes.
//

bool Tool_compositeold::onlyAuxTremoloNotes(HumdrumFile& infile, int line) {
	int attackcount = 0;
	// int sustaincount = 0;
	int auxcount = 0;
	for (int i=0; i<infile[line].getFieldCount(); i++) {
		HTp token = infile.token(line, i);
		if (!token->isKern()) {
			continue;
		}
		if (token->isNull()) {
			continue;
		}
		if (token->isRest()) {
			continue;
		}
		bool attack = token->isNoteAttack();
		if (!attack) {
			// sustaincount++;
			continue;
		}
		attackcount++;
		string value = token->getValue("auto", "ignoreTremoloNote");
		if (value == "1") {
			auxcount++;
		}
	}

	if ((auxcount > 0) && (auxcount == attackcount)) {
		return true;
	} else {
		return false;
	}
}



//////////////////////////////
//
// Tool_compositeold::analyzeLineGroups -- Look at each line for Group A and B and determine if
//    And one of five activity types are possible for the line:
//        group:A:type = "note"   if there is at least one note attack in group A on the line.
//        group:A:type = "ncont"  if there is no attack but at least one note sustain in group A.
//        group:A:type = "rest"   if there is no note attack or sustain but there is a rest start.
//        group:A:type = "rcont"  if there is a rest continuing in group A on the line.
//        group:A:type = "empty"  if there is no activity for group A on the line.
//

void Tool_compositeold::analyzeLineGroups(HumdrumFile& infile) {
	for (int i=0; i<infile.getLineCount(); i++) {
		if (!infile[i].hasSpines()) {
			continue;
		}
		if (!infile[i].data()) {
			continue;
		}
		analyzeLineGroup(infile, i, "A");
		analyzeLineGroup(infile, i, "B");
	}
}



//////////////////////////////
//
// Tool_compositeold::analyzeLineGroup --
//
//     9 = TYPE_UNDEFINED           = "undefined"
//     3 = TYPE_NoteSustainAttack   = "snote"
//     2 = TYPE_NoteAttack          = "note"
//     1 = TYPE_RestAttack          = "rest"
//     0 = TYPE_NONE                = "none"
//    -1 = TYPE_RestSustain         = "rcont"
//    -2 = TYPE_NoteSustain         = "ncont"
//    -3 = TYPE_NoteSustainSustain  = "scont"
//

void Tool_compositeold::analyzeLineGroup(HumdrumFile& infile, int line, const string& target) {
	int groupstate = getGroupNoteType(infile, line, target);
	switch (groupstate) {
		case TYPE_NoteSustainAttack:
			infile[line].setValue("group", target, "type", "snote");
			break;
		case TYPE_NoteAttack:
			infile[line].setValue("group", target, "type", "note");
			break;
		case TYPE_RestAttack:
			infile[line].setValue("group", target, "type", "rest");
			break;
		case TYPE_RestSustain:
			infile[line].setValue("group", target, "type", "rcont");
			break;
		case TYPE_NoteSustain:
			infile[line].setValue("group", target, "type", "ncont");
			break;
		case TYPE_NoteSustainSustain:
			infile[line].setValue("group", target, "type", "scont");
			break;
		case TYPE_NONE:
			infile[line].setValue("group", target, "type", "none");
			break;
		default:
			infile[line].setValue("group", target, "type", "undefined");
			break;
	}
}



//////////////////////////////
//
// Tool_compositeold::getGroupNoteType --
//
//  9 = TYPE_UNDEFINED
//  3 = TYPE_NoteSustainAttack
//  2 = TYPE_NoteAttack
//  1 = TYPE_RestAttack
//  0 = TYPE_NONE
// -1 = TYPE_RestSustain
// -2 = TYPE_NoteSustain
// -3 = TYPE_NoteSustainSustain
//

int Tool_compositeold::getGroupNoteType(HumdrumFile& infile, int line, const string& group) {
	if (!infile[line].isData()) {
		return TYPE_NONE;
	}

	vector<HTp> grouptokens;
	for (int i=0; i<infile[line].getFieldCount(); i++) {
		HTp token = infile.token(line, i);
		if (!token->isKern()) {
			continue;
		}
		string tgroup = token->getValue("auto", "group");
		if (group == tgroup) {
			grouptokens.push_back(token);
		}
	}

	if (grouptokens.empty()) {
		return TYPE_UNDEFINED;
	}

	bool hasRestAttack    = false;
	bool hasRestSustain   = false;
	bool hasNoteAttack    = false;
	bool hasNoteSustain   = false;
	bool hasNoteSAttack   = false;
	bool hasNoteSSustain  = false;

	for (int i=0; i<(int)grouptokens.size(); i++) {
		HTp token = grouptokens[i];
		string value = token->getValue("auto", "ignoreTremoloNote");
		if (value == "1") {
			hasNoteSustain = true;
			// need to check for tie on head note...
			continue;
		}
		if (token->isNull()) {
			HTp resolved = token->resolveNull();
			if (resolved && !resolved->isNull()) {
				if (resolved->isRest()) {
					hasRestSustain = true;
				} else {
					if (resolved->isNoteAttack()) {
						hasNoteSustain = true;
					} else if (resolved->isNoteSustain()) {
						hasNoteSSustain = true;
					}
				}
			}
			continue;
		}
		if (token->isRest()) {
			hasRestAttack = true;
			continue;
		}
		if (token->isNoteAttack()) {
			string value = token->getValue("auto", "ignoreTremoloNote");
			if (value != "1") {
				hasNoteAttack = true;
			}
			continue;
		}
		if (token->isNoteSustain()) {
			hasNoteSAttack = true;
		}
	}

	//  3 = TYPE_NoteSustainAttack
	//  2 = TYPE_NoteAttack
	//  1 = TYPE_RestAttack
	//  0 = TYPE_NONE
	// -1 = TYPE_RestSustain
	// -2 = TYPE_NoteSustain
	// -3 = TYPE_NoteSustainSustain

	if (hasNoteAttack) {
		return TYPE_NoteAttack;
	}
	if (hasNoteSAttack) {
		return TYPE_NoteSustainAttack;
	}
	if (hasNoteSustain) {
		return TYPE_NoteSustain;
	}
	if (hasNoteSSustain) {
		return TYPE_NoteSustainSustain;
	}
	if (hasRestAttack) {
		return TYPE_RestAttack;
	}
	if (hasRestSustain) {
		return TYPE_RestSustain;
	}

	cerr << "Warning: no category for line " << infile[line] << endl;

	return 0;
}



//////////////////////////////
//
// Tool_compositeold::getLineDuration -- Return the duration of the line, but return
//    0 if the line only contains nulls.  Also add the duration of any subsequent
//    lines that are null lines before any data content lines.

HumNum Tool_compositeold::getLineDuration(HumdrumFile& infile, int index, vector<bool>& isNull) {
	if (isNull[index]) {
		return 0;
	}
	if (!infile[index].isData()) {
		return 0;
	}
	HumNum output = infile[index].getDuration();
	for (int i=index+1; i<infile.getLineCount(); i++) {
		if (!infile[i].isData()) {
			continue;
		}
		if (isNull[i]) {
			output += infile[i].getDuration();
		} else {
			break;
		}
	}
	return output;
}



//////////////////////////////
//
// Tool_compositeold::assignGroups -- Add a parameter
//   auto:grouping = "A" or "B" depending on the group.  This
//   can be generalized later to more letters, or arbitrary
//   strings perhaps.  This comes from an interpretation such
//   as *grp:A or *grp:B in the data.  If *grp: is found without
//   a letter, than that group will be null group.


void Tool_compositeold::assignGroups(HumdrumFile& infile) {

	m_assignedGroups = true;

	int maxtrack = infile.getMaxTrack();
	vector<vector<string>> curgroup;
	curgroup.resize(maxtrack + 1);
	for (int i=0; i<(int)curgroup.size(); i++) {
		curgroup[i].resize(100);
	}

	for (int i=0; i<infile.getLineCount(); i++) {
		if (!infile[i].hasSpines()) {
			continue;
		}
		for (int j=0; j<infile[i].getFieldCount(); j++) {
			// checking all spines (not just **kern data).
			HTp token = infile.token(i, j);
			int track = token->getTrack();
			int subtrack = token->getSubtrack();
			if (subtrack > 99) {
				cerr << "Too many subspines!" << endl;
				continue;
			}

			if (*token == "*grp:A") {
				curgroup.at(track).at(subtrack) = "A";
				if (subtrack == 0) {
					for (int k=1; k<(int)curgroup.at(track).size(); k++) {
						curgroup.at(track).at(k) = "A";
					}
				}
				backfillGroup(curgroup, infile, i, track, subtrack, "A");
			}
			if (*token == "*grp:B") {
				curgroup.at(track).at(subtrack) = "B";
				if (subtrack == 0) {
					for (int k=1; k<(int)curgroup.at(track).size(); k++) {
						curgroup.at(track).at(k) = "B";
					}
				}
				backfillGroup(curgroup, infile, i, track, subtrack, "B");
			}
			if (*token == "*grp:") {
				// clear a group:
				curgroup.at(track).at(subtrack) = "";
				if (subtrack == 0) {
					for (int k=1; k<(int)curgroup.at(track).size(); k++) {
						curgroup.at(track).at(k) = "";
					}
				}
				backfillGroup(curgroup, infile, i, track, subtrack, "");
			}

			string group = curgroup.at(track).at(subtrack);
			token->setValue("auto", "group", group);
		}
	}
}



//////////////////////////////
//
// Tool_compositeold::backfillGroup -- Go back and reassign a group to all lines
//   before *grp:A or *grp:B so that time signatures and the like are used as
//   desired even if they come before a new group definition.
//

void Tool_compositeold::backfillGroup(vector<vector<string>>& curgroup, HumdrumFile& infile,
		int line, int track, int subtrack, const string& group) {
	int lastline = -1;
	for (int i=line-1; i>=0; i--) {
		if (infile[i].isData()) {
			lastline = i+1;
			break;
		}
		curgroup.at(track).at(subtrack) = group;
		if (subtrack == 0) {
			for (int k=1; k<(int)curgroup.at(track).size(); k++) {
				curgroup.at(track).at(k) = group;
			}
		}
	}
	if (lastline < 0) {
		lastline = 0;
	}
	for (int i=lastline; i<line; i++) {
		if (infile[i].isData()) {
			break;
		}
		for (int j=0; j<infile[i].getFieldCount(); j++) {
			HTp token = infile.token(i, j);
			int ttrack = token->getTrack();
			if (ttrack != track) {
				continue;
			}
			int tsubtrack = token->getSubtrack();
			if (tsubtrack != subtrack) {
				continue;
			}
			string group = curgroup.at(track).at(subtrack);
			token->setValue("auto", "group", group);
		}
	}
}



//////////////////////////////
//
// Tool_compositeold::printGroupAssignments -- for debugging of group assignments.
//

void Tool_compositeold::printGroupAssignments(HumdrumFile& infile) {
	for (int i=0; i<infile.getLineCount(); i++) {
		if (!infile[i].hasSpines()) {
			cerr << infile[i] << endl;
			continue;
		}
		for (int j=0; j<infile[i].getFieldCount(); j++) {
			HTp token = infile.token(i, j);
			string value = token->getValue("auto", "group");
			cerr << token;
			if (!value.empty()) {
				cerr << "{" << value << "}";
			}
			if (j < infile[i].getFieldCount() - 1) {
				cerr << "\t";
			}
		}
		cerr << endl;
	}
}



//////////////////////////////
//
// Tool_compositeold::reduceTremolos --  Does not do split parallel tremolo states.
//

void Tool_compositeold::reduceTremolos(HumdrumFile& infile) {
	int maxtrack = infile.getMaxTrack();
	vector<bool> tstates(maxtrack + 1, false);
	for (int i=0; i<infile.getLineCount(); i++) {
		if (!infile[i].hasSpines()) {
			continue;
		}
		if (infile[i].isInterpretation()) {
			for (int j=0; j<infile[i].getFieldCount(); j++) {
				HTp token = infile.token(i, j);
				int track = token->getTrack();
				if (*token == "*tremolo") {
					tstates[track] = true;
				} else if (*token == "*Xtremolo") {
					tstates[track] = false;
				}
			}
		}
		if (!infile[i].isData()) {
			continue;
		}
		for (int j=0; j<infile[i].getFieldCount(); j++) {
			HTp token = infile.token(i, j);
			if (!token->isKern()) {
				continue;
			}
			int track = token->getTrack();
			if (!tstates[track]) {
				continue;
			}
			if (token->find("L") != string::npos) {
				checkForTremoloReduction(infile, i, j);
			}
		}
	}
}



//////////////////////////////
//
// Tool_compositeold::checkForTremoloReduction --
//

void Tool_compositeold::checkForTremoloReduction(HumdrumFile& infile, int line, int field) {
	HTp token = infile.token(line, field);
	vector<HTp> notes;
	getBeamedNotes(notes, token);

	if (notes.empty()) {
		return;
	}
	if (notes.size() == 1) {
		return;
	}

	vector<HumNum> durations(notes.size(), 0);
	vector<vector<int>> pitches(notes.size());
	for (int i=0; i<(int)notes.size(); i++) {
		durations[i] = notes[i]->getDuration();
		getPitches(pitches[i], notes[i]);

	}

	vector<int> tgroup(notes.size(), 0);
	int curgroup = 0;
	for (int i=1; i<(int)notes.size(); i++) {
		if (durations[i] != durations[i-1]) {
			tgroup[i] = ++curgroup;
			continue;
		}
		if (!pitchesEqual(pitches[i], pitches[i-1])) {
			tgroup[i] = ++curgroup;
			continue;
		}
		tgroup[i] = curgroup;
	}

	int groupcount = tgroup.back() + 1;
	for (int i=0; i<groupcount; i++) {
		mergeTremoloGroup(notes, tgroup, i);
	}
}



//////////////////////////////
//
// Tool_compositeold::mergeTremoloGroup --
//

void Tool_compositeold::mergeTremoloGroup(vector<HTp>& notes, vector<int> groups, int group) {
	vector<int> tindex;
	for (int i=0; i<(int)notes.size(); i++) {
		if (groups[i] == group) {
			tindex.push_back(i);
		}
	}
	if (tindex.empty()) {
		return;
	}
	if (tindex.size() == 1) {
		return;
	}
	// Consider complicated groups that should not be grouped into a single note (such as 5 16ths).
	// These cases need LO information in any case.

	int starti = tindex[0];
	int endi = tindex.back();

	// Remove (repeating) tremolo notes.
	HumNum starttime = notes[starti]->getDurationFromStart();
	HumNum endtime = notes[endi]->getDurationFromStart();
	HumNum lastdur = notes[endi]->getDuration();
	HumNum duration = endtime - starttime + lastdur;
	string recip = Convert::durationToRecip(duration);
	notes[starti]->setValue("auto", "tremoloRhythm", recip);
	// string text = *notes[0];
	// HumRegex hre;
	// hre.replaceDestructive(text, recip, "\\d+%?\\d*\\.*", "g");
	// hre.replaceDestructive(text, "", "[LJkK]+", "g");
	// notes[0]->setText(text);
	for (int i=starti+1; i<=endi; i++) {
		notes[i]->setValue("auto", "ignoreTremoloNote", 1);
	}
}



//////////////////////////////
//
// Tool_compositeold::pitchesEqual -- also consider ties...
//

bool Tool_compositeold::pitchesEqual(vector<int>& pitches1, vector<int>& pitches2) {
	if (pitches1.size() != pitches2.size()) {
		return false;
	}
	for (int i=0; i<(int)pitches1.size(); i++) {
		if (pitches1[i] != pitches2[i]) {
			return false;
		}
	}
	return true;
}



//////////////////////////////
//
// Tool_compositeold::areAllEqual --
//

bool Tool_compositeold::areAllEqual(vector<HTp>& notes) {
	if (notes.empty()) {
		return false;
	}
	vector<int> pitches;
	getPitches(pitches, notes[0]);
	vector<int> others;
	for (int i=1; i<(int)notes.size(); i++) {
		getPitches(others, notes[i]);
		if (others.size() != pitches.size()) {
			return false;
		}
		for (int j=0; j<(int)others.size(); j++) {
			if (others[j] != pitches[j]) {
				return false;
			}
		}
	}
	return true;
}



//////////////////////////////
//
// Tool_compositeold::getPitches --
//

void Tool_compositeold::getPitches(vector<int>& pitches, HTp token) {
	vector<string> subtokens;
	subtokens = token->getSubtokens();
	pitches.clear();
	pitches.resize(subtokens.size());
	fill(pitches.begin(), pitches.end(), 0);
	for (int i=0; i<(int)subtokens.size(); i++) {
		if (subtokens[i].find("r") != string::npos) {
			continue;
		}
		pitches[i] = Convert::kernToBase40(subtokens[i]);
	}
	if (pitches.size() > 1) {
		sort(pitches.begin(), pitches.end());
	}
}



//////////////////////////////
//
// Tool_compositeold::checkForTremoloReduction --
//

void Tool_compositeold::getBeamedNotes(vector<HTp>& notes, HTp starting) {
	notes.clear();
	notes.push_back(starting);
	int Lcount = (int)count(starting->begin(), starting->end(), 'L');
	int Jcount = (int)count(starting->begin(), starting->end(), 'J');
	int beamcounter = Lcount - Jcount;
	if (beamcounter <= 0) {
		notes.clear();
		return;
	}
	HTp current = starting->getNextToken();
	while (current) {
		if (current->isBarline()) {
			break;
		}
		if (!current->isData()) {
			current = current->getNextToken();
			continue;
		}
		if (*current == ".") {
			current = current->getNextToken();
			continue;
		}
		notes.push_back(current);
		Lcount = (int)count(starting->begin(), starting->end(), 'L');
		Jcount = (int)count(starting->begin(), starting->end(), 'J');
		beamcounter += Lcount - Jcount;
		if (beamcounter <= 0) {
			break;
		}
		current = current->getNextToken();
	}
}



//////////////////////////////
//
// Tool_compositeold::addLabelsAndStria -- add Grouping labels to cmoposite rhythm groups.
//

void Tool_compositeold::addLabelsAndStria(HumdrumFile& infile) {

	// Find lines for labels and label abbreviations
	int hasLabel = 0;
	int hasLabelAbbr = 0;
	for (int i=0; i<infile.getLineCount(); i++) {
		if (infile[i].isData()) {
			break;
		}
		if (!infile[i].isInterpretation()) {
			continue;
		}
		for (int j=0; j<infile[i].getFieldCount(); j++) {
			HTp token = infile.token(i, j);
			if (!token->isKern()) {
				continue;
			}
			if (token->compare(0, 3, "*I\"") == 0) {
				hasLabel = i;
			}
			if (token->compare(0, 3, "*I'") == 0) {
				hasLabelAbbr = i;
			}
		}
	}

	if ((hasLabel == 0) && (hasLabelAbbr == 0)) {
		// Do not add anaylsis labels of score does not have labels.
		return;
	}

	vector<HTp> sstarts;
	infile.getSpineStartList(sstarts);
	for (int i=0; i<(int)sstarts.size(); i++) {
		if (*sstarts[i] == "**kern-grpA") {
			addLabels(sstarts[i], hasLabel, "*I\"Group A", hasLabelAbbr, "*I'Gr.A");
			addStria(infile, sstarts[i]);
			if (m_analysisQ) {
				addVerseLabels(infile, sstarts[i]);
			}
		} else if (*sstarts[i] == "**kern-grpB") {
			addLabels(sstarts[i], hasLabel, "*I\"Group B", hasLabelAbbr, "*I'Gr.B");
			addStria(infile, sstarts[i]);
			if (m_analysisQ) {
				addVerseLabels(infile, sstarts[i]);
			}
		} else if (*sstarts[i] == "**kern-comp") {
			addLabels(sstarts[i], hasLabel, "*I\"Composite", hasLabelAbbr, "*I'Comp.");
			addStria(infile, sstarts[i]);
			if (m_analysisQ) {
				addVerseLabels(infile, sstarts[i]);
			}
		} else if (*sstarts[i] == "**kern-coin") {
			addLabels(sstarts[i], hasLabel, "*I\"Coincident", hasLabelAbbr, "*I'Coin.");
			addStria(infile, sstarts[i]);
			if (m_analysisQ) {
				addVerseLabels(infile, sstarts[i]);
			}
		}
	}
}



//////////////////////////////
//
// Tool_compositeold::addLabels --
//

void Tool_compositeold::addLabels(HTp sstart, int labelIndex, const string& label,
		int abbrIndex, const string& abbr) {

	if (labelIndex > 0) {
		HTp current = sstart;
		int line = current->getLineIndex();
		while (current) {
			if (line != labelIndex) {
				current = current->getNextToken();
				line = current->getLineIndex();
				if (current->isData()) {
					break;
				}
				if (line == labelIndex) {
					break;
				}
				continue;
			}
			break;
		}
		if (current && (line == labelIndex)) {
			// found location to store label
			current->setText(label);
		}
	}

	if (abbrIndex > 0) {
		HTp current = sstart;
		int line = current->getLineIndex();
		while (current && (line < abbrIndex)) {
			if (line != abbrIndex) {
				current = current->getNextToken();
				line = current->getLineIndex();
				if (current->isData()) {
					break;
				}
				if (line == abbrIndex) {
					break;
				}
				continue;
			}
			break;
		}
		if (current && (line == abbrIndex)) {
			// found location to store abbreviation
			current->setText(abbr);
		}
	}

}



//////////////////////////////
//
// Tool_compositeold::addStria -- add stria lines for cmoposite rhythms.
//

void Tool_compositeold::addStria(HumdrumFile& infile, HTp spinestart) {
	if (!spinestart) {
		return;
	}
	HumRegex hre;
	int ttrack = spinestart->getTrack();

	HTp current = spinestart;
	while (current) {
		if (current->isData()) {
			break;
		}
		if (!current->isInterpretation()) {
			current = current->getNextToken();
			continue;
		}
		if (*current == "*") {
			current = current->getNextToken();
			continue;
		}
		if (hre.search(current, "^\\*stria")) {
			// do not add stria token.
			return;
		}
		current = current->getNextToken();
	}

	HLp clefLine  = NULL;
	HLp striaLine = NULL;
	// Check for stria in other parts
	for (int i=0; i<infile.getLineCount(); i++) {
		if (infile[i].isData()) {
			break;
		}
		for (int j=0; j<infile[i].getFieldCount(); j++) {
			HTp token = infile.token(i, j);
			if (hre.search(token, "^\\*clef")) {
				clefLine = &infile[i];
				continue;
			}
			if (hre.search(token, "^\\*stria")) {
				striaLine = &infile[i];
				continue;
			}
		}
	}

	if (striaLine) {
		// place stria token on line
		int track;
		for (int j=0; j<striaLine->getFieldCount(); j++) {
			HTp token = striaLine->token(j);
			track = token->getTrack();
			if (track == ttrack) {
				if (*token == "*") {
					token->setText("*stria1");
					striaLine->createLineFromTokens();
				}
				return;
			}
		}
	}

	if (clefLine) {
		// add stria line to just before clef line.
		HLp striaLine = infile.insertNullInterpretationLineAboveIndex(clefLine->getLineIndex());
		for (int j=0; j<striaLine->getFieldCount(); j++) {
			HTp token = striaLine->token(j);
			int track = clefLine->token(j)->getTrack();
			if (track == ttrack) {
				if (*token == "*") {
					token->setText("*stria1");
					striaLine->createLineFromTokens();
				}
				return;
			}
		}
	}

}



//////////////////////////////
//
// Tool_compositeold::addVerseLabels -- add labels in notation for anlsyses.
//   Input spinestart is analysis spine which is kern-like.  Search for any
//   vdata-like spines to the right of the targetspine to label.
//   If there are no verse labels already, then they will be added just above
//   the first barline (if there is a barline before the first data line), or
//   just before the first data line if there are no starting barlines.
//

void Tool_compositeold::addVerseLabels(HumdrumFile& infile, HTp spinestart) {
	if (!spinestart) {
		return;
	}
	int startline = spinestart->getLineIndex();
	int startfield = spinestart->getFieldIndex();
	for (int j=startfield+1; j<infile[startline].getFieldCount(); j++) {
		HTp token = infile.token(startline, j);
		if (!token->isDataTypeLike("**vdata")) {
			break;
		}
		addVerseLabels2(infile, token);
	}
}



//////////////////////////////
//
// Tool_compositeold::addVerseLabels2 -- add verse labels to specific analysis spine.
//

void Tool_compositeold::addVerseLabels2(HumdrumFile& infile, HTp spinestart) {
	HTp current = spinestart;
	int ttrack = spinestart->getTrack();
	string vlabel = spinestart->getDataType();
	HumRegex hre;
	hre.replaceDestructive(vlabel, "", "^[^-]+-");
	hre.replaceDestructive(vlabel, "", "^\\*+");
	if (vlabel == "") {
		// nothing to do
		return;
	}
	while (current) {
		if (current->isData()) {
			break;
		}
		if (!current->isInterpretation()) {
			current = current->getNextToken();
			continue;
		}
		if (*current == "*") {
			current = current->getNextToken();
			continue;
		}
		if (hre.search(current, "^\\*vv:")) {
			// do not add verse label token.
			return;
		}
		current = current->getNextToken();
	}

	HLp labelLine  = NULL;
	// Check for verse label in other parts
	for (int i=0; i<infile.getLineCount(); i++) {
		if (infile[i].isData()) {
			break;
		}
		for (int j=0; j<infile[i].getFieldCount(); j++) {
			HTp token = infile.token(i, j);
			if (hre.search(token, "^\\*vv:")) {
				labelLine = &infile[i];
				continue;
			}
		}
	}

	if (labelLine) {
		// place verse label token on line
		int track;
		for (int j=0; j<labelLine->getFieldCount(); j++) {
			HTp token = labelLine->token(j);
			track = token->getTrack();
			if (track == ttrack) {
				if (*token == "*") {
					string newlabel = "*vv:";
					newlabel += vlabel;
					token->setText(newlabel);
					labelLine->createLineFromTokens();
				}
				return;
			}
		}
	}

	// no label line, so create one before first barline or before first data line
	HLp tline = NULL;
	for (int i=0; i<infile.getLineCount(); i++) {
		if (infile[i].isBarline()) {
			tline = &infile[i];
			break;
		}
		if (infile[i].isData()) {
			tline = &infile[i];
			break;
		}
	}

	if (tline) {
		// add verse label line just before first barline or data line.
		HLp labelLine = infile.insertNullInterpretationLineAboveIndex(tline->getLineIndex());
		for (int j=0; j<labelLine->getFieldCount(); j++) {
			HTp token = labelLine->token(j);
			int track = tline->token(j)->getTrack();
			if (track == ttrack) {
				if (*token == "*") {
					string newlabel = "*vv:";
					newlabel += vlabel;
					token->setText(newlabel);
					labelLine->createLineFromTokens();
				}
				return;
			}
		}
	}

}



/////////////////////////////////
//
// Tool_deg::ScaleDegree static member variables:
//

bool Tool_deg::ScaleDegree::m_showTiesQ  = false;
bool Tool_deg::ScaleDegree::m_showZerosQ = false;
bool Tool_deg::ScaleDegree::m_octaveQ = false;
string Tool_deg::ScaleDegree::m_forcedKey = "";



/////////////////////////////////
//
// Tool_deg::Tool_deg -- Set the recognized options for the tool.
//

Tool_deg::Tool_deg(void) {
	define("above=b",                                    "display scale degrees above analyzed staff");
	define("arr|arrow|arrows=b",                         "display scale degree alterations as arrows");
	define("b|boxes|box=b",                              "display scale degrees in boxes");
	define("color=s",                                    "display color for scale degrees");
	define("c|circ|circles|circle=b",                    "display scale degrees in circles");
	define("hat|caret|circumflex=b",                     "display hats on scale degrees");
	define("solf|solfege=b",                             "display (relative) solfege syllables instead of scale degree numbers");
	define("I|no-input=b",                               "do not interleave **deg data with input score in output");
	define("kern=b",                                     "prefix composite rhythm **kern spine with -I option");
	define("k|kern-tracks=s",                            "process only the specified kern spines");
	define("kd|dk|key-default|default-key=s",            "default (initial) key if none specified in data");
	define("kf|fk|key-force|force-key|forced-key=s",     "use the given key for analysing deg data (ignore modulations)");
	define("o|octave|octaves|degree=b",                  "encode octave information int **degree spines");
	define("r|recip=b",                                  "prefix output data with **recip spine with -I option");
	define("t|ties=b",                                   "include scale degrees for tied notes");
	define("s|spine-tracks|spine|spines|track|tracks=s", "process only the specified spines");
	define("0|O|z|zero|zeros=b",                         "show rests as scale degree 0");
}



/////////////////////////////////
//
// Tool_deg::run -- Do the main work of the tool.
//

bool Tool_deg::run(HumdrumFileSet& infiles) {
	bool status = true;
	for (int i=0; i<infiles.getCount(); i++) {
		status &= run(infiles[i]);
	}
	return status;
}


bool Tool_deg::run(const string& indata, ostream& out) {
	HumdrumFile infile(indata);
	bool status = run(infile);
	if (hasAnyText()) {
		getAllText(out);
	} else {
		out << infile;
	}
	return status;
}


bool Tool_deg::run(HumdrumFile& infile, ostream& out) {
	bool status = run(infile);
	if (hasAnyText()) {
		getAllText(out);
	} else {
		out << infile;
	}
	return status;
}


bool Tool_deg::run(HumdrumFile& infile) {
	initialize();
	processFile(infile);
	return true;
}



//////////////////////////////
//
// Tool_deg::initialize --  Initializations that only have to be done once
//    for all HumdrumFile segments.
//

void Tool_deg::initialize(void) {
	m_aboveQ   = getBoolean("above");
	m_arrowQ   = getBoolean("arrow");
	m_boxQ     = getBoolean("box");
	m_circleQ  = getBoolean("circle");
	m_colorQ   = getBoolean("color");
	m_hatQ     = getBoolean("hat");
	m_solfegeQ = getBoolean("solfege");

	if (m_colorQ) {
		m_color = getString("color");
	}

	m_degOnlyQ = getBoolean("no-input");
	m_kernQ    = getBoolean("kern");
	m_recipQ   = getBoolean("recip");
	if (m_kernQ) {
		m_recipQ = true;
	}
	m_degTiesQ = getBoolean("ties");
	Tool_deg::ScaleDegree::setShowOctaves(getBoolean("octave"));

	if (getBoolean("spine-tracks")) {
		m_spineTracks = getString("spine-tracks");
	} else if (getBoolean("kern-tracks")) {
		m_kernTracks = getString("kern-tracks");
	}

	if (getBoolean("default-key")) {
		m_defaultKey = getString("default-key");
		if (!m_defaultKey.empty()) {
			if (m_defaultKey[0] != '*') {
				m_defaultKey = "*" + m_defaultKey;
			}
			if (m_defaultKey.find(":") == string::npos) {
				m_defaultKey += ":";
			}
		}
	}

	if (getBoolean("forced-key")) {
		m_defaultKey.clear(); // override --default-key option

		m_forcedKey = getString("forced-key");
		if (!m_forcedKey.empty()) {
			if (m_forcedKey[0] != '*') {
				m_forcedKey = "*" + m_forcedKey;
			}
			if (m_forcedKey.find(":") == string::npos) {
				m_forcedKey += ":";
			}
			Tool_deg::ScaleDegree::setForcedKey(m_forcedKey);
		}
	}

	Tool_deg::ScaleDegree::setShowTies(m_degTiesQ);
	Tool_deg::ScaleDegree::setShowZeros(getBoolean("zeros"));
}



//////////////////////////////
//
// Tool_deg::processFile --
//

void Tool_deg::processFile(HumdrumFile& infile) {
	bool status = setupSpineInfo(infile);
	if (!status) {
		return;
	}

	// Create storage space for scale degree analyses:
	int kernCount = (int)m_selectedKernSpines.size();
	m_degSpines.resize(kernCount);
	for (int i=0; i<kernCount; i++) {
		prepareDegSpine(m_degSpines.at(i), m_selectedKernSpines.at(i), infile);
	}

	// Analyze the scale degrees in the score (for selected spines)
	if (m_degOnlyQ) {
		printDegScore(infile);
	} else {
		printDegScoreInterleavedWithInputScore(infile);
	}
}


//////////////////////////////
//
// Tool_deg::setupSpineInfo --
//

bool Tool_deg::setupSpineInfo(HumdrumFile& infile) {
	infile.getKernSpineStartList(m_kernSpines);

	if (m_kernSpines.empty()) {
		return false;
	}

	// Create a list of only the spine starts that are selected with the -s or -k option.
	// -s spines that are not **kern spines will be ignored, and spine numbers outside
	// of the range of **kern spines in the file will be ignored (such as spine 5 in a
	// file containing four **kern spines).
	m_selectedKernSpines.clear();

	if (!m_kernTracks.empty()) {
		vector<int> tracks = Convert::extractIntegerList(m_kernTracks, (int)m_kernSpines.size());
		// don't allow out-of-sequence values for the tracks list:
		sort(tracks.begin(),tracks.end());
		tracks.erase(unique(tracks.begin(), tracks.end()), tracks.end());
		if (tracks.empty()) {
			return false;
		}
		for (int i=0; i<(int)tracks.size(); i++) {
			int index = tracks.at(i) - 1;
			if ((index < 0) || (index > (int)m_kernSpines.size() - 1)) {
				continue;
			}
			m_selectedKernSpines.push_back(m_kernSpines.at(index));
		}
	} else if (!m_spineTracks.empty()) {
		int maxTrack = infile.getMaxTrack();
		vector<int> tracks = Convert::extractIntegerList(m_spineTracks, maxTrack);
		sort(tracks.begin(),tracks.end());
		tracks.erase(unique(tracks.begin(), tracks.end()), tracks.end());
		if (tracks.empty()) {
			return false;
		}
		for (int i=0; i<(int)tracks.size(); i++) {
			int track = tracks.at(i);
			if ((track < 1) || (track > maxTrack)) {
				continue;
			}
			for (int j=0; j<(int)m_kernSpines.size(); j++) {
				int ktrack = m_kernSpines.at(j)->getTrack();
				if (ktrack == track) {
					m_selectedKernSpines.push_back(m_kernSpines.at(j));
				}
			}
		}
	} else {
		// analyzing all **kern tracks
		m_selectedKernSpines = m_kernSpines;
	}

	if (m_selectedKernSpines.empty()) {
		return false;
	}


	// Finally, store the insertion track for added **deg analysis spines,
	// which is the track number of the next **kern spine (not the next
	// selected **kern spine).  A track of -1 means append the last **deg
	// spine to the end of data lines.
	m_degInsertTrack.resize(m_selectedKernSpines.size());
	for (int i=0; i<(int)m_selectedKernSpines.size(); i++) {
		HTp target = m_selectedKernSpines.at(i);
		for (int j=0; j<(int)m_kernSpines.size(); j++) {
			if (m_kernSpines.at(j) != target) {
				continue;
			}
			if (j < (int)m_kernSpines.size() - 1) {
				m_degInsertTrack.at(i) = m_kernSpines.at(j+1)->getTrack();

			} else {
				m_degInsertTrack.at(i) = -1;
			}
		}
	}

	return true;
}



//////////////////////////////
//
// Tool_deg::printDegScoreInterleavedWithInputScore --
//

void Tool_deg::printDegScoreInterleavedWithInputScore(HumdrumFile& infile) {
	vector<HTp> kernStarts = infile.getKernSpineStartList();
	if (kernStarts.empty()) {
		return;
	}

	m_ipv.clear();

	for (int i=0; i<infile.getLineCount(); i++) {
		if (!infile[i].hasSpines()) {
			m_humdrum_text << infile[i] << endl;
		} else {
			m_humdrum_text << createOutputHumdrumLine(infile, i) << endl;
		}
	}
}



//////////////////////////////
//
// Tool_deg::createOutputHumdrumLine -- Print **deg data within input score.  The **deg
//    spine will be placed in the right-most spine after a **kern spine (so just to
//    the left of the next **kern spine.  Perhaps change to staff-like spines such as
//    **mens.
//

string Tool_deg::createOutputHumdrumLine(HumdrumFile& infile, int lineIndex) {

	// Styling interpretation tracking variables:
	bool aboveStatus          = false;
	bool arrowStatus          = false;
	bool boxStatus            = false;
	bool circleStatus         = false;
	bool colorStatus          = false;
	bool hatStatus            = false;
	bool keyDesignationStatus = false;
	bool solfegeStatus        = false;

	// Keep track of an existing styling line and if such a line is found,
	// then insert a styling interpretation for the new **deg spines here
	// rather than just before the first data line.
	if (!m_ipv.foundData) {
		if (!m_defaultKey.empty() && !keyDesignationStatus && !m_ipv.foundKeyDesignationLine) {
			keyDesignationStatus = isKeyDesignationLine(infile, lineIndex);
		}
		if (!m_forcedKey.empty() && !keyDesignationStatus && !m_ipv.foundKeyDesignationLine) {
			keyDesignationStatus = isKeyDesignationLine(infile, lineIndex);
		}
		if (m_aboveQ && !m_ipv.foundAboveLine) {
			aboveStatus = isDegAboveLine(infile, lineIndex);
		}
		if (m_arrowQ && !m_ipv.foundArrowLine) {
			arrowStatus = isDegArrowLine(infile, lineIndex);
		}
		if (m_boxQ && !m_ipv.foundBoxLine) {
			boxStatus = isDegBoxLine(infile, lineIndex);
		}
		if (m_circleQ && !m_ipv.foundCircleLine) {
			circleStatus = isDegCircleLine(infile, lineIndex);
		}
		if (m_colorQ && !m_ipv.foundColorLine) {
			colorStatus = isDegColorLine(infile, lineIndex);
		}
		if (m_hatQ && !m_ipv.foundHatLine) {
			hatStatus = isDegHatLine(infile, lineIndex);
		}
		if (m_solfegeQ && !m_ipv.foundSolfegeLine) {
			solfegeStatus = isDegSolfegeLine(infile, lineIndex);
		}
	}

	// spineData: The output Humdrum data line stored in a 2D vector.
	// The first index for the primary track/spine, and the second is for
	// the subtracks for each track/spine.  This data will be checked
	// for adjacemtn mergers happening between two different tracks
	// (which for backwards compatibility with the Humdrum Toolkit, is not
	// allowed).
	vector<vector<string>> spineData;

	// Interleave the **deg spines into the input line.  The **deg spines
	// are stored just before the matching index of the spine in m_degSpines
	// in the vector m_degInsertTrack.  If the track in m_degInsertTrack
	// is -1 (only possible in the last position of m_degInsertTrack), then
	// that will be handled append at the end of the line (outside of the
	// following loop).

	int  curDegIndex  = 0;
	bool hasDegMerger = false;
	int  track        = -1000;
	int  lasttrack    = -1000;

	for (int i=0; i<infile[lineIndex].getFieldCount(); i++) {
		HTp token = infile.token(lineIndex, i);
		lasttrack = track;
		track = token->getTrack();

		if ((curDegIndex < (int)m_degSpines.size()) && (track == m_degInsertTrack.at(curDegIndex))) {
			// insert the next **deg spine into spineData
			spineData.resize(spineData.size() + 1);
			for (int j=0; j<(int)m_degSpines.at(curDegIndex).at(lineIndex).size(); j++) {
				string value = m_degSpines.at(curDegIndex).at(lineIndex).at(j).getDegToken();
				checkKeyDesignationStatus(value, keyDesignationStatus);
				checkAboveStatus(value, aboveStatus);
				checkArrowStatus(value, arrowStatus);
				checkBoxStatus(value, boxStatus);
				checkCircleStatus(value, circleStatus);
				checkColorStatus(value, colorStatus);
				checkHatStatus(value, hatStatus);
				checkSolfegeStatus(value, hatStatus);
				spineData.back().push_back(value);
				if (value == "*v") {
					hasDegMerger = true;
				}
			}
			curDegIndex++;
		}

		if (track != lasttrack) {
			spineData.resize(spineData.size() + 1);
		}

		spineData.back().push_back(*token);
	}

	// Add the last **deg spine if necessary:
	if (!m_degInsertTrack.empty() && m_degInsertTrack.back() == -1) {
		spineData.resize(spineData.size() + 1);
		for (int i=0; i<(int)m_degSpines.back().at(lineIndex).size(); i++) {
			string value = m_degSpines.back().at(lineIndex).at(i).getDegToken();
			checkKeyDesignationStatus(value, keyDesignationStatus);
			checkAboveStatus(value, aboveStatus);
			checkArrowStatus(value, arrowStatus);
			checkBoxStatus(value, boxStatus);
			checkCircleStatus(value, circleStatus);
			checkColorStatus(value, colorStatus);
			checkHatStatus(value, hatStatus);
			checkSolfegeStatus(value, hatStatus);
			spineData.back().push_back(value);
			if (value == "*v") {
				hasDegMerger = true;
			}
		}
		curDegIndex++;
	}

	// Keep track of cases where the styling interpretations can be
	// stored in the header (to avoid creating a new line in the
	// output score just before the data to store such interpretations).
	if (keyDesignationStatus) {
		m_ipv.foundKeyDesignationLine = true;
	}
	if (aboveStatus) {
		m_ipv.foundAboveLine = true;
	}
	if (arrowStatus) {
		m_ipv.foundArrowLine = true;
	}
	if (boxStatus) {
		m_ipv.foundBoxLine = true;
	}
	if (circleStatus) {
		m_ipv.foundCircleLine = true;
	}
	if (colorStatus) {
		m_ipv.foundColorLine = true;
	}
	if (hatStatus) {
		m_ipv.foundHatLine = true;
	}
	if (solfegeStatus) {
		m_ipv.foundSolfegeLine = true;
	}


	// if styling interpretation lines were not found before the data,
	// add them just before the data (or change to also before first
	// barline and before first spine manipulator other than exclusive
	// interpretations.
	vector<string> extraLines;
	if (!m_ipv.foundData && infile[lineIndex].isData()) {
		m_ipv.foundData = true;

		if (!m_ipv.foundHatLine) {
			if (m_hatQ && !m_ipv.foundHatLine) {
				string line = printDegInterpretation("*hat", infile, lineIndex);
				if (!line.empty()) {
					extraLines.push_back(line);
				}
			}
		}

		if (!m_ipv.foundColorLine) {
			if (m_colorQ && !m_ipv.foundColorLine) {
				string interp = "*color:";
				interp += m_color;
				string line = printDegInterpretation(interp, infile, lineIndex);
				if (!line.empty()) {
					extraLines.push_back(line);
				}
			}
		}

		if (!m_ipv.foundBoxLine) {
			if (m_boxQ && !m_ipv.foundBoxLine) {
				string line = printDegInterpretation("*box", infile, lineIndex);
				if (!line.empty()) {
					extraLines.push_back(line);
				}
			}
		}

		if (!m_ipv.foundCircleLine) {
			if (m_circleQ && !m_ipv.foundCircleLine) {
				string line = printDegInterpretation("*circ", infile, lineIndex);
				if (!line.empty()) {
					extraLines.push_back(line);
				}
			}
		}

		if (!m_ipv.foundArrowLine) {
			if (m_arrowQ && !m_ipv.foundArrowLine) {
				string line = printDegInterpretation("*arr", infile, lineIndex);
				if (!line.empty()) {
					extraLines.push_back(line);
				}
			}
		}

		if (!m_ipv.foundAboveLine) {
			if (m_aboveQ && !m_ipv.foundAboveLine) {
				string line = printDegInterpretation("*above", infile, lineIndex);
				if (!line.empty()) {
					extraLines.push_back(line);
				}
			}
		}

		if (!m_ipv.foundSolfegeLine) {
			if (m_solfegeQ && !m_ipv.foundSolfegeLine) {
				string line = printDegInterpretation("*solf", infile, lineIndex);
				if (!line.empty()) {
					extraLines.push_back(line);
				}
			}
		}

		if (!m_ipv.foundKeyDesignationLine) {
			if (!m_defaultKey.empty() && !m_ipv.foundKeyDesignationLine) {
				string line = printDegInterpretation(m_defaultKey, infile, lineIndex);
				if (!line.empty()) {
					extraLines.push_back(line);
				}
			}
		}

		if (!m_ipv.foundKeyDesignationLine) {
			if (!m_forcedKey.empty() && !m_ipv.foundKeyDesignationLine) {
				string line = printDegInterpretation(m_forcedKey, infile, lineIndex);
				if (!line.empty()) {
					extraLines.push_back(line);
				}
			}
		}

	}

	if (!hasDegMerger) {
		string output;
		for (int i=0; i<(int)spineData.size(); i++) {
			for (int j=0; j<(int)spineData[i].size(); j++) {
				output += spineData[i][j];
				output += "\t";
			}
		}
		if (!output.empty()) {
			output.resize(output.size() - 1);
		}
		if (extraLines.empty()) {
			return output;
		} else {
			extraLines.push_back(output);
			string newoutput;
			for (int i=0; i<(int)extraLines.size(); i++) {
				newoutput += extraLines[i];
				if (i < (int)extraLines.size() - 1) {
					newoutput += "\n";
				}
			}
			return newoutput;
		}
	}

	// The output contains some spine mergers, so be careful and
	// place any adjacent mergers onto separate lines.
	string output = prepareMergerLine(spineData);
	return output;
}



//////////////////////////////
//
// Tool_deg::checkKeyDesignationStatus --
//

void Tool_deg::checkKeyDesignationStatus(string& value, int keyDesignationStatus) {
	if (keyDesignationStatus && (!m_ipv.foundKeyDesignationLine) && (!m_ipv.foundData)) {
		if (value == "*") {
			if (!m_defaultKey.empty()) {
				value = m_defaultKey;
			} else if (!m_forcedKey.empty()) {
				value = m_forcedKey;
			}
		}
	}
}



//////////////////////////////
//
// Tool_deg::checkAboveStatus -- Add *above interpretation to spine if needed.
//

void Tool_deg::checkAboveStatus(string& value, bool aboveStatus) {
	if (aboveStatus && m_aboveQ && (!m_ipv.foundAboveLine) && (!m_ipv.foundData)) {
		if (value == "*") {
			value = "*above";
		}
	}
}



//////////////////////////////
//
// Tool_deg::checkArrowStatus -- Add *arr interpretation to spine if needed.
//

void Tool_deg::checkArrowStatus(string& value, bool arrowStatus) {
	if (arrowStatus && m_arrowQ && (!m_ipv.foundArrowLine) && (!m_ipv.foundData)) {
		if (value == "*") {
			value = "*arr";
		}
	}
}



//////////////////////////////
//
// Tool_deg::checkBoxStatus -- Add *box interpretation to spine if needed.
//

void Tool_deg::checkBoxStatus(string& value, bool boxStatus) {
	if (boxStatus && m_boxQ && (!m_ipv.foundBoxLine) && (!m_ipv.foundData)) {
		if (value == "*") {
			value = "*box";
		}
	}
}



//////////////////////////////
//
// Tool_deg::checkCircleStatus -- Add *circ interpretation to spine if needed.
//

void Tool_deg::checkCircleStatus(string& value, bool circleStatus) {
	if (circleStatus && m_circleQ && (!m_ipv.foundCircleLine) && (!m_ipv.foundData)) {
		if (value == "*") {
			value = "*circ";
		}
	}
}


//////////////////////////////
//
// Tool_deg::checkColorStatus -- Add *color interpretation to spine if needed.
//

void Tool_deg::checkColorStatus(string& value, bool colorStatus) {
	if (colorStatus && m_colorQ && (!m_ipv.foundColorLine) && (!m_ipv.foundData)) {
		if (value == "*") {
			value = "*color:";
			value += m_color;
		}
	}
}


//////////////////////////////
//
// Tool_deg::checkHatStatus -- Add *hat interpretation to spine if needed.
//

void Tool_deg::checkHatStatus(string& value, bool hatStatus) {
	if (hatStatus && m_hatQ && (!m_ipv.foundHatLine) && (!m_ipv.foundData)) {
		if (value == "*") {
			value = "*hat";
		}
	}
}



//////////////////////////////
//
// Tool_deg::checkSolfegeStatus -- Add *solfege interpretation to spine if needed.
//

void Tool_deg::checkSolfegeStatus(string& value, bool hatStatus) {
	if (hatStatus && m_solfegeQ && (!m_ipv.foundSolfegeLine) && (!m_ipv.foundData)) {
		if (value == "*") {
			value = "*solf";
		}
	}
}



//////////////////////////////
//
// Tool_deg::isKeyDesignationLine -- Returns true if any spine on the line
//    looks like a key designation (such as *G#:loc).
//

bool Tool_deg::isKeyDesignationLine(HumdrumFile& infile, int lineIndex) {
	if (!infile[lineIndex].hasSpines()) {
		return false;
	}
	if (!infile[lineIndex].isInterpretation()) {
		return false;
	}
	for (int i=0; i<infile[lineIndex].getFieldCount(); i++) {
		HTp token = infile.token(lineIndex, i);
		if (token->isKeyDesignation()) {
			return true;
		}
	}
	return false;
}



//////////////////////////////
//
// Tool_deg::isDegAboveLine -- Return true if **deg spines only
//     include *above, *below, interpretations and "*" (but not all "*").
//

bool Tool_deg::isDegAboveLine(HumdrumFile& infile, int lineIndex) {
	// If there are no **deg spines, then don't bother searching for them.
	if (!m_ipv.hasDegSpines) {
		return false;
	}
	if (!infile[lineIndex].isInterpretation()) {
		return false;
	} if (infile[lineIndex].isManipulator()) {
		return false;
	}

	int degCount = 0;
	for (int i=0; i<infile[lineIndex].getFieldCount(); i++) {
		HTp token = infile.token(lineIndex, i);
		if (!(token->isDataType("**deg") || token->isDataType("**degree"))) {
			continue;
		}
		degCount++;
		if (*token == "*above")  { return true; }
		if (*token == "*below") { return true; }
	}
	if (degCount == 0) {
		m_ipv.hasDegSpines = false;
	}

	return false;
}



//////////////////////////////
//
// Tool_deg::isDegArrowLine -- Return true if **deg spines only
//     include *arr, *Xarr, *acc, *Xacc interpretations
//     and "*" (but not all "*").
//

bool Tool_deg::isDegArrowLine(HumdrumFile& infile, int lineIndex) {
	// If there are no **deg spines, then don't bother searching for them.
	if (!m_ipv.hasDegSpines) {
		return false;
	}
	if (!infile[lineIndex].isInterpretation()) {
		return false;
	} if (infile[lineIndex].isManipulator()) {
		return false;
	}

	int degCount = 0;
	for (int i=0; i<infile[lineIndex].getFieldCount(); i++) {
		HTp token = infile.token(lineIndex, i);
		if (!(token->isDataType("**deg") || token->isDataType("**degree"))) {
			continue;
		}
		degCount++;
		if (*token == "*arr")  { return true; }
		if (*token == "*Xarr") { return true; }
		if (*token == "*acc")  { return true; }
		if (*token == "*Xacc") { return true; }
	}
	if (degCount == 0) {
		m_ipv.hasDegSpines = false;
	}

	return false;
}



//////////////////////////////
//
// Tool_deg::isDegBoxLine -- Return true if **deg spines includes
//     any *box, or *Xbox, interpretations and "*" (but not all "*").
//

bool Tool_deg::isDegBoxLine(HumdrumFile& infile, int lineIndex) {
	// If there are no **deg spines, then don't bother searching for them.
	if (!m_ipv.hasDegSpines) {
		return false;
	}
	if (!infile[lineIndex].isInterpretation()) {
		return false;
	} if (infile[lineIndex].isManipulator()) {
		return false;
	}

	int degCount = 0;
	for (int i=0; i<infile[lineIndex].getFieldCount(); i++) {
		HTp token = infile.token(lineIndex, i);
		if (!(token->isDataType("**deg") || token->isDataType("**degree"))) {
			continue;
		}
		degCount++;
		if (*token == "*box")  { return true; }
		if (*token == "*Xbox") { return true; }
	}
	if (degCount == 0) {
		m_ipv.hasDegSpines = false;
	}

	return false;
}



//////////////////////////////
//
// Tool_deg::isDegCircleLine -- Return true if **deg spines includes
//     any *circ, or *Xcirc, interpretations and "*" (but not all "*").
//

bool Tool_deg::isDegCircleLine(HumdrumFile& infile, int lineIndex) {
	// If there are no **deg spines, then don't bother searching for them.
	if (!m_ipv.hasDegSpines) {
		return false;
	}
	if (!infile[lineIndex].isInterpretation()) {
		return false;
	} if (infile[lineIndex].isManipulator()) {
		return false;
	}

	int degCount = 0;
	for (int i=0; i<infile[lineIndex].getFieldCount(); i++) {
		HTp token = infile.token(lineIndex, i);
		if (!(token->isDataType("**deg") || token->isDataType("**degree"))) {
			continue;
		}
		degCount++;
		if (*token == "*circ")  { return true; }
		if (*token == "*Xcirc") { return true; }
	}
	if (degCount == 0) {
		m_ipv.hasDegSpines = false;
	}

	return false;
}



//////////////////////////////
//
// Tool_deg::isDegColorLine -- Return true if **deg spines only
//     include *color interpretations and "*" (but not all "*").
//

bool Tool_deg::isDegColorLine(HumdrumFile& infile, int lineIndex) {
	// If there are no **deg spines, then don't bother searching for them.
	if (!m_ipv.hasDegSpines) {
		return false;
	}
	if (!infile[lineIndex].isInterpretation()) {
		return false;
	} if (infile[lineIndex].isManipulator()) {
		return false;
	}

	int degCount = 0;
	for (int i=0; i<infile[lineIndex].getFieldCount(); i++) {
		HTp token = infile.token(lineIndex, i);
		if (!(token->isDataType("**deg") || token->isDataType("**degree"))) {
			continue;
		}
		degCount++;
		if (token->compare(0, 7, "*color:") == 0)  { return true; }
	}
	if (degCount == 0) {
		m_ipv.hasDegSpines = false;
	}

	return false;
}



//////////////////////////////
//
// Tool_deg::isDegHatLine -- Return true if **deg spines includes
//     any *hat, or *Xhat, interpretations and "*" (but not all "*").
//

bool Tool_deg::isDegHatLine(HumdrumFile& infile, int lineIndex) {
	// If there are no **deg spines, then don't bother searching for them.
	if (!m_ipv.hasDegSpines) {
		return false;
	}
	if (!infile[lineIndex].isInterpretation()) {
		return false;
	} if (infile[lineIndex].isManipulator()) {
		return false;
	}

	int degCount = 0;
	for (int i=0; i<infile[lineIndex].getFieldCount(); i++) {
		HTp token = infile.token(lineIndex, i);
		if (!(token->isDataType("**deg") || token->isDataType("**degree"))) {
			continue;
		}
		degCount++;
		if (*token == "*hat")  { return true; }
		if (*token == "*Xhat") { return true; }
	}
	if (degCount == 0) {
		m_ipv.hasDegSpines = false;
	}

	return false;
}



//////////////////////////////
//
// Tool_deg::isDegSolfegeLine -- Return true if **deg spines includes
//     any *solf, or *Xsolf, interpretations and "*" (but not all "*").
//

bool Tool_deg::isDegSolfegeLine(HumdrumFile& infile, int lineIndex) {
	// If there are no **deg spines, then don't bother searching for them.
	if (!m_ipv.hasDegSpines) {
		return false;
	}
	if (!infile[lineIndex].isInterpretation()) {
		return false;
	} if (infile[lineIndex].isManipulator()) {
		return false;
	}

	int degCount = 0;
	for (int i=0; i<infile[lineIndex].getFieldCount(); i++) {
		HTp token = infile.token(lineIndex, i);
		if (!token->isDataType("**deg")) {
			continue;
		}
		degCount++;
		if (*token == "*solf")  { return true; }
		if (*token == "*Xsolf") { return true; }
	}
	if (degCount == 0) {
		m_ipv.hasDegSpines = false;
	}

	return false;
}



//////////////////////////////
//
// Tool_deg::printDegInterpretation --
//

string Tool_deg::printDegInterpretation(const string& interp, HumdrumFile& infile, int lineIndex) {
	string output;

	int curDegIndex = 0;
	for (int i=0; i<infile[lineIndex].getFieldCount(); i++) {
		HTp token = infile.token(lineIndex, i);
		int track = token->getTrack();

		if ((curDegIndex < (int)m_degSpines.size()) && (track == m_degInsertTrack.at(curDegIndex))) {
			// insert the next **deg spine into spineData
			for (int j=0; j<(int)m_degSpines.at(curDegIndex).at(lineIndex).size(); j++) {
				output += interp;
				output += "\t";
			}
			curDegIndex++;
		}
		output += "*";
		output += "\t";
	}

	// Add the last **deg spine if necessary:
	if (!m_degInsertTrack.empty() && m_degInsertTrack.back() == -1) {
		for (int i=0; i<(int)m_degSpines.back().at(lineIndex).size(); i++) {
			output += interp;
			output += "\t";
		}
		curDegIndex++;
	}
	if (!output.empty()) {
		output.resize(output.size() - 1);
	}
	return output;
}



//////////////////////////////
//
// Tool_deg::prepareMergerLine --
//

string Tool_deg::prepareMergerLine(vector<vector<string>>& merge) {

	// Calculate result of spine manipulations:
	vector<vector<string>> after(merge.size());
	for (int i=0; i<(int)merge.size(); i++) {
		calculateManipulatorOutputForSpine(after.at(i), merge.at(i));
	}

	vector<vector<string>> before(merge.size());
	for (int i=0; i<(int)merge.size(); i++) {
		for (int j=0; j<(int)merge[i].size(); j++) {
			before[i].push_back("*");
		}
	}

	vector<vector<string>> line1(merge.size());
	vector<vector<string>> line2(merge.size());

	// before = spine states before merger
	// merge  = unprocessed merger line
	// after  = spine states after merger
	// Simple example:
	//  (*  * ) (*  * )  A: before line
	//  (*v *v) (*v *v)  B: merge line
	//  (*    ) (*    )  C: after line
	// Output should be:
	// B(*v *v) A(*   * )  line1
	// C(*    ) B(*v	*v)  line2
	//
	// More complicated example:
	//  (*  * ) (*  * ) (*  * ) A
	//  (*v *v) (*v *v) (*v *v) B
	//  (*    ) (*    ) (*    ) C
	//  Output should be:
	//  B(*v *v) A(*   * ) B(*v *v) line1
	//  C(*    ) B(*v  *v) C(*    ) line2
	//
	// Algorithm:
	//   If the current merger is not adjacent to the previous merger
	//       (or there is no previous merger), then copy merger to line1
	//       and after to line2
	//
	//   If the current merger is adjacent to the previous merger
	//       then copy merger to line2 and copy before to line1.
	//
	//   If the current merger is adjacent and the previous merger
	//       was delayed, then copy merger to line1 and after to line2.
	//       (same as first if statement)

	// Keep track delay of each track's merger line for above algorithm.
	vector<bool> delayed;

	for (int i=0; i<(int)merge.size(); i++) {
		if (merge.at(i).empty()) {
			// a track should not have an empty marger spine.
			cerr << "STRANGE CASE 1" << endl;
			continue;
		}
		if ((i > 0) && merge.at(i-1).empty()) {
			cerr << "STRANGE CASE 2" << endl;
			continue;
		}

		if (i == 0) {
			line1.at(i) = merge.at(i);
			line2.at(i) = after.at(i);
			delayed.push_back(false);
		} else if ((merge.at(i).at(0) == "*v") && (merge.at(i-1).back() == "*v")) {
			// the current merge needs to be offset from the previous merge.
			if (delayed.back()) {
				// last spine was already displayed so undelay the next spine:
				line1.at(i) = merge.at(i);
				line1.at(i) = after.at(i);
			} else {
				// last spine was not delayed, so delay this spine:
				line1.at(i) = before.at(i);
				line2.at(i) = merge.at(i);
			}
			delayed.push_back(!delayed.back());
		} else {
			// no delay is needed
			line1.at(i) = merge.at(i);
			line2.at(i) = after.at(i);
			delayed.push_back(false);
		}
	}

	string output;
	for (int i=0; i<(int)line1.size(); i++) {
		for (int j=0; j<(int)line1[i].size(); j++) {
			output += line1[i][j];
			output += "\t";
		}
	}
	output.back() = '\n';
	for (int i=0; i<(int)line2.size(); i++) {
		for (int j=0; j<(int)line2[i].size(); j++) {
			output += line2[i][j];
			output += "\t";
		}
	}
	if (!output.empty()) {
		output.resize(output.size() - 1);
	}
	return output;
}



//////////////////////////////
//
// Tool_deg::calculateManipulatorOutputForSpine -- Deal with *^ *v *- *+ manipulators
//

void Tool_deg::calculateManipulatorOutputForSpine(vector<string>& lineout,
		vector<string>& linein) {

	lineout.clear();
	for (int i=0; i<(int)linein.size(); i++) {
		if (linein[i] == "*^") {
			lineout.push_back("*");
			lineout.push_back("*");
		} else if (linein[i] == "*v") {
			if (i==0) {
				lineout.push_back("*");
			} else if ((i > 0) && (linein[i-1] == "*v")) {
				// do nothing for secondary merger manipulators
			} else {
				// strange data (merging two separate spines?)
				lineout.push_back(linein[i]);
			}
		} else if (linein[i] == "*-") {
			// do nothing
		} else if (linein[i] == "*+") {
			// rare so not well tested (and next line should have an exinterp.)
			lineout.push_back("*");
			lineout.push_back("*");
		} else {
			lineout.push_back(linein[i]);
		}
	}
}



//////////////////////////////
//
// Tool_deg::printDegScore -- **deg spines without any input data.
//

void Tool_deg::printDegScore(HumdrumFile& infile) {
	if (m_degSpines.empty()) {
		return;
	}

	// Variables to keep track of printing interpretations in **recip spine
	HumNum printTimeSignature = -1;
	HumNum printMetricSignature = -1;
	bool foundData  = false;
	bool printClef  = false;
	bool printRecipColor = false;
	bool printStria = false;
	bool printStem  = false;

	// input styling options
	bool printAbove  = !m_aboveQ;
	bool printArrow  = !m_arrowQ;
	bool printBox    = !m_boxQ;
	bool printCircle = !m_circleQ;
	bool printColor  = !m_colorQ;
	bool printHat    = !m_hatQ;
	bool printSolfege = !m_solfegeQ;

	int lineCount = (int)m_degSpines[0].size();
	int spineCount = (int)m_degSpines.size();

	for (int i=0; i<lineCount; i++) {
		if (!m_degSpines[0][i][0].hasSpines()) {
			m_humdrum_text << m_degSpines[0][i][0].getLinkedKernToken() << endl;
			continue;
		}

		// check for **recip spine display options before
		// first data line.
		if (!foundData && m_degSpines[0][i][0].isDataToken()) {
			foundData = true;

			// Add **recip styling options that have not yet been
			// given in the spine.
			if (m_recipQ) {
				if (!printClef) {
					string line = createRecipInterpretation("*clefXyy", i);
					m_humdrum_text << line << endl;
					printClef = true;
				}
				if (!printStria) {
					string line = createRecipInterpretation("*stria0", i);
					m_humdrum_text << line << endl;
					printStria = true;
				}
				if (!printRecipColor) {
					string line = createRecipInterpretation("*color:#fff0", i);
					m_humdrum_text << line << endl;
					printRecipColor = true;
				}
				if (!printStem) {
					string line = createRecipInterpretation("*Xstem", i);
					m_humdrum_text << line << endl;
					printStem = true;
				}

			}

			if (!printAbove) {
				string line = createDegInterpretation("*above", i, m_recipQ);
				m_humdrum_text << line << endl;
				printAbove = true;
			}

			if (!printArrow) {
				string line = createDegInterpretation("*arr", i, m_recipQ);
				m_humdrum_text << line << endl;
				printArrow = true;
			}

			if (!printBox) {
				string line = createDegInterpretation("*box", i, m_recipQ);
				m_humdrum_text << line << endl;
				printBox = true;
			}

			if (!printCircle) {
				string line = createDegInterpretation("*circ", i, m_recipQ);
				m_humdrum_text << line << endl;
				printCircle = true;
			}

			if (!printColor) {
				string interp = "*color:";
				interp += m_color;
				string line = createDegInterpretation(interp, i, m_recipQ);
				m_humdrum_text << line << endl;
				printColor = true;
			}

			if (!printHat) {
				string line = createDegInterpretation("*hat", i, m_recipQ);
				m_humdrum_text << line << endl;
				printHat = true;
			}

			if (!printSolfege) {
				string line = createDegInterpretation("*solf", i, m_recipQ);
				m_humdrum_text << line << endl;
				printSolfege = true;
			}

		}

		for (int j=0; j<spineCount; j++) {

			// recip spine generation:
			if (m_recipQ && (j == 0)) {
				HTp token = infile.token(i, 0);
				if (infile[i].isExclusiveInterpretation()) {
					if (m_kernQ) {
						m_humdrum_text << "**kern";
					} else {
						m_humdrum_text << "**recip";
					}
				} else if (infile[i].isManipulator()) {
					if (*token == "*-") {
						m_humdrum_text << "*-";
					} else {
						m_humdrum_text << "*";
					}
				} else if (infile[i].isInterpretation()) {
					HumNum timestamp = infile[i].getDurationFromStart();
					string timesig;
					string metersig;
					string clef;

					if (timestamp != printTimeSignature) {
						for (int jj=0; jj<infile[i].getFieldCount(); jj++) {
							HTp token = infile.token(i, jj);
							if (!token->isKern()) {
								continue;
							}
							if (token->isTimeSignature()) {
								timesig = *token;
								break;
							}
						}
					}

					if (timestamp != printMetricSignature) {
						for (int jj=0; jj<infile[i].getFieldCount(); jj++) {
							HTp token = infile.token(i, jj);
							if (!token->isKern()) {
								continue;
							}
							if (token->isMeterSignature()) {
								metersig = *token;
								break;
							}
						}
					}

					if (!printClef) {
						for (int jj=0; jj<infile[i].getFieldCount(); jj++) {
							HTp token = infile.token(i, jj);
							if (!token->isKern()) {
								continue;
							}
							if (token->isClef()) {
								clef = "*clefXyy";
								break;
							}
						}
					}

					if (!timesig.empty()) {
						m_humdrum_text << timesig;
						printTimeSignature = timestamp;
					} else if (!metersig.empty()) {
						m_humdrum_text << metersig;
						printMetricSignature = timestamp;
					} else if (!clef.empty()) {
						m_humdrum_text << clef;
						printClef = true;
					} else {
						m_humdrum_text << "*";
					}
				} else if (infile[i].isBarline()) {
					m_humdrum_text << token;
				} else if (infile[i].isLocalComment()) {
					m_humdrum_text << "!";
				} else if (infile[i].isData()) {
					m_humdrum_text << Convert::durationToRecip(infile[i].getDuration());
					if (m_kernQ) {
						m_humdrum_text << m_kernSuffix;
					}
				}
			}
			// end of recip spine generation

			// Print deg spines
			int subspineCount = (int)m_degSpines.at(j).at(i).size();
			for (int k=0; k<subspineCount; k++) {
				if ((j == 0) && (k == 0)) {
					if (m_recipQ) {
						m_humdrum_text << "\t";
					}
				} else if ((j == 0) && (k > 0)) {
					m_humdrum_text << "\t";
				} else if (j > 0) {
					m_humdrum_text << "\t";
				}
				m_humdrum_text << m_degSpines[j][i][k];
			}
		}
		m_humdrum_text << endl;
	}
}



//////////////////////////////
//
// Tool_deg::createRecipInterpretation -- Add a new line to the output of a **deg-only
//    score for a styling option in the **recip (**kern) spine at the start of the
//    line.  The **deg spines all get null interpretations.
//    i = the line index in the proto **deg score that will be used to count the
//        number of interpretations that need to be added.
//

string Tool_deg::createRecipInterpretation(const string& starttok,  int refLine) {
	string output = starttok;
	int count = 0;
	for (int j=0; j < (int)m_degSpines.size(); j++) {
		count += (int)m_degSpines.at(j).at(refLine).size();
	}
	for (int i=0; i<count; i++) {
		output += "\t*";
	}
	return output;
}



//////////////////////////////
//
// Tool_deg::createDegInterpretation -- Add a new line to the output of a **deg-only
//    score for a styling option in the **deg spines.  The
//    line.  The **deg spines all get null interpretations.
//    i = the line index in the proto **deg score that will be used to count the
//        number of interpretations that need to be added.
//

string Tool_deg::createDegInterpretation(const string& degtok,  int refLine, bool addPreSpine) {
	string output;
	if (addPreSpine) {
		output += "*\t";
	}
	int count = 0;
	for (int j=0; j < (int)m_degSpines.size(); j++) {
		count += (int)m_degSpines.at(j).at(refLine).size();
	}
	for (int i=0; i<count; i++) {
		if (i != 0) {
			output += "\t";
		}
		output += degtok;
	}

	return output;
}



//////////////////////////////
//
// Tool_deg::prepareDegSpine -- Convert one **kern spine into a **deg spine.
//

void Tool_deg::prepareDegSpine(vector<vector<ScaleDegree>>& degspine, HTp kernstart,
		HumdrumFile& infile) {
	string mode = "unknown";
	int b40tonic = -1;

	if (!m_defaultKey.empty()) {
		getModeAndTonic(mode, b40tonic, m_defaultKey);
	} else if (!m_forcedKey.empty()) {
		getModeAndTonic(mode, b40tonic, m_forcedKey);
	}

	int lineCount = infile.getLineCount();

	degspine.resize(lineCount);
	int track = kernstart->getTrack();
	HTp current = kernstart;

	bool isUnpitched = false;

	while (current) {
		int line = current->getLineIndex();
		if (!current->getOwner()->hasSpines()) {
			degspine.at(line).resize(1);
			degspine.at(line).back().setLinkedKernToken(current, mode, b40tonic, isUnpitched);
			current = current->getNextToken();
			continue;
		}
		if (current->isKeyDesignation()) {
			getModeAndTonic(mode, b40tonic, *current);

		}
		if (current->isClef()) {
			if (*current == "*clefX") {
				isUnpitched = true;
			} else {
				isUnpitched = false;
			}
		}

		HTp curr = current;
		while (curr) {
			int ttrack = curr->getTrack();
			if (ttrack != track) {
				break;
			}
			degspine.at(line).resize((int)degspine.at(line).size() + 1);
			degspine.at(line).back().setLinkedKernToken(curr, mode, b40tonic, isUnpitched);
			curr = curr->getNextFieldToken();
		}
		current = current->getNextToken();
	}

	// Go back and fill in the non-spine tokens.  This is used to print **deg output
	// without input data.
	for (int i=0; i<lineCount; i++) {
		if (!infile[i].hasSpines()) {
			if (degspine.at(i).empty()) {
				degspine.at(i).resize(1);
			}
			degspine.at(i).back().setLinkedKernToken(infile.token(i, 0), "unknown", 0, true);
		}
	}

	// process melodic contours, etc.

}


//////////////////////////////
//
// Tool_deg::getModeAndTonic -- Convert key designation interpretation into a mode
//     string and a base-40 tonic pitch (162 = middle C)
//

void Tool_deg::getModeAndTonic(string& mode, int& b40tonic, const string& token) {
	string newtoken = token;
	if (!m_forcedKey.empty()) {
		newtoken = m_forcedKey;
	}
	HumRegex hre;
	if (hre.search(newtoken, "^\\*?([A-Ga-g][-#]*):?(.*)$")) {
		string key = hre.getMatch(1);
		string kmode = hre.getMatch(2);
		b40tonic = Convert::kernToBase40(key);
		int middleC = 162; // Convert::kernToBase40("c");
		if (b40tonic < middleC - 2) {
			mode = "major";
		} else {
			mode = "minor";
		}
		if (!kmode.empty()) {
			if      (kmode == "dor") { mode = "dor"; }
			else if (kmode == "phr") { mode = "phr"; }
			else if (kmode == "lyd") { mode = "lyd"; }
			else if (kmode == "mix") { mode = "mix"; }
			else if (kmode == "aeo") { mode = "aeo"; }
			else if (kmode == "loc") { mode = "loc"; }
			else if (kmode == "ion") { mode = "ion"; }
		}
	}
}


///////////////////////////////////////////////////////////////////////////
//
// ScaleDegree helper class within Tool_deg:
//


//////////////////////////////
//
// Tool_deg::ScaleDegree:ScaleDegree -- Constructor
//

Tool_deg::ScaleDegree::ScaleDegree (void) {
	// do nothing
}



//////////////////////////////
//
// Tool_deg::ScaleDegree:~ScaleDegree -- Destructor
//
Tool_deg::ScaleDegree::~ScaleDegree () {
	// do nothing
}



//////////////////////////////
//
// Tool_deg::ScaleDegree:setLinkedKernToken -- Return the
//    **kern token that this ScaleDegree is linked to.
//    NULL token means that there is no link (initialized
//    state when creating a ScaleDegree).   If unpitched
//    is true, this means the **kern token is in a percussion
//    part (*clefX  for percussion clef).
//       default value: unpitched = false
//    Unpitched scale degrees will be output as null data tokens.
//

void Tool_deg::ScaleDegree::setLinkedKernToken(HTp token, const string& mode, int b40tonic, bool unpitched) {
	m_linkedKernToken = token;
	m_unpitched = unpitched;
	if (!unpitched) {
		if (mode == "major") {
			setMajorMode(b40tonic);
		} else if (mode == "minor") {
			setMinorMode(b40tonic);
		} else if (mode == "dor") {
			setDorianMode(b40tonic);
		} else if (mode == "phr") {
			setPhrygianMode(b40tonic);
		} else if (mode == "lyd") {
			setLydianMode(b40tonic);
		} else if (mode == "mix") {
			setMixolydianMode(b40tonic);
		} else if (mode == "aeo") {
			setAeoleanMode(b40tonic);
		} else if (mode == "loc") {
			setLocrianMode(b40tonic);
		} else if (mode == "ion") {
			setIonianMode(b40tonic);
		}
		analyzeTokenScaleDegrees();
	} else {
		m_mode = m_unknown_mode;
		m_b40tonic = -1;
	}

}



//////////////////////////////
//
// Tool_deg::ScaleDegree::analyzeTokenScaleDegrees -- Extract scale degrees from
//    stored **kern token.  The token can be non data, in which case nothing
//    should be done, or it can be a chord, in which case multiple degrees could
//    be extracted.
//

void Tool_deg::ScaleDegree::analyzeTokenScaleDegrees(void) {
	m_subtokens.clear();
	m_degrees.clear();
	m_alters.clear();
	m_octaves.clear();

	if (!m_linkedKernToken) {
		return;
	}
	if (!m_linkedKernToken->isNonNullData()) {
		return;
	}

	// Only processing non-null data from here.
	m_subtokens = m_linkedKernToken->getSubtokens();
	int subtokCount = (int)m_subtokens.size();

	m_degrees.resize(subtokCount);
	fill(m_degrees.begin(), m_degrees.end(), -1);

	m_octaves.resize(subtokCount);
	fill(m_octaves.begin(), m_octaves.end(), -1);

	m_alters.resize(subtokCount);
	fill(m_alters.begin(), m_alters.end(), 0);

	HumRegex hre;
	for (int i=0; i<(int)subtokCount; i++) {
		if (m_unpitched) {
			// Unpitched note (in percussion clef)
			m_degrees[i] = -1;
			m_octaves[i] = -1;
		}
		if (m_subtokens[i].find('r') != string::npos) {
			if (subtokCount == 1) {
				// rest
				m_degrees[i] = 0;
			m_octaves[i] = -1;
			} else {
				// Rest in a chord: a non-sounding harmonic note.
				m_degrees[i] = -1;
				m_octaves[i] = -1;
			}
			continue;
		}
		if (m_subtokens[i].find("R") != string::npos) {
			// Semi-pitch note, so ignore it
			m_degrees[i] = -1;
			m_octaves[i] = -1;
			continue;
		}
		if (!hre.search(m_subtokens[i], "([A-Ga-g]+[-#]*)")) {
			// No pitch information (rhythm only?)
		}
		string kernPitch = hre.getMatch(1);
		int b40 = Convert::kernToBase40(kernPitch);
		// check for negative values
		m_octaves[i] = b40 / 40;
		int sd = ((b40 - m_b40tonic) + 400) % 40;
		switch (sd) {

			case 38: m_degrees[i] = 1; m_alters[i] = -2; break;
			case 39: m_degrees[i] = 1; m_alters[i] = -1; break;
			case  0: m_degrees[i] = 1; m_alters[i] =  0; break;
			case  1: m_degrees[i] = 1; m_alters[i] = +1; break;
			case  2: m_degrees[i] = 1; m_alters[i] = +2; break;

			case  4: m_degrees[i] = 2; m_alters[i] = -2; break;
			case  5: m_degrees[i] = 2; m_alters[i] = -1; break;
			case  6: m_degrees[i] = 2; m_alters[i] =  0; break;
			case  7: m_degrees[i] = 2; m_alters[i] = +1; break;
			case  8: m_degrees[i] = 2; m_alters[i] = +2; break;

			case 10: m_degrees[i] = 3; m_alters[i] = -2; break;
			case 11: m_degrees[i] = 3; m_alters[i] = -1; break;
			case 12: m_degrees[i] = 3; m_alters[i] =  0; break;
			case 13: m_degrees[i] = 3; m_alters[i] = +1; break;
			case 14: m_degrees[i] = 3; m_alters[i] = +2; break;

			case 15: m_degrees[i] = 4; m_alters[i] = -2; break;
			case 16: m_degrees[i] = 4; m_alters[i] = -1; break;
			case 17: m_degrees[i] = 4; m_alters[i] =  0; break;
			case 18: m_degrees[i] = 4; m_alters[i] = +1; break;
			case 19: m_degrees[i] = 4; m_alters[i] = +2; break;

			case 21: m_degrees[i] = 5; m_alters[i] = -2; break;
			case 22: m_degrees[i] = 5; m_alters[i] = -1; break;
			case 23: m_degrees[i] = 5; m_alters[i] =  0; break;
			case 24: m_degrees[i] = 5; m_alters[i] = +1; break;
			case 25: m_degrees[i] = 5; m_alters[i] = +2; break;

			case 27: m_degrees[i] = 6; m_alters[i] = -2; break;
			case 28: m_degrees[i] = 6; m_alters[i] = -1; break;
			case 29: m_degrees[i] = 6; m_alters[i] =  0; break;
			case 30: m_degrees[i] = 6; m_alters[i] = +1; break;
			case 31: m_degrees[i] = 6; m_alters[i] = +2; break;

			case 33: m_degrees[i] = 7; m_alters[i] = -2; break;
			case 34: m_degrees[i] = 7; m_alters[i] = -1; break;
			case 35: m_degrees[i] = 7; m_alters[i] =  0; break;
			case 36: m_degrees[i] = 7; m_alters[i] = +1; break;
			case 37: m_degrees[i] = 7; m_alters[i] = +2; break;

			default: m_degrees[i] = -1; m_alters[i] = -1;
		}

		if (m_mode == m_minor_mode) {
			if ((m_degrees[i] == 3) || (m_degrees[i] == 6) || (m_degrees[i] == 7)) {
				m_alters[i]++;
			}
		}

		if (m_mode == m_dor_mode) {
			if ((m_degrees[i] == 3) || (m_degrees[i] == 7)) {
				m_alters[i]++;
			}
		}

		if (m_mode == m_phr_mode) {
			if ((m_degrees[i] == 2) || (m_degrees[i] == 3) || (m_degrees[i] == 6) || (m_degrees[i] == 7)) {
				m_alters[i]++;
			}
		}

		if (m_mode == m_lyd_mode) {
			if (m_degrees[i] == 4) {
				m_alters[i]--;
			}
		}

		if (m_mode == m_mix_mode) {
			if (m_degrees[i] == 7) {
				m_alters[i]++;
			}
		}

		if (m_mode == m_aeo_mode) {
			if ((m_degrees[i] == 3) || (m_degrees[i] == 6) || (m_degrees[i] == 7)) {
				m_alters[i]++;
			}
		}

		if (m_mode == m_loc_mode) {
			if ((m_degrees[i] == 2) || (m_degrees[i] == 3) || (m_degrees[i] == 5) || (m_degrees[i] == 6) || (m_degrees[i] == 7)) {
				m_alters[i]++;
			}
		}

		if (m_mode == m_ion_mode) {
			// nothing to do
		}
	}
}



//////////////////////////////
//
// Tool_deg::ScaleDegree:getLinkedKernToken -- Return the **kern
//    token that this ScaleDegree is linked to.
//    Returns NULL if it has not been linked.
//

HTp Tool_deg::ScaleDegree::getLinkedKernToken(void) const {
	return m_linkedKernToken;
}



//////////////////////////////
//
// Tool_deg::ScaleDegree:getDegToken -- Convert the ScaleDegre
//     to a **deg token string (or **degree token if including an octave).
//

string Tool_deg::ScaleDegree::getDegToken(void) const {
	HTp token = getLinkedKernToken();
	if (!hasSpines()) {
		if (token) {
			return *token;
		} else {
			return "ERROR1";
		}
	}

	if (isExclusiveInterpretation()) {
		if (m_octaveQ) {
			return "**degree";
		} else {
			return "**deg";
		}
	} else if (isManipulator()) {
		return getManipulator();
	} else if (isInterpretation()) {
		if (isKeyDesignation()) {
			if (m_forcedKey.empty()) {
				return *token;
			} else{
				return "*";
			}
		} else {
			return "*";
		}
	} else if (isLocalComment()) {
		return "!";
	} else if (isNullDataToken()) {
		return ".";
	} else if (isBarline()) {
		return getBarline();
	}

	if (m_unpitched) {
		return ".";
	}

	return generateDegDataToken();
}


//////////////////////////////
//
// Tool_deg::ScaleDegree::generateDegDataToken -- Convert a ScaleDegree
//     object into a **deg data token.  This version of generateDegDataToken()
//     will assemble individual subtokens for chordtones into a single
//     token.
//

string Tool_deg::ScaleDegree::generateDegDataToken(void) const {
	if (!isDataToken()) {
		return "ERROR2 (not a data token)";
	}

	if (isNullDataToken()) {
		return ".";
	}

	// Convert internal data into **deg data token (which may be a chord)

	// scale degrees:
	int subtokenCount = getSubtokenCount();
	if (subtokenCount == 0) {
		return "ERROR3";
	}

	vector<string> subtokens(subtokenCount);;
	for (int i=0; i<subtokenCount; i++) {
		subtokens.at(i) = generateDegDataSubtoken(i);
	}


	// Include tied notes or suppress them:

	if (m_showTiesQ)  {
		// Secondary tied notes scale degrees should be shown.
		string output;
		for (int i=0; i<subtokenCount; i++) {
			output += subtokens[i];
			if (i < subtokenCount - 1) {
				output += " ";
			}
		}
		return output;
	}

	// Removed scale degress for secondary tied notes.
	vector<string> nontied(subtokens.size());
	nontied.clear();
	for (int i=0; i<(int)subtokens.size(); i++) {
		if (subtokens[i].find('_') == string::npos) {
			nontied.push_back(subtokens[i]);
		}
	}

	if (nontied.empty()) {
		return ".";
	}

	int newCount = (int)nontied.size();
	string output;
	for (int i=0; i<newCount; i++) {
		output += nontied[i];
		if (i < newCount - 1) {
			output += " ";
		}
	}
	return output;
}



//////////////////////////////
//
// Tool_deg::ScaleDegree::generateDegDataSubtoken -- Convert the ScaleDegree
//		subtoken (chord note) into **deg data.
//

string Tool_deg::ScaleDegree::generateDegDataSubtoken(int index) const {
	if (!isNonNullDataToken()) {
		return "ERROR5";
	}
	if (index < 0) {
		return "ERROR6";
	}
	if (index >= getSubtokenCount()) {
		return "ERROR7";
	}

	string output;

	// Is the note a secondary tied note? If so, then prefix the scale degree by and underscore.
	bool tiedQ = (m_subtokens.at(index).find('_') != string::npos) || (m_subtokens.at(index).find(']') != string::npos);
	if (tiedQ) {
		output += "_";
	}

	// Add the scale degree:
	int degree = m_degrees.at(index);
	if (degree == 0) {
		output += "r";
		if (m_showZerosQ) {
			output += "0";
		}
   } else if ((degree > 0) && (degree <= 7)) {
		output += to_string(degree);
	} else {
		return "ERROR8";
	}

	// Add the scale degree chromatic alteration:
	if (m_alters.at(index) < 0) {
		for (int i=m_alters.at(index); i<0; i++) {
			output += "-";
		}
	} if (m_alters.at(index) > 0) {
		for (int i=0; i<m_alters.at(index); i++) {
			output += "+";
		}
	}

	// Add octave information if requested:
	if (m_octaveQ && (degree != 0)) {
		output += "/";
		output += to_string(m_octaves.at(index));
	}

	return output;
}



//////////////////////////////
//
// Tool_deg::ScaleDegree:getTimestamp -- An absolute value
//    that represents the starting time of the ScaleDegree
//    from the start of the score (which is at time 0).
//    Units are in quarter-note durations.
//

HumNum Tool_deg::ScaleDegree::getTimestamp(void) const {
	HTp token = getLinkedKernToken();
	if (token) {
		return token->getDurationFromStart();
	} else {
		return -1;
	}
}



//////////////////////////////
//
// Tool_deg::ScaleDegree:getDuration -- Duration of the
//     linked note for the ScaleDegree.
//

HumNum Tool_deg::ScaleDegree::getDuration(void) const {
	HTp token = getLinkedKernToken();
	if (token) {
		return token->getDuration();
	} else {
		return 0;
	}
}



//////////////////////////////
//
// Tool_deg::ScaleDegree:getTiedDuration -- Duration of
//     the linked note including subsequent tied notes.
//

HumNum Tool_deg::ScaleDegree::getTiedDuration(void) const {
	HTp token = getLinkedKernToken();
	if (token) {
		return token->getTiedDuration();
	} else {
		return 0;
	}
}



//////////////////////////////
//
// Tool_deg::ScaleDegree:isExclusiveInterpretation -- Returns
//     true if the ScaleDegree is on an exclusive interpretation
//     line.
//

bool Tool_deg::ScaleDegree::isExclusiveInterpretation(void) const {
	HTp token = getLinkedKernToken();
	if (token) {
		return token->isExclusiveInterpretation();
	} else {
		return false;
	}
}




//////////////////////////////
//
// Tool_deg::ScaleDegree::isNonNullDataToken -- Returns true
//   if the ScaleDegree is a non-null data token.

bool Tool_deg::ScaleDegree::isNonNullDataToken(void) const {
	HTp token = getLinkedKernToken();
	if (token) {
		return token->isNonNullData();
	} else {
		return false;
	}
}



//////////////////////////////
//
// Tool_deg::ScaleDegree::isNullDataToken -- Returns true
//     if the ScaleDegree is a null data token (".").
//

bool Tool_deg::ScaleDegree::isNullDataToken(void) const {
	HTp token = getLinkedKernToken();
	if (token) {
		return token->isNullData();
	} else {
		return false;
	}
}



//////////////////////////////
//
// Tool_deg::ScaleDegree::isDataToken -- Returns true
//     if the ScaleDegree is a data token (either null or
//     contains actual data).
//

bool Tool_deg::ScaleDegree::isDataToken(void) const {
	HTp token = getLinkedKernToken();
	if (token) {
		return token->isData();
	} else {
		return false;
	}
}



//////////////////////////////
//
// Tool_deg::ScaleDegree:isBarline -- Returns true
//     if the ScaleDegree is in a barline.
//

bool Tool_deg::ScaleDegree::isBarline(void) const {
	HTp token = getLinkedKernToken();
	if (token) {
		return token->isBarline();
	} else {
		return false;
	}
}



//////////////////////////////
//
// Tool_deg::ScaleDegree:isInterpretation -- Returns true
//     if the ScaleDegree is in an interpretation line.
//

bool Tool_deg::ScaleDegree::isInterpretation(void) const {
	HTp token = getLinkedKernToken();
	if (token) {
		if (token->isExclusiveInterpretation()) {
			return false;
		} else {
			return token->isInterpretation();
		}
	} else {
		return false;
	}
}



//////////////////////////////
//
// Tool_deg::ScaleDegree:isLocalComment -- Returns true
//     if the ScaleDegree is on a local comment line.
//

bool Tool_deg::ScaleDegree::isLocalComment(void) const {
	HTp token = getLinkedKernToken();
	if (token) {
		return token->isLocalComment();
	} else {
		return false;
	}
}



//////////////////////////////
//
// Tool_deg::ScaleDegree:isGlobalComment -- Returns true
//     if the ScaleDegree is on a global comment line
//

bool Tool_deg::ScaleDegree::isGlobalComment(void) const {
	HTp token = getLinkedKernToken();
	if (token) {
		return token->isGlobalComment();
	} else {
		return false;
	}
}



//////////////////////////////
//
// Tool_deg::ScaleDegree:isReferenceRecord -- Returns true
//    if the ScaleDegree is on a reference record line.
//

bool Tool_deg::ScaleDegree::isReferenceRecord(void) const {
	HTp token = getLinkedKernToken();
	if (token) {
		HLp line = token->getOwner();
		if (line) {
			return line->isReference();
		} else {
			return false;
		}
	} else {
		return false;
	}
}



//////////////////////////////
//
// Tool_deg::ScaleDegree:hasSpines --  Retuns true
//    if the line that the ScaleDegree is on is on a spined
//    line (not global reference record, nor global comment
//    nor an empty line).
//

bool Tool_deg::ScaleDegree::hasSpines(void) const {
	HTp token = getLinkedKernToken();
	if (token) {
		HLp line = token->getOwner();
		if (line) {
			return line->hasSpines();
		} else {
			return false;
		}
	} else {
		return false;
	}
}



//////////////////////////////
//
// Tool_deg::ScaleDegree::getManipulator -- such as *^, ^-, *+, * (null manipulator),
//     but not exclusive interpretation (starting with **).
//

string Tool_deg::ScaleDegree::getManipulator(void) const {
	HTp token = getLinkedKernToken();
	if (!token) {
		return "ERROR4";
	}
	if (token->isManipulator()) {
		return *token;
	} else {
		return "*";
	}
}



//////////////////////////////
//
// Tool_deg::ScaleDegree::isKeyDesignation -- Returns true if is a key designation.
//

bool Tool_deg::ScaleDegree::isKeyDesignation(void) const {
	HTp token = getLinkedKernToken();
	if (token && token->isInterpretation() && token->isKeyDesignation()) {
		return true;
	} else {
		return false;
	}
}



//////////////////////////////
//
// Tool_deg::ScaleDegree::getBarline -- return **kern barline if available.
//

string Tool_deg::ScaleDegree::getBarline(void) const {
	HTp token = getLinkedKernToken();
	if (token && token->isBarline()) {
		return *token;
	} else {
		return "=";
	}
}



//////////////////////////////
//
// Tool_deg::ScaleDegree::isManipulator -- such as *^, ^-, *+, * (null
//     manipulator), but not exclusive interpretation (starting with **).
//

bool Tool_deg::ScaleDegree::isManipulator(void) const {
	HTp token = getLinkedKernToken();
	if (!token) {
		return false;
	} else if (token->isExclusiveInterpretation()) {
		return false;
	} else if (token->isManipulator()) {
		return true;
	} else {
		return false;
	}
}



//////////////////////////////
//
// Tool_deg::ScaleDegree::getSubtokenCount -- Return the number
//      of subtokens (chord notes).
//

int Tool_deg::ScaleDegree::getSubtokenCount(void) const {
	return (int)m_degrees.size();
}



//////////////////////////////
//
// Tool_deg::ScaleDegree::setMajorMode -- CDEFGABC
//

void Tool_deg::ScaleDegree::setMajorMode(int b40tonic) {
	m_mode = m_major_mode;
	m_b40tonic = b40tonic;
}



//////////////////////////////
//
// Tool_deg::ScaleDegree::setMinorMode -- ABCDEFGA
//

void Tool_deg::ScaleDegree::setMinorMode(int b40tonic) {
	m_mode = m_minor_mode;
	m_b40tonic = b40tonic;
}



//////////////////////////////
//
// Tool_deg::ScaleDegree::setDorianMode -- DEFGABCD
//

void Tool_deg::ScaleDegree::setDorianMode(int b40tonic) {
	m_mode = m_dor_mode;
	m_b40tonic = b40tonic;
}



//////////////////////////////
//
// Tool_deg::ScaleDegree::setPhrygianMode -- EFGABCDE
//

void Tool_deg::ScaleDegree::setPhrygianMode(int b40tonic) {
	m_mode = m_phr_mode;
	m_b40tonic = b40tonic;
}



//////////////////////////////
//
// Tool_deg::ScaleDegree::setLydianMode -- FGABCDEF
//

void Tool_deg::ScaleDegree::setLydianMode(int b40tonic) {
	m_mode = m_lyd_mode;
	m_b40tonic = b40tonic;
}



//////////////////////////////
//
// Tool_deg::ScaleDegree::setMixolydianMode -- GABCDEFG
//

void Tool_deg::ScaleDegree::setMixolydianMode(int b40tonic) {
	m_mode = m_mix_mode;
	m_b40tonic = b40tonic;
}



//////////////////////////////
//
// Tool_deg::ScaleDegree::setAeoleanMode -- ABCDEFGA
//

void Tool_deg::ScaleDegree::setAeoleanMode(int b40tonic) {
	m_mode = m_aeo_mode;
	m_b40tonic = b40tonic;
}



//////////////////////////////
//
// Tool_deg::ScaleDegree::setLocrianMode -- BCDEFGAB
//

void Tool_deg::ScaleDegree::setLocrianMode(int b40tonic) {
	m_mode = m_loc_mode;
	m_b40tonic = b40tonic;
}



//////////////////////////////
//
// Tool_deg::ScaleDegree::setIonianMode -- CDEFGABC
//

void Tool_deg::ScaleDegree::setIonianMode(int b40tonic) {
	m_mode = m_ion_mode;
	m_b40tonic = b40tonic;
}



//////////////////////////////
//
// Tool_deg::ScaleDegree::isInMinorMode --
//

bool Tool_deg::ScaleDegree::isInMinorMode(void) const {
	return m_mode == m_minor_mode;
}



//////////////////////////////
//
// Tool_deg::ScaleDegree::isInMajorMode --
//

bool Tool_deg::ScaleDegree::isInMajorMode(void) const {
	return m_mode == m_major_mode;
}



//////////////////////////////
//
// Tool_deg::ScaleDegree::getBase40Tonic --
//

int  Tool_deg::ScaleDegree::getBase40Tonic(void) const {
	return m_b40tonic;
}



//////////////////////////////
//
// operator<<ScaleDegree -- print converted **deg string.
//

ostream& operator<<(ostream& out, Tool_deg::ScaleDegree& degree) {
	out << degree.getDegToken();
	return out;
}

ostream& operator<<(ostream& out, Tool_deg::ScaleDegree* degree) {
	if (degree == NULL) {
		out << "{null}";
		return out;
	} else {
		out << (*degree);
		return out;
	}
}





/////////////////////////////////
//
// Tool_dissonant::Tool_dissonant -- Set the recognized options for the tool.
//

Tool_dissonant::Tool_dissonant(void) {
	define("r|raw=b",                        "print raw grid");
	define("p|percent=b",                    "print counts as percentages");
	define("s|suppress=b",                   "suppress dissonant notes");
	define("d|diatonic=b",                   "print diatonic grid");
	define("D|no-dissonant=b",               "don't do dissonance anaysis");
	define("m|midi-pitch=b",                 "print midi-pitch grid");
	define("b|base-40=b",                    "print base-40 grid");
	define("l|metric-levels=b",              "use metric levels in analysis");
	define("k|kern=b",                       "print kern pitch grid");
	define("V|voice-functions=b",            "do cadential-voice-function analysis");
	define("v|voice-number=b",               "print voice number of dissonance");
	define("f|self-number=b",                "print self voice number of dissonance");
	define("debug=b",                        "print grid cell information");
	define("u|undirected=b",                 "use undirected dissonance labels");
	define("c|count=b",                      "count dissonances by category");
	define("i|x|e|exinterp=s:**cdata-rdiss", "specify exinterp for **diss spines");
	define("color|colorize|color-by-rhythm=b",        "color dissonant notes by beat level");
	define("color2|colorize2|color-by-interval=b",    "color dissonant notes by dissonant interval");
}



/////////////////////////////////
//
// Tool_dissonant::run -- Do the main work of the tool.
//

bool Tool_dissonant::run(HumdrumFileSet& infiles) {
	bool status = true;
	for (int i=0; i<infiles.getCount(); i++) {
		status &= run(infiles[i]);
	}
	return status;
}


bool Tool_dissonant::run(const string& indata, ostream& out) {

	if (getBoolean("undirected")) {
		fillLabels2();
	} else {
		fillLabels();
	}

	HumdrumFile infile(indata);
	bool status = run(infile);
	if (hasAnyText()) {
		getAllText(out);
	} else {
		out << infile;
	}
	return status;
}


bool Tool_dissonant::run(HumdrumFile& infile, ostream& out) {
	if (getBoolean("undirected")) {
		fillLabels2();
	} else {
		fillLabels();
	}

	bool status = run(infile);
	if (hasAnyText()) {
		getAllText(out);
	} else {
		out << infile;
	}
	return status;
}


bool Tool_dissonant::run(HumdrumFile& infile) {

	if (getBoolean("voice-number")) {
		m_voicenumQ = true;
	}
	if (getBoolean("self-number")) {
		m_selfnumQ = true;
	}

	if (getBoolean("undirected")) {
		fillLabels2();
	} else {
		fillLabels();
	}

	NoteGrid grid(infile);

	if (getBoolean("debug")) {
		grid.printGridInfo(cerr);
		// return 1;
	} else if (getBoolean("raw")) {
		grid.printRawGrid(m_free_text);
		return 1;
	} else if (getBoolean("diatonic")) {
		grid.printDiatonicGrid(m_free_text);
		return 1;
	} else if (getBoolean("midi-pitch")) {
		grid.printMidiGrid(m_free_text);
		return 1;
	} else if (getBoolean("base-40")) {
		grid.printBase40Grid(m_free_text);
		return 1;
	} else if (getBoolean("kern")) {
		grid.printKernGrid(m_free_text);
		return 1;
	}

	diss2Q = false;
	diss7Q = false;
	diss4Q = false;

	dissL0Q = false;
	dissL1Q = false;
	dissL2Q = false;

	suppressQ = getBoolean("suppress");
	voiceFuncsQ = getBoolean("voice-functions");

	vector<vector<string>> results;
	vector<vector<string>> results2;
	vector<vector<string>> voiceFuncs;
	vector<vector<NoteCell*>> attacks;
	vector<vector<NoteCell*>> attacks2;

	attacks.resize(grid.getVoiceCount());
	results.resize(grid.getVoiceCount());
	for (int i=0; i<(int)results.size(); i++) {
		results[i].resize(infile.getLineCount());
	}
	doAnalysis(results, grid, attacks, getBoolean("debug"));

	if (suppressQ) {
		suppressDissonances(infile, grid, attacks, results);

		// should update low-level durations in suppressDissonances, but
		// being lazy and re-analyze spines.  If there was any error in
		// the durations, there will be no output from the program probably.
		infile.analyzeStructure();

		NoteGrid grid2(infile);
		results2.resize(grid2.getVoiceCount());
		for (int i=0; i<(int)results2.size(); i++) {
			results2[i].clear();
			results2[i].resize(infile.getLineCount());
		}
		vector<vector<NoteCell*>> attacks2;
		doAnalysis(results2, grid2, attacks2, getBoolean("debug"));

	}

	if (suppressQ) {
		if (getBoolean("count")) {
			printCountAnalysis(results2);
			return false;
		} else {
			string exinterp = getString("exinterp");
			vector<HTp> kernspines = infile.getKernSpineStartList();
			infile.appendDataSpine(results2.back(), "", exinterp);
			for (int i = (int)results2.size()-1; i>0; i--) {
				int track = kernspines[i]->getTrack();
				infile.insertDataSpineBefore(track, results2[i-1], "", exinterp);
			}
			printColorLegend(infile);

			adjustColorization(infile);
			infile.createLinesFromTokens();

			return true;
		}
	} else if (voiceFuncsQ) { // run cadnetial-voice-function analysis if requested
		if (getBoolean("count")) {
			printCountAnalysis(voiceFuncs);
			return false;
		}

		voiceFuncs.resize(grid.getVoiceCount());
		for (int i=0; i<(int)voiceFuncs.size(); i++) {
			voiceFuncs[i].resize(infile.getLineCount());
		}
		for (int i=0; i<grid.getVoiceCount(); i++) {
			findCadentialVoiceFunctions(results, grid, attacks[i], voiceFuncs, i);
		}

		string exinterp = getString("exinterp");
		vector<HTp> kernspines = infile.getKernSpineStartList();
		infile.appendDataSpine(voiceFuncs.back(), "", exinterp);
		for (int i = (int)voiceFuncs.size()-1; i>0; i--) {
			int track = kernspines[i]->getTrack();
			infile.insertDataSpineBefore(track, voiceFuncs[i-1], "", exinterp);
		}
		printColorLegend(infile);

		adjustColorization(infile);
		infile.createLinesFromTokens();

		infile.createLinesFromTokens();
		return true;
	} else {
		if (getBoolean("count")) {
			printCountAnalysis(results);
			return false;
		} else {
			string exinterp = getString("exinterp");
			vector<HTp> kernspines = infile.getKernSpineStartList();
			infile.appendDataSpine(results.back(), "", exinterp);
			for (int i = (int)results.size()-1; i>0; i--) {
				int track = kernspines[i]->getTrack();
				infile.insertDataSpineBefore(track, results[i-1], "", exinterp);
			}
			printColorLegend(infile);
			adjustColorization(infile);
			infile.createLinesFromTokens();
			return true;
		}
	}
}



/////////////////////////////
//
// Tool_dissonant::adjustColorization -- The dissonance analysis will color the
//      dissonances before they are labeled.  This function will adjust the colorization
//      related to suspensions: (1) The G/g labels for the suspension agent will be
//      uncolored.  These are treated as consonant notes against which the sustain
//      note causes the dissonance.  and (2) Sustain note attacks are colored in
//      retrospect (unless the sustain occurs on a tied portion of the note).
//

void Tool_dissonant::adjustColorization(HumdrumFile& infile) {
	// New spines were added so need to re-analyze spine structure:
	infile.analyzeBaseFromLines();

	bool colorizeQ = getBoolean("colorize");
	bool colorize2Q = getBoolean("colorize2");
	if (!(colorizeQ || colorize2Q)) {
		// nothing to do
		return;
	}

	// see Tool_dissonant::printColorLegend() for the labels that are used for coloring
	// (if they are changed, then they will have to be changed here since they are
	// currently hard-coded to specific strings.
	string clev0 = "N";  // strong dissonance (at semi-breve level)
	string clev1 = "@";  // weaker dissonance (at minim level)
	string clev2 = "+";  // weakest dissonance (at semi-minim level or lower)

	string c2lev0 = "@";  // dissonance caused by a 2nd harmonic interval
	string c2lev1 = "+";  // dissonance caused by a 7th harmonic interval
	string c2lev2 = "N";  // dissonance caused by a perfect 4th harmonic interval with the bass.

	vector<string> markers;
	if (colorizeQ) {
		markers.push_back(clev0);
		markers.push_back(clev1);
		markers.push_back(clev2);
	} else {
		markers.push_back(c2lev0);
		markers.push_back(c2lev1);
		markers.push_back(c2lev2);
	}

	vector<HTp> sstarts;
	infile.getSpineStartList(sstarts, getString("exinterp"));
	for (int i=0; i<(int)sstarts.size(); i++) {
		adjustColorForVoice(sstarts[i], markers);
	}

	for (int i=0; i<(int)sstarts.size(); i++) {
      adjustSuspensionColors(sstarts[i]);
	}

}



//////////////////////////////
//
// Tool_dissonant::adjustColorForVoice --
//

void Tool_dissonant::adjustColorForVoice(HTp spinestart, vector<string>& labels) {
	HTp current = spinestart;
	current = current->getNextToken();
	HumRegex hre;
	string query;
	query += "[";
	for (int i=0; i<(int)labels.size(); i++) {
		query += labels[i];
	}
	query += "]";

	string binaryAgent  = m_labels[AGENT_BIN];
	string ternaryAgent = m_labels[AGENT_TERN];

	while (current) {
		if (!current->isData()) {
			current = current->getNextToken();
			continue;
		}
		if (current->isNull()) {
			current = current->getNextToken();
			continue;
		}
		if (hre.search(current, binaryAgent)) {
			removeAgentColor(current, binaryAgent, query);
		} else if (hre.search(current, ternaryAgent)) {
			removeAgentColor(current, ternaryAgent, query);
		}

		current = current->getNextToken();
	}

}



//////////////////////////////
//
// Tool_dissonant::adjustSuspensionColors --
//

void Tool_dissonant::adjustSuspensionColors(HTp spinestart) {
	HTp current = spinestart->getNextToken();
	string marks;
	while (current) {
		if (current->isData() && !current->isNull()) {
			marks = current->getValue("auto", "marks");
			if (!marks.empty()) {
				addSuspensionMarkToNote(current, marks);
			}
		}
		current = current->getNextToken();
	}
}



//////////////////////////////
//
// Tool_dissonant::addSuspensionMarkToNote -- Find the note attack of a suspension, and
//    add the mark(s) to that note.
//

void Tool_dissonant::addSuspensionMarkToNote(HTp start, const string& marks) {
	HTp current = start->getPreviousFieldToken();
	// first travel back on the line to the first occurrence found of **kern.
	while (current && !current->isKern()) {
		current = current->getPreviousFieldToken();
	}
	if (!current) {
		return;
	}
	if (!current->isKern()) {
		cerr << "STRANGE ERROR NOT IN KERN" << endl;
		return;
	}
	while (current) {
		if (current->isData() && !current->isNull()) {
			break;
		}
		current = current->getPreviousToken();
	}

	if (current->isNull()) {
		current = current->resolveNull();
	}
	if (!current) {
		return;
	}
	if (!current->isNote()) {
		// some strange problem if this case occurs
		return;
	}
	string tok = *current;
	tok += marks;
	current->setText(tok);
}



///////////////////////////////
//
// Tool_dissonant::removeAgentColor --
//

void Tool_dissonant::removeAgentColor(HTp disslabel, const string& marker, const string& query) {
	HTp kern = disslabel->getPreviousFieldToken();
	while (kern) {
		if (kern->isKern()) {
			break;
		}
	}
	if (!kern) {
		// something went wrong: no kern data associated with label.
		return;
	}
	string value = *kern;
	string query2 = "(" + query + ")";
	HumRegex hre;
	if (hre.search(value, query2)) {
		string marker = hre.getMatch(1);
		addLabelToSuspensions(disslabel, marker);
		hre.replaceDestructive(value, "", query);
		kern->setText(value);
	}
}



//////////////////////////////
//
// Tool_dissonant::isSuspension -- returns true if the label is for a suspension.
//

bool Tool_dissonant::isSuspension(HTp token) {
	if (token->find(m_labels[SUS_BIN])              != string::npos) { return true; }
	if (token->find(m_labels[SUS_TERN])             != string::npos) { return true; }
	if (token->find(m_labels[ORNAMENTAL_SUS])       != string::npos) { return true; }
	if (token->find(m_labels[FAKE_SUSPENSION_STEP]) != string::npos) { return true; }
	if (token->find(m_labels[SUS_NO_AGENT_LEAP])    != string::npos) { return true; }
	if (token->find(m_labels[SUS_NO_AGENT_STEP])    != string::npos) { return true; }
	return false;
}



//////////////////////////////
//
// Tool_dissonant::addLabelToSuspensions --
//

void Tool_dissonant::addLabelToSuspensions(HTp disslabel, const string& marker) {
	string exinterp = getString("exinterp");

	HTp current = disslabel->getPreviousFieldToken();
	while (current) {
		if (current->isDataType(exinterp)) {
			if (isSuspension(current)) {
				string marks = current->getValue("auto", "marks");
				if (marks.find(marker) == string::npos) {
					marks += marker;
					current->setValue("auto", "marks", marks);
				}
			}
		}
		current = current->getPreviousFieldToken();
	}

	current = disslabel->getNextFieldToken();
	while (current) {
		if (current->isDataType(exinterp)) {
			if (isSuspension(current)) {
				string marks = current->getValue("auto", "marks");
				if (marks.find(marker) == string::npos) {
					marks += marker;
					current->setValue("auto", "marks", marks);
				}
			}
		}
		current = current->getNextFieldToken();
	}
}



//////////////////////////////
//
// Tool_dissonant::suppressDissonances -- remove dissonances.
//

void Tool_dissonant::suppressDissonances(HumdrumFile& infile, NoteGrid& grid,
		vector<vector<NoteCell*>>& attacks, vector<vector<string>>& results) {

	// Loop over the dissonance results one full row at a time. The point of doing it
	// one row at a time instead of one voice at a time is so that a weak dissonance in
	// any voice will cause other consonant notes to get reduced away if they begin
	// at that same moment in the piece and last no longer than the weak dissonance.

	vector<HTp> kernstarts;
	infile.getKernSpineStartList(kernstarts);
	vector<int> kernTrackToVoiceIndex(infile.getMaxTrack()+1, -1);
	for (int i=0; i<(int)kernstarts.size(); i++) {
		int track = kernstarts[i]->getTrack();
		kernTrackToVoiceIndex[track] = i;
	}

	if (results.size() != kernstarts.size()) {
		cerr << "Error: size of results does not match staves in score" << endl;
		return;
	}

	HumNum maxWeakDur;  // Dur of longest weak dissonance starting at this row in any voice.
	HTp maxToken = NULL; // Note which has the longest duration and is dissonant on line.

	for (int i=0; i<infile.getLineCount(); i++) {
		if (!infile[i].isData()) {
			// skip non-note lines.
			continue;
		}

		// Loop over all the voices in this row to find the longest weak dissonance:
		maxWeakDur = 0;
		maxToken = NULL;
		for (int j=0; j<(int)infile[i].getFieldCount(); j++) {
			HTp token = infile[i].token(j);
			if (!token->isKern()) {
				continue;
			}
			int v = kernTrackToVoiceIndex.at(token->getTrack());
			if (results[v][i].empty() || (results[v][i] == ".")) {
				continue;
			}
			// cerr << "\tCHECKING DISSONANCE " << results[v][i] << " for note " << token << endl;
			HumNum notedur = token->getTiedDuration();

			if ((results[v][i] == m_labels[PASSING_DOWN]) ||
					(results[v][i] == m_labels[PASSING_UP]) ||
					(results[v][i] == m_labels[NEIGHBOR_DOWN]) ||
					(results[v][i] == m_labels[NEIGHBOR_UP]) ||
					(results[v][i] == m_labels[CAMBIATA_DOWN_S]) ||
					(results[v][i] == m_labels[CAMBIATA_UP_S]) ||
					(results[v][i] == m_labels[CAMBIATA_DOWN_L]) ||
					(results[v][i] == m_labels[CAMBIATA_UP_L]) ||
					(results[v][i] == m_labels[ECHAPPEE_DOWN]) ||
					(results[v][i] == m_labels[ECHAPPEE_UP]) ||
					(results[v][i] == m_labels[ANT_DOWN]) ||
					(results[v][i] == m_labels[ANT_UP]) ||
					(results[v][i] == m_labels[REV_ECHAPPEE_DOWN]) ||
					(results[v][i] == m_labels[REV_ECHAPPEE_UP]) ||
					(results[v][i] == m_labels[REV_CAMBIATA_DOWN]) ||
					(results[v][i] == m_labels[REV_CAMBIATA_UP]) ||
					(results[v][i] == m_labels[DBL_NEIGHBOR_DOWN]) ||
					(results[v][i] == m_labels[DBL_NEIGHBOR_UP]) ) {
				if (notedur > maxWeakDur) {
					maxWeakDur = notedur;
					maxToken = token;
				}
			}
		}
		if (maxToken == NULL) {
			// No dissonant note of the required type on this line.
			continue;
		}

		// cerr << "\tMAX DUR OF DISSONANT NOTE ON LINE: " << maxWeakDur << " FOR NOTE " << maxToken << endl;

		for (int j=0; j<(int)infile[i].getFieldCount(); j++) {
			HTp token = infile[i].token(j);
			if (!token->isKern()) {
				continue;
			}
			if (token->isNull()) {
				continue;
			}
			if (token->isRest()) {
				continue;
			}
			if (!token->isNoteAttack()) {
				continue;
			}
			int v = kernTrackToVoiceIndex.at(token->getTrack());
			if (results[v][i].empty() || (results[v][i] == ".")) {
				continue;
			}
			HumNum notedur = token->getTiedDuration();
			if ((results[v][i] == m_labels[SUS_BIN]) ||
					(results[v][i] == m_labels[SUS_TERN]) ||
					(results[v][i] == m_labels[AGENT_BIN]) ||
					(results[v][i] == m_labels[AGENT_TERN])) {
				continue;
			} else if (notedur <= maxWeakDur) {
				mergeWithPreviousNote(infile, i, j);
			}
		}
	}

	for (int i=0; i<(int)attacks.size(); i++) {
		suppressDissonancesInVoice(infile, grid, i, attacks[i], results[i]);
	}
	for (int i=0; i<(int)attacks.size(); i++) {
		suppressSusOrnamentsInVoice(infile, grid, i, attacks[i], results[i]);
	}

}



//////////////////////////////
//
// Tool_dissonant::suppressDissonancesInVoice --
//

void Tool_dissonant::suppressDissonancesInVoice(HumdrumFile& infile,
		NoteGrid& grid, int vindex, vector<NoteCell*>& attacks,
		vector<string>& results) {

	for (int i=0; i<(int)attacks.size(); i++) {
		int lineindex = attacks[i]->getLineIndex();
		int fieldindex = attacks[i]->getFieldIndex();
		if ((results[lineindex] == "") || (results[lineindex] == ".") ) {
			continue;
		}

		HTp token = infile.token(lineindex, fieldindex);
		if (token->isNull()) {
			// The note was removed already in stage 1.
			continue;
		}
		if (!token->isNoteAttack()) {
			// The note was already merged with the previous note.
			continue;
		}

		if ((results[lineindex] == m_labels[THIRD_Q_PASS_UP]) ||
			(results[lineindex] == m_labels[THIRD_Q_PASS_DOWN]) ||
			(results[lineindex] == m_labels[THIRD_Q_LOWER_NEI]) ||
			(results[lineindex] == m_labels[THIRD_Q_UPPER_NEI]) ||
			(results[lineindex] == m_labels[ACC_PASSING_UP]) ||
			(results[lineindex] == m_labels[ACC_PASSING_DOWN]) ||
			(results[lineindex] == m_labels[ACC_LO_NEI]) ||
			(results[lineindex] == m_labels[ACC_UP_NEI]) ||
			(results[lineindex] == m_labels[RES_PITCH]) ||
			(results[lineindex] == m_labels[APP_UPPER]) ||
			(results[lineindex] == m_labels[APP_LOWER]) ||
			(results[lineindex] == m_labels[PARALLEL_DOWN]) ||
			(results[lineindex] == m_labels[PARALLEL_UP]) ||
			(results[lineindex] == m_labels[ORNAMENTAL_SUS]) ||
			(results[lineindex] == m_labels[CHANSON_IDIOM]) ) {
			// cerr << "MERGING " << token << " with next note" << endl;
			mergeWithNextNote(infile, lineindex, fieldindex);
		}
	}
}



//////////////////////////////
//
// Tool_dissonant::suppressDissonancesInVoice --
//

void Tool_dissonant::suppressSusOrnamentsInVoice(HumdrumFile& infile,
		NoteGrid& grid, int vindex, vector<NoteCell*>& attacks,
		vector<string>& results) {

	for (int i=0; i<(int)attacks.size(); i++) {
		int lineindex = attacks[i]->getLineIndex();
		int fieldindex = attacks[i]->getFieldIndex();
		if ((results[lineindex] == "") || (results[lineindex] == ".") ) {
			continue;
		}

		HTp token = infile.token(lineindex, fieldindex);
		if (token->isNull()) {
			// The note was removed already in stage 1.
			continue;
		}
		if (!token->isNoteAttack()) {
			// The note was already merged with the previous note.
			continue;
		}

		if (((results[lineindex] == m_labels[SUS_BIN]) ||
			 (results[lineindex] == m_labels[SUS_TERN])) &&
			(i < ((int)attacks.size() - 3)) ) {
			HumNum durn   = attacks[i+1]->getDuration();
			HumNum durnn  = attacks[i+2]->getDuration();
			HumNum durnnn  = attacks[i+3]->getDuration();
			double intn   = *attacks[i+1] - *attacks[i];
			double intnn  = *attacks[i+2] - *attacks[i+1];
			double intnnn = *attacks[i+3] - *attacks[i+2];
			double levn   = attacks[i+1]->getMetricLevel();
			double levnn  = attacks[i+2]->getMetricLevel();
			double levnnn = attacks[i+3]->getMetricLevel();
			int lineindexn = attacks[i+1]->getLineIndex();
			int lineindexnn = attacks[i+2]->getLineIndex();
			int lineindexnnn = attacks[i+3]->getLineIndex();
			HTp tokenn = infile.token(lineindexn, fieldindex);
			HTp tokennn = infile.token(lineindexnn, fieldindex);
			HTp tokennnn = infile.token(lineindexnnn, fieldindex);

			if ((durn == durnn) && (durn < durnnn) && (levn > levnnn) &&
				(intn == -1) && (intnn == -1) && (intnnn == 1) ) { // turn figure anticipation of resolution phase
				if ((results[lineindexnn] == ".") && (!tokennn->isNull()) &&
					(tokennn->isNoteAttack()) ) {
					mergeWithPreviousNote(infile, lineindexnn, vindex);
				}
				if ((results[lineindexn] == ".") && (!tokenn->isNull()) &&
					(tokenn->isNoteAttack()) ) {
					mergeWithPreviousNote(infile, lineindexn, vindex);
				}
			} else if ((durn == durnn) && (durn == durnnn) && (levn > levnn) &&
				(levnn < levnnn) && (intn == -1) && (intnn == 0) &&
				(intnnn == -1) && (results[lineindexnnn] == ".") &&
				(!tokennnn->isNull()) && (tokennnn->isNoteAttack()) ) { // Du Fay ornament
				mergeWithPreviousNote(infile, lineindexnnn, vindex);
			}
		}
		if (((results[lineindex] == m_labels[SUS_BIN]) ||
			 (results[lineindex] == m_labels[SUS_TERN])) &&
			(i < ((int)attacks.size() - 2)) ) {
			HumNum durn  = attacks[i+1]->getDuration();
			HumNum durnn = attacks[i+2]->getDuration();
			double intn  = *attacks[i+1] - *attacks[i];
			double intnn = *attacks[i+2] - *attacks[i+1];
			double levn  = attacks[i+1]->getMetricLevel();
			double levnn = attacks[i+2]->getMetricLevel();
			int lineindexn = attacks[i+1]->getLineIndex();
			HTp tokenn = infile.token(lineindexn, fieldindex);

			if ((durn <= durnn) && (levn >= levnn) && (intn == -1) &&
				(intnn == 0) && (results[lineindexn] == ".") &&
				(!tokenn->isNull()) && (tokenn->isNoteAttack()) ) { // anticipation of resolution phase
				mergeWithPreviousNote(infile, lineindexn, vindex);
			}
		}
	}
}



/////////////////////////////
//
// Tool_dissonant::mergeWithPreviousNote --  NoteCell version.
//

void Tool_dissonant::mergeWithPreviousNote(HumdrumFile& infile, NoteCell* cell) {
	int lineindex = cell->getLineIndex();
	int fieldindex = cell->getFieldIndex();
	mergeWithPreviousNote(infile, lineindex, fieldindex);
}



/////////////////////////////
//
// Tool_dissonant::mergeWithNextNote --  NoteCell version.
//

void Tool_dissonant::mergeWithNextNote(HumdrumFile& infile, NoteCell* cell) {
	int lineindex = cell->getLineIndex();
	int fieldindex = cell->getFieldIndex();
	mergeWithNextNote(infile, lineindex, fieldindex);
}



//////////////////////////////
//
// Tool_dissonant::mergeWithPreviousNote --  Will not handle chords correctly.
//     Input note is presumed to be a note attack.
//

void Tool_dissonant::mergeWithPreviousNote(HumdrumFile& infile, int line, int field) {
	HTp cnote = infile.token(line, field);  // current note (attack)
	HTp pnote = cnote->getPreviousNNDT();   // previous note (not necessarily attack)

	if (pnote == NULL) {
		// no previous note;
		return;
	}

	if (pnote->isRest()) {
		// previous note comes before a rest, so don't merge.
		return;
	}

	// cerr << "GOING TO MERGE\t" << cnote << "\tWITH PREVIOUS NOTE" << endl;
	// cerr << "\tPREVIOUS NOTE: " << pnote << endl;

	int pline = pnote->getLineIndex();
	int cline = cnote->getLineIndex();
	bool barline = false;
	for (int i=pline; i<=cline; i++) {
		if (infile[i].isBarline()) {
			barline = true;
			break;
		}
	}

	// bool ctie = pnote->find("[") != string::npos;
	// bool ptie = pnote->find("]") != string::npos;

	if (!barline) {
		// cerr << "\tNOTES IN SAME MEASURE, MERGE IF REASONABLE RHYTHM" << endl;
		HumNum cdur = cnote->getDuration();
		HumNum pdur = pnote->getDuration();
		HumNum dur = cdur + pdur;
		string recip = Convert::durationToRecip(dur);
		// cerr << "\tCOMBINED RHYTHM OF NOTES IS " << recip << endl;
		if (recip.find("%") == string::npos) {
			simplePreviousMerge(pnote, cnote);
			return;
		}
	}

	// cerr << "MERGING VIA TIES" << endl;
	// if (barline) {
	// 	cerr << "\tBARLINE BETWEEN NOTES, USE TIE METHOD" << endl;
	// }

	mergeWithPreviousNoteViaTies(pnote, cnote);
}



//////////////////////////////
//
// Tool_dissonant::mergeWithPreviousNoteViaTies --  Not for use with chords.
//

void Tool_dissonant::mergeWithPreviousNoteViaTies(HTp pnote, HTp cnote) {
	auto loc = pnote->find("]");
	if (loc != string::npos) {
		// change tie end to tie continue
		string text = *pnote;
		text.replace(loc, 1, "_");
		pnote->setText(text);
	} else {
		// The previous note should be a note attack, so start a tie on it.
		string text = "[" + *pnote;
		pnote->setText(text);
	}

	loc = cnote->find("[");
	if (loc != string::npos) {
		// change tie start to tie continue and change all following
		// pitches to that of the previous note.
		string text = *cnote;
		text.replace(loc, 1, "_");

		string pitch = "";
		HumRegex hre;
		if (hre.search(*pnote, "([A-Ga-g]+[#-n]*[iXy]*)")) {
			pitch = hre.getMatch(1);
		} else {
			cerr << "NO PITCH FOUND IN TARGET NOTE " << pnote << endl;
			return;
		}
		changePitchOfTieGroupFollowing(cnote, pitch);
	} else {
		// add tie end to note and change to previous pitch
		string text = *cnote + "]";
		cnote->setText(text);
		changePitch(pnote, cnote);
	}
}



//////////////////////////////
//
// Tool_dissonant::simplePreviousMerge -- Merge two notes which are in the same measure
//   and generate a printable duration when summed together.  Also deal with tied notes
//   attached to the cnote.  Does not work with chords.
//

void Tool_dissonant::simplePreviousMerge(HTp pnote, HTp cnote) {
	bool ctie = cnote->find("[") != string::npos;
	bool ptie = pnote->find("]") != string::npos;

	if (ptie && ctie) {
		// Previous note is part of a tie group and ctie is part of a tie group
		// so the merged tie will be parts of both previous and current groups.
		auto loc = pnote->find("]");
		if (loc != string::npos) {
			string text = *pnote;
			text.replace(loc, 1, "_");
		}
	} else if ((!ptie) && ctie) {
		// Current note is tied to other notes, so the previous note, which is an
		// attack, should be converted to be the start of a tie group.
		string text = "[" + *pnote;
		pnote->setText(text);
	} else if (ptie && (!ctie)) {
		// Don't do anything: the merged note will still be the end of a tie group
	} else if ((!ptie) && (!ctie)) {
		// No need to deall with ties
	}

	HumNum cdur = cnote->getDuration();
	HumNum pdur = pnote->getDuration();
	HumNum dur = cdur + pdur;
	changeDurationOfNote(pnote, dur);

	if (cnote->find("[") == string::npos) {
		// current note is not the start of a tie group, so
		// replace it with a null token and return.  Ideally
		// the low-level duration of the token should also be
		// set to zero.
		cnote->setText(".");
		return;
	}

	// The current note is part of a tie group, so change the pitch
	// of each note in the tie group (after the current note) to the
	// pitch of the previous note, then delete the current note and
	// replace with a null token.

	string pitch = "";
	HumRegex hre;
	if (hre.search(*pnote, "([A-Ga-g]+[#-n]*[iXy]*)")) {
		pitch = hre.getMatch(1);
	} else {
		cerr << "NO PITCH FOUND IN TARGET NOTE " << pnote << endl;
		return;
	}

	changePitchOfTieGroupFollowing(cnote, pitch);

	// also should set the low-level duration of the token to 0.
	cnote->setText(".");
}



//////////////////////////////
//
// Tool_dissonant::simpleNextMerge -- Merge two notes which are in the same measure
//   and generate a printable duration when summed together.  Also deal with tied notes
//   attached to the cnote.  Does not work with chords. Makes the pitch of the
//   next note start at the time point of the current note and last for the
//   comibined duration of the two original notes. The next note gets replaced
//   with a placeholder token.
//

void Tool_dissonant::simpleNextMerge(HTp cnote, HTp nnote) {
	bool ctie = cnote->find("]") != string::npos;
	bool ntie = nnote->find("[") != string::npos;

	if (ctie && ntie) {
		// Current note is part of a tie group and ctie is part of a tie group
		// so the merged tie will be parts of both previous and current groups.
		auto loc = cnote->find("]");
		if (loc != string::npos) {
			string text = *cnote;
			text.replace(loc, 1, "_");
		}
	} else if ((!ctie) && ntie) {
		// Next note is tied to other notes, so the current note, which is an
		// attack, should be converted to be the start of a tie group.
		string text = "[" + *cnote;
		cnote->setText(text);
	}

	HumNum cdur = cnote->getDuration();
	HumNum ndur = nnote->getDuration();
	HumNum dur = cdur + ndur;
	changeDurationOfNote(cnote, dur);
	changePitch(cnote, nnote);
	nnote->setText(".");
	return;
}



//////////////////////////////
//
// Tool_dissonant::changePitchOfTieGroupFollowing --
//

void Tool_dissonant::changePitchOfTieGroupFollowing(HTp note, const string& pitch) {
	int b40 = Convert::kernToBase40(note);
	if (b40 <= 0) {
		cerr << "SOME STRANGE ERROR:  NOTE HAS NO PITCH: " << note << endl;
		return;
	}
	HumRegex hre;
	HTp tok = note;
	bool lastQ = false;
	while (tok) {
		if (lastQ) {
			break;
		}
		int b40new = Convert::kernToBase40(tok);
		if (b40 != b40new) {
			// not the same pitch as the start of the note.
			break;
		}
		string text = *tok;
		hre.replaceDestructive(text, pitch, "[A-Ga-g]+[#-n]*[iXx]*");
		tok->setText(text);
		tok = tok->getNextNNDT();
		if (!tok) {
			break;
		}
		if (tok->find("]") != string::npos) {
			lastQ = true;
		}
	}
}



//////////////////////////////
//
// Tool_dissonant::changeDurationOfNote -- Should also change low-level duration of note.
//

void Tool_dissonant::changeDurationOfNote(HTp note, HumNum dur) {
	string recip = Convert::durationToRecip(dur);
	HumRegex hre;
	if (note->find("q") != string::npos) {
		cerr << "STRANGE ERROR: note is a grace note" << endl;
		return;
	}
	if (hre.search(*note, "^([^\\d.%]*)([\\d.%]+)(.*)")) {
		string text = hre.getMatch(1);
		text += recip;
		text += hre.getMatch(3);
		note->setText(text);
	} else {
		cerr << "STRANGE ERROR: no duration on note" << endl;
		return;
	}
}



//////////////////////////////
//
// Tool_dissonant::mergeWithNextNote --  will not handle chords correctly.
//     Used to reduce out accented dissonances.
//

void Tool_dissonant::mergeWithNextNote(HumdrumFile& infile, int line, int field) {
	HTp cnote = infile.token(line, field);  // current note (attack)
	if (!cnote) {
		return;
	}
	HTp nnote = cnote->getNextNNDT();   // next note
	if (!nnote) {
		return;
	}
	if (nnote->isNull()) {
		return;
	}
	if (nnote->isRest()) {
		// next event is a rest, so don't merge.
		return;
	}

	int cline = cnote->getLineIndex();   // current note's line
	int nline = nnote->getLineIndex();   // next note's line
	bool barline = false;
	for (int i=cline; i<=nline; i++) {
		if (infile[i].isBarline()) {
			barline = true;
			break;
		}
	}

	if (!barline) {
		// cerr << "\tNOTES IN SAME MEASURE, MERGE IF REASONABLE RHYTHM" << endl;
		HumNum cdur = cnote->getDuration();
		HumNum ndur = nnote->getDuration();
		HumNum dur = cdur + ndur;
		string recip = Convert::durationToRecip(dur);
		// cerr << "\tCOMBINED RHYTHM OF NOTES IS " << recip << endl;
		if (recip.find("%") == string::npos) {
			simpleNextMerge(cnote, nnote);   // TODO: Make a "simpleNextMerge(cnote, nnote)"
			return;
		}
	}

	// I'm not sure if a version of this function will be necessary for
	// next-note/strong-dissonance reduction.
	// mergeWithNextNoteViaTies(pnote, cnote);
}



//////////////////////////////
//
// Tool_dissonant::changePitch -- will not handle chords correctly.
//   First note is source for pitch and second is target for pitch.
//

void Tool_dissonant::changePitch(HTp note2, HTp note1) {
	int b40 = Convert::kernToBase40(note1);
	string pitch = Convert::base40ToKern(b40);
	HumRegex hre;
	string n2 = *note2;
	hre.replaceDestructive(n2, pitch, "[A-Ga-gr#-]+[ixX]*");
	note2->setText(n2);
}



//////////////////////////////
//
// Tool_dissonant::changeDuration -- will not handle chords correctly.
//    Adds duration of note2 to note1 and replaces note2 with a
//    placeholder "." token.
//

// void Tool_dissonant::changeDuration(HTp note1, HTp note2) {
// 	HumNum dur1 = note1->getDuration();
// 	HumNum dur2 = note2->getDuration();
// 	HumNum sumdur = dur1 + dur2;
// 	// note1.setDuration(sumdur); // The setDuration() function doesn't exist yet.
// 	// note2->setText(".");
// }



//////////////////////////////
//
// Tool_dissonant::printColorLegend --
//

void Tool_dissonant::printColorLegend(HumdrumFile& infile) {
	if (getBoolean("colorize")) {
		if (dissL0Q) {
			infile.appendLine("!!!RDF**kern: N = strong dissonant marked note, color=\"#bb3300\"");
		}
		if (dissL1Q) {
			infile.appendLine("!!!RDF**kern: @ = weak 1 dissonant marked note, color=\"#33bb00\"");
		}
		if (dissL2Q) {
			infile.appendLine("!!!RDF**kern: + = weak 2 dissonant marked note, color=\"#0099ff\"");
		}
	} else if (getBoolean("colorize2")) {
		if (diss2Q) {
			infile.appendLine("!!!RDF**kern: @ = dissonant 2nd, marked note, color=\"#33bb00\"");
		}
		if (diss7Q) {
			infile.appendLine("!!!RDF**kern: + = dissonant 7th, marked note, color=\"#0099ff\"");
		}
		if (diss4Q) {
			infile.appendLine("!!!RDF**kern: N = dissonant 4th marked note, color=\"#bb3300\"");
		}
	}
}



//////////////////////////////
//
// Tool_dissonant::doAnalysis -- do a basic melodic analysis of all parts.
//

void Tool_dissonant::doAnalysis(vector<vector<string>>& results,
		NoteGrid& grid, vector<vector<NoteCell*>>& attacks, bool debug) {
	attacks.resize(grid.getVoiceCount());

	for (int i=0; i<grid.getVoiceCount(); i++) {
		attacks[i].clear();
		doAnalysisForVoice(results, grid, attacks[i], i, debug);
	}

	for (int i=0; i<grid.getVoiceCount(); i++) {
		findFakeSuspensions(results, grid, attacks[i], i);
	}

	for (int i=0; i<grid.getVoiceCount(); i++) {
		findLs(results, grid, attacks[i], i);
	}

	for (int i=0; i<grid.getVoiceCount(); i++) {
		findYs(results, grid, attacks[i], i);
	}

	for (int i=0; i<grid.getVoiceCount(); i++) {
		findAppoggiaturas(results, grid, attacks[i], i);
	}
}



//////////////////////////////
//
// Tool_dissonant::doAnalysisForVoice -- do analysis for a single voice by
//     subtracting NoteCells to calculate the diatonic intervals.
//

void Tool_dissonant::doAnalysisForVoice(vector<vector<string>>& results,
		NoteGrid& grid, vector<NoteCell*>& attacks, int vindex, bool debug) {
	attacks.clear();
	grid.getNoteAndRestAttacks(attacks, vindex);

	if (debug) {
		cerr << "=======================================================";
		cerr << endl;
		cerr << "Note attacks for voice number "
		     << grid.getVoiceCount()-vindex << ":" << endl;
		for (int i=0; i<(int)attacks.size(); i++) {
			attacks[i]->printNoteInfo(cerr);
		}
	}
	bool nodissonanceQ = getBoolean("no-dissonant");
	bool colorizeQ = getBoolean("colorize");
	bool colorize2Q = getBoolean("colorize2");

	HumNum durpp = -1; // duration of previous previous note
	HumNum durp;       // duration of previous melodic note
	HumNum dur;        // duration of current note
	HumNum durn;       // duration of next melodic note
	HumNum odur = -1; // duration of current note in other voice which may have started earlier
	HumNum odurn = -1; // duration of next note in other voice
	double intp;       // diatonic interval from previous melodic note
	double intpp = -99;// diatonic interval to previous melodic note
	double intn;       // diatonic interval to next melodic note
	double levp;       // metric level of the previous melodic note
	double lev;        // metric level of the current note
	double levn;       // metric level of the next melodic note
	int lineindex;     // line in original Humdrum file content that contains note
	int lineindexpp = -1;// line in original Humdrum file content that contains the previous previous note
	// int lineindexn; // next line in original Humdrum file content that contains note
	int attackindexn;  // slice in NoteGrid content that contains next note attack
	int sliceindex;    // current timepoint in NoteGrid.
	int oattackindexn = -1; // next note attack index of the other voice involved in the diss.
	vector<double> harmint(grid.getVoiceCount());  // harmonic intervals
	bool dissonant;    // true if  note is dissonant with other sounding notes.
	char marking = '\0';
	int ovoiceindex = -1;
	string unexp_label; // default dissonance label if none of the diss types apply
	int refMeterNum;    // the numerator of the reference voice's notated time signature
	HumNum refMeterDen; // the denominator of the reference voice's notated time signature
	int othMeterNum;    // the numerator of the other voice's notated time signature
	HumNum othMeterDen; // the denominator of the other voice's notated time signature
	bool ternAgent = false;  // true if the ref voice would be a valid agent of a ternary susp. But if true, the diss is not necessarily a susp.

	for (int i=1; i<(int)attacks.size() - 1; i++) {
		sliceindex = attacks[i]->getSliceIndex();
		lineindex = attacks[i]->getLineIndex();
		// lineindexn = attacks[i+1]->getLineIndex();
		attackindexn = attacks[i]->getNextAttackIndex();

		marking = '\0';

		// calculate harmonic intervals:
		int lowestnote = 1000;
		double tpitch;
		// int lowestnotei = -1;
		for (int j=0; j<(int)harmint.size(); j++) {
			tpitch = grid.cell(j, sliceindex)->getAbsDiatonicPitch();
			if (!Convert::isNaN(tpitch)) {
				if (tpitch <= lowestnote) {
					lowestnote = (int)tpitch;
					// lowestnotei = j;
				}
			}
			if (j == vindex) {
				harmint[j] = 0;
			}

			harmint[j] = *grid.cell(j, sliceindex) -
					*grid.cell(vindex, sliceindex);

/*
			if (j < vindex) {
				harmint[j] = *grid.cell(vindex, sliceindex) -
						*grid.cell(j, sliceindex);
			} else {
				harmint[j] = *grid.cell(j, sliceindex) -
						*grid.cell(vindex, sliceindex);
			}
*/
		}

		// check if current note is dissonant to another sounding note:
		dissonant = false;

		int nextj = 0;
		int j = 0;

RECONSIDER:

		int value = 0;
		for (j=nextj; j<(int)harmint.size(); j++) {
			if (j == vindex) {
				// don't compare to self
				continue;
			}
			if (Convert::isNaN(harmint[j])) {
				// rest, so ignore
				continue;
			}

			value = (int)harmint[j] % 7; // remove octaves from interval, can return negative ints

			int vpitch = (int)grid.cell(vindex, sliceindex)->getAbsDiatonicPitch();
			int otherpitch = (int)grid.cell(j, sliceindex)->getAbsDiatonicPitch();

			if ((value == 1) || (value == -1)) {
				// forms a second with another sounding note
				dissonant = true;
				diss2Q = true;
				marking = '@';
				unexp_label = m_labels[UNLABELED_Z2];
				ovoiceindex = j;
				oattackindexn = getNextPitchAttackIndex(grid, ovoiceindex, sliceindex);
				break;
			} else if ((value == 6) || (value == -6)) {
				// forms a seventh with another sounding note
				dissonant = true;
				diss7Q = true;
				marking = '+';
				unexp_label = m_labels[UNLABELED_Z7];
				ovoiceindex = j;
				oattackindexn = getNextPitchAttackIndex(grid, ovoiceindex, sliceindex);
				break;
			} else if (
					((value == 3) && !((((vpitch-lowestnote) % 7) == 2) ||
					                     (((vpitch-lowestnote) % 7) == 4))) ||
					((value == -3) && !((((otherpitch-lowestnote) % 7) == 2) ||
					                      (((otherpitch-lowestnote) % 7) == 4)))
					) {
				// If the harmonic interval between two notes is a fourth and
				// the lower pitch in the interval is not a a third or a fifth
				// above the lowest note.
				dissonant = true;
				diss4Q = true;
				marking = 'N';
				unexp_label = m_labels[UNLABELED_Z4];
				// ovoiceindex = lowestnotei;
				ovoiceindex = j;
				// oattackindexn = grid.cell(ovoiceindex, sliceindex)->getNextAttackIndex();
				oattackindexn = getNextPitchAttackIndex(grid, ovoiceindex, sliceindex);
				break;
			}
		}
		nextj = j+1;


/*
		double vpitch = grid.cell(vindex, sliceindex)->getAbsDiatonicPitch();
		if (vpitch - lowestnote > 0) {
			if (int(vpitch - lowestnote) % 7 == 3) {
				diss4Q = true;
				dissonant = true;
				marking = 'N';
				ovoiceindex = lowestnotei;
				oattackindexn = grid.cell(ovoiceindex, sliceindex)->getNextAttackIndex();
				unexp_label = m_labels[UNLABELED_Z4];
			}
		}
*/


		// Don't label current note if not dissonant with other sounding notes.
		if (!dissonant) {
			if (!nodissonanceQ) {
				continue;
			}
		}

		if (colorizeQ) {
			double metriclevel = attacks[i]->getMetricLevel();
			if (metriclevel <= 0) {
				dissL0Q = true;
				marking = 'N';
			} else if (metriclevel < 2) {
				dissL1Q = true;
				marking = '@';
			} else {
				dissL2Q = true;
				marking = '+';
			}
		}

		if ((colorizeQ || colorize2Q) && marking) {
			// mark note
			string text = *attacks[i]->getToken();
			if (text.find(marking) == string::npos) {
				text += marking;
				attacks[i]->getToken()->setText(text);
			}
		}

		// variables for dissonant voice
		durp = attacks[i-1]->getDuration();
		dur  = attacks[i]->getDuration();
		durn = attacks[i+1]->getDuration();
		intp = *attacks[i] - *attacks[i-1];
		intn = *attacks[i+1] - *attacks[i];
		levp = attacks[i-1]->getMetricLevel();
		lev  = attacks[i]->getMetricLevel();
		levn = attacks[i+1]->getMetricLevel();
		if (i >= 2) {
			intpp = *attacks[i-1] - *attacks[i-2];
			durpp = attacks[i-2]->getDuration();
			lineindexpp = attacks[i-2]->getLineIndex();
		}

		// Non-suspension test cases ////////////////////////////////////////////

		// valid_acc_exit determines if the other (accompaniment) voice conforms to the
		// standards of all dissonant types except suspensions.

		// The reference (dissonant) voice moves out of the dissonance to a different
		// pitch at the same time or before the other (accompaniment) voice moves to a
		// different pitch class or a rest:
		bool valid_acc_exit = oattackindexn < attackindexn ? false : true;
		if (oattackindexn < 0) {
			valid_acc_exit = true;
		}

		// Suspension test cases ////////////////////////////////////////////////

		// Condition 2: The other (dissonant) voice stayed in place or repeated the
		//    same pitch at the onset of this dissonant interval.
		bool condition2 = true;
		bool condition2b = false;
		double opitch = grid.cell(ovoiceindex, sliceindex)->getSgnMidiPitch();
		double opitchDia = grid.cell(ovoiceindex, sliceindex)->getAbsDiatonicPitch();
		int lastonoteindex = grid.cell(ovoiceindex, sliceindex)->getPrevAttackIndex();
		double lopitch = NAN;
		if (lastonoteindex >= 0) {
			lopitch = grid.cell(ovoiceindex, lastonoteindex)->getAbsMidiPitch();
			double lopitchDia = grid.cell(ovoiceindex, lastonoteindex)->getAbsDiatonicPitch();
			if (abs(int(opitchDia - lopitchDia)) == 7) {
				condition2b = true;
			}
		} else {
			condition2 = false;
		}
		if (opitch < 0) {
			condition2 = true;
		} else if (opitch != lopitch) {
			condition2 = false;
		}

		int oattackindexp = grid.cell(ovoiceindex, sliceindex)->getPrevAttackIndex();
		int oattackindexc = grid.cell(ovoiceindex, sliceindex)->getCurrAttackIndex();
		odur = grid.cell(ovoiceindex, oattackindexc)->getDuration();
		int olineindexc = grid.cell(ovoiceindex, oattackindexc)->getLineIndex();
		double opitchp = NAN;
		if (oattackindexp >= 0) {
			opitchp = grid.cell(ovoiceindex, oattackindexp)->getAbsDiatonicPitch();
		}

		opitch = grid.cell(ovoiceindex, sliceindex)->getAbsDiatonicPitch();
		int oattackindexn = grid.cell(ovoiceindex, sliceindex)->getNextAttackIndex();

		int olineindexn = -1;
		if (oattackindexn >= 0) {
			olineindexn = grid.cell(ovoiceindex, oattackindexn)->getLineIndex();
		}
		double opitchn = NAN;
		if (oattackindexn >= 0) {
			opitchn = grid.cell(ovoiceindex, oattackindexn)->getAbsDiatonicPitch();
			odurn = grid.cell(ovoiceindex, oattackindexn)->getDuration();
		}
		int oattackindexnn = -1;
		if (oattackindexn >= 0) {
			oattackindexnn = grid.cell(ovoiceindex, oattackindexn)->getNextAttackIndex();
		}
		double opitchnn = NAN;
		if (oattackindexnn >= 0) {
			opitchnn = grid.cell(ovoiceindex, oattackindexnn)->getAbsDiatonicPitch();
		}

		// Condition 3: The other (dissonant) voice leaves its note before
		//    or at the same time as the accompaniment (reference) voice leaves
		//    its pitch class.  [The voices can leave their pitch classes for
		//    another note or for a rest.]
		bool condition3a = oattackindexn <= attackindexn ? true : false;

		// For ornamented suspensions.
		bool condition3b = oattackindexnn <= attackindexn ? true : false;

		// valid_sus_acc: determines if the reference voice conforms to the
		// standards of the accompaniment voice for suspensions.
		bool valid_sus_acc = condition2 && condition3a;
		bool valid_ornam_sus_acc = condition2 && condition3b;

		double ointp = opitch - opitchp;
		double ointn = opitchn - opitch;
		double ointnn = opitchnn - opitchn;

		// To distinguish between binary and ternary suspensions and agents
		// int    getMeterTop          (void);
		// HumNum getMeterBottom       (void);

		// Assign time signature ints here:
		refMeterNum = attacks[i]->getMeterTop();
		refMeterDen = attacks[i]->getMeterBottom();
		othMeterNum = grid.cell(ovoiceindex, sliceindex)->getMeterTop();
		othMeterDen = grid.cell(ovoiceindex, sliceindex)->getMeterBottom();
		HumNum threehalves(3, 2);
		HumNum sixteenthirds(16, 3);
		if (othMeterDen == 0) {
			othMeterDen = 8;
		} else if (othMeterDen == 1) {
			othMeterDen = 4;
		} else if (othMeterDen == 4) {
			othMeterDen = 1;
		}

		ternAgent = false;
		if (((othMeterNum % 3 == 0) && (odur >= othMeterDen)) && // the durational value of the meter's denominator groups in threes and the sus lasts at least as long as the denominator
			(results[ovoiceindex][lineindex] != m_labels[SUS_BIN]) && // the other voice hasn't already been labeled as a binary suspension
			((dur == othMeterDen*2) || // the ref note lasts 2 times as long as the meter's denominator
			 ((dur == othMeterDen*threehalves) && ((intn == 0) || (intn == -1))) || // ref note lasts 1.5 times the meter's denominator and next note is a tenorizans ornament
			 ((dur == othMeterDen*threehalves) && ((unexp_label == m_labels[UNLABELED_Z4]) || (intn == 3))) || // 4-3 susp where agent leaps to diatonic pitch class of resolution
			 ((dur == sixteenthirds) && (refMeterNum == 3) && (refMeterDen == threehalves)) || // special case for 3/3 time signature
			 ((odur == othMeterDen*threehalves) && (ointn == -1) && (odurn == 2) && (ointnn == 0)) || // change of agent suspension with ant of resolution
			 ((dur == othMeterDen) && (odur == othMeterDen*2)) || // unornamented change of agent suspension
			 ((dur == othMeterDen) && (odur == othMeterDen) && (durp == 2) &&
			  (levp == 0) && (lev == 1) & (levn == 1))) ) { // perfection is on 4th minim of 6/2, see Jos2302 m. 34
// TO DO: fix case from Ano2002 m. 29
// Also fix Bus1001a m. 57 => conflicting agents
// Bus1001b m. 135 susp. should be ternary
// Bus2007 m. 43 should be binary and 47 shouldn't be a susp at all!
// Com1002a m. 49 all should be ternary => conflicting agents
// Sort out Bus3038 m. 16
			ternAgent = true;
		}

		if (((lev >= levn) || ((lev == 2) && (dur == .5))) && (lev >= levp) &&
			(dur <= durp) && (condition2 || condition2b) && valid_acc_exit) { // weak dissonances
			if (intp == -1) { // descending dissonances
				if (intn == -1) { // downward passing tone
					results[vindex][lineindex] = m_labels[PASSING_DOWN];
				} else if (intn == 1) { // lower neighbor
					results[vindex][lineindex] = m_labels[NEIGHBOR_DOWN];
				} else if ((intn == 0) && (dur <= 2)) { // descending anticipation
					results[vindex][lineindex] = m_labels[ANT_DOWN];
				} else if (intn > 1) { // lower échappée
					results[vindex][lineindex] = m_labels[ECHAPPEE_DOWN];
				} else if (intn < -1) { // descending short nota cambiata
					results[vindex][lineindex] = m_labels[CAMBIATA_DOWN_S];
				}
			} else if (intp == 1) { // ascending dissonances
				if (intn == 1) { // rising passing tone
					results[vindex][lineindex] = m_labels[PASSING_UP];
				} else if (intn == -1) { // upper neighbor
					results[vindex][lineindex] = m_labels[NEIGHBOR_UP];
				} else if (intn < -1) { // upper échappée
					results[vindex][lineindex] = m_labels[ECHAPPEE_UP];
				} else if ((intn == 0) && (dur <= 2)) { // rising anticipation
					results[vindex][lineindex] = m_labels[ANT_UP];
				} else if (intn > 1) { // ascending short nota cambiata
					results[vindex][lineindex] = m_labels[CAMBIATA_UP_S];
				}
			} else if (intp < -1) {
				if (intn == 1) { // reverse lower échappée
					results[vindex][lineindex] = m_labels[REV_ECHAPPEE_DOWN];
				} else if (intn == -1) { // reverse descending nota cambiata
					results[vindex][lineindex] = m_labels[REV_CAMBIATA_DOWN];
				}
			} else if (intp > 1) {
				if (intn == -1) { // reverse upper échappée
					results[vindex][lineindex] = m_labels[REV_ECHAPPEE_UP];
				} else if (intn == 1) { // reverse ascending nota cambiata
					results[vindex][lineindex] = m_labels[REV_CAMBIATA_UP];
				}
			}
		} else if ((durp >= 2) && (dur == 1) && (lev < levn) && valid_acc_exit &&
					 (condition2 || condition2b) && (lev == 1)) {
			if (intp == -1) {
				if (intn == -1) { // dissonant third quarter descending passing tone
					results[vindex][lineindex] = m_labels[THIRD_Q_PASS_DOWN];
				} else if (intn == 1) { // dissonant third quarter lower neighbor
					results[vindex][lineindex] = m_labels[THIRD_Q_LOWER_NEI];
				}
			} else if (intp == 1) {
				if (intn == 1) { // dissonant third quarter ascending passing tone
					results[vindex][lineindex] = m_labels[THIRD_Q_PASS_UP];
				} else if (intn == -1) { // dissonant third quarter upper neighbor
					results[vindex][lineindex] = m_labels[THIRD_Q_UPPER_NEI];
				}
			}
		} else if (((lev > levp) || (durp+durp+durp+durp == dur)) &&
				   (lev == levn) && condition2 && (intn == -1) &&
				   (dur == (durn+durn)) && ((dur+dur) <= odur)) {
			if (fabs(intp) > 1.0) {
				results[vindex][lineindex] = m_labels[SUS_NO_AGENT_LEAP];
			} else if ((fabs(intp) == 1.0) || ((intp == 0) && (fabs(intpp) == 1.0))) {
				results[vindex][lineindex] = m_labels[SUS_NO_AGENT_STEP];
			}
		}

		/////////////////////////////
		////
		//// Code to apply binary or ternary suspension and agent labels and
		//// also suspension ornament and chanson idiom labels

		else if (valid_sus_acc && ((ointn == -1) || ((ointn == -2) && (ointnn == 1)))) {
			if ((durpp == 1) && (durp == 1) && (intpp == -1) && (intp == 1) &&
					((results[vindex][lineindexpp] == m_labels[THIRD_Q_PASS_DOWN]) ||
					(results[vindex][lineindexpp] == m_labels[ACC_PASSING_DOWN]) ||
					(results[vindex][lineindexpp] == m_labels[UNLABELED_Z7]) ||
					(results[vindex][lineindexpp] == m_labels[UNLABELED_Z4]))) {
				results[vindex][lineindexpp] = m_labels[CHANSON_IDIOM];
			}
			if (ternAgent) { // ternary agent and suspension
				results[vindex][lineindex] = m_labels[AGENT_TERN];
				results[ovoiceindex][lineindex] = m_labels[SUS_TERN];
			} else if (((odur == .5) || (odur == 1)) && // purely ornamental suspension
						((odurn == .5) || (odurn == 1)) &&
						(ointn == -1) && (ointnn == -1) ) {
				results[vindex][lineindex] = m_labels[AGENT_BIN];
				results[ovoiceindex][lineindex] = m_labels[ORNAMENTAL_SUS];
			} else { // binary agent and suspension
				results[vindex][lineindex] = m_labels[AGENT_BIN];
				results[ovoiceindex][lineindex] = m_labels[SUS_BIN];
			}
		} else if (valid_ornam_sus_acc && ((ointn == 0) && (ointnn == -1))) {
			if ((durpp == 1) && (durp == 1) && (intpp == -1) && (intp == 1) &&
					((results[vindex][lineindexpp] == m_labels[THIRD_Q_PASS_DOWN]) ||
					(results[vindex][lineindexpp] == m_labels[ACC_PASSING_DOWN]) ||
					(results[vindex][lineindexpp] == m_labels[UNLABELED_Z7]) ||
					(results[vindex][lineindexpp] == m_labels[UNLABELED_Z4]))) {
				results[vindex][lineindexpp] = m_labels[CHANSON_IDIOM];
			}
			if (ternAgent) { // ternary agent and suspension
				results[vindex][lineindex] = m_labels[AGENT_TERN];
				results[ovoiceindex][lineindex] = m_labels[SUS_TERN];
			} else { // binary agent and suspension
				results[vindex][lineindex] = m_labels[AGENT_BIN];
				results[ovoiceindex][lineindex] = m_labels[SUS_BIN];
			} // repeated-note of suspension
			results[ovoiceindex][olineindexn] = m_labels[SUSPENSION_REP];
		} else if (valid_ornam_sus_acc && ((ointn == 1) && (ointnn == -2))) {
			if ((durpp == 1) && (durp == 1) && (intpp == -1) && (intp == 1) &&
					((results[vindex][lineindexpp] == m_labels[THIRD_Q_PASS_DOWN]) ||
					(results[vindex][lineindexpp] == m_labels[ACC_PASSING_DOWN]) ||
					(results[vindex][lineindexpp] == m_labels[UNLABELED_Z7]) ||
					(results[vindex][lineindexpp] == m_labels[UNLABELED_Z4]))) {
				results[vindex][lineindexpp] = m_labels[CHANSON_IDIOM];
			}
			if (ternAgent) { // ternary agent and suspension
				results[vindex][lineindex] = m_labels[AGENT_TERN];
				results[ovoiceindex][lineindex] = m_labels[SUS_TERN];
			} else { // binary agent and suspension
				results[vindex][lineindex] = m_labels[AGENT_BIN];
				results[ovoiceindex][lineindex] = m_labels[SUS_BIN];
			} // This ornament is consonant against the agent so no ornament label.
		}

/////////////////////////////

		if (i < ((int)attacks.size() - 2)) { // expand the analysis window

			double intnn = *attacks[i+2] - *attacks[i+1];
			HumNum durnn = attacks[i+2]->getDuration();       // dur of note after next
			// double levnn = attacks[i+2]->getMetricLevel(); // lev of note after next

			if ((dur <= durp) && (lev >= levp) && (lev >= levn) &&
					(intp == -1) && (intn == -2) && (intnn == 1)) { // long-form descending cambiata
				results[vindex][lineindex] = m_labels[CAMBIATA_DOWN_L];
			} else if ((dur <= durp) && (lev >= levp) && (lev >= levn) &&
					(intp == 1) && (intn == 2) && (intnn == -1)) { // long-form ascending nota cambiata
				results[vindex][lineindex] = m_labels[CAMBIATA_UP_L];
			}
		}

		// Decide whether to give an unexplained dissonance label to the ref.
		// voice if none of the dissonant conditions above apply.
		bool refLeaptTo = fabs(intp) > 1 ? true : false;
		bool othLeaptTo = fabs(ointp) > 1 ? true : false;
		bool refLeaptFrom = fabs(intn) > 1 ? true : false;
		bool othLeaptFrom = fabs(ointn) > 1 ? true : false;

		if ((results[vindex][lineindex] == "") && // this voice doesn't already have a dissonance label
				((olineindexc < lineindex) || // other voice does not attack at this point
				((olineindexc == lineindex) && (dur < odur)) || // both voices attack together, but ref voice leaves dissonance first
				(((olineindexc == lineindex) && (dur == odur)) && // both voices enter and leave dissonance simultaneously
				((!refLeaptFrom && othLeaptFrom) || // ref voice leaves diss by step or rep and other voice leaves by leap
				(refLeaptTo && refLeaptFrom && othLeaptTo && othLeaptFrom) || // both voices enter and leave diss by leap
				((fabs(intp) == 1) && (intn == 0) && ((fabs(ointp)) > 0 || (fabs(ointn) > 0))) || // ref voice enters by step, leaves by rep, other v repeats no more than once
				((fabs(intp) == 1) && (fabs(intn) == 1) && !othLeaptTo && !othLeaptFrom) || // ref voice enters and leaves by step, other voice by step or rep
				((fabs(intp) == 1) && (intn == 0) && !othLeaptTo && (ointn == 0)) || // ref enters by step and leaves by rep, other v enters by step or rep and leaves by rep
				(!refLeaptTo && refLeaptFrom && othLeaptFrom))))) { // ref voice enters diss by step or rep and both voices leave by leap
			results[vindex][lineindex] = unexp_label;
		}


		// If the note was labeled as an unknown dissonance, then go back and check
		// against another note with which it might have a known dissonant function.
		// Also go back if this voice was identified as an agent, because it may be
		// the agent of multiple patients.
		if ((results[vindex][lineindex] == m_labels[UNLABELED_Z4]) ||
				(results[vindex][lineindex] == m_labels[UNLABELED_Z7]) ||
				(results[vindex][lineindex] == m_labels[AGENT_BIN]) ||
				(results[vindex][lineindex] == m_labels[AGENT_TERN])) {
			if (nextj < (int)harmint.size()) {
				goto RECONSIDER;
			}
		}
	}

}



//////////////////////////////
//
// Tool_dissonant::findFakeSuspensions --
//

void Tool_dissonant::findFakeSuspensions(vector<vector<string>>& results, NoteGrid& grid,
		vector<NoteCell*>& attacks, int vindex) {
	double intp;        // abs value of diatonic interval from previous melodic note
	int lineindexn;     // line index of the next note in the voice
	bool sfound;        // boolean for if a suspension is found after a Z dissonance

	for (int i=1; i<(int)attacks.size()-1; i++) {
		int lineindex = attacks[i]->getLineIndex();
		if ((results[vindex][lineindex].find("Z") == string::npos) &&
			(results[vindex][lineindex].find("z") == string::npos) &&
			(results[vindex][lineindex].find("M") == string::npos) &&
			(results[vindex][lineindex].find("m") == string::npos)) {
			continue;
		}
		intp = fabs(*attacks[i] - *attacks[i-1]);
		lineindexn = attacks[i+1]->getLineIndex();
		sfound = false;
		for (int j=lineindex + 1; j<=lineindexn; j++) {
			if ((results[vindex][j].compare(0, 1, "s") == 0) ||
					(results[vindex][j].compare(0, 1, "S") == 0)) {
				sfound = true;
				break;
			}
		}
		if (!sfound) {
			continue;
		}
		// Also may need to check for the existance of another voice attacked before Z
		// and sustained through to the beginning of the resolution.

		if (intp == 1) { // Apply labels for normal fake suspensions.
			results[vindex][lineindex] = m_labels[FAKE_SUSPENSION_STEP];
		} else if (intp > 1) {
			results[vindex][lineindex] = m_labels[FAKE_SUSPENSION_LEAP];
		} else if (i > 1) { // as long as i > 1 intpp will be in range.
			double intpp = fabs(*attacks[i-1] - *attacks[i-2]);
			if (intp == 0) { // fake suspensions preceded by an anticipation.
				if (intpp == 1) {
					results[vindex][lineindex] = m_labels[FAKE_SUSPENSION_STEP];
				} else if (intpp > 1) {
					results[vindex][lineindex] = m_labels[FAKE_SUSPENSION_LEAP];
				}
			}
		}
	}
}

//////////////////////////////
//
// Tool_dissonant::findLs --
//
void Tool_dissonant::findLs(vector<vector<string>>& results, NoteGrid& grid,
		vector<NoteCell*>& attacks, int vindex) {
	HumNum dur;        // duration of current note;
	HumNum odur;       // duration of current note in other voice which may have started earlier;
	double intp;       // diatonic interval from previous melodic note
	double intn;       // diatonic interval to next melodic note
	double ointp;      // diatonic interval from previous melodic note in other voice
	double ointn;      // diatonic interval to next melodic note in other voice
	int lineindex;     // line in original Humdrum file content that contains note
	int olineindex;   // line in original Humdrum file content that contains other voice note
	int sliceindex;    // current timepoint in NoteGrid.
	int oattackindexp; // line index of other voice's previous note
	int oattackindexc; // line index of other voice's current note
	int oattackindexn; // line index of other voice's next note
	double opitchp;    // previous pitch in other voice
	double opitch;     // current pitch in other voice
	double opitchn;    // next pitch in other voice

	for (int i=1; i<(int)attacks.size()-1; i++) {
		lineindex = attacks[i]->getLineIndex();
		if ((results[vindex][lineindex].find("Z") == string::npos) &&
			(results[vindex][lineindex].find("z") == string::npos)) {
			continue;
		}
		dur  = attacks[i]->getDuration();
		intp = *attacks[i] - *attacks[i-1];
		intn = *attacks[i+1] - *attacks[i];
		sliceindex = attacks[i]->getSliceIndex();

		for (int j=0; j<(int)grid.getVoiceCount(); j++) { // j is the voice index of the other voice
			if (vindex == j) { // only compare different voices
				continue;
			}
			if ((results[j][lineindex] == m_labels[AGENT_BIN]) ||
				(results[j][lineindex] == m_labels[AGENT_TERN]) ||
				(results[j][lineindex] == m_labels[UNLABELED_Z7]) ||
				(results[j][lineindex] == m_labels[UNLABELED_Z4]) ||
				(results[j][lineindex] == "")) {
				continue; // skip if other voice is an agent, unexplainable, or empty.
			}
			oattackindexc = grid.cell(j, sliceindex)->getCurrAttackIndex();
			olineindex = grid.cell(j, oattackindexc)->getLineIndex();
			if (olineindex != lineindex) { // if olineindex == lineindex then oattackindexp is in range
				continue; // skip if other voice doesn't attack at the same time
			}
			oattackindexp = grid.cell(j, sliceindex)->getPrevAttackIndex();
			odur = grid.cell(j, oattackindexc)->getDuration();
			if (dur != odur) { // if dur == odur then the oattackindexn will be in range
				continue;
			}
			opitchp = grid.cell(j, oattackindexp)->getAbsDiatonicPitch();
			opitch = grid.cell(j, sliceindex)->getAbsDiatonicPitch();
			oattackindexn = grid.cell(j, sliceindex)->getNextAttackIndex();
			opitchn = grid.cell(j, oattackindexn)->getAbsDiatonicPitch();
			ointp = opitch - opitchp;
			ointn = opitchn - opitch;
			if ((intp == ointp) && (intn == ointn)) { // this note moves in parallel with an identifiable dissonance
				if (intp > 0) {
					results[vindex][lineindex] = m_labels[PARALLEL_UP];
					break;
				} else if (intp < 0) {
					results[vindex][lineindex] = m_labels[PARALLEL_DOWN];
					break;
				}
			}
		}
	}
}

//////////////////////////////
//
// Tool_dissonant::findYs --
//
void Tool_dissonant::findYs(vector<vector<string>>& results, NoteGrid& grid,
		vector<NoteCell*>& attacks, int vindex) {
	double intp;       // diatonic interval from previous melodic note
	double intn;       // diatonic interval to next melodic note
	int lineindex;     // line in original Humdrum file content that contains note
	int olineindex;    // line in original Humdrum file content that contains other voice note
	int sliceindex;    // current timepoint in NoteGrid
	int attackindexn;  // line index of next note
	int oattackindexc; // line index of other voice current note
	int oattackindexn; // line index of other voice's next note
	double pitch;      // current pitch in this voice
	double opitch;     // current pitch in other voice
	bool onlyWithValids; // note is only dissonant with identifiable dissonances
	bool valid_acc_exit; // if accompaniment voice conforms to necessary standards

	for (int i=1; i<(int)attacks.size()-1; i++) {
		lineindex = attacks[i]->getLineIndex();
		if ((results[vindex][lineindex].find("Z") == string::npos) &&
			(results[vindex][lineindex].find("z") == string::npos)) {
			continue;
		}
		intp = *attacks[i] - *attacks[i-1];
		intn = *attacks[i+1] - *attacks[i];
		sliceindex = attacks[i]->getSliceIndex();

		int lowestnote = 1000; // lowest sounding diatonic note in any voice at this sliceindex
		double tpitch;
		for (int v=0; v<(int)grid.getVoiceCount(); v++) {
			tpitch = grid.cell(v, sliceindex)->getAbsDiatonicPitch();
			if (!Convert::isNaN(tpitch)) {
				if (tpitch <= lowestnote) {
					lowestnote = (int)tpitch;
				}
			}
		}

		onlyWithValids = true;
		for (int j=0; j<(int)grid.getVoiceCount(); j++) { // j = index of other voice
			if ((vindex == j) || (onlyWithValids == false)) {
				continue;
			}
			oattackindexc = grid.cell(j, sliceindex)->getCurrAttackIndex();
			oattackindexn = grid.cell(j, sliceindex)->getNextAttackIndex();
			attackindexn = attacks[i]->getNextAttackIndex();
			pitch = attacks[i]->getAbsDiatonicPitch();
			opitch = grid.cell(j, sliceindex)->getAbsDiatonicPitch();
			olineindex = grid.cell(j, oattackindexc)->getLineIndex();
			int thisInt = int(opitch - pitch); // diatonic interval in this pair
			int thisMod7 = thisInt % 7; // simplify octaves out of thisInt
			valid_acc_exit = oattackindexn < attackindexn ? false : true;
			if (oattackindexn < 0) {
				valid_acc_exit = true;
			}

			if (((thisMod7 == 1) || (thisMod7 == -6)) && // creates 2nd or 7th diss
				((results[j][lineindex] == m_labels[SUS_BIN]) || // other voice is susp
				 (results[j][lineindex] == m_labels[SUS_TERN])) &&
				(fabs(intp) == 1) && (intn == -1) && valid_acc_exit) {
				results[vindex][lineindex] = m_labels[RES_PITCH];
				onlyWithValids = false;
			} else if (((abs(thisMod7) == 1) || (abs(thisMod7) == 6)  ||
					((thisInt > 0) && (thisMod7 == 3) &&
					!(((int(pitch-lowestnote) % 7) == 2) ||
					((int(pitch-lowestnote) % 7) == 4))) ||
					((thisInt < 0) && (thisMod7 == -3) && // a fourth by inversion is -3 and -3%7 = -3.
					!(((int(opitch-lowestnote) % 7) == 2) ||
					((int(opitch-lowestnote) % 7) == 4)))) &&
					((results[j][olineindex] == m_labels[AGENT_BIN]) ||
					(results[j][olineindex] == m_labels[AGENT_TERN]) ||
					(results[j][olineindex] == m_labels[UNLABELED_Z7]) ||
					(results[j][olineindex] == m_labels[UNLABELED_Z4]) ||
					((results[j][olineindex] == "") &&
					((results[j][lineindex] != m_labels[SUS_BIN]) &&
					(results[j][lineindex] != m_labels[SUS_TERN]))))) {
				onlyWithValids = false;
			}
		}

		if (onlyWithValids && ((results[vindex][lineindex] == m_labels[UNLABELED_Z7]) ||
				(results[vindex][lineindex] == m_labels[UNLABELED_Z4]))) {
			if (intp > 0) {
				results[vindex][lineindex] = m_labels[ONLY_WITH_VALID_UP];
			} else if (intp <= 0) {
				results[vindex][lineindex] = m_labels[ONLY_WITH_VALID_DOWN];
			}
		}
	}
}

//////////////////////////////
//
// Tool_dissonant::findAppoggiaturas --
//
void Tool_dissonant::findAppoggiaturas(vector<vector<string>>& results, NoteGrid& grid,
		vector<NoteCell*>& attacks, int vindex) {
	HumNum durpp;      // duration of previous previous note
	HumNum durp;       // duration of previous note
	HumNum dur;        // duration of current note
	HumNum durn;       // duration of next note
	double intp;       // diatonic interval from previous melodic note
	double intn;       // diatonic interval to next melodic note
	double lev;        // metric level of the current note
	double levn;       // metric level of the next melodic note
	int lineindexp;    // line in original Humdrum file content that contains previous note
	int lineindex;     // line in original Humdrum file content that contains note
	int sliceindex;    // current timepoint in NoteGrid.
	int attackindexn;  // line index of ref voice's next note
	int oattackindexn; // line index of other voice's next note
	double pitch;      // current pitch in ref voice
	double opitch;     // current pitch in other voice
	bool ant_down;     // if the current note was preceded by a descending anticipation
	bool ant_up;       // if the current note was preceded by an ascending anticipation
	bool ant_leapt_to; // if the current note was preceded by an anticipation leapt to

	for (int i=1; i<(int)attacks.size()-1; i++) {
		lineindexp = attacks[i-1]->getLineIndex();
		lineindex = attacks[i]->getLineIndex();
		if ((results[vindex][lineindex].find("Z") == string::npos) &&
			(results[vindex][lineindex].find("z") == string::npos) &&
			(results[vindex][lineindex].find("J") == string::npos) &&
			(results[vindex][lineindex].find("j") == string::npos)) {
			continue;
		}
		durp = attacks[i-1]->getDuration();
		dur  = attacks[i]->getDuration();
		durn = attacks[i+1]->getDuration();
		intp = *attacks[i] - *attacks[i-1];
		intn = *attacks[i+1] - *attacks[i];
		lev  = attacks[i]->getMetricLevel();
		levn = attacks[i+1]->getMetricLevel();
		sliceindex = attacks[i]->getSliceIndex();

		if (!((lev <= levn) && (dur <= durn))) {
			continue; // go on when the voice with Z label doesn't fulfill its metric or durational requirements
		}

		// determine if current note was preceded by an anticipation (which may be a consonant anticipation)
		ant_down     = false;
		ant_up       = false;
		ant_leapt_to = false;
		if (i > 1) {
			durpp = attacks[i-2]->getDuration();
			if ((intp == 0) && (durp <= dur) && (durp <= durpp)) {
				if ((*attacks[i-1] - *attacks[i-2]) == -1) {
					ant_down = true;
				} else if ((*attacks[i-1] - *attacks[i-2]) == 1) {
					ant_up = true;
				} else if (fabs(*attacks[i-1] - *attacks[i-2]) > 1) {
					ant_leapt_to = true;
				}
			}
		}

		int lowestnote = 1000; // lowest sounding diatonic note in any voice at this sliceindex
		double tpitch;
		for (int v=0; v<(int)grid.getVoiceCount(); v++) {
			tpitch = grid.cell(v, sliceindex)->getAbsDiatonicPitch();
			if ((!Convert::isNaN(tpitch)) && (tpitch <= lowestnote)) {
				lowestnote = (int)tpitch;
			}
		}

		for (int j=0; j<(int)grid.getVoiceCount(); j++) { // j is the voice index of the other voice
			if (vindex == j) { // only compare different voices
				continue;
			}

			attackindexn = attacks[i]->getNextAttackIndex();
			oattackindexn = grid.cell(j, sliceindex)->getNextAttackIndex();
			if (oattackindexn < attackindexn) {
				continue; // skip this pair if other voice leaves diss first
			}

			pitch = attacks[i]->getAbsDiatonicPitch();
			opitch = grid.cell(j, sliceindex)->getAbsDiatonicPitch();
			int thisInt = int(opitch - pitch); // diatonic interval in this pair
			int thisMod7 = thisInt % 7; // simplify octaves out of thisInt

			// see if the pair creates a dissonant interval
			if (!((abs(thisMod7) == 1) || (abs(thisMod7) == 6)  ||
					((thisInt > 0) && (thisMod7 == 3) &&
					!(((int(pitch-lowestnote) % 7) == 2) ||
					((int(pitch-lowestnote) % 7) == 4))) ||
					((thisInt < 0) && (thisMod7 == -3) && // a fourth by inversion is -3 and -3%7 == -3.
					!(((int(opitch-lowestnote) % 7) == 2) ||
					((int(opitch-lowestnote) % 7) == 4))))) {
				continue;
			} else if (((intp == -1) || ant_down) && ((lev <= levn) && (dur <= durn)) &&
						((results[vindex][lineindex] == m_labels[UNLABELED_Z7]) ||
						(results[vindex][lineindex] == m_labels[UNLABELED_Z4]))) {
				if (intn == -1) {
					results[vindex][lineindex] = m_labels[ACC_PASSING_DOWN]; // descending accented passing tone
				} else if (intn == 1) {
					results[vindex][lineindex] = m_labels[ACC_LO_NEI]; // accented lower neighbor
				}
			} else if (((intp == 1) || ant_up) && ((lev <= levn) && (dur <= durn)) &&
						((results[vindex][lineindex] == m_labels[UNLABELED_Z7]) ||
						(results[vindex][lineindex] == m_labels[UNLABELED_Z4]))) {
				if (intn == 1) {
					results[vindex][lineindex] = m_labels[ACC_PASSING_UP]; // rising accented passing tone
				} else if (intn == -1) {
					results[vindex][lineindex] = m_labels[ACC_UP_NEI]; // accented upper neighbor
				}
			} else if (intn == -1) {
				if ((intp == 2) && (results[vindex][lineindexp] == m_labels[ECHAPPEE_DOWN]) &&
					(((results[vindex][lineindex] == m_labels[UNLABELED_Z7]) ||
						(results[vindex][lineindex] == m_labels[UNLABELED_Z4]) ||
						(results[vindex][lineindex] == m_labels[REV_ECHAPPEE_UP])) ||
					 ((lev <= levn) && (dur <= durn)))) {
					results[vindex][lineindexp] = m_labels[DBL_NEIGHBOR_DOWN];
					results[vindex][lineindex]  = m_labels[DBL_NEIGHBOR_DOWN];
				} else if (((fabs(intp) > 1) || ant_leapt_to) &&
							((lev <= levn) && (dur <= durn)) &&
							((results[vindex][lineindex] == m_labels[UNLABELED_Z7]) ||
							(results[vindex][lineindex] == m_labels[UNLABELED_Z4]))) { // upper appoggiatura
					results[vindex][lineindex] = m_labels[APP_UPPER];
				}
			} else if (intn == 1) {
				if ((intp == -2) && (results[vindex][lineindexp] == m_labels[ECHAPPEE_UP]) &&
					(((results[vindex][lineindex] == m_labels[UNLABELED_Z7]) ||
						(results[vindex][lineindex] == m_labels[UNLABELED_Z4]) ||
						(results[vindex][lineindex] == m_labels[REV_ECHAPPEE_DOWN])) ||
					 ((lev <= levn) && (dur <= durn)))) {
					results[vindex][lineindexp] = m_labels[DBL_NEIGHBOR_UP];
					results[vindex][lineindex]  = m_labels[DBL_NEIGHBOR_UP];
				} else if (((fabs(intp) > 1) || ant_leapt_to) &&
							((lev <= levn) && (dur <= durn)) &&
							((results[vindex][lineindex] == m_labels[UNLABELED_Z7]) ||
							(results[vindex][lineindex] == m_labels[UNLABELED_Z4]))) { // lower appoggiatura
					results[vindex][lineindex] = m_labels[APP_LOWER];
				}
			}
		}
	}
}




//////////////////////////////
//
// Tool_dissonant::findCadentialVoiceFunctions -- identify the cadential-voice
//		functions present in each voice. These are the single-line constituents
//		of Renaissance cadences. Five basic types are identified: Cantizans,
//		Altizans, Tenorizans, Leaping Contratenor, and Bassizans. Since the
//		cadential-voice functions are identified contrapuntally, a Cantizans or
//		Altizans must be found set against any of the other three types for
//		anything to be detected.
//
void Tool_dissonant::findCadentialVoiceFunctions(vector<vector<string>>& results, NoteGrid& grid,
		vector<NoteCell*>& attacks, vector<vector<string>>& voiceFuncs, int vindex) {
	double int2;      // diatonic interval to next melodic note
	double int3 = -22; // diatonic interval from next melodic note to following note
	double int4;      // diatonic interval from note three to note four
	double oint2;     // diatonic interval to next melodic note in other voice
	double oint3;     // diatonic interval from next melodic note to following note
	double oint4;     // diatonic interval from third to fourth note in other voice
	double oint5;     // diatonic interval from third to fifth note in other voice
	int lineindex;    // line in original Humdrum file that contains note
	int lineindex2;   // line in original Humdrum file that contains note one event later
	int lineindex3;   // line in original Humdrum file that contains note two events later
	int lineindex4;   // line in original Humdrum file content that contains note three events later
	int sliceindex;   // current timepoint in NoteGrid.
	int attInd2;      // line index of ref voice's next attack
	int attInd3;      // line index of ref voice's attack two events later
	int attInd4;      // line index of ref voice's attack three events later
	int oattInd2;     // line index of other voice's next attack
	int oattInd3;     // line index of other voice's third attack
	int oattInd4;     // line index of other voice's fourth attack
	int oattInd5;     // line index of other voice's fifth attack
	double pitch;     // current pitch in ref voice
	double opitch;    // current pitch in other voice
	double opitch2;   // pitch of next note in other voice
	double opitch3;   // pitch of third note in other voice
	double opitch4;   // pitch of fourth note in other voice
	double opitch5;   // pitch of fifth note in other voice

	for (int i=1; i<(int)attacks.size()-1; i++) {
		lineindex  = attacks[i]->getLineIndex();
		// pass over if ref voice is not an agent
		if ((results[vindex][lineindex] != m_labels[AGENT_BIN]) &&
			(results[vindex][lineindex] != m_labels[AGENT_TERN])) {
			continue;
		}
		int2 = *attacks[i+1] - *attacks[i];
		sliceindex = attacks[i]->getSliceIndex();

		for (int j=0; j<(int)grid.getVoiceCount(); j++) { // j is the voice index of the other voice
			if (vindex == j) { // only compare different voices
				continue;
			}

			// skip if other voice isn't a patient
			if ((results[j][lineindex] != m_labels[SUS_BIN]) &&
				(results[j][lineindex] != m_labels[SUS_TERN])) {
				continue;
			}

			oattInd2 = -22;
			oattInd3 = -22;
			oattInd4 = -22;
			oint2    = -22;
			oint3    = -22;
			oint4    = -22;
			oint5    = -22;
			pitch    = attacks[i]->getAbsDiatonicPitch();
			opitch   = grid.cell(j, sliceindex)->getAbsDiatonicPitch();
			lineindex2 = attacks[i+1]->getLineIndex();
			attInd2  = attacks[i]->getNextAttackIndex();
			oattInd2 = grid.cell(j, sliceindex)->getNextAttackIndex();

			if (oattInd2 > 0) {
				opitch2 = grid.cell(j, oattInd2)->getAbsDiatonicPitch();
				oint2 = opitch2 - opitch;
				oattInd3 = grid.cell(j, oattInd2)->getNextAttackIndex();
			} else { // all cadence types need at least 3 attacks in other voice
				continue;
			}
			if (oattInd3 > 0) {
				opitch3 = grid.cell(j, oattInd3)->getAbsDiatonicPitch();
				oint3 = opitch3 - opitch2;
				oattInd4 = grid.cell(j, oattInd3)->getNextAttackIndex();
			} else { // all cadence types need at least 3 attacks in other voice
				continue;
			}
			int thisInt = int(opitch - pitch); // diatonic interval in this pair
			int thisMod7 = thisInt % 7; // simplify octaves out of thisInt

			// agent voice has 2 attacks, patient has 3 notes
			if (((thisMod7 == 6) || (thisMod7 == -1)) && (attInd2 == oattInd3) &&
				(oint2 == -1) && (oint3 == 1)) {
				if (int2 == -1) { // "^7xs 1 6sx -2 8xx$"
					voiceFuncs[j][lineindex2] = "C"; // cantizans
					voiceFuncs[vindex][lineindex2] = "T"; // tenorizans
				} else if (int2 == 1) { // "^7xs 1 6sx 2 6xx$"
					voiceFuncs[j][lineindex2] = "C"; // cantizans
					voiceFuncs[vindex][lineindex2] = "t"; // evaded tenorizans
				}
			} else if ((thisMod7 == 3) && ((int2 == -4) || (int2 == 3)) &&
				(attInd2 == oattInd3) && (oint2 == -1) && (oint3 == 1)) { // "^4xs 1 3sx -5 8xx$"
				voiceFuncs[j][lineindex2] = "C"; // cantizans
				voiceFuncs[vindex][lineindex2] = "B"; // bassizans
			} else if ((thisMod7 == 3) && (int2 == 1) && (attInd2 == oattInd3) &&
				(oint2 == -1) && (oint3 == 1)) { // "^4xs 1 3sx 2 3xx$"
				voiceFuncs[j][lineindex2] = "C"; // cantizans
				voiceFuncs[vindex][lineindex2] = "b"; // evaded bassizans
			} else if ((thisMod7 == 3) && (int2 == 7) && (attInd2 == oattInd3) &&
				(oint2 == -1) && (oint3 == 1)) { // "^11xs 1 10sx 8 4xx$"
				voiceFuncs[j][lineindex2] = "C"; // cantizans
				voiceFuncs[vindex][lineindex2] = "L"; // leaping contratenor
			} else if ((thisMod7 == 3) && (int2 == -1) && (attInd2 == oattInd3) &&
				(oint2 == -1) && (oint3 == 1)) { // "^4xs 1 3sx -2 5xx$"
				voiceFuncs[j][lineindex2] = "A"; // altizans
				voiceFuncs[vindex][lineindex2] = "T"; // tenorizans
			}

			// agent voice has 3 attacks, patient has 3 notes
			if ((i + 3) < int(attacks.size())) {
				int3 = *attacks[i+2] - *attacks[i+1];
				attInd3  = attacks[i+1]->getNextAttackIndex();
				lineindex3 = attacks[i+2]->getLineIndex();
				if (((thisMod7 == 6) || (thisMod7 == -1)) && (int2 == -1) &&
					(results[vindex][lineindex2] == m_labels[ANT_DOWN]) &&
					(attInd3 == oattInd3) && (oint2 == -1) && (oint3 == 1)) {
					voiceFuncs[j][lineindex3] = "C"; // cantizans
					voiceFuncs[vindex][lineindex3] = "T"; // tenorizans
				} else if ((thisMod7 == 3) && (int2 == -1) && (attInd3 == oattInd3) &&
					(results[vindex][lineindex2] == m_labels[ANT_DOWN]) &&
					(oint2 == -1) && (oint3 == 1)) { // "^4xs 1 3sx -2 5xx$"
					voiceFuncs[j][lineindex3] = "A"; // altizans
					voiceFuncs[vindex][lineindex3] = "T"; // tenorizans
				} else if ((thisMod7 == 3) && (int2 == 2) && (int3 == -1) &&
					(attInd3 == oattInd3) && (oint2 == -1) && (oint3 == 1)) {
					voiceFuncs[j][lineindex3] = "C"; // cantizans
					voiceFuncs[vindex][lineindex3] = "b"; // evaded bassizans
				}
			}

			// agent voice has 4 attacks, patient has 3 notes
			if ((i + 4) < int(attacks.size())) {
				int4 = *attacks[i+3] - *attacks[i+2];
				attInd4  = attacks[i+2]->getNextAttackIndex();
				lineindex4 = attacks[i+3]->getLineIndex();
				if ((int2 == -1) && (int3 == 1) && (int4 == 1) &&
					(attInd4 == oattInd3) && (oint2 == -1) && (oint3 == 1) &&
					(attInd2 > oattInd2)) {
					if (thisMod7 == 3) { // ex. Obr1001a m. 85
						voiceFuncs[j][lineindex4] = "C"; // cantizans
						voiceFuncs[vindex][lineindex4] = "b"; // ornamented evaded bassizans
					} else if ((thisMod7 == 6) || (thisMod7 == -1)) { // ex. Obr1001b m. 36
						voiceFuncs[j][lineindex4] = "C"; // cantizans
						voiceFuncs[vindex][lineindex4] = "t"; // ornamented evaded tenorizans
					}
				}
			}

			// agent voice has 2 attacks, patient has 4 notes
			if (oattInd4 > 0) {
				opitch4 = grid.cell(j, oattInd4)->getAbsDiatonicPitch();
				oint4 = opitch4 - opitch3;
				oattInd5 = grid.cell(j, oattInd4)->getNextAttackIndex();
			} else { // the following cadence types need 4 attacks in other voice
				continue;
			}
			if (((thisMod7 == 6) || (thisMod7 == -1)) && (attInd2 == oattInd4) &&
				(oint2 == -1) && (oint3 == -1) && (oint4 == 2)) {
				if (int2 == -1) {
					voiceFuncs[j][lineindex2] = "C"; // cantizans
					voiceFuncs[vindex][lineindex2] = "T"; // tenorizans
				} else if (int2 == 1) {
					voiceFuncs[j][lineindex2] = "C"; // cantizans
					voiceFuncs[vindex][lineindex2] = "t"; // evaded tenorizans
				}
			} else if ((thisMod7 == 3) && ((int2 == -4) || (int2 == 3)) &&
				(attInd2 == oattInd4) && (oint2 == -1) && (oint3 == -1) &&
				(oint4 == 2)) { // under-third cadence
				voiceFuncs[j][lineindex2] = "C"; // cantizans
				voiceFuncs[vindex][lineindex2] = "B"; // bassizans
			} else if ((thisMod7 == 3) && (int2 == 7) && (attInd2 == oattInd4) &&
				(oint2 == -1) && (oint3 == -1) && (oint4 == 2)) { // under-third cadence
				voiceFuncs[j][lineindex2] = "C"; // cantizans
				voiceFuncs[vindex][lineindex2] = "L"; // leaping contratenor
			} else if ((thisMod7 == 3) && (int2 == -1) && (attInd2 == oattInd4) &&
				(oint2 == -1) && (oint3 == -1) && (oint4 == 2)) { // under-third cadence
				voiceFuncs[j][lineindex2] = "A"; // altizans
				voiceFuncs[vindex][lineindex2] = "T"; // tenorizans
			}

			// agent voice has 2 attacks, patient has 5 notes
			if (oattInd5 > 0) {
				opitch5 = grid.cell(j, oattInd5)->getAbsDiatonicPitch();
				oint5 = opitch5 - opitch4;
			} else { // the following cadence types need 5 attacks in other voice
				continue;
			}
			if (((thisMod7 == 6) || (thisMod7 == -1)) && (attInd2 == oattInd5) &&
				(oint2 == -1) && (oint3 == 0) && (oint4 == -1) && (oint5 == 2)) {
				if (int2 == -1) {
					voiceFuncs[j][lineindex2] = "C"; // cantizans
					voiceFuncs[vindex][lineindex2] = "T"; // tenorizans
				} else if (int2 == 1) {
					voiceFuncs[j][lineindex2] = "C"; // cantizans
					voiceFuncs[vindex][lineindex2] = "t"; // evaded tenorizans
				}
			} else if (((thisMod7 == 6) || (thisMod7 == -1)) && (attInd2 == oattInd5) &&
				(oint2 == -1) && (oint3 == -1) && (oint4 == 1) && (oint5 == 1)) {
				if (int2 == -1) {
					voiceFuncs[j][lineindex2] = "C"; // cantizans
					voiceFuncs[vindex][lineindex2] = "T"; // tenorizans
				} else if (int2 == 1) {
					voiceFuncs[j][lineindex2] = "C"; // cantizans
					voiceFuncs[vindex][lineindex2] = "t"; // evaded tenorizans
				}
			} else if ((thisMod7 == 3) && (attInd2 == oattInd5) && (oint2 == -1) &&
				(((oint3 == 0) && (oint4 == -1) && (oint5 == 2)) || // under-third cadence
				 ((oint3 == -1) && (oint4 == 1) && (oint5 == 1)))) { // anticipated resolution phase
				if ((int2 == -4) || (int2 == 3)) {
					voiceFuncs[j][lineindex2] = "C"; // cantizans
					voiceFuncs[vindex][lineindex2] = "B"; // bassizans
				} else if (int2 == 1) {
					voiceFuncs[j][lineindex2] = "C"; // cantizans
					voiceFuncs[vindex][lineindex2] = "b"; // evaded bassizans
				}
			} else if ((thisMod7 == 3) && (int2 == 7) && (attInd2 == oattInd5) &&
				(oint2 == -1) && (oint3 == 0) && (oint4 == -1) && (oint5 == 2)) { // under-third cadence
				voiceFuncs[j][lineindex2] = "C"; // cantizans
				voiceFuncs[vindex][lineindex2] = "L"; // leaping contratenor
			} else if ((thisMod7 == 3) && (int2 == -1) &&
				(attInd2 == oattInd5) && (oint2 == -1) &&
				(((oint3 == 0) && (oint4 == -1) && (oint5 == 2)) || // under-third cadence
				 ((oint3 == -1) && (oint4 == 1) && (oint5 == 1)))) { // anticipated resolution phase
				voiceFuncs[j][lineindex2] = "A"; // altizans
				voiceFuncs[vindex][lineindex2] = "T"; // tenorizans
			}
		}
	}
}



///////////////////////////////
//
// printCountAnalysis --
//

void Tool_dissonant::printCountAnalysis(vector<vector<string>>& data) {

	map<string, bool> reduced;
	bool brief = getBoolean("u");
	bool percentQ = getBoolean("percent");

	vector<map<string, int>> analysis;
	analysis.resize(data.size());
	int i;
	int j;
	for (i=0; i<(int)data.size(); i++) {
		for (j=0; j<(int)data[i].size(); j++) {
			if (analysis[i].find(data[i][j]) != analysis[i].end()) {
				analysis[i][data[i][j]]++;
			} else {
				analysis[i][data[i][j]] = 1;
			}
		}
	}

	m_humdrum_text << "**rdis";
	if (brief) {
		m_humdrum_text << "u";
	}
	m_humdrum_text << "\t**sum";
	for (j=0; j<(int)analysis.size(); j++) {
		m_humdrum_text << "\t" << "**v" << j + 1;
	}
	m_humdrum_text << endl;

	int sumsum = 0;
	int sum;
	string item;
	for (i=0; i<(int)LABELS_SIZE; i++) {
		if (i == UNLABELED_Z2) {
			continue;
		}
		if (i == UNLABELED_Z7) {
			continue;
		}

		item = m_labels[i];

		if (brief && (reduced.find(item) != reduced.end())) {
			continue;
		}
		reduced[item] = 1;

		sum = 0;
		for (j=0; j<(int)analysis.size(); j++) {
			if (analysis[j].find(item) != analysis[j].end()) {
				sum += analysis[j][item];
				// Don't include agents in dissonant note summation.
				if ((item != m_labels[AGENT_TERN]) && (item != m_labels[AGENT_BIN])) {
					sumsum += analysis[j][item];
				}
			}
		}

		if (sum == 0) {
			continue;
		}

		m_humdrum_text << item;
		m_humdrum_text << "\t" << sum;

		for (int j=0; j<(int)analysis.size(); j++) {
			m_humdrum_text << "\t";
			if (analysis[j].find(item) != analysis[j].end()) {
				if (percentQ) {
					if ((item == m_labels[AGENT_BIN]) || (item == m_labels[AGENT_TERN])) {
						m_humdrum_text << ".";
					} else {
						m_humdrum_text << int(analysis[j][item] * 1.0 / sum * 1000.0 + 0.5) / 10.0;
					}
				} else {
					m_humdrum_text << analysis[j][item];
				}
			} else {
				m_humdrum_text << 0;
			}
		}
		m_humdrum_text << endl;
	}

	m_humdrum_text << "*-\t*-";
	for (j=0; j<(int)analysis.size(); j++) {
		m_humdrum_text << "\t" << "*-";
	}
	m_humdrum_text << endl;

	m_humdrum_text << "!!total_dissonances:\t" << sumsum << endl;

}



//////////////////////////////
//
// Tool_dissonant::getNextPitchAttackIndex -- Get the [line] index of the next
//     note attack, excluding any repeated pitch note attacks.
//

int Tool_dissonant::getNextPitchAttackIndex(NoteGrid& grid, int voicei, int sliceindex) {
	double pitch = NAN;
	int endslice = -1;
	if (sliceindex >= 0) {
		pitch = grid.cell(voicei, sliceindex)->getAbsMidiPitch();
		endslice = grid.cell(voicei, sliceindex)->getNextAttackIndex();
	}

	double pitch2 = NAN;
	if (endslice >= 0) {
		pitch2 = grid.cell(voicei, endslice)->getAbsMidiPitch();
	}

	if (Convert::isNaN(pitch)) {
		return endslice;
	}

	while (pitch == pitch2) {
		endslice = grid.cell(voicei, endslice)->getNextAttackIndex();
		pitch2 = NAN;
		if (endslice >= 0) {
			pitch2 = grid.cell(voicei, endslice)->getAbsMidiPitch();
		} else {
			break;
		}
	}

	return endslice;
}



//////////////////////////////
//
// Tool_dissonant::fillLabels -- Assign the labels for non-harmonic tone analysis.
//

void Tool_dissonant::fillLabels(void) {
	m_labels.resize(LABELS_SIZE);
	m_labels[PASSING_UP          ] = "P"; // rising passing tone
	m_labels[PASSING_DOWN        ] = "p"; // downward passing tone
	m_labels[NEIGHBOR_UP         ] = "N"; // upper neighbor
	m_labels[NEIGHBOR_DOWN       ] = "n"; // lower neighbor
	m_labels[ECHAPPEE_UP         ] = "E"; // upper échappée
	m_labels[ECHAPPEE_DOWN       ] = "e"; // lower échappée
	m_labels[CAMBIATA_UP_S       ] = "C"; // ascending short nota cambiata
	m_labels[CAMBIATA_DOWN_S     ] = "c"; // descending short nota cambiata
	m_labels[CAMBIATA_UP_L       ] = "K"; // ascending long nota cambiata
	m_labels[CAMBIATA_DOWN_L     ] = "k"; // descending long nota cambiata
	m_labels[REV_CAMBIATA_UP     ] = "I"; // incomplete anterior upper neighbor
	m_labels[REV_CAMBIATA_DOWN   ] = "i"; // incomplete anterior lower neighbor
	m_labels[REV_ECHAPPEE_UP     ] = "J"; // incomplete posterior upper neighbor
	m_labels[REV_ECHAPPEE_DOWN   ] = "j"; // incomplete posterior lower neighbor
	m_labels[ANT_UP              ] = "A"; // rising anticipation
	m_labels[ANT_DOWN            ] = "a"; // descending anticipation
	m_labels[DBL_NEIGHBOR_UP     ] = "D"; // double neighbor beginning with upper neighbor
	m_labels[DBL_NEIGHBOR_DOWN   ] = "d"; // double neighbor beginning with lower neighbor
	m_labels[THIRD_Q_PASS_UP     ] = "Q"; // dissonant third quarter ascending passing tone
	m_labels[THIRD_Q_PASS_DOWN   ] = "q"; // dissonant third quarter descending passing tone
	m_labels[THIRD_Q_UPPER_NEI   ] = "B"; // dissonant third quarter upper neighbor
	m_labels[THIRD_Q_LOWER_NEI   ] = "b"; // dissonant third quarter lower neighbor
	m_labels[ACC_PASSING_UP      ] = "V"; // ascending accented passing tone
	m_labels[ACC_PASSING_DOWN    ] = "v"; // descending accented passing tone
	m_labels[ACC_UP_NEI          ] = "W"; // accented upper neighbor
	m_labels[ACC_LO_NEI          ] = "w"; // accented lower neighbor
	m_labels[APP_UPPER           ] = "T"; // appoggiatura resolving down by step
	m_labels[APP_LOWER           ] = "t"; // appoggiatura resolving up by step
	m_labels[SUS_BIN             ] = "s"; // binary suspension
	m_labels[SUS_TERN            ] = "S"; // ternary suspension
	m_labels[AGENT_BIN           ] = "g"; // binary agent
	m_labels[AGENT_TERN          ] = "G"; // ternary agent
	m_labels[SUSPENSION_REP      ] = "r"; // suspension repeated note
	m_labels[FAKE_SUSPENSION_LEAP] = "F"; // fake suspension approached by leap
	m_labels[FAKE_SUSPENSION_STEP] = "f"; // fake suspension approached by step or by anticipation
	m_labels[SUS_NO_AGENT_LEAP   ] = "M"; // suspension missing a normal agent approached by leap
	m_labels[SUS_NO_AGENT_STEP   ] = "m"; // suspension missing a normal agent approached by step or by anticipation
	m_labels[CHANSON_IDIOM       ] = "h"; // chanson idiom
	m_labels[ORNAMENTAL_SUS      ] = "o"; // purely ornamental suspension
	m_labels[PARALLEL_UP         ] = "L"; // moves up in parallel with identifiable dissonance
	m_labels[PARALLEL_DOWN       ] = "l"; // moves down in parallel with identifiable dissonance
	m_labels[RES_PITCH           ] = "x"; // note of resolution of a suspension against suspension dissonance
	m_labels[ONLY_WITH_VALID_UP  ] = "Y"; // only dissonant against identifiable dissonances, approached from below
	m_labels[ONLY_WITH_VALID_DOWN] = "y"; // only dissonant against identifiable dissonances, approached from above
	m_labels[UNKNOWN_DISSONANCE  ] = "Z"; // unknown dissonance
	m_labels[UNLABELED_Z2        ] = "Z"; // unknown dissonance, 2nd interval
	m_labels[UNLABELED_Z7        ] = "Z"; // unknown dissonance, 7th interval
	m_labels[UNLABELED_Z4        ] = "z"; // unknown dissonance, 4th interval
}



//////////////////////////////
//
// Tool_dissonant::fillLabels2 -- Assign the labels for non-harmonic tone analysis.
//     This version without direction separation.
//

void Tool_dissonant::fillLabels2(void) {
	m_labels.resize(LABELS_SIZE);
	m_labels[PASSING_UP          ] = "P"; // rising passing tone
	m_labels[PASSING_DOWN        ] = "P"; // downward passing tone
	m_labels[NEIGHBOR_UP         ] = "N"; // upper neighbor
	m_labels[NEIGHBOR_DOWN       ] = "N"; // lower neighbor
	m_labels[ECHAPPEE_UP         ] = "E"; // upper échappée
	m_labels[ECHAPPEE_DOWN       ] = "E"; // lower échappée
	m_labels[CAMBIATA_UP_S       ] = "C"; // ascending short nota cambiata
	m_labels[CAMBIATA_DOWN_S     ] = "C"; // descending short nota cambiata
	m_labels[CAMBIATA_UP_L       ] = "K"; // ascending long nota cambiata
	m_labels[CAMBIATA_DOWN_L     ] = "K"; // descending long nota cambiata
	m_labels[REV_CAMBIATA_UP     ] = "I"; // incomplete anterior upper neighbor
	m_labels[REV_CAMBIATA_DOWN   ] = "I"; // incomplete anterior lower neighbor
	m_labels[REV_ECHAPPEE_UP     ] = "J"; // incomplete posterior upper neighbor
	m_labels[REV_ECHAPPEE_DOWN   ] = "J"; // incomplete posterior lower neighbor
	m_labels[ANT_UP              ] = "A"; // rising anticipation
	m_labels[ANT_DOWN            ] = "A"; // descending anticipation
	m_labels[DBL_NEIGHBOR_UP     ] = "D"; // double neighbor beginning with upper neighbor
	m_labels[DBL_NEIGHBOR_DOWN   ] = "D"; // double neighbor beginning with lower neighbor
	m_labels[THIRD_Q_PASS_UP     ] = "Q"; // dissonant third quarter ascending passing tone
	m_labels[THIRD_Q_PASS_DOWN   ] = "Q"; // dissonant third quarter descending passing tone
	m_labels[THIRD_Q_UPPER_NEI   ] = "B"; // dissonant third quarter upper neighbor
	m_labels[THIRD_Q_LOWER_NEI   ] = "B"; // dissonant third quarter lower neighbor
	m_labels[ACC_PASSING_UP      ] = "V"; // ascending accented passing tone
	m_labels[ACC_PASSING_DOWN    ] = "V"; // descending accented passing tone
	m_labels[ACC_UP_NEI          ] = "W"; // accented upper neighbor
	m_labels[ACC_LO_NEI          ] = "W"; // accented lower neighbor
	m_labels[APP_UPPER           ] = "T"; // appoggiatura resolving down by step
	m_labels[APP_LOWER           ] = "T"; // appoggiatura resolving up by step
	m_labels[SUS_BIN             ] = "S"; // binary suspension
	m_labels[SUS_TERN            ] = "S"; // ternary suspension
	m_labels[AGENT_BIN           ] = "G"; // binary agent
	m_labels[AGENT_TERN          ] = "G"; // ternary agent
	m_labels[SUSPENSION_REP      ] = "R"; // suspension repeated note
	m_labels[FAKE_SUSPENSION_LEAP] = "F"; // fake suspension approached by leap
	m_labels[FAKE_SUSPENSION_STEP] = "F"; // fake suspension approached by step or anticipation
	m_labels[SUS_NO_AGENT_LEAP   ] = "M"; // suspension missing a normal agent approached by leap
	m_labels[SUS_NO_AGENT_STEP   ] = "M"; // suspension missing a normal agent approached by step or anticipation
	m_labels[CHANSON_IDIOM       ] = "H"; // chanson idiom
	m_labels[ORNAMENTAL_SUS      ] = "O"; // purely ornamental suspension
	m_labels[PARALLEL_UP         ] = "L"; // moves up in parallel with identifiable dissonance
	m_labels[PARALLEL_DOWN       ] = "L"; // moves down in parallel with identifiable dissonance
	m_labels[RES_PITCH           ] = "X"; // note of resolution of a suspension against suspension dissonance
	m_labels[ONLY_WITH_VALID_UP  ] = "Y"; // only dissonant against identifiable dissonances, approached from below
	m_labels[ONLY_WITH_VALID_DOWN] = "Y"; // only dissonant against identifiable dissonances, approached from above
	m_labels[UNKNOWN_DISSONANCE  ] = "Z"; // unknown dissonance
	m_labels[UNLABELED_Z2        ] = "Z"; // unknown dissonance, 2nd interval
	m_labels[UNLABELED_Z7        ] = "Z"; // unknown dissonance, 7th interval
	m_labels[UNLABELED_Z4        ] = "Z"; // unknown dissonance, 4th interval
}




/////////////////////////////////
//
// Tool_double::Tool_double -- Set the recognized options for the tool.
//

Tool_double::Tool_double(void) {
}



/////////////////////////////////
//
// Tool_double::run -- Primary interfaces to the tool.
//

bool Tool_double::run(HumdrumFileSet& infiles) {
	bool status = true;
	for (int i=0; i<infiles.getCount(); i++) {
		status &= run(infiles[i]);
	}
	return status;
}


bool Tool_double::run(const string& indata, ostream& out) {
	HumdrumFile infile(indata);
	bool status = run(infile);
	if (hasAnyText()) {
		getAllText(out);
	} else {
		out << infile;
	}
	return status;
}


bool Tool_double::run(HumdrumFile& infile, ostream& out) {
	bool status = run(infile);
	if (hasAnyText()) {
		getAllText(out);
	} else {
		out << infile;
	}
	return status;
}

//
// In-place processing of file:
//

bool Tool_double::run(HumdrumFile& infile) {
	processFile(infile);

	// Re-load the text for each line from their tokens.
	infile.createLinesFromTokens();

	// Need to adjust the line numbers for tokens for later
	// processing.
	m_humdrum_text << infile;
	return true;
}



//////////////////////////////
//
// Tool_double::processFile --
//

void Tool_double::processFile(HumdrumFile& infile) {
	terminalBreveToTerminalLong(infile);
	doubleRhythms(infile);
	adjustBeams(infile);
}



//////////////////////////////
//
// Tool_double::terminalBreveToTerminalLong --
//

void Tool_double::terminalBreveToTerminalLong(HumdrumFile& infile) {
	HumRegex hre;
	for (int i=0; i<infile.getLineCount(); i++) {
		if (!infile[i].isReference()) {
			continue;
		}
		HTp token = infile.token(i, 0);
		if (token->find("terminal breve") == string::npos) {
			continue;
		}
		string text = *token;
		hre.replaceDestructive(text, "terminal long", "terminal breve", "g");
		token->setText(text);
	}
}


//////////////////////////////
//
// Tool_double::adjustBeams -- Assuming non-lazy beams.
//

void Tool_double::adjustBeams(HumdrumFile& infile) {
	for (int i=0; i<infile.getStrandCount(); i++) {
		HTp sstart = infile.getStrandStart(i);
		if (!sstart->isKern()) {
			continue;
		}
		HTp send   = infile.getStrandEnd(i);
		adjustBeams(sstart, send);
	}
}


void Tool_double::adjustBeams(HTp sstart, HTp send) {
	// Remove one level of beaming from notes.  This method
	// requires non-lazy beaming.
	HTp current = sstart;
	vector<HTp> notes;
	current = current->getNextToken();
	while (current) {
		if (current->isBarline()) {
			processBeamsForMeasure(notes);
			notes.clear();
			current = current->getNextToken();
			continue;
		}
		if (!current->isData()) {
			current = current->getNextToken();
			continue;
		}
		if (current->isNull()) {
			current = current->getNextToken();
			continue;
		}
		notes.push_back(current);
		current = current->getNextToken();
	}
}



//////////////////////////////
//
// Tool_double::processBeamsForMeasure --
//

void Tool_double::processBeamsForMeasure(vector<HTp>& notes) {
	int lastlevel = 0;
	int level = 0;
	HumRegex hre;
	for (int i=0; i<(int)notes.size(); i++) {
		int Lcount = 0;
		int Jcount = 0;
		for (int j=0; j<(int)notes[i]->size(); j++) {
			if (notes[i]->at(j) == 'L') {
				Lcount++;
			} else if (notes[i]->at(j) == 'J') {
				Jcount++;
			}
		}
		level += Lcount - Jcount;
		if ((lastlevel == 0) && (level > 0)) {
			// remove one L:
			string text = *notes[i];
			hre.replaceDestructive(text, "", "L");
			notes[i]->setText(text);
		} else if ((level == 0) && (lastlevel > 0)) {
			// remove one J:
			string text = *notes[i];
			hre.replaceDestructive(text, "", "J");
			notes[i]->setText(text);
		}

		if (notes[i]->find("k") != string::npos) {
			if ((level == 0) && (lastlevel == 1)) {
				// remove k:
				string text = *notes[i];
				hre.replaceDestructive(text, "", "k");
				notes[i]->setText(text);
			}
		}

		if (notes[i]->find("K") != string::npos) {
			if ((level == 1) && (lastlevel == 0)) {
				// remove K:
				string text = *notes[i];
				hre.replaceDestructive(text, "", "K");
				notes[i]->setText(text);
			}
		}

		lastlevel = level;
	}
}



//////////////////////////////
//
// Tool_double::doubleRhythms --
//

void Tool_double::doubleRhythms(HumdrumFile& infile) {
	HumRegex hre;
	for (int i=0; i<infile.getLineCount(); i++) {
		if (infile[i].isData()) {
			for (int j=0; j<infile[i].getFieldCount(); j++) {
				HTp token = infile.token(i, j);
				if (!token->isKern()) {
					continue;
				}
				if (token->isNull()) {
					continue;
				}

				// extract duration without dot
				string text = token->getText();
				HumNum durnodot = Convert::recipToDurationNoDots(text);
				durnodot *= 2;
				string newrhythm = Convert::durationToRecip(durnodot);
				hre.replaceDestructive(text, newrhythm, "\\d+%?\\d*");
				token->setText(text);
			}
		} else if (infile[i].isInterpretation()) {
			// Double time signature bottom numbers:
			for (int j=0; j<infile[i].getFieldCount(); j++) {
				HTp token = infile.token(i, j);
				if (hre.search(token, "^\\*M(\\d+)/(\\d+)%(\\d+)")) {
					int bot1 = hre.getMatchInt(2);
					int bot2 = hre.getMatchInt(3);
					string text = *token;
					string replacement = "/" + to_string(bot1);
					replacement += "%" + to_string(bot2*2);
					hre.replaceDestructive(text, replacement, "/\\d+");
					token->setText(text);

				} else if (hre.search(token, "^\\*M(\\d+)/(\\d+)")) {
					int bot = hre.getMatchInt(2);
					int bot2 = -100;
					if (bot == 4) {
						bot = 2;
					} else if (bot == 2) {
						bot = 1;
					} else if (bot == 1) {
						bot = 0;
					} else if (bot == 3) {
						bot = 3;
						bot2 = 2;
					} else {
						cerr << "Warning: ignored time signature: " << token << endl;
					}
					string text = *token;
					string replacement = "/" + to_string(bot);
					if (bot2 >= 0) {
						replacement += "%" + to_string(bot2);
					}
					hre.replaceDestructive(text, replacement, "/\\d+");
					token->setText(text);
				}
			}
		}
	}
}




/////////////////////////////////
//
// Tool_esac2hum::Tool_esac2hum -- Set the recognized options for the tool.
//

Tool_esac2hum::Tool_esac2hum(void) {
	define("debug=b", "Print debugging statements");
	define("v|verbose=s", "Print verbose messages");
	define("e|embed-esac=b", "Eembed EsAC data in output");
	define("a|analyses|analysis=b", "Generate EsAC analysis fields");
}



//////////////////////////////
//
// Tool_esac2hum::convert -- Convert an EsAC data into Humdrum data.
//

bool Tool_esac2hum::convertFile(ostream& out, const string& filename) {
	initialize();
	ifstream file(filename);
	if (file) {
		return convert(out, file);
	}
	return false;
}


bool Tool_esac2hum::convert(ostream& out, istream& input) {
	convertEsacToHumdrum(out, input);
	return true;
}


bool Tool_esac2hum::convert(ostream& out, const string& input) {
	stringstream ss;
	ss << input;
	convertEsacToHumdrum(out, ss);
	return true;
}


//////////////////////////////
//
// Tool_esac2hum::initialize --
//

void Tool_esac2hum::initialize(void) {
	m_debugQ     = getBoolean("debug");      // print debugging information
	m_verboseQ   = getBoolean("verbose");    // print input EsAC MEL[] data when true
	m_verbose    = getString("verbose");     // p = phrase, m=measure, n=note
	m_embedEsacQ = getBoolean("embed-esac"); // don't print input EsAC data
	m_analysisQ  = getBoolean("analyses");   // embed analysis in EsAC data
	if (m_analysisQ) {
		m_embedEsacQ = true;
	}
}



//////////////////////////////
//
// Tool_esac2hum::convertEsacToHumdrum --
//

void Tool_esac2hum::convertEsacToHumdrum(ostream& output, istream& infile) {
	m_inputline = 0;
	m_prevline = "";

	vector<string> song;  // contents of one EsAC song, extracted from input stream
	song.reserve(1000);

	while (!infile.eof()) {
		if (m_debugQ) {
			cerr << "Getting a song..." << endl;
		}
		bool status = getSong(song, infile);
		if (!status) {
			cerr << "Error getting a song" << endl;
			continue;
		}
		if (m_debugQ) {
			cerr << "Got a song ..." << endl;
		}
		if (song.empty()) {
			cerr << "Song is empty" << endl;
			continue;
		}
		if (song.size() < 4) {
			cerr << "Song is too short" << endl;
			continue;
		}
		convertSong(output, song);
	}
}



//////////////////////////////
//
// Tool_esac2hum::getSong -- get a song from a multiple-song EsAC file.
//     Search for a CUT[] line which indicates the first line of the data.
//     There will/can be some text above the CUT[] line.  The CUT[] field
//     may contain newlnes, so searching only for CUT[ to also handle these
//     cases.
//

bool Tool_esac2hum::getSong(vector<string>& song, istream& infile) {
	song.resize(0);
	m_globalComments.clear();

	HumRegex hre;
	string buffer;

	// First find the next CUT[] line in the input which indcates
	// the start of a song.  There typically is a non-empty line just above CUT[]
	// containing information about the collection.
	if (m_cutline.empty()) {
		while (!infile.eof()) {
			getline(infile, buffer);

			if (hre.search(buffer, "^[!#]{2,}")) {
				hre.search(buffer, "^([!#]{2,})(.*)$");
				string prefix = hre.getMatch(1);
				string postfix = hre.getMatch(2);
				hre.replaceDestructive(prefix, "!", "#", "g");
				string comment = prefix + postfix;
				m_globalComments.push_back(comment);
				continue;
			}

			cleanText(buffer);
			m_inputline++;
			if (buffer.compare(0, 4, "CUT[") == 0) {
				m_cutline = buffer;
				break;
			} else {
				m_prevline = buffer;
				continue;
			}
		}
	}

	if (m_cutline.empty()) {
		return false;
	}

	if (infile.eof()) {
		return false;
	}

	if (!hre.search(m_prevline, "^\\s*$")) {
		song.push_back(m_prevline);
	}
	song.push_back(m_cutline);

	m_prevline.clear();
	m_cutline.clear();

	bool expectingCloseQ = false;

	// Now collect lines for the song until another CUT[] is found
	// (do not store previous line above CUT[] which is the source edition label.)
	while (!infile.eof()) {
		getline(infile, buffer);

		if (hre.search(buffer, "^#{2,}")) {
			hre.search(buffer, "^(#{2,})(.*)$");
			string prefix = hre.getMatch(1);
			string postfix = hre.getMatch(2);
			hre.replaceDestructive(prefix, "!", "#", "g");
			string comment = prefix + postfix;
			m_globalComments.push_back(comment);
			continue;
		}

		cleanText(buffer);
		m_inputline++;
		if (m_debugQ) {
			cerr << "READ LINE: " << buffer << endl;
		}
		if (expectingCloseQ) {
			if (buffer.find("[") != string::npos) {
				cerr << "Strange error on line " << m_inputline << ": " << buffer << endl;
				continue;
			} else if (!hre.search(buffer, "[\\[\\]]")) {
				// intermediate parameter line (not starting or ending)
				song.push_back(buffer);
				continue;
			}

			if (hre.search(buffer, "^[^\\]]*\\]\\s*$")) {
				// closing bracket
				expectingCloseQ = 0;
				song.push_back(buffer);
				continue;
			} else {
				cerr << "STRANGE CASE HERE " << buffer << endl;
			}
			continue;
		}

		if (hre.search(buffer, "^\\s*$")) {
			continue;
		}

		if (hre.search(buffer, "^[A-Za-z][^\\[\\]]*$")) {
			// collection line
			m_prevline = buffer;
			return true;
		}

		if (hre.search(buffer, "^[A-Za-z]+\\s*\\[[^\\]]*\\s*$")) {
			// parameter with opening [
			expectingCloseQ = true;
		}

		song.push_back(buffer);
	}

	if (expectingCloseQ) {
		cerr << "Strange case: expecting closing of a song parameter around line " << m_inputline++ << endl;
	}

	return true;
}



//////////////////////////////
//
// Tool_esac2hum::cleanText -- remove \x88 and \x98 bytes from string (should not affect UTF-8 encodings)
//     since those bytes do not seem to be involved with any UTF-8 characters.
//

void Tool_esac2hum::cleanText(std::string& buffer) {
	HumRegex hre;

	// Fix UTF-8 double encodings (related to editing with Windows-1252 or ISO-8859-2 programs):

	// Ą: c3 84 c2 84 - c4 84
	hre.replaceDestructive(buffer, "\xc4\x84", "\xc3\x84\xc2\x84", "g");

	// ą: c3 84 c2 85 - c4 85
	hre.replaceDestructive(buffer, "\xc4\x85", "\xc3\x84\xc2\x85", "g");

	// Ć: c3 84 c2 86 -> c4 86
	hre.replaceDestructive(buffer, "\xc4\x86", "\xc3\x84\xc2\x86", "g");

	// ć: c3 84 c2 87 -> c4 87
	hre.replaceDestructive(buffer, "\xc4\x87", "\xc3\x84\xc2\x87", "g");

	// Ę: c3 84 c2 98 -> c4 98
	hre.replaceDestructive(buffer, "\xc4\x98", "\xc3\x84\xc2\x98", "g");

	// ę: c3 84 c2 99 -> c4 99
	hre.replaceDestructive(buffer, "\xc4\x99", "\xc3\x84\xc2\x99", "g");

	// Ł: c4 b9 c2 81 -> c5 81
	hre.replaceDestructive(buffer, "\xc5\x81", "\xc4\xb9\xc2\x81", "g");

	// ł: c4 b9 c2 82 -> c5 82
	hre.replaceDestructive(buffer, "\xc5\x82", "\xc4\xb9\xc2\x82", "g");

	// Ń: c4 b9 c2 83 -> c5 83
	hre.replaceDestructive(buffer, "\xc5\x83", "\xc4\xb9\xc2\x83", "g");

	// ń: c4 b9 c2 84 -> c5 84
	hre.replaceDestructive(buffer, "\xc5\x84", "\xc4\xb9\xc2\x84", "g");

	// Ó: c4 82 c5 93 -> c3 93 (note: not sequential with ó)
	hre.replaceDestructive(buffer, "\xc3\x93", "\xc4\x82\xc5\x93", "g");

	// ó: c4 82 c5 82 -> c3 b3 (note: not sequential with Ó)
	hre.replaceDestructive(buffer, "\xc3\xb3", "\xc4\x82\xc5\x82", "g");

	// Ś: c4 b9 c2 9a -> c5 9a
	hre.replaceDestructive(buffer, "\xc5\x9a", "\xc4\xb9\xc2\x9b", "g");

	// ś: c4 b9 c2 9b -> c5 9b
	hre.replaceDestructive(buffer, "\xc5\x9b", "\xc4\xb9\xc2\x9b", "g");

	// Ź: c4 b9 c5 9a -> c5 b9
	hre.replaceDestructive(buffer, "\xc5\xb9", "\xc4\xb9\xc5\x9a", "g");

	// ź: c4 b9 c5 9f -> c5 ba
	hre.replaceDestructive(buffer, "\xc5\xba", "\xc4\xb9\xc5\x9f", "g");

	// Ż: c4 b9 c5 a5 -> c5 bb
	hre.replaceDestructive(buffer, "\xc5\xbb", "\xc4\xb9\xc5\xa5", "g");

	// ż:  c4 b9 c5 ba -> c5 bc
	hre.replaceDestructive(buffer, "\xc5\xbc", "\xc4\xb9\xc5\xba", "g");


	// Random leftover characters from some character conversion:
	hre.replaceDestructive(buffer, "", "[\x88\x98]", "g");

	// Remove MS-DOS newline character at ends of lines:
	if (!buffer.empty()) {
		if (buffer.back() == 0x0d) {
			// windows newline piece
			buffer.resize(buffer.size() - 1);
		}
	}
	// In VHV, when saving content to the local computer in EsAC mode, the 0x0d character should be added back.
}



//////////////////////////////
//
// Tool_esac2hum::trimSpaces -- remove any trailing or leading spaces.
//

string Tool_esac2hum::trimSpaces(const string& input) {
	string output = input;
	HumRegex hre;
	hre.replaceDestructive(output, "", "^\\s+");
	hre.replaceDestructive(output, "", "\\s+$");
	return output;
}



//////////////////////////////
//
// Tool_esac2hum::convertSong --
//

void Tool_esac2hum::convertSong(ostream& output, vector<string>& infile) {
	getParameters(infile);
	processSong();
	// printParameters();
	printHeader(output);
	printScoreContents(output);
	printFooter(output, infile);
}



//////////////////////////////
//
// Tool_esac2hum::processSong -- parse and preliminary conversion to Humdrum.
//

void Tool_esac2hum::processSong(void) {
	string mel = m_score.m_params["MEL"];
	m_score.parseMel(mel);
}



//////////////////////////////
//
// Tool_esac2hum::printScoreContents --
//

void Tool_esac2hum::printScoreContents(ostream& output) {

	vector<string>& errors = m_score.m_errors;
	if (!errors.empty()) {
		for (int z=0; z<(int)errors.size(); z++) {
			output << "!!" << errors.at(z) << endl;
		}
	}

	if (!m_score.m_clef.empty()) {
		output << m_score.m_clef << endl;
	}
	if (!m_score.m_keysignature.empty()) {
		output << m_score.m_keysignature << endl;
	}
	if (!m_score.m_keydesignation.empty()) {
		output << m_score.m_keydesignation << endl;
	}
	if (!m_score.m_timesig.empty()) {
		output << m_score.m_timesig << endl;
	}

	for (int i=0; i<(int)m_score.size(); i++) {
		Tool_esac2hum::Phrase& phrase = m_score.at(i);
		if (m_verbose.find("p") != string::npos) {
			output << "!!esac-phrase: " << phrase.esac;
			if (m_verbose.find("pi") != string::npos) {
				output << " [";
				output << "ticks:" << phrase.m_ticks;
				output << "]";
			}
			vector<string>& errors = phrase.m_errors;
			if (!errors.empty()) {
				for (int z=0; z<(int)errors.size(); z++) {
					output << "!!" << errors.at(z) << endl;
				}
			}
			output << endl;
		}

		for (int j=0; j<(int)phrase.size(); j++) {

			Tool_esac2hum::Measure& measure = phrase.at(j);
			if ((j == 0) && (i > 0)) {
				output << "!!LO:LB:g=esac" << endl;
			}
			if (measure.m_barnum != 0) { // don't print barline if first is pickup
				output << "=";
				if (measure.m_barnum > 0) {
					output << measure.m_barnum;
				} else if (measure.m_barnum == -1) {
					output << "-"; // "non-controlling" barline.
				} else {
					// visible barline, but not assigned a measure
					// number (probably need more analysis to assign
					// a measure number to this barline).
				}
				output  << endl;
			}
			if (m_verbose.find("m") != string::npos) {
				output << "!!esac-measure: " << measure.esac;
				if (m_verbose.find("mi") != string::npos) {
					output << " [";
					output << "ticks:" << measure.m_ticks;
					if (measure.isComplete())  {
						output << "; CM";
					}
					if (measure.isPartialBegin())  {
						output << "; PB";
					}
					if (measure.isPartialEnd())  {
						output << "; PE";
					}
					if (measure.isUnassigned())  {
						output << "; UN";
					}
					output << "]";
				}
				output << endl;
			}
			vector<string>& errors = measure.m_errors;
			if (!errors.empty()) {
				for (int z=0; z<(int)errors.size(); z++) {
					output << "!!" << errors.at(z) << endl;
				}
			}

			// print time signature change
			if (!measure.m_measureTimeSignature.empty()) {
				output << measure.m_measureTimeSignature << endl;
			}

			for (int k=0; k<(int)measure.size(); k++) {

				Tool_esac2hum::Note& note = measure.at(k);
				if (m_verbose.find("n") != string::npos) {
					output << "!!esac-note: " << note.esac;
					if (m_verbose.find("ni") != string::npos) {
						output << " [";
						output << "ticks:" << note.m_ticks;
						output << ", deg:" << note.m_degree;
						output << ", alt:" << note.m_alter;
						output << ", oct:" << note.m_octave;
						output << "]";
					}
					vector<string>& errors = note.m_errors;
					if (!errors.empty()) {
						for (int z=0; z<(int)errors.size(); z++) {
							output << "!!" << errors.at(z) << endl;
						}
					}
					output << endl;
				}
				output << note.m_humdrum << endl;

			}
		}
	}

	if (m_score.hasFinalBarline()) {
		output << "==" << endl;
	} else {
		output << "=" << endl;
	}
}



//////////////////////////////
//
// Tool_esac2hum::Score::parseMel --
//

bool Tool_esac2hum::Score::parseMel(const string& mel) {
	clear();
	reserve(100);

	HumRegex hre;
	if (hre.search(mel, "^\\s*$")) {
		// no data;
		cerr << "ERROR: MEL parameter is empty or non-existent" << endl;
		return false;
	}

	vector<string> lines;
	string line;

	stringstream linestream;
	linestream << mel;

	int lineNumber = 0;
	while (std::getline(linestream, line)) {
		lineNumber++;
		if (hre.search(line, "^\\s*$")) {
			// Skip blank lines
			continue;
		}
		string unknown = line;
		hre.replaceDestructive(unknown, "", "[\\^0-9b\\s/._#()+-]+", "g");
		if (!unknown.empty()) {
			cerr << "Unknown characters " << ">>" << unknown << "<< " << " on mel line " << lineNumber << ": " << line << endl;
		}
		line = Tool_esac2hum::trimSpaces(line);
		lines.push_back(line);
	}

	m_finalBarline = false;
	for (int i=0; i<(int)lines.size(); i++) {
		string line = lines[i];
		if (i == (int)lines.size() - 1) {
			if (hre.search(line, "^(.*)\\s*//\\s*$")) {
				m_finalBarline = true;
				lines.back() = hre.getMatch(1);
			}
		}
	}
	// remove the last line if it is only "//":
	if (!lines.empty()) {
		if (hre.search(lines.back(), "^\\s*$")) {
			lines.resize(lines.size() - 1);
		}
	}
	if (lines.empty()) {
		cerr << "ERROR: No notes in MEL data" << endl;
		return false;
	}

	for (int i=0; i<(int)lines.size(); i++) {
		resize(size() + 1);
		back().parsePhrase(lines[i]);
	}

	analyzeTies();
	analyzePhrases();
	generateHumdrumNotes();
	calculateClef();
	calculateKeyInformation();
	calculateTimeSignatures();

	return true;
}



//////////////////////////////
//
// Tool_esac2hum::Score::assignFreeMeasureNumbers -- The time signature
//    is "FREI", so assign a measure number to eavery barline, not checking
//    for pickup or partial measures.
//

void Tool_esac2hum::Score::assignFreeMeasureNumbers(void) {
	vector<Tool_esac2hum::Measure*> measurelist;
	getMeasureList(measurelist);

	int barnum = 1;
	for (int i=0; i<(int)measurelist.size(); i++) {
		measurelist[i]->m_barnum = barnum++;
		measurelist[i]->m_partialBegin = false;
		measurelist[i]->m_partialEnd = false;
		measurelist[i]->m_complete = true;
	}
}



//////////////////////////////
//
// Tool_esac2hum::Score::assignSingleMeasureNumbers -- There is a
//    single time signature for the entire melody, so identify full
//    and unfull measures, marking full that match the time signature
//    duration as complete, and then try to pair measures and look
//    for a pickup measure at the start of the music.
//    The Measure::tsticks is the expected duration of the measure
//    according to the time signature.
//

void Tool_esac2hum::Score::assignSingleMeasureNumbers(void) {
	vector<Tool_esac2hum::Measure*> measurelist;
	getMeasureList(measurelist);

	if (measurelist.empty()) {
		// strange error: no measures;
		return;
	}

	// first identify complete measures:
	for (int i=0; i<(int)measurelist.size(); i++) {
		if (measurelist[i]->m_tsticks == measurelist[i]->m_ticks) {
			measurelist[i]->setComplete();
		}
	}

	// check for pickup measure at beginning of music
	if (measurelist[0]->m_ticks < measurelist[0]->m_tsticks) {
		measurelist[0]->setPartialEnd();
		// check for partial measure at end that matches end measure
		if (measurelist.back()->m_ticks < measurelist.back()->m_tsticks) {
			measurelist.back()->setPartialBegin();
		}
	}

	// search for pairs of partial measures
	for (int i=1; i<(int)measurelist.size(); i++) {
		if (!measurelist[i]->isUnassigned()) {
			continue;
		}
		if (!measurelist[i-1]->isUnassigned()) {
			continue;
		}
		double ticks1 = measurelist[i-1]->m_ticks;
		double ticks2 = measurelist[i]->m_ticks;
		double tsticks1 = measurelist[i-1]->m_tsticks;
		double tsticks2 = measurelist[i]->m_tsticks;
		if (tsticks1 != tsticks2) {
			// strange error;
			continue;
		}
		if (ticks1 + ticks2 == tsticks2) {
			measurelist[i-1]->setPartialBegin();
			measurelist[i]->setPartialEnd();
		}
	}

	// Now assign barlines to measures. that are complete or
	// partial starts.
	int barnum = 1;
	for (int i=0; i<(int)measurelist.size(); i++) {
		if (measurelist[i]->isComplete()) {
			measurelist[i]->m_barnum = barnum++;
		} else if (measurelist[i]->isPartialBegin()) {
			measurelist[i]->m_barnum = barnum++;
		} else if (measurelist[i]->isPartialEnd()) {
			measurelist[i]->m_barnum = -1;
		}
	}
	if (measurelist[0]->isPartialEnd()) {
		measurelist[0]->m_barnum = 0; // pickup: don't add barline on first measure
	}
}



//////////////////////////////
//
// Tool_esac2hum::Measure::isUnassigned --
//

bool Tool_esac2hum::Measure::isUnassigned(void) {
	return !(m_complete || m_partialBegin || m_partialEnd);
}


//////////////////////////////
//
// Tool_esac2hum::Measure::setComplete --
//

void Tool_esac2hum::Measure::setComplete(void) {
	m_complete     = true;
	m_partialBegin = false;
	m_partialEnd   = false;
}



//////////////////////////////
//
// Tool_esac2hum::Measure::isComplete --
//

bool Tool_esac2hum::Measure::isComplete(void) {
	return m_complete;
}



//////////////////////////////
//
// Tool_esac2hum::Measure::setPartialBegin --
//

void Tool_esac2hum::Measure::setPartialBegin(void) {
	m_complete     = false;
	m_partialBegin = true;
	m_partialEnd   = false;
}



//////////////////////////////
//
// Tool_esac2hum::Measure::isPartialBegin --
//

bool Tool_esac2hum::Measure::isPartialBegin(void) {
	return m_partialBegin;
}




//////////////////////////////
//
// Tool_esac2hum::Measure::setPartialEnd --
//

void Tool_esac2hum::Measure::setPartialEnd(void) {
	m_complete     = false;
	m_partialBegin = false;
	m_partialEnd   = true;
}



//////////////////////////////
//
// Tool_esac2hum::Measure::isPartialEnd --
//

bool Tool_esac2hum::Measure::isPartialEnd(void) {
	return m_partialEnd;
}



//////////////////////////////
//
// Tool_esac2hum::Score::calculateTimeSignatures --
//

void Tool_esac2hum::Score::calculateTimeSignatures(void) {
	string ts = m_params["_time"];
	ts = trimSpaces(ts);
	if (ts.find("FREI") != string::npos) {
		m_timesig = "*MX";
		setAllTimesigTicks(0.0);
		assignFreeMeasureNumbers();
		return;
	}


	HumRegex hre;
	if (hre.search(ts, "^(\\d+)/(\\d+)$")) {
		m_timesig = "*M" + ts;
		int top = hre.getMatchInt(1);
		int bot = hre.getMatchInt(2);
		// check if bot is a power of two?
		double tsticks = top * m_minrhy / bot;
		setAllTimesigTicks(tsticks);
		assignSingleMeasureNumbers();
		return;
	} else if (hre.search(ts, "^(\\d+/\\d+(?:\\s+|$)){2,}$")) {
		prepareMultipleTimeSignatures(ts);
	}

	// Complicated case where the time signature changes
	vector<string> timesigs;
	hre.split(timesigs, ts, "\\s+");
	if (timesigs.size() < 2) {
		string error = "ERROR: Cannot find time signature(s) in KEY[] field: ";
		error += m_params["KEY"];
		m_errors.push_back(error);
		return;
	}

/* ggg
	vector<double> bticks(timesigs.size(), 0);
	for (int i=0; i<(int)bticks
*/


}


//////////////////////////////
//
// Tool_esac2hum::Score::prepareMultipleTimeSignatures --
//    N.B.: Will have problems when the duration of time siganture
//    in a list are the same such as "4/4 2/2".
//

void Tool_esac2hum::Score::prepareMultipleTimeSignatures(const string& ts) {
	vector<string> tss;
	HumRegex hre;
	string timesigs = ts;
	hre.split(tss, timesigs, "\\s+");
	if (tss.size() < 2) {
		cerr << "Time sigs: " << ts << " needs to have at least two time signatures" << endl;
	}

	// Calculate tick duration of time signature in list:
	vector<double> tsticks(tss.size(), 0);
	for (int i=0; i<(int)tss.size(); i++) {
		if (!hre.search(tss[i], "^(\\d+)/(\\d+)$")) {
			continue;
		}
		int top = hre.getMatchInt(1);
		int bot = hre.getMatchInt(2);
		double ticks = top * m_minrhy / bot;
		tsticks[i] = ticks;
	}

	//cerr << "\nMultiple time signatures in melody: " << endl;
	//for (int i=0; i<(int)tss.size(); i++) {
	//	cerr << "(" << i+1 << "): " << tss[i] << "\tticks:" << tsticks[i] << endl;
	//}
	//cerr << endl;

	// First assign a time signature to every inner measure in a phrase, which
	// is presumed to be a complete measure:
	for (int i=0; i<(int)size(); i++) {
		Tool_esac2hum::Phrase& phrase = at(i);
		for (int j=1; j<(int)phrase.size()-1; j++) {
			Tool_esac2hum::Measure& measure = phrase.at(j);
			for (int k=0; k<(int)tss.size(); k++) {
				if (tsticks[k] == measure.m_ticks) {
					measure.m_measureTimeSignature = "*M" + tss[k];
					measure.setComplete();
				}
			}
		}
	}

	// Now check if the measure at the end and beginning
	// of the next phrase are both complete.  If not then
	// calculate partial measure pairs.
	for (int i=0; i<(int)size()-1; i++) {
		Tool_esac2hum::Phrase& phrase = at(i);
		Tool_esac2hum::Phrase& nextphrase = at(i+1);
		if (phrase.size() < 2) {
			// deal with phrases with a single measure later
			continue;
		}
		if (nextphrase.size() < 2) {
			// deal with phrases with a single measure later
			continue;
		}
		Tool_esac2hum::Measure& measure = phrase.back();
		Tool_esac2hum::Measure& nextmeasure = nextphrase.at(0);

		int mticks  = measure.m_ticks;
		int nmticks = nextmeasure.m_ticks;

		int found1 = -1;
		int found2 = -1;

		for (int j=(int)tss.size() - 1; j>=0; j--) {
			if (tsticks.at(j) == mticks) {
				found1 = j;
			}
			if (tsticks.at(j) == nmticks) {
				found2 = j;
			}
		}
		if ((found1 >= 0) && (found2 >= 0)) {
			// The two measures are complete
			measure.m_measureTimeSignature = "*M" + tss[found1];
			nextmeasure.m_measureTimeSignature = "*M" + tss[found2];
			measure.setComplete();
			nextmeasure.setComplete();
		} else {
			// See if the sum of the two measures match
			// a listed time signature.  if so, then they
			// form two partial measures.
			int ticksum = mticks + nmticks;
			for (int z=0; z<(int)tsticks.size(); z++) {
				if (tsticks.at(z) == ticksum) {
					nextmeasure.m_barnum = -1;
					measure.m_measureTimeSignature = "*M" + tss.at(z);
					nextmeasure.m_measureTimeSignature = "*M" + tss.at(z);
					measure.setPartialBegin();
					nextmeasure.setPartialEnd();
				}
			}
		}
	}

	// Check if the first measure is a complete time signature in duration.
	// If not then mark as pickup measure.  If incomplete and last measure
	// is incomplete, then merge into a single measure (partial start for
	// last measure and partial end for first measure.
	if (empty()) {
		// no data
	} else if ((size() == 1) && (at(0).size() <= 1)) {
		// single measure in melody
	} else {
		Tool_esac2hum::Measure& firstmeasure = at(0).at(0);
		Tool_esac2hum::Measure& lastmeasure  = back().back();

		double firstticks = firstmeasure.m_ticks;
		double lastticks = lastmeasure.m_ticks;

		int foundfirst = -1;
		int foundlast  = -1;

		for (int i=(int)tss.size() - 1; i>=0; i--) {
			if (tsticks.at(i) == firstticks) {
				foundfirst = i;
			}
			if (tsticks.at(i) == lastticks) {
				foundlast = i;
			}
		}

		if ((foundfirst >= 0) && (foundlast >= 0)) {
			// first and last measures are both complete
			firstmeasure.m_measureTimeSignature = "*M" + tss.at(foundfirst);
			lastmeasure.m_measureTimeSignature = "*M" + tss.at(foundlast);
			firstmeasure.setComplete();
			lastmeasure.setComplete();
		} else {
			// if both sum to a time signature than assigned that time signature to both
			double sumticks = firstticks + lastticks;
			int sumfound = -1;
			for (int i=0; i<(int)tsticks.size(); i++) {
				if (tsticks[i] == sumticks) {
					sumfound = i;
					break;
				}
			}
			if (sumfound >= 0) {
				// First and last meatures match a time signture, so
				// use that time signture for both, mark firt measure
				// last pickup (barnum -> 0), and mark last as partial
				// measure start
				firstmeasure.m_measureTimeSignature = "*M" + tss.at(sumfound);
				lastmeasure.m_measureTimeSignature = "*M" + tss.at(sumfound);
				firstmeasure.m_barnum = 0;
				firstmeasure.setPartialEnd();
				lastmeasure.setPartialBegin();
			} else if ((foundfirst >= 0) && (foundlast < 0)) {
				firstmeasure.setComplete();
				lastmeasure.setPartialBegin();
			} else if ((foundfirst < 0) && (foundlast >= 0)) {
				firstmeasure.setPartialEnd();
				lastmeasure.setComplete();
			}
		}
	}


	// Now assign bar numbers
	// First probalby check for pairs of uncategorized measure durations (deal with that later).
	vector<Tool_esac2hum::Measure*> measurelist;
	getMeasureList(measurelist);
	int barnum = 1;
	for (int i=0; i<(int)measurelist.size(); i++) {
		if ((i == 0) && measurelist.at(i)->isPartialEnd()) {
			measurelist.at(i)->m_barnum = 0;
			continue;
		}
		if (measurelist.at(i)->isComplete()) {
			measurelist.at(i)->m_barnum = barnum++;
		} else if (measurelist.at(i)->isPartialBegin()) {
			measurelist.at(i)->m_barnum = barnum++;
		} else if (measurelist.at(i)->isPartialEnd()) {
			measurelist.at(i)->m_barnum = -1;
		} else {
			measurelist.at(i)->m_errors.push_back("UNCATEGORIZED MEASURE");
		}
	}

	// Now remove duplicate time signatures
	string current = "";
	for (int i=0; i<(int)measurelist.size(); i++) {
		if (measurelist.at(i)->m_measureTimeSignature == current) {
			measurelist.at(i)->m_measureTimeSignature = "";
		} else {
			current = measurelist.at(i)->m_measureTimeSignature;
		}
	}

}



//////////////////////////////
//
// Tool_esac2hum::Score::setAllTimeSigTicks -- Used for calculating bar numbers;
//

void Tool_esac2hum::Score::setAllTimesigTicks(double ticks) {
	vector<Tool_esac2hum::Measure*> measurelist;
	getMeasureList(measurelist);

	for (int i=0; i<(int)measurelist.size(); i++) {
		measurelist[i]->m_tsticks = ticks;
	}
}



//////////////////////////////
//
// Tool_esac2hum::Score::calculateKeyInformation --
//

void Tool_esac2hum::Score::calculateKeyInformation(void) {
	vector<Tool_esac2hum::Note*> notelist;
	getNoteList(notelist);

	vector<int> b40pcs(40, 0);
	for (int i=0; i<(int)notelist.size(); i++) {
		int pc = notelist[i]->m_b40degree;
		if ((pc >= 0) && (pc < 40)) {
			b40pcs.at(pc)++;
		}
	}

	string tonic = m_params["_tonic"];
	if (tonic.empty()) {
		// no tonic for some strange reason
		// error will be reported when calculating Humdrum pitches.
		return;
	}
	char letter = std::toupper(tonic[0]);

	// Compare counts of third and sixth pitch classes:
	int majorsum = b40pcs.at(12) + b40pcs.at(29);
	int minorsum = b40pcs.at(11) + b40pcs.at(28);
	if (minorsum > majorsum) {
		letter = std::tolower(letter);
	}
	string flats;
	string sharps;
	for (int i=1; i<(int)tonic.size(); i++) {
		if (tonic[i] == 'b') {
			flats += "-";
		} else if (tonic[i] == '#') {
			sharps += "#";
		}
	}

	m_keydesignation = "*";
	m_keydesignation += letter;

	if (!flats.empty() && !sharps.empty()) {
		m_errors.push_back("ERROR: tonic note cannot include both sharps and flats.");
	}
	if (!flats.empty()) {
		m_keydesignation += flats;
	} else {
		m_keydesignation += sharps;
	}
	m_keydesignation += ":";

	if (std::isupper(letter)) {

		// major key signature
		if (m_keydesignation == "*C:") {
			m_keysignature = "*k[]";
		} else if (m_keydesignation == "*G:") {
			m_keysignature = "*k[f#]";
		} else if (m_keydesignation == "*D:") {
			m_keysignature = "*k[f#c#]";
		} else if (m_keydesignation == "*A:") {
			m_keysignature = "*k[f#c#g#]";
		} else if (m_keydesignation == "*E:") {
			m_keysignature = "*k[f#c#g#d#]";
		} else if (m_keydesignation == "*B:") {
			m_keysignature = "*k[f#c#g#d#a#]";
		} else if (m_keydesignation == "*F#:") {
			m_keysignature = "*k[f#c#g#d#a#e#]";
		} else if (m_keydesignation == "*C#:") {
			m_keysignature = "*k[f#c#g#d#a#e#b#]";
		} else if (m_keydesignation == "*F:") {
			m_keysignature = "*k[b-]";
		} else if (m_keydesignation == "*B-:") {
			m_keysignature = "*k[b-e-]";
		} else if (m_keydesignation == "*E-:") {
			m_keysignature = "*k[b-e-a-]";
		} else if (m_keydesignation == "*A-:") {
			m_keysignature = "*k[b-e-a-d-]";
		} else if (m_keydesignation == "*D-:") {
			m_keysignature = "*k[b-e-a-d-g-]";
		} else if (m_keydesignation == "*G-:") {
			m_keysignature = "*k[b-e-a-d-g-c-]";
		} else if (m_keydesignation == "*C-:") {
			m_keysignature = "*k[b-e-a-d-g-f-]";
		} else {
			m_errors.push_back("ERROR: invalid/exotic key signature required.");
		}

	} else  {

		// minor key signature
		if (m_keydesignation == "*a:") {
			m_keysignature = "*k[]";
		} else if (m_keydesignation == "*e:") {
			m_keysignature = "*k[f#]";
		} else if (m_keydesignation == "*b:") {
			m_keysignature = "*k[f#c#]";
		} else if (m_keydesignation == "*f#:") {
			m_keysignature = "*k[f#c#g#]";
		} else if (m_keydesignation == "*c#:") {
			m_keysignature = "*k[f#c#g#d#]";
		} else if (m_keydesignation == "*g#:") {
			m_keysignature = "*k[f#c#g#d#a#]";
		} else if (m_keydesignation == "*d#:") {
			m_keysignature = "*k[f#c#g#d#a#e#]";
		} else if (m_keydesignation == "*a#:") {
			m_keysignature = "*k[f#c#g#d#a#e#b#]";
		} else if (m_keydesignation == "*d:") {
			m_keysignature = "*k[b-]";
		} else if (m_keydesignation == "*g:") {
			m_keysignature = "*k[b-e-]";
		} else if (m_keydesignation == "*c:") {
			m_keysignature = "*k[b-e-a-]";
		} else if (m_keydesignation == "*f:") {
			m_keysignature = "*k[b-e-a-d-]";
		} else if (m_keydesignation == "*b-:") {
			m_keysignature = "*k[b-e-a-d-g-]";
		} else if (m_keydesignation == "*e-:") {
			m_keysignature = "*k[b-e-a-d-g-c-]";
		} else if (m_keydesignation == "*a-:") {
			m_keysignature = "*k[b-e-a-d-g-c-f-]";
		} else {
			m_errors.push_back("ERROR: invalid/exotic key signature required.");
		}
	}

}



//////////////////////////////
//
// Tool_esac2hum::Score::calculateClef --
//

void Tool_esac2hum::Score::calculateClef(void) {
	vector<Tool_esac2hum::Note*> notelist;
	getNoteList(notelist);

	double sum = 0;
	double count = 0;
	int min12 = 1000;
	int max12 = -1000;

	for (int i=0; i<(int)notelist.size(); i++) {
		int b12 = notelist[i]->m_b12;
		if (b12 > 0) {
			sum += b12;
			count++;
			if (b12 < min12) {
				min12 = b12;
			}
			if (b12 > max12) {
				max12 = b12;
			}
		}
	}
	double average = sum / count;


	if ((min12 > 54) && (average >= 60.0)) {
		m_clef = "*clefG2";
	} else if ((max12 < 67) && (average < 60.0)) {
		m_clef = "*clefF4";
	} else if ((min12 > 47) && (min12 <= 57) && (max12 < 77) && (max12 >= 65)) {
		m_clef = "*clefGv2";
	} else if (average < 60.0) {
		m_clef = "*clefF2";
	} else {
		m_clef = "*clefG2";
	}
}



//////////////////////////////
//
// Tool_esac2hum::generateHumdrumNotes --
//

void Tool_esac2hum::Score::generateHumdrumNotes(void) {
	vector<Tool_esac2hum::Note*> notelist;
	getNoteList(notelist);

	string tonic = m_params["_tonic"];
	if (tonic.empty()) {
		string error = "Error: cannot find tonic pitch in KEY[] field: ";
		error += m_params["KEY"];
		m_errors.push_back(error);
		return;
	}
	char letter = std::tolower(tonic[0]);
	m_b40tonic = 40 * 4 + 2;  // start with middle C
	switch (letter) {
		case 'd': m_b40tonic +=  6; break;
		case 'e': m_b40tonic += 12; break;
		case 'f': m_b40tonic += 17; break;
		case 'g': m_b40tonic += 23; break;
		case 'a': m_b40tonic += 29; break;
		case 'b': m_b40tonic += 35; break;
	}
	int flats = 0;
	int sharps = 0;
	for (int i=1; i<(int)tonic.size(); i++) {
		if (tonic[i] == 'b') {
			flats++;
		} else if (tonic[i] == '#') {
			sharps++;
		}
	}
	if (flats > 0) {
		m_b40tonic -= flats;
	} else if (sharps > 0) {
		m_b40tonic += sharps;
	}

	string minrhy = m_params["_minrhy"];
	if (minrhy.empty()) {
		m_errors.push_back("Error: cannot find KEY[] minrhy");
		return;
	}

	m_minrhy = std::stoi(minrhy);
	// maybe check of power of two?

	for (int i=0; i<(int)notelist.size(); i++) {
		notelist.at(i)->generateHumdrum(m_minrhy, m_b40tonic);
	}

}



//////////////////////////////
//
// Tool_esac2hum::Note::generateHumdrum -- convert EsAC note to Humdrum note token.
//

void Tool_esac2hum::Note::generateHumdrum(int minrhy, int b40tonic) {
	string pitch;
	if (m_degree != 0) {
		m_b40degree = 0;
		switch (abs(m_degree)) {
			case 2: m_b40degree += 6;  break;
			case 3: m_b40degree += 12; break;
			case 4: m_b40degree += 17; break;
			case 5: m_b40degree += 23; break;
			case 6: m_b40degree += 29; break;
			case 7: m_b40degree += 35; break;
		}
		if ((m_alter >= -2) && (m_alter <= 2)) {
			m_b40degree += m_alter;
		} else {
			m_errors.push_back("Error: chromatic alteration on note too large");
		}
		m_b40 = 40 * m_octave + m_b40degree + b40tonic;
		pitch = Convert::base40ToKern(m_b40);
		// m_b12 is used for calculating clef later on.
		m_b12 = Convert::base40ToMidiNoteNumber(m_b40);
	} else {
		pitch = "r";
		m_b40 = -1000;
		m_b40degree = -1000;
	}

	HumNum duration(1, minrhy);
	int multiplier = (1 << m_underscores);
	duration *= multiplier;
	duration *= 4;  // convert from whole notes to quarter notes
	duration *= m_factor;
	string recip = Convert::durationToRecip(duration);
	for (int i=0; i<m_dots; i++) {
		recip += ".";
	}

	m_humdrum.clear();
	if (m_phraseBegin) {
		m_humdrum += "{";
	}

	if (m_tieBegin && !m_tieEnd) {
		m_humdrum += "[";
	}

	m_humdrum += recip;
	m_humdrum += pitch;

	if (!m_tieBegin && m_tieEnd) {
		m_humdrum += "]";
	} else if (m_tieBegin && m_tieEnd) {
		m_humdrum += "_";
	}

	if (m_phraseEnd) {
		m_humdrum += "}";
	}
}



//////////////////////////////
//
// Tool_esac2hum::Score::analyzeTies -- Create a list of notes
//     in each phrase and then assign a phrase start to the
//     first non-rest note, and phrase end to the last non-rest note.
//

void Tool_esac2hum::Score::analyzeTies(void) {
	vector<Tool_esac2hum::Note*> notelist;
	getNoteList(notelist);

	for (int i=1; i<(int)notelist.size(); i++) {
		// negative m_degree indicates a tied note to previous note
		if (notelist.at(i)->m_degree < 0) {
			// Tied note, so link to previous note.
			notelist.at(i)->m_tieEnd = true;
			notelist.at(i-1)->m_tieBegin = true;
			if (notelist.at(i-1)->m_degree >= 0) {
				notelist.at(i)->m_degree = -notelist.at(i-1)->m_degree;
				// Copy chromatic alteration and octave:
				notelist[i]->m_alter = notelist.at(i-1)->m_alter;
				notelist[i]->m_octave = notelist.at(i-1)->m_octave;
			}
		}
	}
}



//////////////////////////////
//
// Tool_esac2hum::Score::getNoteList -- Return a list of all notes
//      in the score.
//

void Tool_esac2hum::Score::getNoteList(vector<Tool_esac2hum::Note*>& notelist) {
	notelist.clear();
	for (int i=0; i<(int)size(); i++) {
		Tool_esac2hum::Phrase& phrase = at(i);
		for (int j=0; j<(int)phrase.size(); j++) {
			Tool_esac2hum::Measure& measure = phrase[j];
			for (int k=0; k<(int)measure.size(); k++) {
				notelist.push_back(&measure.at(k));
			}
		}
	}
}



//////////////////////////////
//
// Tool_esac2hum::Score::getMeasureList --
//

void Tool_esac2hum::Score::getMeasureList(vector<Tool_esac2hum::Measure*>& measurelist) {
	measurelist.clear();
	for (int i=0; i<(int)size(); i++) {
		Tool_esac2hum::Phrase& phrase = at(i);
		for (int j=0; j<(int)phrase.size(); j++) {
			Tool_esac2hum::Measure& measure = phrase[j];
			measurelist.push_back(&measure);
		}
	}
}



//////////////////////////////
//
// Tool_esac2hum::Score::analyzePhrases -- Create a list of notes in the score
//     and then search for ^ (-1 degrees) which mean a tied continuation
//     of the previous note.
//

void Tool_esac2hum::Score::analyzePhrases(void) {
	// first create a list of the notes in the score
	vector<Tool_esac2hum::Note*> notelist;
	for (int i=0; i<(int)size(); i++) {
		getPhraseNoteList(notelist, i);

		if (notelist.empty()) {
			at(i).m_errors.push_back("ERROR: no notes in phrase.");
			return;
		}

		// Find the first non-rest note and mark with phrase start:
		bool foundNote = false;
		for (int j=0; j<(int)notelist.size(); j++) {
			if (notelist.at(j)->m_degree <= 0) {
				continue;
			}
			foundNote = true;
			notelist.at(j)->m_phraseBegin = true;
			break;
		}

		if (!foundNote) {
			at(i).m_errors.push_back("Error: cannot find any notes in phrase.");
			continue;
		}

		// Find the last non-rest note and mark with phrase end:
		for (int j=(int)notelist.size()-1; j>=0; j--) {
			if (notelist.at(j)->m_degree <= 0) {
				continue;
			}
			notelist.at(j)->m_phraseEnd = true;
			break;
		}
	}
}


//////////////////////////////
//
// Tool_esac2hum::Score::getPhraseNoteList -- Return a list of all notes
//      in the 0-indexed phrase
//

void Tool_esac2hum::Score::getPhraseNoteList(vector<Tool_esac2hum::Note*>& notelist, int index) {
	notelist.clear();
	if (index < 0) {
		m_errors.push_back("ERROR: trying to access a negative phrase index");
		return;
	}
	if (index >= (int)size()) {
		m_errors.push_back("ERROR: trying to access a phrase index that is too large");
		return;
	}
	Tool_esac2hum::Phrase& phrase = at(index);

	for (int i=0; i<(int)phrase.size(); i++) {
		Tool_esac2hum::Measure& measure = phrase[i];
		for (int j=0; j<(int)measure.size(); j++) {
			Tool_esac2hum::Note& note = measure.at(j);
			notelist.push_back(&note);
		}
	}
}



//////////////////////////////
//
// Tool_esac2hum::Phrase::getNoteList -- Return a list of all notes
//      in the phrase.
//

void Tool_esac2hum::Phrase::getNoteList(vector<Tool_esac2hum::Note*>& notelist) {
	notelist.clear();
	Tool_esac2hum::Phrase& phrase = *this;

	for (int i=0; i<(int)phrase.size(); i++) {
		Tool_esac2hum::Measure& measure = phrase[i];
		for (int j=0; j<(int)measure.size(); j++) {
			Tool_esac2hum::Note& note = measure.at(j);
			notelist.push_back(&note);
		}
	}
}



//////////////////////////////
//
// Tool_esac2hum::Phrase::parsePhrase --
//

bool Tool_esac2hum::Phrase::parsePhrase(const string& phrase) {
	esac = phrase;

	vector<string> bars;

	HumRegex hre;
	string newphrase = phrase;
	newphrase = trimSpaces(newphrase);
	hre.split(bars, newphrase, "\\s+");
	if (bars.empty()) {
		cerr << "Funny error with no measures" << endl;
		return false;
	}
	int length = (int)bars.size();
	for (int i=0; i<length; i++) {
		resize(size() + 1);
		back().parseMeasure(bars[i]);
	}

	// Calculate ticks for phrase:
	m_ticks = 0;
	for (int i=0; i<(int)size(); i++) {
		m_ticks += at(i).m_ticks;
	}

	return true;
}



//////////////////////////////
//
// Tool_esac2hum::Measure::parseMeasure --
//     Also deal with () for ties.
//     Split notes by digit.  Prefix characters attached to digit:
//        ^: equivalent to digit, tied to previous note.
//        -: digit is scale degree in lower octave.
//        (: slur start
//

bool Tool_esac2hum::Measure::parseMeasure(const string& measure) {
	esac = measure;
	vector<string> tokens;
	vector<HumNum> factors;
	HumNum factor = 1;
	int length = (int)measure.size();
	for (int i=0; i<length; i++) {
		if (measure[i] == '(') {
			factor = 2;
			factor /= 3;
		}

		bool marker = false;
		if (std::isdigit(measure[i])) {
			marker = true;
		} else if (measure[i] == '^') {  // tie placeholder for degree digit
			marker = true;
		} else if (measure[i] == '(') {  // tuplet start
			marker = true;
		} else if (measure[i] == '-') {  // octave lower
			marker = true;
		} else if (measure[i] == '+') {  // octave higher
			marker = true;
		}

		if (marker && !tokens.empty() && !tokens.back().empty()) {
			char checkChar = tokens.back().back();
			if (checkChar == '(') {
				marker = false;
			} else if (checkChar == '-') {
				marker = false;
			} else if (checkChar == '+') {
				marker = false;
			}
		}

		if (marker) {
			tokens.resize(tokens.size() + 1);
			tokens.back() += measure[i];
			factors.resize(factors.size() + 1);
			factors.back() = factor;
		} else {
			if (!tokens.empty()) {
				tokens.back() += measure[i];
			} else {
				cerr << "!!ERROR: unknown character at start of measure: " << measure << endl;
			}
		}

		if (measure[i] == ')') {
			factor = 1;
		}
	}

	if (tokens.empty()) {
		cerr << "!!ERROR: In measure: " << measure << ": no notes to parts." << endl;
		return false;
	}

	for (int i=0; i<(int)tokens.size(); i++) {
		resize(size() + 1);
		back().parseNote(tokens[i], factors[i]);
	}

	// Calculate ticks for measure:
	m_ticks = 0;
	for (int i=0; i<(int)size(); i++) {
		m_ticks += at(i).m_ticks;
	}

	return true;
}



//////////////////////////////
//
// Tool_esac2hum::Note::parseNote --
//

bool Tool_esac2hum::Note::parseNote(const string& note, HumNum factor) {
	esac = note;

	int minus = 0;
	int plus = 0;
	int b = 0;
	int s = 0;
	m_degree = 0;
	m_dots = 0;

	for (int i=0; i<(int)note.size(); i++) {
		if (note[i] == '.') {        // augmentation dot
			m_dots++;
		} else if (note[i] == '_') { // duration modifier
			m_underscores++;
		} else if (note[i] == '-') { // lower octave
			minus++;
		} else if (note[i] == '+') { // upper octave
			plus++;
		} else if (note[i] == 'b') { // flat
			b++;
		} else if (note[i] == '#') { // sharp
			s++;
		} else if (isdigit(note[i])) {
			m_degree = note[i] - '0';
		} else if (note[i] == '^') { // tied to previous note
			m_degree = -1000;
		}
	}

	m_ticks = 1 << m_underscores;
	if (m_dots > 0) {
		m_ticks = m_ticks * (2.0 - 1.0/(1 << m_dots));
	}

	if (b > 2) {
		cerr << "!! ERROR: more than double flat not parseable, note: " << esac << endl;
	}
	if (s > 2) {
		cerr << "!! ERROR: more than double sharp not parseable, note: " << esac << endl;
	}

	m_alter = s - b;
	m_octave = plus - minus;

	m_factor = factor;

	return true;
}



//////////////////////////////
//
// Tool_esac2hum::printHeader --
//

void Tool_esac2hum::printHeader(ostream& output) {
	string filename = createFilename();
	output << "!!!!SEGMENT: " << filename << endl;

	string title = m_score.m_params["_title"];
	output << "!!!OTL:";
	if (!title.empty()) {
		output << " " << title;
	}
	output << endl;
	// sometimes CUT[] has two lines, and the sescond is the text incipit:
	string incipit = m_score.m_params["_incipit"];
	if (!incipit.empty()) {
		output << "!!!TIN: " << incipit << endl;
	}

	string source = m_score.m_params["_source"];
	output << "!!!source:";
	if (!source.empty()) {
		output << " " << source;
	}
	output << endl;

	string id = m_score.m_params["_id"];
	output << "!!!id:";
	if (!id.empty()) {
		output << " " << id;
	}
	output << endl;

	string signature = m_score.m_params["SIG"];
	output << "!!!signature:";
	if (!signature.empty()) {
		output << " " << signature;
	}
	output << endl;

	output << "**kern" << endl;
}



//////////////////////////////
//
// Tool_esac2hum::createFilename -- from SIG[] and CUT[], with spaces in CUT[] turned into
//     underscores and accents removed from characters.
//
//     Also need to deal with decomposed accents, if necessary:
//         0x0301: Combining acute accent
//         0x0300: Combining grave accent
//         0x0302: Combining circumflex accent
//         0x0303: Combining tilde
//         0x0308: Combining diaeresis (umlaut)
//         0x0327: Combining cedilla
//         0x0328: Combining ogonek
//         0x0304: Combining macron
//         0x0306: Combining breve
//         0x0307: Combining dot above
//         0x0323: Combining dot below
//         0x030A: Combining ring above
//         0x030B: Combining double acute accent
//         0x030C: Combining caron
//
//
//    std::unordered_map<char, char> m_accent_map = {
//         {'á', 'a'}, {'à', 'a'}, {'ä', 'a'}, {'â', 'a'}, {'ã', 'a'}, {'å', 'a'},
//         {'é', 'e'}, {'è', 'e'}, {'ë', 'e'}, {'ê', 'e'},
//         {'í', 'i'}, {'ì', 'i'}, {'ï', 'i'}, {'î', 'i'},
//         {'ó', 'o'}, {'ò', 'o'}, {'ö', 'o'}, {'ô', 'o'}, {'õ', 'o'}, {'ø', 'o'},
//         {'ú', 'u'}, {'ù', 'u'}, {'ü', 'u'}, {'û', 'u'},
//         {'ý', 'y'}, {'ÿ', 'y'},
//         {'ñ', 'n'}, {'ç', 'c'},
//         {'ą', 'a'}, {'ć', 'c'}, {'ę', 'e'}, {'ł', 'l'}, {'ń', 'n'},
//         {'ś', 's'}, {'ź', 'z'}, {'ż', 'z'}
//    };

string Tool_esac2hum::createFilename(void) {
	string source = m_score.m_params["_source"];
	string prefix;
	string sig = m_score.m_params["SIG"];
	string title = m_score.m_params["_title"];
	string id  = m_score.m_params["_id"];
	if (sig.empty()) {
		sig = id;
	}

	HumRegex hre;
	// Should not be spaces, but just in case;
	hre.replaceDestructive(sig, "", "\\s+", "g");
	hre.replaceDestructive(source, "", "\\s+", "g");

	if (!m_filePrefix.empty()) {
		prefix = m_filePrefix;
		source = "";
	}

	// Convert spaces to underscores:
	hre.replaceDestructive(title, "_", "\\s+", "g");
	// Remove accents:
	hre.replaceDestructive(title, "a", "á", "g");
	hre.replaceDestructive(title, "a", "à", "g");
	hre.replaceDestructive(title, "a", "ä", "g");
	hre.replaceDestructive(title, "a", "â", "g");
	hre.replaceDestructive(title, "a", "ã", "g");
	hre.replaceDestructive(title, "a", "å", "g");
	hre.replaceDestructive(title, "e", "é", "g");
	hre.replaceDestructive(title, "e", "è", "g");
	hre.replaceDestructive(title, "e", "ë", "g");
	hre.replaceDestructive(title, "e", "ê", "g");
	hre.replaceDestructive(title, "i", "í", "g");
	hre.replaceDestructive(title, "i", "ì", "g");
	hre.replaceDestructive(title, "i", "ï", "g");
	hre.replaceDestructive(title, "i", "î", "g");
	hre.replaceDestructive(title, "o", "ó", "g");
	hre.replaceDestructive(title, "o", "ò", "g");
	hre.replaceDestructive(title, "o", "ö", "g");
	hre.replaceDestructive(title, "o", "ô", "g");
	hre.replaceDestructive(title, "o", "õ", "g");
	hre.replaceDestructive(title, "o", "ø", "g");
	hre.replaceDestructive(title, "u", "ú", "g");
	hre.replaceDestructive(title, "u", "ù", "g");
	hre.replaceDestructive(title, "u", "ü", "g");
	hre.replaceDestructive(title, "u", "û", "g");
	hre.replaceDestructive(title, "y", "ý", "g");
	hre.replaceDestructive(title, "y", "ÿ", "g");
	hre.replaceDestructive(title, "n", "ñ", "g");
	hre.replaceDestructive(title, "c", "ç", "g");
	hre.replaceDestructive(title, "a", "ą", "g");
	hre.replaceDestructive(title, "c", "ć", "g");
	hre.replaceDestructive(title, "e", "ę", "g");
	hre.replaceDestructive(title, "l", "ł", "g");
	hre.replaceDestructive(title, "n", "ń", "g");
	hre.replaceDestructive(title, "s", "ś", "g");
	hre.replaceDestructive(title, "z", "ź", "g");
	hre.replaceDestructive(title, "z", "ż", "g");
	hre.replaceDestructive(title, "", "[^a-zA-Z0-9-_.]", "g");

	std::transform(title.begin(), title.end(), title.begin(),
			[](unsigned char c) { return std::tolower(c); });

	string output;
	if (!prefix.empty()) {
		output += prefix + "-";
	} else if (!source.empty()) {
		if (hre.search(source, "^DWOK(\\d+)$")) {
			string volume = hre.getMatch(1);
			if (volume.size() == 1) {
				volume = "0" + volume;
			}
			if (!sig.empty()) {
				if (hre.search(sig, "^(\\d\\d)")) {
					string volume2 = hre.getMatch(1);
					if (volume == volume2) {
						source = "DWOK";
						output += source;
					}
				} else {
					output += source + "-";
				}
			} else {
				output += source + "-";
			}
		} else {
			output += source + "-";
		}
	}
	output += sig;
	if (!(sig.empty() || title.empty())) {
		output += "-";
	}
	output += title;
	if (output.empty()) {
		output = "file";
	}
	output += m_filePostfix;

	return output;
}



//////////////////////////////
//
// Tool_esac2hum::getParameters --
//

void Tool_esac2hum::getParameters(vector<string>& infile) {
	m_score.m_params.clear();
	HumRegex hre;
	bool expectingCloseQ = false;
	string lastKey = "";
	for (int i=0; i<(int)infile.size(); i++) {
		if (hre.search(infile[i], "^\\s*$")) {
			continue;
		}
		if ((i == 0) && hre.search(infile[i], "^([A-Z_a-z][^\\]\\[]*)\\s*$")) {
			m_score.m_params["_source"] = hre.getMatch(1);
			continue;
		}
		if (expectingCloseQ) {
			if (infile[i].find("[") != string::npos) {
				cerr << "Strange case searching for close: " << infile[i] << endl;
			} else if (infile[i].find("]") == string::npos) {
				// continuing a parameter:
				if (lastKey == "") {
					cerr << "Strange case of no last key when closing parameter: " << infile[i] << endl;
				} else {
					m_score.m_params[lastKey] += "\n" + infile[i];
				}
			} else if (hre.search(infile[i], "^([^\\]]+)\\]\\s*$")) {
				// closing a parameter:
				if (lastKey == "") {
					cerr << "Strange case B of no last key when closing parameter: " << infile[i] << endl;
				} else {
					string value = hre.getMatch(1);
					m_score.m_params[lastKey] += "\n" + value;
					expectingCloseQ = false;
					continue;
				}
			} else {
				cerr << "Problem closing parameter: " << infile[i] << endl;
			}
			continue;
		} else if (hre.search(infile[i], "^\\s*([A-Z_a-z]+)\\s*\\[([^\\]]*)\\]\\s*$")) {
			// single line parameter
			string key   = hre.getMatch(1);
			string value = hre.getMatch(2);

			// Rare cases where the key has lower case letters that should not be there:
			std::transform(key.begin(), key.end(), key.begin(),
					[](unsigned char c) { return std::toupper(c); });

			m_score.m_params[key] = value;
			continue;
		} else if (hre.search(infile[i], "^\\s*([A-Z_a-z]+)\\s*\\[([^\\]]*)\\s*$")) {
			// opening of a parameter
			string key   = hre.getMatch(1);
			string value = hre.getMatch(2);

			// Rare cases where the key has lower case letters that should not be there:
			std::transform(key.begin(), key.end(), key.begin(),
					[](unsigned char c) { return std::toupper(c); });

			m_score.m_params[key] = value;
			lastKey = key;
			expectingCloseQ = true;
			continue;
		} else if (hre.search(infile[i], "^#")) {
			// Do nothing: an external comment, or embedded filter processed
			// when filter loading the file.
		} else {
			cerr << "UNKNOWN CASE: " << infile[i] << endl;
		}
	}

	// The CUT[] line can be multiple lines, the first being the title and
	// the second being the text incipit.  Split them into _title and _incipit
	// fields (not checking if more than two lines):
	string cut = m_score.m_params["CUT"];
	if (hre.search(cut, "^\\s*(.*?)\\n(.*?)\\s*$", "s")) {
		m_score.m_params["_title"]   = trimSpaces(hre.getMatch(1));
		m_score.m_params["_incipit"] = trimSpaces(hre.getMatch(2));
	} else {
		// Don't know if CUT[] is title or incipit, but assign to title.
		m_score.m_params["_title"] = trimSpaces(cut);
		m_score.m_params["_incipit"] = "";
	}

	string key = m_score.m_params["KEY"];
	if (hre.search(key, "^\\s*([^\\s]+)\\s+(\\d+)\\s+([A-Gacdefg][b#]*)\\s+(.*?)\\s*$")) {
		string id     = hre.getMatch(1);
		string minrhy = hre.getMatch(2);
		string tonic  = hre.getMatch(3);
		if (tonic.size() >= 1) {
			if (tonic[0] == 'b') {
				cerr << "Error: key signature cannot be 'b'." << endl;
			} else {
				if (std::islower(tonic[0])) {
					cerr << "Warning: Tonic note should be upper case." << endl;
					tonic[0] = std::toupper(tonic[0]);
				}
			}
		}
		string time   = hre.getMatch(4);
		m_score.m_params["_id"]     = id;
		m_score.m_params["_minrhy"] = minrhy;
		m_score.m_params["_tonic"]  = tonic;
		m_score.m_params["_time"]   = time;
		m_minrhy = stoi(minrhy);
	} else {
		cerr << "Problem parsing KEY parameter: " << key << endl;
	}

	string trd = m_score.m_params["TRD"];
	if (hre.search(trd, "^\\s*(.*)\\ss\\.")) {
		m_score.m_params["_source_trd"] = hre.getMatch(1);
	}

	// Search for page number in TRD[] record.   The canonical form is:
	//     s. #
	// or multipage:
   //     s. #-#
   // or multipage alternate:
   //     s. # - #
	// There are variants to also handle, however:
	//     s.#
   //     str. #     (particularly DWOK16)
   //     str.#
   //     s #
   //     .s #
   //     S.#
   //     s, #

	if (hre.search(trd, "\\bs(?:tr)?\\.?\\s*(\\d+)\\s*-\\s*(\\d+)?", "i")) {
		// s.   #-#
		// s    #-#
		// str. #-#
		m_score.m_params["_page"] = hre.getMatch(1) + "-" + hre.getMatch(2);
	} else if (hre.search(trd, "\\bs(?:tr)?\\.?\\s*(\\d+)", "i")) {
		// s. #
		// s #
		// str. #
		m_score.m_params["_page"] = hre.getMatch(1);
	} else if (hre.search(trd, "\\bs,\\s*(\\d+)\\s*-\\s*(\\d+)?", "i")) {
		// s, #-#
	} else if (hre.search(trd, "\\bs,\\s*(\\d+)", "i")) {
		// s, #
	} else {
		cerr << "CANNOT FIND PAGE NUMBER IN " << trd << endl;
	}

	if (m_debugQ) {
		printParameters();
	}

	if (hre.search(m_score.m_params["_source_trd"], "^\\s*(DWOK\\d+)")) {
		m_dwokQ = true;
	} else if (hre.search(m_score.m_params["_source"], "^\\s*(DWOK\\d+)")) {
		m_dwokQ = true;
	}

}



//////////////////////////////
//
// Tool_esac2hum::printParameters --
//

void Tool_esac2hum::printParameters(void) {
	cerr << endl;
	cerr << "========================================" << endl;
	for (const auto& [key, value] : m_score.m_params) {
		cerr << "Key: " << key << ", Value: " << value << endl;
	}
	cerr << "========================================" << endl;
	cerr << endl;
}



//////////////////////////////
//
// Tool_esac2hum::printBemComment --
//

void Tool_esac2hum::printBemComment(ostream& output) {
	string bem = m_score.m_params["BEM"];
	if (bem.empty()) {
		return;
	}
	HumRegex hre;
	hre.replaceDestructive(bem, " ", "\n", "g");
	output << "!!!ONB: " << bem << endl;
}



//////////////////////////////
//
// Tool_esac2hum::printFooter --
//

void Tool_esac2hum::printFooter(ostream& output, vector<string>& infile) {
	output << "*-" << endl;

	printBemComment(output);
	printPdfLinks(output);
	printPageNumbers(output);
	printConversionDate(output);


	if (m_embedEsacQ) {
		output << "!!@@BEGIN: ESAC" << endl;
		output << "!!@CONTENTS:" << endl;;
		for (int i=0; i<(int)infile.size(); i++) {
			output << "!!" << infile[i] << endl;
		}
		if (m_analysisQ) {
			embedAnalyses(output);
		}
		output << "!!@@END: ESAC" << endl;
	}

	if (!m_globalComments.empty()) {
		for (int i=0; i<(int)m_globalComments.size(); i++) {
			output << m_globalComments.at(i) << endl;
		}
	}
}



///////////////////////////////
//
// Tool_esac2hum::printPageNumbers --
//

void Tool_esac2hum::printPageNumbers(ostream& output) {
	HumRegex hre;
	string trd = m_score.m_params["TRD"];
	if (hre.search(trd, "\\bs\\.\\s*(\\d+)\\s*-\\s*(\\d+)", "im")) {
		output << "!!!page: " << hre.getMatch(1) << "-" << hre.getMatch(2) << endl;
	} else if (hre.search(trd, "\\bs\\.\\s*(\\d+)", "im")) {
		output << "!!!page: " << hre.getMatch(1) << endl;
	}
}



///////////////////////////////
//
// Tool_esac::embedAnalyses --
//

void Tool_esac2hum::embedAnalyses(ostream& output) {
	m_score.doAnalyses();
	string MEL_SEM  = m_score.m_params["MEL_SEM"];
	string MEL_RAW  = m_score.m_params["MEL_RAW"];
	string NO_REP   = m_score.m_params["NO_REP"];
	string RTM      = m_score.m_params["RTM"];
	string SCL_DEG  = m_score.m_params["SCL_DEG"];
	string SCL_SEM  = m_score.m_params["SCL_SEM"];
	string PHR_NO   = m_score.m_params["PHR_NO"];
	string PHR_BARS = m_score.m_params["PHR_BARS"];
	string PHR_CAD  = m_score.m_params["PHR_CAD"];
	string ACC      = m_score.m_params["ACC"];

	bool allEmptyQ = true;
	if      (!MEL_SEM.empty() ) { allEmptyQ = false; }
	else if (!MEL_RAW.empty() ) { allEmptyQ = false; }
	else if (!NO_REP.empty()  ) { allEmptyQ = false; }
	else if (!RTM.empty()     ) { allEmptyQ = false; }
	else if (!SCL_DEG.empty() ) { allEmptyQ = false; }
	else if (!SCL_SEM.empty() ) { allEmptyQ = false; }
	else if (!PHR_NO.empty()  ) { allEmptyQ = false; }
	else if (!PHR_BARS.empty()) { allEmptyQ = false; }
	else if (!PHR_CAD.empty() ) { allEmptyQ = false; }
	else if (!ACC.empty()     ) { allEmptyQ = false; }

	if (allEmptyQ) {
		// no analyses for some strange reason.
		return;
	}
	output << "!!@ANALYSES:" << endl;
	if (!MEL_SEM.empty() ) { output << "!!MEL_SEM["  << MEL_SEM  << "]" << endl; }
	if (!MEL_RAW.empty() ) { output << "!!MEL_RAW["  << MEL_RAW  << "]" << endl; }
	if (!NO_REP.empty()  ) { output << "!!NO_REP["   << NO_REP   << "]" << endl; }
	if (!RTM.empty()     ) { output << "!!RTM["      << RTM      << "]" << endl; }
	if (!SCL_DEG.empty() ) { output << "!!SCL_DEG["  << SCL_DEG  << "]" << endl; }
	if (!SCL_SEM.empty() ) { output << "!!SCL_SEM["  << SCL_SEM  << "]" << endl; }
	if (!PHR_NO.empty()  ) { output << "!!PHR_NO["   << PHR_NO   << "]" << endl; }
	if (!PHR_BARS.empty()) { output << "!!PHR_BARS[" << PHR_BARS << "]" << endl; }
	if (!PHR_CAD.empty() ) { output << "!!PHR_CAD["  << PHR_CAD  << "]" << endl; }
	if (!ACC.empty()     ) { output << "!!ACC["      << ACC      << "]" << endl; }

}


///////////////////////////////
//
// Tool_esac2hum::printPdfLinks --
//

void Tool_esac2hum::printPdfLinks(ostream& output) {
	if (m_dwokQ) {
		output << "!!!URL: https://kolberg.ispan.pl/webesac Kolberg WebEsAC" << endl;
	} else {
		output << "!!!URL: http://webesac.pcss.pl WebEsAC" << endl;
	}

	if (!m_dwokQ) {
		return;
	}

	output << "!!!URL: https::kolberg.ispan.pl/dwok/tomy Oskar Kolberg: Complete Works digital edition" << endl;

	printKolbergPdfUrl(output);

}



///////////////////////////////
//
// Tool_esac2hum::printCoversionDate --
//

void Tool_esac2hum::printConversionDate(ostream& output) {
	std::time_t t = std::time(nullptr);
	std::tm* now = std::localtime(&t);
	output << "!!!ONB: Converted on ";
	output << std::put_time(now, "%Y/%m/%d");
	output << " with esac2hum" << endl;
}



//////////////////////////////
//
// Tool_esac2hum::Score::doAnalyses --
//

void Tool_esac2hum::Score::doAnalyses(void) {
	analyzeMEL_SEM();
	analyzeMEL_RAW();
	analyzeNO_REP();
	analyzeRTM();
	analyzeSCL_DEG();
	analyzeSCL_SEM();
	analyzePHR_NO();
	analyzePHR_BARS();
	analyzePHR_CAD();
	analyzeACC();
}



//////////////////////////////
//
// Tool_esac2hum::Score::analyzeMEL_SEM -- Current
//   algorithm: calculate intervals across rest, ignore tied notes.
//   values are differences between m_b12 of notes.
//

void Tool_esac2hum::Score::analyzeMEL_SEM(void) {
	vector<Tool_esac2hum::Note*> notelist;
	getNoteList(notelist);

	vector<int> b12s;  // list of notes to calculate intervals between

	for (int i=0; i<(int)notelist.size(); i++) {
		if (notelist[i]->isRest()) {
			continue;
		}
		if (notelist[i]->m_tieEnd) {
			continue;
		}
		b12s.push_back(notelist[i]->m_b12);
	}

	string output;
	for (int i=1; i<(int)b12s.size(); i++) {
		int difference = b12s[i] - b12s[i-1];
		output += to_string(difference);
		if (i < (int)b12s.size() - 1) {
			output += " ";
		}
	}

	m_params["MEL_SEM"] = output;
}



//////////////////////////////
//
// Tool_esac2hum::Score::analyzeMEL_RAW -- Remove rhythms from MEL[] data.
//    Preserve spaces as in original MEL[];
//    What to do with parentheses? Currently removed.
//    What to do with tied notes?  Currently removed.
//

void Tool_esac2hum::Score::analyzeMEL_RAW(void) {
	string output = m_params["MEL"];
	HumRegex hre;
	hre.replaceDestructive(output, "", "[^\\d+\\sb#-]+", "g");
	hre.replaceDestructive(output, "", "\\s*//\\s*$");
	hre.replaceDestructive(output, "\n!!", "\n", "g");
	m_params["MEL_RAW"] = output;
}



//////////////////////////////
//
// Tool_esac2hum::Score::analyzeNO_REP -- Return
//     the non-repeated notes/rests without rhythms
//     in each phrase with a newlines between phrases
//     and no spaces between notes or measures.
//

void Tool_esac2hum::Score::analyzeNO_REP(void) {
	string output;
	for (int i=0; i<(int)size(); i++) {
		Tool_esac2hum::Phrase& phrase = at(i);
		string line = phrase.getNO_REP();
		if (i > 0) {
			output += "\n    ";
		}
		output += line;
	}

	HumRegex hre;
	hre.replaceDestructive(output, "\n!!", "\n", "g");

	m_params["NO_REP"] = output;
}



//////////////////////////////
//
// Tool_esac2hum::Phrase::getNO_REP -- Return
//     the non-repeated notes/rests without rhythms
//     with no spaces between notes or measures.
//     What to do if line starts with an ending tied note?
//     Currently ignoring leading tied end notes.
//

string Tool_esac2hum::Phrase::getNO_REP(void) {
	vector<Tool_esac2hum::Note*> notelist;
	getNoteList(notelist);
	string output;
	int foundNonTie = false;
	string lastitem = "";
	for (int i=0; i<(int)notelist.size(); i++) {
		if (!foundNonTie && notelist[i]->m_tieEnd) {
			continue;
		}
		foundNonTie = true;
		string curitem = notelist[i]->getScaleDegree();
		if (curitem != lastitem) {
			output += curitem;
			lastitem = curitem;
		}
	}
	return output;
}



//////////////////////////////
//
// Tool_esac2hum::Score::analyzeRTM -- Convert pitches/rests to "x".
//      What to do with tied notes?  Leaving ^ in for now.
//      What to do with ()?  Removing for now.
//

void Tool_esac2hum::Score::analyzeRTM(void) {
	string output = m_params["MEL"];
	HumRegex hre;
	hre.replaceDestructive(output, "", "[()]+", "g");
	hre.replaceDestructive(output, "x", "[+-]*(\\d|\\^)[b#]*", "g");
	hre.replaceDestructive(output, "", "\\s*//\\s*$");
	hre.replaceDestructive(output, "\n!!", "\n", "g");
	m_params["RTM"] = output;
}



//////////////////////////////
//
// Tool_esac2hum::Score::analyzeSCL_DEG -- List of scale degrees
//     present in melody from lowest to highest with no spaces between
//     the scale degrees.
//

void Tool_esac2hum::Score::analyzeSCL_DEG(void) {
	vector<Tool_esac2hum::Note*> notelist;
	getNoteList(notelist);
	map<int, Tool_esac2hum::Note*> list;
	for (int i=0; i<(int)notelist.size(); i++) {
		if (notelist[i]->isRest()) {
			continue;
		}
		if (notelist[i]->m_tieEnd) {
			continue;
		}
		int b40 = notelist[i]->m_b40;
		list[b40] = notelist[i];
	}

	string output;
	for (const auto& pair : list) {
		output += pair.second->getScaleDegree();
	}
	m_params["SCL_DEG"] = output;
}



//////////////////////////////
//
// Tool_esac2hum::Score::analyzeSCL_SEM -- Get the semitone
//    between scale degrees in SCL_DEG analysis.
//

void Tool_esac2hum::Score::analyzeSCL_SEM(void) {
	vector<Tool_esac2hum::Note*> notelist;
	getNoteList(notelist);
	map<int, Tool_esac2hum::Note*> list;
	for (int i=0; i<(int)notelist.size(); i++) {
		if (notelist[i]->isRest()) {
			continue;
		}
		if (notelist[i]->m_tieEnd) {
			continue;
		}
		int b40 = notelist[i]->m_b40;
		list[b40] = notelist[i];
	}

	string output;
	Tool_esac2hum::Note* lastnote = nullptr;
	for (const auto& pair : list) {
		if (lastnote == nullptr) {
			lastnote = pair.second;
			continue;
		}
		int second = pair.second->m_b12;
		int first = lastnote->m_b12;
		int difference = second -first;
		if (!output.empty()) {
			output += " ";
		}
		output += to_string(difference);
		lastnote = pair.second;
	}
	m_params["SCL_SEM"] = output;
}



//////////////////////////////
//
// Tool_esac2hum::Score::analyzePHR_NO --
//

void Tool_esac2hum::Score::analyzePHR_NO(void) {
	int phraseCount = (int)size();
	m_params["PHR_NO"] = to_string(phraseCount);
}



//////////////////////////////
//
// Tool_esac2hum::Score::analyzePHR_BARS -- Return the number
//    of measures in each phrase.
//

void Tool_esac2hum::Score::analyzePHR_BARS(void) {
	string output;
	for (int i=0; i<(int)size(); i++) {
		Tool_esac2hum::Phrase& phrase = at(i);
		int barCount = phrase.getFullMeasureCount();
		output += to_string(barCount);
		if (i < (int)size() - 1) {
			output += " ";
		}
	}
	m_params["PHR_BARS"] = output;
}



//////////////////////////////
//
// Tool_esac2hum:::Phrase::getFullMeasureCount -- Return the number
//      of measures, but subtrack one if the first measure is a
//      partialEnd and the last is a partialBegin.
//

int Tool_esac2hum::Phrase::getFullMeasureCount(void) {
	int measureCount = (int)size();
	if (measureCount < 2) {
		return measureCount;
	}
	if (at(0).isPartialEnd() && back().isPartialBegin()) {
		measureCount--;
	}

	// if the fist is partial and the last is not, also -1
	if (at(0).isPartialEnd() && back().isComplete()) {
		measureCount--;
	}

	// if the fist is complete and the last is incomplete, also -1
	if (at(0).isComplete() && back().isPartialBegin()) {
		measureCount--;
	}


	// what to do if first measure is pickup (and maybe last measure)?
	return measureCount;
}



//////////////////////////////
//
// Tool_esac2hum::Score::analyzePHR_CAD -- Give a space-delimited
//     list of the last scale degree of each phrase.
//

void Tool_esac2hum::Score::analyzePHR_CAD(void) {
	string output;
	for (int i=0; i<(int)size(); i++) {
		Tool_esac2hum::Phrase& phrase = at(i);
		output += phrase.getLastScaleDegree();
		if (i < (int)size() - 1) {
			output += " ";
		}
	}
	m_params["PHR_CAD"] = output;
}



//////////////////////////////
//
// Tool_esac2hum::Phrase::getLastScaleDegree --
//

string Tool_esac2hum::Phrase::getLastScaleDegree(void) {
	vector<Tool_esac2hum::Note*> notelist;
	getNoteList(notelist);

	for (int i=(int)notelist.size() - 1; i>=0; i--) {
		if (notelist[i]->isPitch()) {
			return notelist[i]->getScaleDegree();
		}
	}

	return "?";
}


//////////////////////////////
//
// Tool_esac2hum::Note::getScaleDegree -- return the scale degree
//     string for the note, such as: 6, -6, +7b, 5#.
//

string Tool_esac2hum::Note::getScaleDegree(void) {
	string output;
	if (m_octave < 0) {
		for (int i=0; i<-m_octave; i++) {
			output += "-";
		}
	} else if (m_octave > 0) {
		for (int i=0; i<m_octave; i++) {
			output += "+";
		}
	}
	output += to_string(m_degree);
	if (m_alter < 0) {
		for (int i=0; i<-m_alter; i++) {
			output += "b";
		}
	} else if (m_alter > 0) {
		for (int i=0; i<m_alter; i++) {
			output += "#";
		}
	}
	return output;
}



//////////////////////////////
//
// Tool_esac2hum::Note::isPitch -- return true if scale degree is not 0.
//

bool Tool_esac2hum::Note::isPitch(void) {
	return (m_degree > 0);
}



//////////////////////////////
//
// Tool_esac2hum::Note::isRest -- return true if scale degree is 0.
//

bool Tool_esac2hum::Note::isRest(void) {
	return (m_degree <= 0);
}



//////////////////////////////
//
// Tool_esac2hum::Score::analyzeACC --  The first scale degree
//     of each (complete) meausre, or partial measure start.
//     the scale degress for each phrase are placed into a word
//     without spaces, and then a space between each phrase.
//
//     Todo: Deal with tied notes at starts of measures.
//

void Tool_esac2hum::Score::analyzeACC(void) {
	string output;
	for (int i=0; i<(int)size(); i++) {
		Tool_esac2hum::Phrase& phrase = at(i);
		for (int j=0; j<(int)phrase.size(); j++) {
			Tool_esac2hum::Measure& measure = phrase.at(j);
			if (measure.isComplete()) {
				output += measure.at(0).getScaleDegree();
			}
		}
		if (i < (int)size() -1) {
			output += " ";
		}
	}
	m_params["ACC"] =  output;
}



//////////////////////////////
//
// Tool_esac2hum::getKolbergInfo --
//

Tool_esac2hum::KolbergInfo Tool_esac2hum::getKolbergInfo(int volume) {

	if (!m_initialized) {
		m_initialized = true;
		// Parameters:          Polish volume title,                                                          English translation,                                    print start page, Equivalent start scan (pdf page), Plate scan page vector
		m_kinfo.emplace( 1,     KolbergInfo("Pieśni ludu polskego",                                          "Polish folk songs",                                                    3,  99, {149, 150, 167, 168, 233, 234, 251, 252, 317, 318, 335, 336, 401, 402, 419, 420, 485, 486, 503, 504}));
		m_kinfo.emplace( 2,     KolbergInfo("Sandomierskie",                                                 "Sandomierz",                                                          23,  34, {}));
		m_kinfo.emplace( 3,     KolbergInfo("Kujawy I",                                                      "Kuyavia I (north central Poland)",                                   209, 221, {}));
		m_kinfo.emplace( 4,     KolbergInfo("Kujawy II",                                                     "Kuyavia II (north central Poland)",                                   69,  83, {}));
		m_kinfo.emplace( 5,     KolbergInfo("Krakowskie I",                                                  "Crakow I",                                                           194, 222, {}));
		m_kinfo.emplace( 6,     KolbergInfo("Krakowskie II",                                                 "Crakow II",                                                            5,  29, {49, 50}));
		//               7:     Krakowskie III/Crakow III: no music
		m_kinfo.emplace( 8,     KolbergInfo("Krakowskie IV",                                                 "Crakow IV",                                                          162, 182, {}));
		m_kinfo.emplace( 9,     KolbergInfo("W. Ks. Poznańskie I",                                           "Grand Duchy of Poznań I",                                            117, 141, {}));
		m_kinfo.emplace(10,     KolbergInfo("W. Ks. Poznańskie II",                                          "Grand Duchy of Poznań II",                                            60,  76, {}));
		m_kinfo.emplace(11,     KolbergInfo("W. Ks. Poznańskie III",                                         "Grand Duchy of Poznań III",                                           39,  57, {}));
		m_kinfo.emplace(12,     KolbergInfo("W. Ks. Poznańskie IV",                                          "Grand Duchy of Poznań IV",                                             3,  19, {}));
		m_kinfo.emplace(13,     KolbergInfo("W. Ks. Poznańskie V",                                           "Grand Duchy of Poznań V",                                              3,  27, {}));
		m_kinfo.emplace(14,     KolbergInfo("W. Ks. Poznańskie VI",                                          "Grand Duchy of Poznań VI",                                           157, 165, {}));
		m_kinfo.emplace(15,     KolbergInfo("W. Ks. Poznańskie VII",                                         "Grand Duchy of Poznań VII",                                          317, 327, {}));
		m_kinfo.emplace(16,     KolbergInfo("Lubelskie I",                                                   "Lublin Voivodeship I",                                               105, 125, {}));
		m_kinfo.emplace(17,     KolbergInfo("Lubelskie II",                                                  "Lublin Voivodeship II",                                                1,  23, {}));
		m_kinfo.emplace(18,     KolbergInfo("Kieleckie I",                                                   "Kielce Voivodeship I",                                                49,  65, {}));
		m_kinfo.emplace(19,     KolbergInfo("Kieleckie II",                                                  "Kielce Voivodeship II",                                                1,  15, {}));
		m_kinfo.emplace(20,     KolbergInfo("Radomskie I",                                                   "Radom Voivodeship I",                                                 75,  95, {}));
		m_kinfo.emplace(21,     KolbergInfo("Radomskie II",                                                  "Radom Voivodeship II",                                                 1,  19, {}));
		m_kinfo.emplace(22,     KolbergInfo("Łęczyckie",                                                     "Łęczyca Voivodeship",                                                 18,  36, {}));
		m_kinfo.emplace(23,     KolbergInfo("Kaliskie",                                                      "Kalisz Region",                                                       54,  68, {}));
		m_kinfo.emplace(24,     KolbergInfo("Mazowsze I",                                                    "Mazovia I",                                                           79, 103, {}));
		m_kinfo.emplace(25,     KolbergInfo("Mazowsze II",                                                   "Mazovia II",                                                           1,  26, {}));
		//              26:     Mazowsze III/Mazovia III: no music
		m_kinfo.emplace(27,     KolbergInfo("Mazowsze IV",                                                   "Mazovia IV",                                                         115, 134, {}));
		m_kinfo.emplace(28,     KolbergInfo("Mazowsze V",                                                    "Mazovia V",                                                           64,  83, {}));
		m_kinfo.emplace(29,     KolbergInfo("Pokucie I",                                                     "Pokuttia I",                                                          90, 122, {}));
		m_kinfo.emplace(30,     KolbergInfo("Pokucie II",                                                    "Pokuttia II",                                                          1,  14, {}));
		m_kinfo.emplace(31,     KolbergInfo("Pokucie III",                                                   "Pokuttia III",                                                        10,  31, {}));
		//              32:     Pokucie IV/Pokuttia IV: no music
		m_kinfo.emplace(33,     KolbergInfo("Chełmskie I",                                                   "Chełm Voivodeship I",                                                114, 150, {163, 164, 175, 176, 177, 178}));
		m_kinfo.emplace(34,     KolbergInfo("Chełmskie II",                                                  "Chełm Voivodeship II",                                                 4,  21, {}));
		m_kinfo.emplace(35,     KolbergInfo("Przemyskie",                                                    "Przemyśl Voivodeship",                                                11,  47, {93, 94, 115, 116, 167, 168}));
		//              36:     Wołyń/Volhynia: complications
		//              37:     Miscellanea I/Miscellanea I: no music
		//              38,     Miscellanea II/Miscellanea II: no music
		m_kinfo.emplace(39,     KolbergInfo("Pomorze",                                                       "Pomerania",                                                           67, 115, {129, 130, 147, 148}));
		m_kinfo.emplace(40,     KolbergInfo("Mazury Pruskie",                                                "Prussian Masuria",                                                    96, 155, {356, 357, 358, 359, 464, 465, 482, 483}));

		m_kinfo.emplace(41,     KolbergInfo("Mazowsze VI",                                                   "Mazovia VI",                                                          20, 95,  {108, 109, 126, 127, 288, 289, 306, 307, 388, 389, 406, 407}));
		m_kinfo.emplace(42,     KolbergInfo("Mazowsze VII",                                                  "Mazovia VII",                                                          6,  15, {}));
		m_kinfo.emplace(43,     KolbergInfo("Śląsk",                                                         "Silesia",                                                             21,  62, {74, 75}));
		m_kinfo.emplace(44,     KolbergInfo("Góry i Podgórze I",                                             "Mountains and Foothills I",                                           64, 110, {111, 112, 129, 130, 195, 196, 213, 214, 343, 344, 361, 362}));
		m_kinfo.emplace(45,     KolbergInfo("Góry i Podgórze II",                                            "Mountains and Foothills II",                                           1,  11, {91, 92, 109, 110, 335, 336, 353, 354, 499, 500}));
		m_kinfo.emplace(46,     KolbergInfo("Kaliskie i Sieradzkie",                                         "Kalisz Region and Sieradz Voivodeship",                                3,  29, {43, 44, 61, 62, 175, 176, 193, 194}));
		m_kinfo.emplace(47,     KolbergInfo("Podole",                                                        "Podolia",                                                             59, 105, {151, 152, 153, 154, 155, 156, 157, 158}));
		m_kinfo.emplace(48,     KolbergInfo("Tarnowskie-Rzeszowskie",                                        "Tarnów-Rzeszów Voivodeship",                                          65, 103, {119, 120, 233, 234, 251, 252}));
		m_kinfo.emplace(49,     KolbergInfo("Sanockie-Krośnieńskie I",                                       "Sanok-Krosno Voivodeship I",                                         109, 185, {189, 190, 239, 240, 257, 258, 387, 388, 405, 406, 455, 456, 473, 474}));
		m_kinfo.emplace(50,     KolbergInfo("Sanockie-Krośnieńskie II",                                      "Sanok-Krosno Voivodeship II",                                          1,  14, {30, 31, 48, 49, 114, 115, 132, 133, 198, 199, 216 ,217, 282, 283, 300, 301, 366, 367, 384, 385}));
		//              51:     Sanockie-Krośnieńskie III/Sanok-Krosno Voivodeship III: no music
		m_kinfo.emplace(52,     KolbergInfo("Białoruś-Polesie",                                              "Belarus-Polesia",                                                    116, 169, {182, 183, 200, 201, 266, 267, 284, 285, 382, 383, 400, 401, 530, 531, 548, 549}));
		m_kinfo.emplace(53,     KolbergInfo("Litwa",                                                         "Lithuania",                                                          142, 176, {195, 196, 325, 326, 359, 360, 441, 442, 459, 460}));
		m_kinfo.emplace(54,     KolbergInfo("Ruś karpacka I",                                                "Carpathian Ruthenia I",                                              267, 365, {371, 372}));
		m_kinfo.emplace(55,     KolbergInfo("Ruś karpacka II",                                               "Carpathian Ruthenia II",                                              22,  37, {48, 49, 146, 147, 164, 165, 214, 215, 232, 233, 426, 427, 444, 445}));
		m_kinfo.emplace(56,     KolbergInfo("Ruś czerwona I",                                                "Red Ruthenia I",                                                      61, 157, {173, 174, 191, 192, 209, 210, 259, 260, 27, 278, 311, 312, 329, 330, 443, 444, 461, 462}));
		//              57:     Ruś czerwona II/Red Ruthenia II, 14, 19, {70, 71, 88, 89}: complications
		//              58:     Materiały do etnografii Słowian wschodnich/Materials for the ethnography of the Eastern Slavs
		//              59/I,   Materiały do etnografii Słowian zachodnich i południowych. Cz. I Łużyce/Materials for the ethnography of western and southern Slavs. Part I Lusatia
		//              59/II,  Materiały do etnografii Słowian zachodnich i południowych. Cz. II Czechy, Słowacja/Materials for the ethnography of western and southern Slavs. Part II Czech Republic, Slovakia
		//              59/III, Materiały do etnografii Słowian zachodnich i południowych. Cz. III Słowiańszczyzna południowa/Materials for the ethnography of western and southern Slavs. Part III Southern Slavs
		//              60:     Przysłowia/Proverbs: no music
		//              61,     Pisma muzyczne, cz. I/Musical writings, part I: no music
		//              62,     Pisma muzyczne, cz. II/"Musical writings, part II, 25, 33: not in EsAC
		//              63,     Studia, rozprawy i artykuły/Studies, dissertations and articles", 55, 113: not in EsAC
		m_kinfo.emplace(64,     KolbergInfo("Korespondencja Oskara Kolberga, cz. I (1837-1876)",             "Correspondence of Oskar Kolberg, Part I (1837-1876)",                216, 261, {}));
		//              65:     Korespondencja Oskara Kolberga, cz. II (1877-1882)/Correspondence of Oskar Kolberg, Part II (1877-1882): no music
		//              66:     Korespondencja Oskara Kolberga, cz. III (1883-1890)/Correspondence of Oskar Kolberg, Part III (1883-1890): no music
		///             67:     Pieśni i melodie ludowe w opracowaniu fortepianowym, cz. I-II/Folk songs and melodies in piano arrangement, part I-II, 3, 22, not in EsAC
		//              68:     Kompozycje wokalno-instrumentalne/Vocal and instrumental compositions, 3, 49, not in EsAC
		//              69:     Kompozycje fortepianowe/Piano compositions: 3, 39, not in EsAC
		//              70:     Pieśni ludu polskiego. Supl. do t.1/Polish folk songs: Supplement to Volume 1: no music
		m_kinfo.emplace(71,     KolbergInfo("Sandomierskie. Suplement do t. 2 Dzieła Wszystkie",             "Sandomierz Voivodeship. Supplement to vol. 2 of The Complete Works",   3,  40, {116, 117, 134, 135}));
		m_kinfo.emplace(72.1,   KolbergInfo("Kujawy. Suplement do t. 3 i 4, cz. I, Kuyavia",                 "Supplement to vol. 3 and 4, part I",                                   3,  30, {292, 293, 310, 311}));
		//              72.2,   Kujawy. Suplement do t. 3 i 4, cz. II, Kuyavia/Supplement to vol. 3 and 4, part II,: missing information about pages?
		m_kinfo.emplace(73,     KolbergInfo("Krakowsike. Suplement do T. 5-8",                               "Cracow: Supplement to Volumes 5-8",                                   39, 163, {297, 298, 407, 408}));
		//              74:     Wielkie Księstwo Poznańskie. Suplement do t. 9-15/The Grand Duchy of Poznan. Supplement to vol. 9-15: no music
		m_kinfo.emplace(75,     KolbergInfo("Lubelskie. Supplement do tomów 16-17",                          "Lublin: Supplement to volumes 16-17",                                  4,  60, {281, 282, 283, 284, 285, 286, 287, 288, 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, 299, 300, 301, 302, 303, 304, 305, 306, 307, 308, 309, 310, 311, 312, 313, 314, 315, 316, 317, 318, 319, 320, 321, 322, 323, 324}));
		m_kinfo.emplace(76,     KolbergInfo("Kieleckie. Supplement do T. 18-19",                             "Kielce: Supplement to Volumes 18-19",                                  5,  41, {}));
		//              77:     Radomskie. Suplement do t. 20 i 21. I/Radomskie: Supplement to Volumes 20-21. I: complications
		m_kinfo.emplace(78,     KolbergInfo("Łęczyckie. Suplement do t. 22",                                 "Łęczyca Voivodeship: Supplement to Volume 22",                         3,   1, {}));
		m_kinfo.emplace(79,     KolbergInfo("Kaliskie. Suplement do t. 23",                                  "Kalisz Region. Supplement to vol. 23",                                 3,  38, {}));
		m_kinfo.emplace(80,     KolbergInfo("Mazowsze. Suplement do t. 24-28, cz. I",                        "Mazovia. Supplement to vol. 24-28, part I",                            7,  89, {}));
		m_kinfo.emplace(81,     KolbergInfo("Pokucie. Suplement do tomów 29-32",                             "Corrections: Supplement to Volumes 29-32",                             3,  73, {121, 122, 139, 140}));
		m_kinfo.emplace(82,     KolbergInfo("Chełmskie. Suplement do T. 33 i 34",                            "Chełm supplement to Volumes 33 and 34",                                7, 105, {}));
		m_kinfo.emplace(83,     KolbergInfo("Przemyskie. Suplement do tomu 35 DWOK",                         "Przemyśl Voivodeship: Supplement to Volume 35 DWOK",                   9, 112, {380, 381, 382, 383, 384, 385, 386, 387}));
		m_kinfo.emplace(84,     KolbergInfo("Wołyń. Suplement do t. 36., Volhynia",                          "Supplement to Volume 36",                                             35,  97, {313, 314, 315, 316, 317, 318, 319, 320}));


	}

	auto it = m_kinfo.find(volume);
	if (it != m_kinfo.end()) {
		return it->second;
	} else {
		return KolbergInfo();
	}
}



//////////////////////////////
//
// Tool_esac::getKolbergUrl --
//

string Tool_esac2hum::getKolbergUrl(int volume) {
	if ((volume < 1) || (volume > 84)) {
		// Such a volume does not exist, return empty string.
		return "";
	}

	stringstream ss;
	ss << std::setw(3) << std::setfill('0') << volume;
	// not https://
	string url = "http://www.oskarkolberg.pl/MediaFiles/";
	url += ss.str();
	url += "dwok.pdf";

	KolbergInfo kinfo = getKolbergInfo(volume);
	if (kinfo.titlePL.empty()) {
		// Do not have the page number info for volume, so just give URL for the volume.
		return url;
	}

	string pageinfo = m_score.m_params["_page"];
	int printPage = 0;
	if (!pageinfo.empty()) {
		HumRegex hre;
		if (hre.search(pageinfo, "(\\d+)")) {
			printPage = hre.getMatchInt(1);
		} else {
		     cerr << "XX PRINT PAGE: " << printPage << "\t_page: " << pageinfo << endl;
		}
	} else {
		cerr << "YY PRINT PAGE: " << printPage << "\t_page: IS EMPTY: " << pageinfo << endl;
	}

	// Calculate the scan page that matches with the print page:
	int startPrintPage = kinfo.firstPrintPage;
	int startScanPage  = kinfo.firstScanPage;
	int scanPage = calculateScanPage(printPage, startPrintPage, startScanPage, kinfo.plates);

	url += "#page=" + to_string(scanPage);

	if (!kinfo.titlePL.empty()) {
		url += " @PL{Oskar Kolberg Dzieła Wszystkie " + to_string(volume) + ": " + kinfo.titlePL;
		url += ", s. " + pageinfo;
		url += "}";
	}

	if (!kinfo.titleEN.empty()) {
		url += " @EN{Oskar Kolberg Complete Works " + to_string(volume) + ": " + kinfo.titleEN;
		url += ", p";
		if (pageinfo.find("-") != string::npos) {
			url += "p";
		}
		url += ". " + pageinfo;
		url += "}";
	}

	if (kinfo.titlePL.empty() && kinfo.titleEN.empty()) {
		url += " @PL{Oskar Kolberg Dzieła Wszystike " + to_string(volume);
		url += " @PL{Oskar Kolberg Complete Works " + to_string(volume);
	}

	return url;
}



//////////////////////////////
//
// Tool_esac2hum::printKolbergPdfUrl --
//

void Tool_esac2hum::printKolbergPdfUrl(ostream& output) {
	string source = m_score.m_params["_source"];
	HumRegex hre;
	if (!hre.search(source, "^DWOK(\\d+)")) {
		return;
	}
	int volume = hre.getMatchInt(1);
	string url = getKolbergUrl(volume);
	if (!url.empty()) {
		output << "!!!URL-pdf: " << url << endl;
	}
}



//////////////////////////////
//
// Tool_esac2hum::calculateScanPage --
//

int Tool_esac2hum::calculateScanPage(int inputPrintPage, int printPage, int scanPage, const std::vector<int>& platePages) {
	int currentPrintPage = printPage;
	int currentScanPage = scanPage;
	size_t plateIndex = 0;

	// Iterate until we reach the input print page
	while (currentPrintPage < inputPrintPage) {
		++currentScanPage;  // Increment the scan page

		// Check if the current scan page matches the current plate page in the vector
		if (plateIndex < platePages.size() && currentScanPage == platePages[plateIndex]) {
			// Skip the plate page (increment scanPage but not printPage)
			++plateIndex;
		} else {
			// If not a plate page, increment the print page
			++currentPrintPage;
		}
	}

	return currentScanPage;
}




/////////////////////////////////
//
// Tool_esac2humold::Tool_esac2humold -- Set the recognized options for the tool.
//

Tool_esac2humold::Tool_esac2humold(void) {
	define("debug=b",            "print debug information");
	define("v|verbose=b",        "verbose output");
	define("h|header=s:",        "header filename for placement in output");
	define("t|trailer=s:",       "trailer filename for placement in output");
	define("s|split=s:file",     "split song info into separate files");
	define("x|extension=s:.krn", "split filename extension");
	define("f|first=i:1",        "number of first split filename");
	define("author=b",           "author of program");
	define("version=b",          "compilation info");
	define("example=b",          "example usages");
	define("help=b",             "short description");
}



//////////////////////////////
//
// Tool_esac2humold::convert -- Convert a MusicXML file into
//     Humdrum content.
//

bool Tool_esac2humold::convertFile(ostream& out, const string& filename) {
	ifstream file(filename);
	stringstream s;
	if (file) {
		s << file.rdbuf();
		file.close();
	}
	return convert(out, s.str());
}


bool Tool_esac2humold::convert(ostream& out, istream& input) {
	convertEsacToHumdrum(out, input);
	return true;
}


bool Tool_esac2humold::convert(ostream& out, const string& input) {
	stringstream ss;
	ss << input;
	convertEsacToHumdrum(out, ss);
	return true;
}




//////////////////////////////
//
// Tool_esac2humold::initialize --
//

bool Tool_esac2humold::initialize(void) {
	// handle basic options:
	if (getBoolean("author")) {
		cerr << "Written by Craig Stuart Sapp, "
			  << "craig@ccrma.stanford.edu, March 2002" << endl;
		return false;
	} else if (getBoolean("version")) {
		cerr << getCommand() << ", version: 6 June 2017" << endl;
		cerr << "compiled: " << __DATE__ << endl;
		return false;
	} else if (getBoolean("help")) {
		usage(getCommand());
		return false;
	} else if (getBoolean("example")) {
		example();
		return false;
	}

	debugQ   = getBoolean("debug");
	verboseQ = getBoolean("verbose");

	if (getBoolean("header")) {
		if (!getFileContents(header, getString("header"))) {
			return false;
		}
	} else {
		header.resize(0);
	}
	if (getBoolean("trailer")) {
		if (!getFileContents(trailer, getString("trailer"))) {
			return false;
		}
	} else {
		trailer.resize(0);
	}

	if (getBoolean("split")) {
		splitQ = 1;
	}
	namebase = getString("split");
	fileextension = getString("extension");
	firstfilenum = getInteger("first");
	return true;
}



//////////////////////////////////////////////////////////////////////////


//////////////////////////////
//
// Tool_esac2humold::convertEsacToHumdrum --
//

void Tool_esac2humold::convertEsacToHumdrum(ostream& output, istream& infile) {
	initialize();
	vector<string> song;
	song.reserve(400);
	int init = 0;
	// int filecounter = firstfilenum;
	string outfilename;
	string numberstring;
	// ofstream outfile;
	while (!infile.eof()) {
		if (debugQ) {
			cerr << "Getting a song..." << endl;
		}
		getSong(song, infile, init);
		if (debugQ) {
			cerr << "Got a song ..." << endl;
		}
		init = 1;
		convertSong(song, output);
	}
}



//////////////////////////////
//
// Tool_esac2humold::getSong -- get a song from the EsAC file
//

bool Tool_esac2humold::getSong(vector<string>& song, istream& infile, int init) {
	string holdbuffer;
	song.resize(0);
	if (init) {
		// do nothing holdbuffer has the CUT[] information
	} else {
		while (!infile.eof() && holdbuffer.compare(0, 4, "CUT[") != 0) {
			getline(infile, holdbuffer);
			if (verboseQ) {
				cerr << "Contents: " << holdbuffer << endl;
			}
			if (holdbuffer.compare(0, 2, "!!") == 0) {
				song.push_back(holdbuffer);
			}
		}
		if (infile.eof()) {
			return false;
		}
	}

	if (!infile.eof()) {
		song.push_back(holdbuffer);
	} else {
		return false;
	}

	getline(infile, holdbuffer);
	chopExtraInfo(holdbuffer);
	inputline++;
	if (verboseQ) {
		cerr << "READ LINE: " << holdbuffer << endl;
	}
	while (!infile.eof() && (holdbuffer.compare(0, 4, "CUT[", 4) != 0)) {
		song.push_back(holdbuffer);
		getline(infile, holdbuffer);
		chopExtraInfo(holdbuffer);
		inputline++;
		if (verboseQ) {
			cerr << "READ ANOTHER LINE: " << holdbuffer << endl;
		}
	}

	return true;
}



//////////////////////////////
//
// Tool_esac2humold::chopExtraInfo -- remove phrase number information from Luxembourg data.
//

void Tool_esac2humold::chopExtraInfo(string& buffer) {
	HumRegex hre;
	hre.replaceDestructive(buffer, "", "^\\s+");
	hre.replaceDestructive(buffer, "", "\\s+$");
}



//////////////////////////////
//
// Tool_esac2humold::printHumdrumHeaderInfo --
//

void Tool_esac2humold::printHumdrumHeaderInfo(ostream& out, vector<string>& song) {
	for (int i=0; i<(int)song.size(); i++) {
		if (song[i].size() == 0) {
			continue;
		}
		if (song[i].compare(0, 2, "!!") == 0) {
			out << song[i] << "\n";
			continue;
		}
		if ((song[i][0] == ' ') || (song[i][0] == '\t')) {
			continue;
		}
		break;
	}
}



//////////////////////////////
//
// Tool_esac2humold::printHumdrumFooterInfo --
//

void Tool_esac2humold::printHumdrumFooterInfo(ostream& out, vector<string>& song) {
	int i = 0;
	for (i=0; i<(int)song.size(); i++) {
		if (song[i].size() == 0) {
			continue;
		}
		if (song[i].compare(0, 2, "!!") == 0) {
			continue;
		}
		if ((song[i][0] == ' ') || (song[i][0] == '\t')) {
			continue;
		}
		break;
	}
	int j = i;
	for (j=i; j<(int)song.size(); j++) {
		if (song[j].compare(0, 2, "!!") == 0) {
			out << song[j] << "\n";
		}
	}
}



//////////////////////////////
//
// Tool_esac2humold::convertSong --
//

void Tool_esac2humold::convertSong(vector<string>& song, ostream& out) {

	int i;
	if (verboseQ) {
		for (i=0; i<(int)song.size(); i++) {
			out << song[i] << "\n";
		}
	}

	printHumdrumHeaderInfo(out, song);

	string key;
	double mindur = 1.0;
	string meter;
	int tonic = 0;
	getKeyInfo(song, key, mindur, tonic, meter, out);

	vector<NoteData> songdata;
	songdata.resize(0);
	songdata.reserve(1000);
	getNoteList(song, songdata, mindur, tonic);
	placeLyrics(song, songdata);

	vector<int> numerator;
	vector<int> denominator;
	getMeterInfo(meter, numerator, denominator);

	postProcessSongData(songdata, numerator, denominator);

	printTitleInfo(song, out);
	out << "!!!id: "    << key  << "\n";

	// check for presence of lyrics
	int textQ = 0;
	for (i=0; i<(int)songdata.size(); i++) {
		if (songdata[i].text !=  "") {
			textQ = 1;
			break;
		}
	}

	for (i=0; i<(int)header.size(); i++) {
		out << header[i] << "\n";
	}

	out << "**kern";
	if (textQ) {
		out << "\t**text";
	}
	out << "\n";

	printKeyInfo(songdata, tonic, textQ, out);
	for (i=0; i<(int)songdata.size(); i++) {
		printNoteData(songdata[i], textQ, out);
	}
	out << "*-";
	if (textQ) {
		out << "\t*-";
	}
	out << "\n";

	out << "!!!minrhy: ";
	out << Convert::durationFloatToRecip(mindur)<<"\n";
	out << "!!!meter";
	if (numerator.size() > 1) {
		out << "s";
	}
	out << ": "  << meter;
	if ((meter == "frei") || (meter == "Frei")) {
		out << " [unmetered]";
	} else if (meter.find('/') == string::npos) {
		out << " interpreted as [";
		for (i=0; i<(int)numerator.size(); i++) {
			out << numerator[i] << "/" << denominator[i];
			if (i < (int)numerator.size()-1) {
				out << ", ";
			}
		}
		out << "]";
	}
	out << "\n";

	printBibInfo(song, out);
	printSpecialChars(out);

	for (i=0; i<(int)songdata.size(); i++) {
		if (songdata[i].lyricerr) {
			out << "!!!RWG: Lyric placement mismatch "
				  << "in phrase (too many syllables) " << songdata[i].phnum << " ["
				  << key << "]\n";
			break;
		}
	}

	for (i=0; i<(int)trailer.size(); i++) {
		out << trailer[i] << "\n";
	}

	printHumdrumFooterInfo(out, song);

/*
	if (!splitQ) {
		out << "\n!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" << endl;
	}
*/
}



//////////////////////////////
//
// Tool_esac2humold::placeLyrics -- extract lyrics (if any) and place on correct notes
//

bool Tool_esac2humold::placeLyrics(vector<string>& song, vector<NoteData>& songdata) {
	int start = -1;
	int stop = -1;
	getLineRange(song, "TXT", start, stop);
	if (start < 0) {
		// no TXT[] field, so don't do anything
		return true;
	}
	int line = 0;
	vector<string> lyrics;
	string buffer;
	for (line=0; line<=stop-start; line++) {
		if (song[line+start].size() <= 4) {
			cerr << "Error: lyric line is too short!: "
				  << song[line+start] << endl;
			return false;
		}
		buffer = song[line+start].substr(4);
		if (line == stop - start) {
			auto loc = buffer.rfind(']');
			if (loc != string::npos) {
				buffer.resize(loc);
			}
		}
		if (buffer == "") {
			continue;
		}
		getLyrics(lyrics, buffer);
		cleanupLyrics(lyrics);
		placeLyricPhrase(songdata, lyrics, line);
	}

	return true;
}



//////////////////////////////
//
// Tool_esac2humold::cleanupLyrics -- add preceeding dashes, avoid starting *'s if any,
//    and convert _'s to spaces.
//

void Tool_esac2humold::cleanupLyrics(vector<string>& lyrics) {
	int length;
	int length2;
	int i, j, m;
	int lastsyl = 0;
	for (i=0; i<(int)lyrics.size(); i++) {
		length = (int)lyrics[i].size();
		for (j=0; j<length; j++) {
			if (lyrics[i][j] == '_') {
				lyrics[i][j] = ' ';
			}
		}

		if (i > 0) {
			if ((lyrics[i] != ".") &&
				 (lyrics[i] != "")  &&
				 (lyrics[i] != "%") &&
				 (lyrics[i] != "^") &&
				 (lyrics[i] != "|") &&
				 (lyrics[i] != " ")) {
				lastsyl = -1;
				for (m=i-1; m>=0; m--) {
					if ((lyrics[m] != ".") &&
						 (lyrics[m] != "")  &&
						 (lyrics[m] != "%") &&
						 (lyrics[i] != "^") &&
						 (lyrics[m] != "|") &&
						 (lyrics[m] != " ")) {
						lastsyl = m;
						break;
					}
				}
				if (lastsyl >= 0) {
					length2 = (int)lyrics[lastsyl].size();
					if (lyrics[lastsyl][length2-1] == '-') {
						for (j=0; j<=length; j++) {
							lyrics[i][length - j + 1] = lyrics[i][length - j];
						}
						lyrics[i][0] = '-';
					}
				}
			}
		}

		// avoid *'s on the start of lyrics by placing a space before
		// them if they exist.
		if (lyrics[i][0] == '*') {
			length = (int)lyrics[i].size();
			for (j=0; j<=length; j++) {
				lyrics[i][length - j + 1] = lyrics[i][length - j];
			}
			lyrics[i][0] = ' ';
		}

		// avoid !'s on the start of lyrics by placing a space before
		// them if they exist.
		if (lyrics[i][0] == '!') {
			length = (int)lyrics[i].size();
			for (j=0; j<=length; j++) {
				lyrics[i][length - j + 1] = lyrics[i][length - j];
			}
			lyrics[i][0] = ' ';
		}

	}

}



///////////////////////////////
//
// Tool_esac2humold::getLyrics -- extract the lyrics from the text string.
//

void Tool_esac2humold::getLyrics(vector<string>& lyrics, const string& buffer) {
	lyrics.resize(0);
	int zero1 = 0;
	string current;
	int zero2 = 0;
	zero2 = zero1 + zero2;

	int length = (int)buffer.size();
	int i;

	i = 0;
	while (i<length) {
		current = "";
		if (buffer[i] == ' ') {
			current = ".";
			lyrics.push_back(current);
			i++;
			continue;
		}

		while (i < length && buffer[i] != ' ') {
			current += buffer[i++];
		}
		lyrics.push_back(current);
		i++;
	}

}



//////////////////////////////
//
// Tool_esac2humold::placeLyricPhrase -- match lyrics from a phrase to the songdata.
//

bool Tool_esac2humold::placeLyricPhrase(vector<NoteData>& songdata, vector<string>& lyrics, int line) {
	int i = 0;
	int start = 0;
	int found = 0;

	if (lyrics.empty()) {
		return true;
	}

	// find the phrase to which the lyrics belongs
	for (i=0; i<(int)songdata.size(); i++) {
		if (songdata[i].phnum == line) {
			found = 1;
			break;
		}
	}
	start = i;

	if (!found) {
		cerr << "Error: cannot find music for lyrics line " << line << endl;
		cerr << "Error near input data line: " << inputline << endl;
		return false;
	}

	for (i=0; i<(int)lyrics.size() && i+start < (int)songdata.size(); i++) {
		if ((lyrics[i] == " ") || (lyrics[i] == ".") || (lyrics[i] == "")) {
			if (songdata[i+start].pitch < 0) {
				lyrics[i] = "%";
			} else {
				lyrics[i] = "|";
			}
			// lyrics[i] = ".";
		}
		songdata[i+start].text = lyrics[i];
		songdata[i+start].lyricnum = line;
		if (line != songdata[i+start].phnum) {
			songdata[i+start].lyricerr = 1;   // lyric does not line up with music
		}
	}

	return true;
}



//////////////////////////////
//
// Tool_esac2humold::printSpecialChars -- print high ASCII character table
//

void Tool_esac2humold::printSpecialChars(ostream& out) {
	int i;
	for (i=0; i<(int)chartable.size(); i++) {
		if (chartable[i]) {
		switch (i) {
			case 129:   out << "!!!RNB" << ": symbol: &uuml;  = u umlaut (UTF-8: "
							     << (char)0xc3 << (char)0xb3 << ")\n";    break;
			case 130:   out << "!!!RNB" << ": symbol: &eacute;= e acute  (UTF-8: "
							     << (char)0xc3 << (char)0xa9 << ")\n";    break;
			case 132:   out << "!!!RNB" << ": symbol: &auml;  = 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: &icirc; = 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: &auml;  = 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: &ouml;  = 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: &oacute;= o acute  (UTF-8: "
							     << (char)0xc3 << (char)0xb3 << ")\n";    break;
			case 163:   out << "!!!RNB" << ": symbol: &uacute;= 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: &Oacute;= O acute  (UTF-8: "
							     << (char)0xc3 << (char)0x93 << ")\n";    break;
			case 225:   out << "!!!RNB" << ": symbol: &szlig; = sz ligature (UTF-8: "
							     << (char)0xc3 << (char)0x9f << ")\n";    break;
			case 0xdf:  out << "!!!RNB" << ": symbol: &szlig; = 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: &auml;      = 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: &oacute;= o acute  (UTF-8: "
							     << (char)0xc3 << (char)0xb3 << ")\n";    break;
			case 252:   out << "!!!RNB" << ": symbol: &uuml;  = u umlaut (UTF-8: "
							     << (char)0xc3 << (char)0xbc << ")\n";    break;
//         default:
		}
		}
		chartable[i] = 0;
	}
}



//////////////////////////////
//
// Tool_esac2humold::printTitleInfo -- print the first line of the CUT[] field.
//

bool Tool_esac2humold::printTitleInfo(vector<string>& song, ostream& out) {
	int start = -1;
	int stop = -1;
	getLineRange(song, "CUT", start, stop);
	if (start == -1) {
		cerr << "Error: cannot find CUT[] field in song: " << song[0] << endl;
		return false;
	}

	string buffer;
	buffer = song[start].substr(4);
	if (buffer.back() == ']') {
		buffer.resize((int)buffer.size() - 1);
	}

	out << "!!!OTL: ";
	for (int i=0; i<(int)buffer.size(); i++) {
		printChar(buffer[i], out);
	}
	out << "\n";

	return true;
}



//////////////////////////////
//
// Tool_esac2humold::printChar -- print text characters, translating high-bit data
//    if required.
//

void Tool_esac2humold::printChar(unsigned char c, ostream& out) {
	out << c;
/*
	if (c < 128) {
		out << c;
	} else {
		chartable[c]++;
		switch (c) {
			case 129:   out << "&uuml;";    break;
			case 130:   out << "&eacute;";  break;
			case 132:   out << "&auml;";    break;
			case 134:   out << "$c";        break;
			case 136:   out << "$l";        break;
			case 140:   out << "&icirc;";   break;
			case 141:   out << "$X";        break;   // Z acute
			case 142:   out << "&auml;";    break;   // ?
			case 143:   out << "$C";        break;
			case 148:   out << "&ouml;";    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 << "&oacute;";  break;
			case 163:   out << "&uacute;";  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 << "&Oacute;";  break;
			case 225:   out << "&szlig;";   break;
			case 0xdf:  out << "&szlig;";   break;
			// Polish version:
			// case 228:   out << "$n";        break;
			// Luxembourg version (for some reason...)
			case 228:   out << "&auml;";        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 << "&oacute;";  break;  // 1250 encoding
			case 252:   out << "&uuml;";    break;
			default:    out << c;
		}
	}
*/
}



//////////////////////////////
//
// Tool_esac2humold::printKeyInfo --
//

void Tool_esac2humold::printKeyInfo(vector<NoteData>& songdata, int tonic, int textQ,
		ostream& out) {
	vector<int> pitches(40, 0);
	int pitchsum = 0;
	int pitchcount = 0;
	int i;
	for (i=0; i<(int)songdata.size(); 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
	vector<int> diatonic(7, 0);
	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];

	if (minor > major) {
		// minor key (or related mode)
		out  << "*" << Convert::base40ToKern(40 * 4 + tonic) << ":";
		if (textQ) {
			out  << "\t*" << Convert::base40ToKern(40 * 4 + tonic) << ":";
		}
		out << "\n";
	} else {
		// major key (or related mode)
		out  << "*" << Convert::base40ToKern(40 * 3 + tonic) << ":";
		if (textQ) {
			out  << "\t*" << Convert::base40ToKern(40 * 3 + tonic) << ":";
		}
		out << "\n";
	}

}


//////////////////////////////
//
// Tool_esac2humold::getAccidentalMax --
//

int Tool_esac2humold::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;
	}
}


//////////////////////////////
//
// Tool_esac2humold::postProcessSongData -- clean up data and do some interpreting.
//

void Tool_esac2humold::postProcessSongData(vector<NoteData>& songdata, vector<int>& numerator,
		vector<int>& denominator) {
	int i, j;
	// move phrase start markers off of rests and onto the
	// first note that it finds
	for (i=0; i<(int)songdata.size()-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=(int)songdata.size()-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=(int)songdata.size()-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.size() == 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<(int)songdata.size(); 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=(int)songdata.size()-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<(int)songdata.size(); i++) {
			if (songdata[i].bar == 1) {
				songdata[i].barnum = barnum++;
			}
		}

	} else if (numerator.size() == 1 && numerator[0] == -1) {
		// handle free meter

		// number the barline
		firstdur = dur;
		barnum = 1;
		for (i=0; i<(int)songdata.size(); i++) {
			if (songdata[i].bar == 1) {
				songdata[i].barnum = barnum++;
			}
		}

	} else {
		// handle multiple time signatures

		// get the duration of each type of meter:
		vector<double> meterdurs;
		meterdurs.resize(numerator.size());
		for (i=0; i<(int)meterdurs.size(); i++) {
			meterdurs[i] = 4.0 / denominator[i] * numerator[i];
		}

		// measure beat counts:
		dur = 0.0;
		for (i=(int)songdata.size()-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<(int)numerator.size(); 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<(int)songdata.size()-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<(int)numerator.size(); 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<(int)numerator.size(); 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<(int)numerator.size(); i++) {
			if (fabs(firstdur - meterdurs[i]) < 0.001) {
				barnum = 2;
				break;
			}
		}
		for (i=0; i<(int)songdata.size(); i++) {
			if (songdata[i].bar == 1) {
				songdata[i].barnum = barnum++;
			}
		}


	}

}



//////////////////////////////
//
// Tool_esac2humold::getMeterInfo --
//

void Tool_esac2humold::getMeterInfo(string& meter, vector<int>& numerator,
		vector<int>& denominator) {
	numerator.clear();
	denominator.clear();
	HumRegex hre;
	hre.replaceDestructive(meter, "", "^\\s+");
	hre.replaceDestructive(meter, "", "\\s+$");
	if (hre.search(meter, "^(\\d+)/(\\d+)$")) {
		numerator.push_back(hre.getMatchInt(1));
		denominator.push_back(hre.getMatchInt(2));
		return;
	}
	if (hre.search(meter, "^frei$", "i")) {
		numerator.push_back(-1);
		denominator.push_back(-1);
		return;
	}
	cerr << "NEED TO DEAL WITH METER: " << meter << endl;
}



//////////////////////////////
//
// Tool_esac2humold::getLineRange -- get the staring line and ending line of a data
//     field.  Returns -1 if the data field was not found.
//

void Tool_esac2humold::getLineRange(vector<string>& song, const string& field,
		int& start, int& stop) {
	string searchstring = field;;
	searchstring += "[";
	start = stop = -1;
	for (int i=0; i<(int)song.size(); i++) {
		auto loc = song[i].find(']');
		if (song[i].compare(0, searchstring.size(), searchstring) == 0) {
			start = i;
			if (loc != string::npos) {
				stop = i;
				break;
			}
		} else if ((start >= 0) && (loc != string::npos)) {
			stop = i;
			break;
		}
	}
}



//////////////////////////////
//
// Tool_esac2humold::getNoteList -- get a list of the notes and rests and barlines in
//    the MEL field.
//

bool Tool_esac2humold::getNoteList(vector<string>& song, vector<NoteData>& songdata, double mindur,
		int tonic) {
	songdata.resize(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].size() < 4) {
			cerr << "Error: invalid line in MEL[]: " << song[i] << endl;
			return false;
		}
		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 && (j < (int)song[i].size())) {
			// oldstate = state;
			switch (song[i][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][j] - '0'];
					state = STATE_NOTE;
					break;
				case 'O':
					degree =  major[0];
					state = STATE_NOTE;
					break;

				// Barline information:
				case ' ':
					state = STATE_BAR;
					if (song[i][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 : cerr << "Error: unknown character " << song[i][j]
							      << " on the line: " << song[i] << endl;
							 return false;
			}
			j++;
			switch (song[i][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][j+1] == ' ') nextstate = STATE_BAR;
					 else if (song[i][j+1] == '/') nextstate = -2;
					 break;
				case '\0':
					phend = 1;
					break;
				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[(int)songdata.size()-1].pitch;
					 if (songdata[(int)songdata.size()-1].tieend) {
						 songdata[(int)songdata.size()-1].tiecont = 1;
						 songdata[(int)songdata.size()-1].tieend = 0;
					 } else {
						 songdata[(int)songdata.size()-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.push_back(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 true;
				 }
			}
		}
		phnum++;
	}

	return true;
}



//////////////////////////////
//
// Tool_esac2humold::printNoteData --
//

void Tool_esac2humold::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";
	}
	if (data.phstart == 1) {
		out << "{";
	}
	if (data.slstart == 1) {
		out << "(";
	}
	if (data.tiestart == 1) {
		out << "[";
	}
	out << Convert::durationFloatToRecip(data.duration);
	if (data.pitch < 0) {
		out << "r";
	} else {
		out << Convert::base40ToKern(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 (data.text == "") {
			if (data.pitch < 0) {
				data.text = "%";
			} else {
				data.text = "|";
			}
		}
		if (data.pitch < 0 && (data.text.find('%') == string::npos)) {
			out << "%";
		}
		if (data.text == " *") {
			if (data.pitch < 0) {
				data.text = "%*";
			} else {
				data.text = "|*";
			}
		}
		if (data.text == "^") {
			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";
	}
}



//////////////////////////////
//
// Tool_esac2humold::getKeyInfo -- look for a KEY[] entry and extract the data.
//
// ggg fix this function
//

bool Tool_esac2humold::getKeyInfo(vector<string>& song, string& key, double& mindur,
		int& tonic, string& meter, ostream& out) {
	int i;
	for (i=0; i<(int)song.size(); i++) {
		if (song[i].compare(0, 4, "KEY[") == 0) {
			key = song[i][4]; // letter
			key += song[i][5]; // number
			key += song[i][6]; // number
			key += song[i][7]; // number
			key += song[i][8]; // number
			if (!isspace(song[i][9])) {
				key += song[i][9];  // optional letter (sometimes ' or ")
			}
			if (!isspace(song[i][10])) {
				key += song[i][10];  // illegal but possible extra letter
			}
			if (song[i][10] != ' ') {
				out << "!! Warning key field is not complete" << endl;
				out << "!!Key field: " << song[i] << endl;
			}

			mindur = (song[i][11] - '0') * 10 + (song[i][12] - '0');
			mindur = 4.0 / mindur;

			string tonicstr;
			if (song[i][14] != ' ') {
				tonicstr[0] = song[i][14];
				if (tolower(song[i][15]) == 'b') {
					tonicstr[1] = '-';
				} else {
					tonicstr[1] = song[i][15];
				}
				tonicstr[2] = '\0';
			} else {
				tonicstr = song[i][15];
			}

			// convert German notation to English for note names
			// Hopefully all references to B will mean English B-flat.
			if (tonicstr == "B") {
				tonicstr = "B-";
			}
			if (tonicstr == "H") {
				tonicstr = "B";
			}

			tonic = Convert::kernToBase40(tonicstr);
			if (tonic <= 0) {
				cerr << "Error: invalid tonic on line: " << song[i] << endl;
				return false;
			}
			tonic = tonic % 40;
			meter = song[i].substr(17);
			if (meter.back() != ']') {
				cerr << "Error with meter on line: " << song[i] << endl;
				cerr << "Meter area: " << meter << endl;
				cerr << "Expected ] as last character but found " << meter.back() << endl;
				return false;
			} else {
				meter.resize((int)meter.size() - 1);
			}
			return true;
		}
	}
	cerr << "Error: did not find a KEY field" << endl;
	return false;
}



///////////////////////////////
//
// Tool_esac2humold::getFileContents -- read a file into the array.
//

bool Tool_esac2humold::getFileContents(vector<string>& array, const string& filename) {
	ifstream infile(filename.c_str());
	array.reserve(100);
	array.resize(0);

	if (!infile.is_open()) {
		cerr << "Error: cannot open file: " << filename << endl;
		return false;
	}

	char holdbuffer[1024] = {0};

	infile.getline(holdbuffer, 256, '\n');
	while (!infile.eof()) {
		array.push_back(holdbuffer);
		infile.getline(holdbuffer, 256, '\n');
	}

	infile.close();
	return true;
}



//////////////////////////////
//
// Tool_esac2humold::example --
//

void Tool_esac2humold::example(void) {


}



//////////////////////////////
//
// Tool_esac2humold::usage --
//

void Tool_esac2humold::usage(const string& command) {

}



//////////////////////////////
//
// Tool_esac2humold::printBibInfo --
//

void Tool_esac2humold::printBibInfo(vector<string>& song, ostream& out) {
	int i, j;
	char buffer[32] = {0};
	int start = -1;
	int stop  = -1;
	int count = 0;
	string templine;

	for (i=0; i<(int)song.size(); i++) {
		if (song[i] == "") {
			continue;
		}
		if (song[i][0] != ' ') {
			if (song[i].size() < 4 || song[i][3] != '[') {
				if (song[i].compare(0, 2, "!!") != 0) {
					out << "!! " << song[i] << "\n";
				}
				continue;
			}
			strncpy(buffer, song[i].c_str(), 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;
			templine = "";
			for (j=start; j<=stop; j++) {
				if (song[j].size() < 4) {
					continue;
				}
				if (stop - start == 0) {
					templine = song[j].substr(4);
					auto loc = templine.find(']');
					if (loc != string::npos) {
						templine.resize(loc);
					}
					if (templine != "") {
						out << "!!!" << buffer << ": ";
						printString(templine, out);
						out << "\n";
					}

				} else if (j==start) {
					out << "!!!" << buffer << count++ << ": ";
					printString(song[j].substr(4), out);
					out << "\n";
				} else if (j==stop) {
					templine = song[j].substr(4);
					auto loc = templine.find(']');
					if (loc != string::npos) {
						templine.resize(loc);
					}
					if (templine != "") {
						out << "!!!" << buffer << count++ << ": ";
						printString(templine, out);
						out << "\n";
					}
				} else {
					out << "!!!" << buffer << count++ << ": ";
					printString(&(song[j][4]), out);
					out << "\n";
				}
			}
		}
	}
}



//////////////////////////////
//
// Tool_esac2humold::printString -- print characters in string.
//

void Tool_esac2humold::printString(const string& string, ostream& out) {
	for (int i=0; i<(int)string.size(); i++) {
		printChar(string[i], out);
	}
}





/////////////////////////////////
//
// Tool_extract::Tool_extract -- Set the recognized options for the tool.
//

Tool_extract::Tool_extract(void) {
	define("P|F|S|x|exclude=s:",        "remove listed spines from output");
	define("i=s:",                      "exclusive interpretation list to extract from input");
	define("I=s:",                      "exclusive interpretation exclusion list");
	define("f|p|s|field|path|spine=s:", "for extraction of particular spines");
	define("C|count=b",                 "print a count of the number of spines in file");
	define("c|cointerp=s:**kern",       "exclusive interpretation for cospines");
	define("g|grep=s:",                 "extract spines which match a given regex.");
	define("r|reverse=b",               "reverse order of spines by **kern group");
	define("R=s:**kern",                "reverse order of spine by exinterp group");
	define("t|trace=s:",                "use a trace file to extract data");
	define("e|expand=b",                "expand spines with subspines");
	define("k|kern=s",                  "extract by kern spine group");
	define("K|reverse-kern=s",          "extract by kern spine group top to bottom numbering");
	define("E|expand-interp=s:",        "expand subspines limited to exinterp");
	define("m|model|method=s:d",        "method for extracting secondary spines");
	define("M|cospine-model=s:d",       "method for extracting cospines");
	define("Y|no-editoral-rests=b",     "do not display yy marks on interpreted rests");
	define("n|name|b|blank=s:**blank",  "name if exinterp added with 0");
	define("no-empty|no-empties=b",     "suppress spines with only null data tokens");
	define("empty|empties=b",           "only keep spines with only null data tokens");
	define("spine-list=b",              "show spine list and then exit");
	define("no-rest|no-rests=b",        "remove **kern spines containing only rests (and their co-spines)");

	define("debug=b",                   "print debugging information");
	define("author=b",                  "author of the program");
	define("version=b",                 "compilation info");
	define("example=b",                 "example usages");
	define("h|help=b",                  "short description");
}



/////////////////////////////////
//
// Tool_extract::run -- Primary interfaces to the tool.
//

bool Tool_extract::run(HumdrumFileSet& infiles) {
	bool status = true;
	for (int i=0; i<infiles.getCount(); i++) {
		status &= run(infiles[i]);
	}
	return status;
}


bool Tool_extract::run(const string& indata, ostream& out) {
	HumdrumFile infile(indata);
	bool status = run(infile);
	if (hasAnyText()) {
		getAllText(out);
	} else {
		out << infile;
	}
	return status;
}


bool Tool_extract::run(HumdrumFile& infile, ostream& out) {
	bool status = run(infile);
	if (hasAnyText()) {
		getAllText(out);
	} else {
		out << infile;
	}
	return status;
}

//
// In-place processing of file:
//

bool Tool_extract::run(HumdrumFile& infile) {
	initialize(infile);
	processFile(infile);
	// Re-load the text for each line from their tokens.
	// infile.createLinesFromTokens();
	return true;
}



//////////////////////////////
//
// Tool_extract::processFile --
//

void Tool_extract::processFile(HumdrumFile& infile) {
	if (countQ) {
		m_free_text << infile.getMaxTrack() << endl;
		return;
	}
	if (expandQ) {
		expandSpines(field, subfield, model, infile, expandInterp);
	} else if (interpQ) {
		getInterpretationFields(field, subfield, model, infile, interps,
				interpstate);
	} else if (reverseQ) {
		reverseSpines(field, subfield, model, infile, reverseInterp);
	} else if (removerestQ) {
		fillFieldDataByNoRest(field, subfield, model, grepString, infile,
			interpstate);
	} else if (grepQ) {
		fillFieldDataByGrep(field, subfield, model, grepString, infile,
			interpstate);
	} else if (emptyQ) {
		fillFieldDataByEmpty(field, subfield, model, infile, interpstate);
	} else if (noEmptyQ) {
		fillFieldDataByNoEmpty(field, subfield, model, infile, interpstate);
	} else if (fieldQ || excludeQ) {
		fillFieldData(field, subfield, model, fieldstring, infile);
	}

	if (spineListQ) {
		m_free_text << "-s ";
		for (int i=0; i<(int)field.size(); i++) {
			m_free_text << field[i];
			if (i < (int)field.size() - 1) {
				m_free_text << ",";
			}
		}
		m_free_text << endl;
		return;
	}

	if (debugQ && !traceQ) {
		m_free_text << "!! Field Expansion List:";
		for (int j=0; j<(int)field.size(); j++) {
			m_free_text << " " << field[j];
			if (subfield[j]) {
				m_free_text << (char)subfield[j];
			}
			if (model[j]) {
				m_free_text << (char)model[j];
			}
		}
		m_free_text << endl;
	}

	// preserve SEGMENT filename if present (now printed in main())
	// infile.printNonemptySegmentLabel(m_humdrum_text);

	// analyze the input file according to command-line options
	if (fieldQ || grepQ || removerestQ) {
		extractFields(infile, field, subfield, model);
	} else if (excludeQ) {
		excludeFields(infile, field, subfield, model);
	} else if (traceQ) {
		extractTrace(infile, tracefile);
	} else {
		m_humdrum_text << infile;
	}
}



//////////////////////////////
//
// Tool_extract::getNullDataTracks --
//

vector<int> Tool_extract::getNullDataTracks(HumdrumFile& infile) {
	vector<int> output(infile.getMaxTrack() + 1, 1);
	for (int i=0; i<infile.getLineCount(); i++) {
		if (!infile[i].isData()) {
			continue;
		}
		for (int j=0; j<infile[i].getFieldCount(); j++) {
			HTp token = infile.token(i, j);
			int track = token->getTrack();
			if (!output[track]) {
				continue;
			}
			if (!token->isNull()) {
				output[track] = 0;
			}
		}
		// maybe exit here if all tracks are non-null
	}

	return output;
}



//////////////////////////////
//
// Tool_extract::fillFieldDataByEmpty -- Only keep the spines which contain only
//    null data tokens.
//

void Tool_extract::fillFieldDataByEmpty(vector<int>& field, vector<int>& subfield,
		vector<int>& model, HumdrumFile& infile, int negate) {

	field.reserve(infile.getMaxTrack()+1);
	subfield.reserve(infile.getMaxTrack()+1);
	model.reserve(infile.getMaxTrack()+1);
	field.resize(0);
	subfield.resize(0);
	model.resize(0);
	vector<int> nullTrack = getNullDataTracks(infile);

	int zero = 0;
	for (int i=1; i<(int)nullTrack.size(); i++) {
		if (negate) {
			if (!nullTrack[i]) {
				field.push_back(i);
				subfield.push_back(zero);
				model.push_back(zero);
			}
		} else {
			if (nullTrack[i]) {
				field.push_back(i);
				subfield.push_back(zero);
				model.push_back(zero);
			}
		}
	}

}



//////////////////////////////
//
// Tool_extract::fillFieldDataByNoEmpty -- Only keep spines which are not all
//   null data tokens.
//

void Tool_extract::fillFieldDataByNoEmpty(vector<int>& field, vector<int>& subfield,
		vector<int>& model, HumdrumFile& infile, int negate) {

	field.reserve(infile.getMaxTrack()+1);
	subfield.reserve(infile.getMaxTrack()+1);
	model.reserve(infile.getMaxTrack()+1);
	field.resize(0);
	subfield.resize(0);
	model.resize(0);
	vector<int> nullTrack = getNullDataTracks(infile);
	for (int i=1; i<(int)nullTrack.size(); i++) {
		nullTrack[i] = !nullTrack[i];
	}

	int zero = 0;
	for (int i=1; i<(int)nullTrack.size(); i++) {
		if (negate) {
			if (!nullTrack[i]) {
				field.push_back(i);
				subfield.push_back(zero);
				model.push_back(zero);
			}
		} else {
			if (nullTrack[i]) {
				field.push_back(i);
				subfield.push_back(zero);
				model.push_back(zero);
			}
		}
	}
}



//////////////////////////////
//
// Tool_extract::fillFieldDataByNoRest --  Find the spines which
//    contain only rests and remove them.  Also remove cospines (non-kern spines
//    to the right of the kern spine containing only rests).  If there are
//    *part# interpretations in the data, then any spine which is all rests
//    will not be removed if there is another **kern spine with the same
//    part number if it is also not all rests.
//

void Tool_extract::fillFieldDataByNoRest(vector<int>& field, vector<int>& subfield,
		vector<int>& model, const string& searchstring, HumdrumFile& infile,
		int state) {

	field.clear();
	subfield.clear();
	model.clear();


	// Check every **kern spine for any notes.  If there is a note
	// then the tracks variable for that spine will be marked
	// as non-zero.
	vector<int> tracks(infile.getMaxTrack() + 1, 0);
	int track;
	int partline = 0;
	bool dataQ = false;
	for (int i=0; i<infile.getLineCount(); i++) {
		if ((!partline) && (!dataQ) && infile[i].hasSpines()) {

		}
		if (!infile[i].isData()) {
			continue;
		}
		dataQ = true;
		for (int j=0; j<infile[i].getFieldCount(); j++) {
			HTp token = infile.token(i, j);
			if (!token->isKern()) {
				continue;
			}
			if (token->isNull()) {
				continue;
			}
			if (token->isRest()) {
				continue;
			}
			track = token->getTrack();
			tracks[track] = 1;
		}
	}

	// Go back and mark any empty spines as non-empty if they
	// are in a part that contains multiple staves. I.e., only
	// delete a staff if all staves for the part are empty.
	// There should be a single *part# line at the start of the
	// score.
	if (partline > 0) {
		vector<HTp> kerns;
		for (int i=0; i<infile[partline].getFieldCount(); i++) {
			HTp token = infile.token(partline, i);
			if (!token->isKern()) {
				continue;
			}
			kerns.push_back(token);
		}
		for (int i=0; i<(int)kerns.size(); i++) {
			for (int j=i+1; j<(int)kerns.size(); j++) {
				if (*kerns[i] != *kerns[j]) {
					continue;
				}
				if (kerns[i]->find("*part") == string::npos) {
					continue;
				}
				int track1 = kerns[i]->getTrack();
				int track2 = kerns[j]->getTrack();
				int state1 = tracks[track1];
				int state2 = tracks[track2];
				if ((state1 && !state2) || (state2 && !state1)) {
					// Prevent empty staff from being removed
					// from a multi-staff part:
					tracks[track1] = 1;
					tracks[track2] = 1;
				}
			}
		}
	}


	// deal with co-spines
	vector<HTp> sstarts;
	infile.getSpineStartList(sstarts);
	for (int i=0; i<(int)sstarts.size(); i++) {
		if (!sstarts[i]->isKern()) {
			track = sstarts[i]->getTrack();
			tracks[track] = 1;
		}
	}

	// remove co-spines attached to removed kern spines
	for (int i=0; i<(int)sstarts.size(); i++) {
		if (!sstarts[i]->isKern()) {
			continue;
		}
		if (tracks[sstarts[i]->getTrack()] != 0) {
			continue;
		}
		for (int j=i+1; j<(int)sstarts.size(); j++) {
			if (sstarts[j]->isKern()) {
				break;
			}
			track = sstarts[j]->getTrack();
			tracks[track] = 0;
		}
	}

	int zero = 0;
	for (int i=1; i<(int)tracks.size(); i++) {
		if (state != 0) {
			tracks[i] = !tracks[i];
		}
		if (tracks[i]) {
			field.push_back(i);
			subfield.push_back(zero);
			model.push_back(zero);
		}
	}

}



//////////////////////////////
//
// Tool_extract::fillFieldDataByGrep --
//

void Tool_extract::fillFieldDataByGrep(vector<int>& field, vector<int>& subfield,
		vector<int>& model, const string& searchstring, HumdrumFile& infile,
		int state) {

	field.reserve(infile.getMaxTrack()+1);
	subfield.reserve(infile.getMaxTrack()+1);
	model.reserve(infile.getMaxTrack()+1);
	field.resize(0);
	subfield.resize(0);
	model.resize(0);

	vector<int> tracks;
	tracks.resize(infile.getMaxTrack()+1);
	fill(tracks.begin(), tracks.end(), 0);
	HumRegex hre;
	int track;

	int i, j;
	for (i=0; i<infile.getLineCount(); i++) {
		if (!infile[i].hasSpines()) {
			continue;
		}
		for (j=0; j<infile[i].getFieldCount(); j++) {
			if (hre.search(infile.token(i, j), searchstring, "")) {
				track = infile[i].token(j)->getTrack();
				tracks[track] = 1;
			}
		}
	}

	int zero = 0;
	for (i=1; i<(int)tracks.size(); i++) {
		if (state != 0) {
			tracks[i] = !tracks[i];
		}
		if (tracks[i]) {
			field.push_back(i);
			subfield.push_back(zero);
			model.push_back(zero);
		}
	}
}



//////////////////////////////
//
// Tool_extract::getInterpretationFields --
//

void Tool_extract::getInterpretationFields(vector<int>& field, vector<int>& subfield,
		vector<int>& model, HumdrumFile& infile, string& interps, int state) {
	vector<string> sstrings; // search strings
	sstrings.reserve(100);
	sstrings.resize(0);

	int i, j, k;
	string buffer;
	buffer = interps;

	HumRegex hre;
	hre.replaceDestructive(buffer, "", "\\s+", "g");

	int start = 0;
	while (hre.search(buffer, start, "^([^,]+)")) {
		sstrings.push_back(hre.getMatch(1));
		start = hre.getMatchEndIndex(1);
	}

	if (debugQ) {
		m_humdrum_text << "!! Interpretation strings to search for: " << endl;
		for (i=0; i<(int)sstrings.size(); i++) {
			m_humdrum_text << "!!\t" << sstrings[i] << endl;
		}
	}

	vector<int> tracks;
	tracks.resize(infile.getMaxTrack()+1);
	fill(tracks.begin(), tracks.end(), 0);

	// Algorithm below could be made more efficient by
	// not searching the entire file...
	for (i=0; i<infile.getLineCount(); i++) {
		if (!infile[i].isInterpretation()) {
			continue;
		}
		for (j=0; j<infile[i].getFieldCount(); j++) {
			for (k=0; k<(int)sstrings.size(); k++) {
				if (sstrings[k] == *infile.token(i, j)) {
					tracks[infile[i].token(j)->getTrack()] = 1;
				}
			}
		}
	}

	field.reserve(tracks.size());
	subfield.reserve(tracks.size());
	model.reserve(tracks.size());

	field.resize(0);
	subfield.resize(0);
	model.resize(0);

	int zero = 0;
	for (i=1; i<(int)tracks.size(); i++) {
		if (state == 0) {
			tracks[i] = !tracks[i];
		}
		if (tracks[i]) {
			field.push_back(i);
			subfield.push_back(zero);
			model.push_back(zero);
		}
	}

}



//////////////////////////////
//
// Tool_extract::expandSpines --
//

void Tool_extract::expandSpines(vector<int>& field, vector<int>& subfield, vector<int>& model,
		HumdrumFile& infile, string& interp) {

	vector<int> splits;
	splits.resize(infile.getMaxTrack()+1);
	fill(splits.begin(), splits.end(), 0);

	int i, j;
	for (i=0; i<infile.getLineCount(); i++) {
		if (!infile[i].isManipulator()) {
			continue;
		}

		for (j=0; j<infile[i].getFieldCount(); j++) {
			if (strchr(infile.token(i, j)->getSpineInfo().c_str(), '(') != NULL) {
				splits[infile[i].token(j)->getTrack()] = 1;
			}
		}
	}

	field.reserve(infile.getMaxTrack()*2);
	field.resize(0);

	subfield.reserve(infile.getMaxTrack()*2);
	subfield.resize(0);

	model.reserve(infile.getMaxTrack()*2);
	model.resize(0);

	bool allQ = interp.empty();

	vector<int> dummyfield;
	vector<int> dummysubfield;
	vector<int> dummymodel;
	getInterpretationFields(dummyfield, dummysubfield, model, infile, interp, 1);

	vector<int> interptracks;

	interptracks.resize(infile.getMaxTrack()+1);
	fill(interptracks.begin(), interptracks.end(), 0);

	for (i=0; i<(int)dummyfield.size(); i++) {
		interptracks[dummyfield[i]] = 1;
	}

	int aval = 'a';
	int bval = 'b';
	int zero = 0;
	for (i=1; i<(int)splits.size(); i++) {
		if (splits[i] && (allQ || interptracks[i])) {
			field.push_back(i);
			subfield.push_back(aval);
			model.push_back(zero);
			field.push_back(i);
			subfield.push_back(bval);
			model.push_back(zero);
		} else {
			field.push_back(i);
			subfield.push_back(zero);
			model.push_back(zero);
		}
	}

	if (debugQ) {
		m_humdrum_text << "!!expand: ";
		for (i=0; i<(int)field.size(); i++) {
			m_humdrum_text << field[i];
			if (subfield[i]) {
				m_humdrum_text << (char)subfield[i];
			}
			if (i < (int)field.size()-1) {
				m_humdrum_text << ",";
			}
		}
		m_humdrum_text << endl;
	}
}



//////////////////////////////
//
// Tool_extract::reverseSpines -- reverse the order of spines, grouped by the
//   given exclusive interpretation.
//

void Tool_extract::reverseSpines(vector<int>& field, vector<int>& subfield,
		vector<int>& model, HumdrumFile& infile, const string& exinterp) {

	vector<int> target;
	target.resize(infile.getMaxTrack()+1);
	fill(target.begin(), target.end(), 0);

	vector<HTp> trackstarts;
	infile.getSpineStartList(trackstarts);

	for (int t=0; t<(int)trackstarts.size(); t++) {
		if (trackstarts[t]->isDataType(exinterp)) {
			target.at(t + 1) = 1;
		}
	}

	field.reserve(infile.getMaxTrack()*2);
	field.resize(0);

	int lasti = (int)target.size();
	for (int i=(int)target.size()-1; i>0; i--) {
		if (target[i]) {
			lasti = i;
			field.push_back(i);
			for (int j=i+1; j<(int)target.size(); j++) {
				if (!target.at(j)) {
					field.push_back(j);
				} else {
					break;
				}
			}
		}
	}

	// if the grouping spine is not first, then preserve the
	// locations of the pre-spines.
	int extras = 0;
	if (lasti != 1) {
		extras = lasti - 1;
		field.resize(field.size()+extras);
		for (int i=0; i<(int)field.size()-extras; i++) {
			field[(int)field.size()-1-i] = field[(int)field.size()-1-extras-i];
		}
		for (int i=0; i<extras; i++) {
			field[i] = i+1;
		}
	}

	if (debugQ) {
		m_humdrum_text << "!!reverse: ";
		for (int i=0; i<(int)field.size(); i++) {
			m_humdrum_text << field[i] << " ";
		}
		m_humdrum_text << endl;
	}

	subfield.resize(field.size());
	fill(subfield.begin(), subfield.end(), 0);

	model.resize(field.size());
	fill(model.begin(), model.end(), 0);
}



//////////////////////////////
//
// Tool_extract::fillFieldData --
//

void Tool_extract::fillFieldData(vector<int>& field, vector<int>& subfield,
		vector<int>& model, string& fieldstring, HumdrumFile& infile) {

	int maxtrack = infile.getMaxTrack();

	field.reserve(maxtrack);
	field.resize(0);

	subfield.reserve(maxtrack);
	subfield.resize(0);

	model.reserve(maxtrack);
	model.resize(0);

	HumRegex hre;
	string buffer = fieldstring;
	hre.replaceDestructive(buffer, "", "\\s", "gs");
	int start = 0;
	string tempstr;
	vector<int> tempfield;
	vector<int> tempsubfield;
	vector<int> tempmodel;
	while (hre.search(buffer,  start, "^([^,]+,?)")) {
		tempfield.clear();
		tempsubfield.clear();
		tempmodel.clear();
		processFieldEntry(tempfield, tempsubfield, tempmodel, hre.getMatch(1), infile);
		start += hre.getMatchEndIndex(1);
		field.insert(field.end(), tempfield.begin(), tempfield.end());
		subfield.insert(subfield.end(), tempsubfield.begin(), tempsubfield.end());
		model.insert(model.end(), tempmodel.begin(), tempmodel.end());
	}
}



//////////////////////////////
//
// Tool_extract::processFieldEntry --
//   3-6 expands to 3 4 5 6
//   $   expands to maximum spine track
//   $-1 expands to maximum spine track minus 1, etc.
//

void Tool_extract::processFieldEntry(vector<int>& field,
		vector<int>& subfield, vector<int>& model, const string& astring,
		HumdrumFile& infile) {

	int finitsize = (int)field.size();
	int maxtrack = infile.getMaxTrack();

	vector<HTp> ktracks;
	infile.getKernSpineStartList(ktracks);
	int maxkerntrack = (int)ktracks.size();

	int modletter;
	int subletter;

	HumRegex hre;
	string buffer = astring;

	// remove any comma left at end of input astring (or anywhere else)
	hre.replaceDestructive(buffer, "", ",", "g");

	// first remove $ symbols and replace with the correct values
	if (kernQ) {
		removeDollarsFromString(buffer, maxkerntrack);
	} else {
		removeDollarsFromString(buffer, maxtrack);
	}

	int zero = 0;
	if (hre.search(buffer, "^(\\d+)-(\\d+)$")) {
		int firstone = hre.getMatchInt(1);
		int lastone  = hre.getMatchInt(2);

		if ((firstone < 1) && (firstone != 0)) {
			m_error_text << "Error: range token: \"" << astring << "\""
			             << " contains too small a number at start: " << firstone << endl;
			m_error_text << "Minimum number allowed is " << 1 << endl;
			return;
		}
		if ((lastone < 1) && (lastone != 0)) {
			m_error_text << "Error: range token: \"" << astring << "\""
			             << " contains too small a number at end: " << lastone << endl;
			m_error_text << "Minimum number allowed is " << 1 << endl;
			return;
		}
		if (firstone > maxtrack) {
			m_error_text << "Error: range token: \"" << astring << "\""
			             << " contains number too large at start: " << firstone << endl;
			m_error_text << "Maximum number allowed is " << maxtrack << endl;
			return;
		}
		if (lastone > maxtrack) {
			m_error_text << "Error: range token: \"" << astring << "\""
			             << " contains number too large at end: " << lastone << endl;
			m_error_text << "Maximum number allowed is " << maxtrack << endl;
			return;
		}

		if (firstone > lastone) {
			for (int i=firstone; i>=lastone; i--) {
				field.push_back(i);
				subfield.push_back(zero);
				model.push_back(zero);
			}
		} else {
			for (int i=firstone; i<=lastone; i++) {
				field.push_back(i);
				subfield.push_back(zero);
				model.push_back(zero);
			}
		}
	} else if (hre.search(buffer, "^(\\d+)([a-z]*)")) {
		int value = hre.getMatchInt(1);
		modletter = 0;
		subletter = 0;
		if (hre.getMatch(2) ==  "a") {
			subletter = 'a';
		}
		if (hre.getMatch(2) ==  "b") {
			subletter = 'b';
		}
		if (hre.getMatch(2) ==  "c") {
			subletter = 'c';
		}
		if (hre.getMatch(2) ==  "d") {
			modletter = 'd';
		}
		if (hre.getMatch(2) ==  "n") {
			modletter = 'n';
		}
		if (hre.getMatch(2) ==  "r") {
			modletter = 'r';
		}

		if ((value < 1) && (value != 0)) {
			m_error_text << "Error: range token: \"" << astring << "\""
			             << " contains too small a number at end: " << value << endl;
			m_error_text << "Minimum number allowed is " << 1 << endl;
			return;
		}
		if (value > maxtrack) {
			m_error_text << "Error: range token: \"" << astring << "\""
			             << " contains number too large at start: " << value << endl;
			m_error_text << "Maximum number allowed is " << maxtrack << endl;
			return;
		}
		field.push_back(value);
		if (value == 0) {
			subfield.push_back(zero);
			model.push_back(zero);
		} else {
			subfield.push_back(subletter);
			model.push_back(modletter);
		}
	}

	if (!kernQ) {
		return;
	}

	// Insert fields to next **kern spine.
	vector<int> newfield;
	vector<int> newsubfield;
	vector<int> newmodel;

	vector<HTp> trackstarts;
	infile.getTrackStartList(trackstarts);
	int spine;

	// convert kern tracks into spine tracks:
	for (int i=finitsize; i<(int)field.size(); i++) {
		if (field[i] > 0) {
			spine = ktracks[field[i]-1]->getTrack();
		   field[i] = spine;
		}
	}

	int startspineindex, stopspineindex;
	for (int i=0; i<(int)field.size(); i++) {
		newfield.push_back(field[i]); // copy **kern spine index into new list
		newsubfield.push_back(subfield[i]);
		newmodel.push_back(model[i]);

		// search for non **kern spines after specified **kern spine:
		startspineindex = field[i] + 1 - 1;
		stopspineindex = maxtrack;
		for (int j=startspineindex; j<stopspineindex; j++) {
			if (trackstarts[j]->isKern()) {
				break;
			}
			newfield.push_back(j+1);
			newsubfield.push_back(zero);
			newmodel.push_back(zero);
		}
	}

	field    = newfield;
	subfield = newsubfield;
	model    = newmodel;
}



//////////////////////////////
//
// Tool_extract::removeDollarsFromString -- substitute $ sign for maximum track count.
//

void Tool_extract::removeDollarsFromString(string& buffer, int maxtrack) {
	HumRegex hre;
	char buf2[128] = {0};
	int value2;

	if (hre.search(buffer, "\\$$")) {
		snprintf(buf2, 128, "%d", maxtrack);
		hre.replaceDestructive(buffer, buf2, "\\$$");
	}

	if (hre.search(buffer, "\\$(?![\\d-])")) {
		// don't know how this case could happen, however...
		snprintf(buf2, 128, "%d", maxtrack);
		hre.replaceDestructive(buffer, buf2, "\\$(?![\\d-])", "g");
	}

	if (hre.search(buffer, "\\$0")) {
		// replace $0 with maxtrack (used for reverse orderings)
		snprintf(buf2, 128, "%d", maxtrack);
		hre.replaceDestructive(buffer, buf2, "\\$0", "g");
	}

	while (hre.search(buffer, "\\$(-?\\d+)")) {
		value2 = maxtrack - abs(hre.getMatchInt(1));
		snprintf(buf2, 128, "%d", value2);
		hre.replaceDestructive(buffer, buf2, "\\$-?\\d+");
	}
}



//////////////////////////////
//
// Tool_extract::excludeFields -- print all spines except the ones in the list of fields.
//

void Tool_extract::excludeFields(HumdrumFile& infile, vector<int>& field,
		vector<int>& subfield, vector<int>& model) {
	int start = 0;
	for (int i=0; i<infile.getLineCount(); i++) {
		if (!infile[i].hasSpines()) {
			m_humdrum_text << infile[i] << '\n';
			continue;
		} else {
			start = 0;
			for (int j=0; j<infile[i].getFieldCount(); j++) {
				if (isInList(infile[i].token(j)->getTrack(), field)) {
					continue;
				}
				if (start != 0) {
					m_humdrum_text << '\t';
				}
				start = 1;
				m_humdrum_text << infile.token(i, j);
			}
			if (start != 0) {
				m_humdrum_text << endl;
			}
		}
	}
}



//////////////////////////////
//
// Tool_extract::extractFields -- print all spines in the list of fields.
//

void Tool_extract::extractFields(HumdrumFile& infile, vector<int>& field,
		vector<int>& subfield, vector<int>& model) {

	HumRegex hre;
	int start = 0;
	int target;
	int subtarget;
	int modeltarget;
	string spat;
	bool foundBarline = true;

	for (int i=0; i<infile.getLineCount(); i++) {
		if (!infile[i].hasSpines()) {
			m_humdrum_text << infile[i] << '\n';
			continue;
		}

		if (infile[i].isManipulator()) {
			dealWithSpineManipulators(infile, i, field, subfield, model);
			continue;
		}

		if (infile[i].isBarline()) {
			foundBarline = true;
		}

		start = 0;
		for (int t=0; t<(int)field.size(); t++) {
			target = field[t];
			subtarget = subfield[t];
			modeltarget = model[t];
			if (modeltarget == 0) {
				switch (subtarget) {
					case 'a':
					case 'b':
						modeltarget = submodel;
						break;
					case 'c':
						modeltarget = comodel;
				}
			}
			if (target == 0) {
				if (start != 0) {
					m_humdrum_text << '\t';
				}
				start = 1;
				if (!infile[i].isManipulator()) {
					if (infile[i].isLocalComment()) {
						m_humdrum_text << "!";
					} else if (infile[i].isBarline()) {
						m_humdrum_text << infile[i].token(0);
					} else if (infile[i].isData()) {
						if (foundBarline) {
							if (addRestsQ) {
								HumNum dur = infile[i].getDurationToBarline();
								m_humdrum_text << Convert::durationToRecip(dur);
							} else {
								m_humdrum_text << ".";
							}
						} else {
							m_humdrum_text << ".";
						}
						// interpretations handled in dealWithSpineManipulators()
						// [obviously not, so adding a blank one here
					} else if (infile[i].isInterpretation()) {
						HTp token = infile.token(i, 0);
						if (token->isExpansionLabel()) {
							m_humdrum_text << token;
						} else if (token->isExpansionList()) {
							m_humdrum_text << token;
						} else {
							if (addRestsQ) {
								printInterpretationForKernSpine(infile, i);
							} else {
								m_humdrum_text << "*";
							}
						}
					}
				}
			} else {
				for (int j=0; j<infile[i].getFieldCount(); j++) {
					if (infile[i].token(j)->getTrack() != target) {
						continue;
					}
					switch (subtarget) {
					case 'a':
						getSearchPat(spat, target, "a");
						if (hre.search(infile.token(i,j)->getSpineInfo(), spat) ||
								!hre.search(infile.token(i, j)->getSpineInfo(), "\\(")) {
							if (start != 0) {
								m_humdrum_text << '\t';
							}
							start = 1;
							m_humdrum_text << infile.token(i, j);
						}
						break;
					case 'b':
						getSearchPat(spat, target, "b");
						if (hre.search(infile.token(i, j)->getSpineInfo(), spat)) {
							if (start != 0) {
								m_humdrum_text << '\t';
							}
							start = 1;
							m_humdrum_text << infile.token(i, j);
						} else if (!hre.search(infile.token(i, j)->getSpineInfo(),
								"\\(")) {
							if (start != 0) {
								m_humdrum_text << '\t';
							}
							start = 1;
							dealWithSecondarySubspine(field, subfield, model, t,
									infile, i, j, modeltarget);
						}
						break;
					case 'c':
						if (start != 0) {
							m_humdrum_text << '\t';
						}
						start = 1;
						dealWithCospine(field, subfield, model, t, infile, i, j,
								modeltarget, modeltarget, cointerp);
						break;
					default:
						if (start != 0) {
							m_humdrum_text << '\t';
						}
						start = 1;
						m_humdrum_text << infile.token(i, j);
					}
				}
			}
		}

		if (infile[i].isData()) {
			foundBarline = false;
		}

		if (start != 0) {
			m_humdrum_text << endl;
		}
	}
}



//////////////////////////////
//
// Tool_extract::printInterpretationForKernSpine --
//

void Tool_extract::printInterpretationForKernSpine(HumdrumFile& infile, int index) {
	HTp kerntok = NULL;
	for (int j=0; j<infile[index].getFieldCount(); j++) {
		HTp token = infile.token(index, j);
		if (!token->isKern()) {
			continue;
		}
		kerntok = token;
		break;
	}

	if (kerntok == NULL) {
		m_humdrum_text << "*";
		return;
	}

	if (*kerntok == "*") {
		m_humdrum_text << kerntok;
		return;
	}

	if (kerntok->isKeySignature()) {
		m_humdrum_text << kerntok;
		return;
	}
	if (kerntok->isKeyDesignation()) {
		m_humdrum_text << kerntok;
		return;
	}
	if (kerntok->isTimeSignature()) {
		m_humdrum_text << kerntok;
		return;
	}
	if (kerntok->isMensurationSymbol()) {
		m_humdrum_text << kerntok;
		return;
	}
	if (kerntok->isTempo()) {
		m_humdrum_text << kerntok;
		return;
	}
	if (kerntok->isInstrumentName()) {
		m_humdrum_text << "*I\"";
		return;
	}
	if (kerntok->isInstrumentAbbreviation()) {
		m_humdrum_text << "*I'";
		return;
	}

	m_humdrum_text << "*";
}



//////////////////////////////
//
// Tool_extract::dealWithCospine -- extract the required token(s) from a co-spine.
//

void Tool_extract::dealWithCospine(vector<int>& field, vector<int>& subfield, vector<int>& model,
		int targetindex, HumdrumFile& infile, int line, int cospine,
		int comodel, int submodel, const string& cointerp) {

	vector<string> cotokens;
	cotokens.reserve(50);

	string buffer;
	int i, j, k;
	int index;

	if (infile[line].isInterpretation()) {
		m_humdrum_text << infile.token(line, cospine);
		return;
	}

	if (infile[line].isBarline()) {
		m_humdrum_text << infile.token(line, cospine);
		return;
	}

	if (infile[line].isLocalComment()) {
		m_humdrum_text << infile.token(line, cospine);
		return;
	}

	int count = infile[line].token(cospine)->getSubtokenCount();
	for (k=0; k<count; k++) {
		buffer = infile.token(line, cospine)->getSubtoken(k);
		cotokens.resize(cotokens.size()+1);
		index = (int)cotokens.size()-1;
		cotokens[index] = buffer;
	}

	vector<int> spineindex;
	vector<int> subspineindex;

	spineindex.reserve(infile.getMaxTrack()*2);
	spineindex.resize(0);

	subspineindex.reserve(infile.getMaxTrack()*2);
	subspineindex.resize(0);

	for (j=0; j<infile[line].getFieldCount(); j++) {
		if (infile.token(line, j)->isDataType(cointerp)) {
			continue;
		}
		if (*infile.token(line, j) == ".") {
			continue;
		}
		count = infile[line].token(j)->getSubtokenCount();
		for (k=0; k<count; k++) {
			buffer = infile[line].token(j)->getSubtoken(k);
			if (comodel == 'r') {
				if (buffer == "r") {
					continue;
				}
			}
			spineindex.push_back(j);
			subspineindex.push_back(k);
		}
	}

	if (debugQ) {
		m_humdrum_text << "\n!!codata:\n";
		for (i=0; i<(int)cotokens.size(); i++) {
			m_humdrum_text << "!!\t" << i << "\t" << cotokens[i];
			if (i < (int)spineindex.size()) {
				m_humdrum_text << "\tspine=" << spineindex[i];
				m_humdrum_text << "\tsubspine=" << subspineindex[i];
			} else {
				m_humdrum_text << "\tspine=.";
				m_humdrum_text << "\tsubspine=.";
			}
			m_humdrum_text << endl;
		}
	}

	string buff;

	int start = 0;
	for (i=0; i<(int)field.size(); i++) {
		if (infile.token(line, field[i])->isDataType(cointerp)) {
			continue;
		}

		for (j=0; j<infile[line].getFieldCount(); j++) {
			if (infile[line].token(j)->getTrack() != field[i]) {
				continue;
			}
			if (subfield[i] == 'a') {
				getSearchPat(buff, field[i], "a");
				if ((strchr(infile.token(line, j)->getSpineInfo().c_str(), '(') == NULL) ||
					(infile.token(line, j)->getSpineInfo().find(buff) != string::npos)) {
					printCotokenInfo(start, infile, line, j, cotokens, spineindex,
							subspineindex);
				}
			} else if (subfield[i] == 'b') {
				// this section may need more work...
				getSearchPat(buff, field[i], "b");
				if ((strchr(infile.token(line, j)->getSpineInfo().c_str(), '(') == NULL) ||
					(strstr(infile.token(line, j)->getSpineInfo().c_str(), buff.c_str()) != NULL)) {
					printCotokenInfo(start, infile, line, j, cotokens, spineindex,
							subspineindex);
				}
			} else {
				printCotokenInfo(start, infile, line, j, cotokens, spineindex,
					subspineindex);
			}
		}
	}
}



//////////////////////////////
//
// Tool_extract::printCotokenInfo --
//

void Tool_extract::printCotokenInfo(int& start, HumdrumFile& infile, int line, int spine,
		vector<string>& cotokens, vector<int>& spineindex,
		vector<int>& subspineindex) {
	int i;
	int found = 0;
	for (i=0; i<(int)spineindex.size(); i++) {
		if (spineindex[i] == spine) {
			if (start == 0) {
				start++;
			} else {
				m_humdrum_text << subtokenseparator;
			}
			if (i<(int)cotokens.size()) {
				m_humdrum_text << cotokens[i];
			} else {
				m_humdrum_text << ".";
			}
		found = 1;
		}
	}
	if (!found) {
		if (start == 0) {
			start++;
		} else {
			m_humdrum_text << subtokenseparator;
		}
		m_humdrum_text << ".";
	}
}



//////////////////////////////
//
// Tool_extract::dealWithSecondarySubspine -- what to print if a secondary spine
//     does not exist on a line.
//

void Tool_extract::dealWithSecondarySubspine(vector<int>& field, vector<int>& subfield,
		vector<int>& model, int targetindex, HumdrumFile& infile, int line,
		int spine, int submodel) {

	int& i = line;
	int& j = spine;

	HumRegex hre;
	string buffer;
	if (infile[line].isLocalComment()) {
		if ((submodel == 'n') || (submodel == 'r')) {
			m_humdrum_text << "!";
		} else {
			m_humdrum_text << infile.token(i, j);
		}
	} else if (infile[line].isBarline()) {
		m_humdrum_text << infile.token(i, j);
	} else if (infile[line].isInterpretation()) {
		if ((submodel == 'n') || (submodel == 'r')) {
			m_humdrum_text << "*";
		} else {
			m_humdrum_text << infile.token(i, j);
		}
	} else if (infile[line].isData()) {
		if (submodel == 'n') {
			m_humdrum_text << ".";
		} else if (submodel == 'r') {
			if (*infile.token(i, j) == ".") {
				m_humdrum_text << ".";
			} else if (infile.token(i, j)->find('q') != string::npos) {
				m_humdrum_text << ".";
			} else if (infile.token(i, j)->find('Q') != string::npos) {
				m_humdrum_text << ".";
			} else {
				buffer = *infile.token(i, j);
				if (hre.search(buffer, "{")) {
					m_humdrum_text << "{";
				}
				// remove secondary chord notes:
				hre.replaceDestructive(buffer, "", " .*");
				// remove unnecessary characters (such as stem direction):
				hre.replaceDestructive(buffer, "",
						"[^}pPqQA-Ga-g0-9.;%#nr-]", "g");
				// change pitch to rest:
				hre.replaceDestructive(buffer, "[A-Ga-g#n-]+", "r");
				// add editorial marking unless -Y option is given:
				if (editorialInterpretation != "") {
					if (hre.search(buffer, "rr")) {
						 hre.replaceDestructive(buffer, editorialInterpretation, "(?<=rr)");
						 hre.replaceDestructive(buffer, "r", "rr");
					} else {
						 hre.replaceDestructive(buffer, editorialInterpretation, "(?<=r)");
					}
				}
				m_humdrum_text << buffer;
			}
		} else {
			m_humdrum_text << infile.token(i, j);
		}
	} else {
		m_error_text << "Should not get to this line of code" << endl;
		return;
	}
}




//////////////////////////////
//
// Tool_extract::getSearchPat --
//

void Tool_extract::getSearchPat(string& spat, int target, const string& modifier) {
	if (modifier.size() > 20) {
		m_error_text << "Error in GetSearchPat" << endl;
		return;
	}
	spat.reserve(16);
	spat = "\\(";
	spat += to_string(target);
	spat += "\\)";
	spat += modifier;
}



//////////////////////////////
//
// Tool_extract::dealWithSpineManipulators -- check for proper Humdrum syntax of
//     spine manipulators (**, *-, *x, *v, *^) when creating the output.
//

void Tool_extract::dealWithSpineManipulators(HumdrumFile& infile, int line,
		vector<int>& field, vector<int>& subfield, vector<int>& model) {

	vector<int> vmanip;  // counter for *v records on line
	vmanip.resize(infile[line].getFieldCount());
	fill(vmanip.begin(), vmanip.end(), 0);

	vector<int> xmanip; // counter for *x record on line
	xmanip.resize(infile[line].getFieldCount());
	fill(xmanip.begin(), xmanip.end(), 0);

	int i = 0;
	int j;
	for (j=0; j<(int)vmanip.size(); j++) {
		if (*infile.token(line, j) == "*v") {
			vmanip[j] = 1;
		}
		if (*infile.token(line, j) == "*x") {
			xmanip[j] = 1;
		}
	}

	int counter = 1;
	for (i=1; i<(int)xmanip.size(); i++) {
		if ((xmanip[i] == 1) && (xmanip[i-1] == 1)) {
			xmanip[i] = counter;
			xmanip[i-1] = counter;
			counter++;
		}
	}

	counter = 1;
	i = 0;
	while (i < (int)vmanip.size()) {
		if (vmanip[i] == 1) {
			while ((i < (int)vmanip.size()) && (vmanip[i] == 1)) {
				vmanip[i] = counter;
				i++;
			}
			counter++;
		}
		i++;
	}

	vector<int> fieldoccur;  // nth occurance of an input spine in the output
	fieldoccur.resize(field.size());
	fill(fieldoccur.begin(), fieldoccur.end(), 0);

	vector<int> trackcounter; // counter of input spines occurances in output
	trackcounter.resize(infile.getMaxTrack()+1);
	fill(trackcounter.begin(), trackcounter.end(), 0);

	for (i=0; i<(int)field.size(); i++) {
		if (field[i] != 0) {
			trackcounter[field[i]]++;
			fieldoccur[i] = trackcounter[field[i]];
		}
	}

	vector<string> tempout;
	vector<int> vserial;
	vector<int> xserial;
	vector<int> fpos;     // input column of output spine

	tempout.reserve(1000);
	tempout.resize(0);

	vserial.reserve(1000);
	vserial.resize(0);

	xserial.reserve(1000);
	xserial.resize(0);

	fpos.reserve(1000);
	fpos.resize(0);

	string spat;
	string spinepat;
	HumRegex hre;
	int subtarget;
	int modeltarget;
	int xdebug = 0;
	int vdebug = 0;
	int suppress = 0;
	int target;
	int tval;
	for (int t=0; t<(int)field.size(); t++) {
		target = field[t];
		subtarget = subfield[t];
		modeltarget = model[t];
		if (modeltarget == 0) {
			switch (subtarget) {
				case 'a':
				case 'b':
					modeltarget = submodel;
					break;
				case 'c':
					modeltarget = comodel;
			}
		}
		suppress = 0;
		if (target == 0) {
			if (infile.token(line, 0)->compare(0, 2, "**") == 0) {
				storeToken(tempout, blankName);
				tval = 0;
				vserial.push_back(tval);
				xserial.push_back(tval);
				fpos.push_back(tval);
			} else if (*infile.token(line, 0) == "*-") {
				storeToken(tempout, "*-");
				tval = 0;
				vserial.push_back(tval);
				xserial.push_back(tval);
				fpos.push_back(tval);
			} else {
				storeToken(tempout, "*");
				tval = 0;
				vserial.push_back(tval);
				xserial.push_back(tval);
				fpos.push_back(tval);
			}
		} else {
			for (j=0; j<infile[line].getFieldCount(); j++) {
				if (infile[line].token(j)->getTrack() != target) {
					continue;
				}
		// filter by subfield
		if (subtarget == 'a') {
			getSearchPat(spat, target, "b");
			if (hre.search(infile.token(line, j)->getSpineInfo(), spat)) {
						continue;
			}
		} else if (subtarget == 'b') {
			getSearchPat(spat, target, "a");
			if (hre.search(infile.token(line, j)->getSpineInfo(), spat)) {
				continue;
			}
		}

				switch (subtarget) {
				case 'a':

					if (!hre.search(infile.token(line, j)->getSpineInfo(), "\\(")) {
						if (*infile.token(line, j)  == "*^") {
							 storeToken(tempout, "*");
						} else {
							 storeToken(tempout, *infile.token(line, j));
						}
					} else {
						getSearchPat(spat, target, "a");
						spinepat =  infile.token(line, j)->getSpineInfo();
						hre.replaceDestructive(spinepat, "\\(", "\\(", "g");
						hre.replaceDestructive(spinepat, "\\)", "\\)", "g");

						if ((*infile.token(line, j) == "*v") &&
							    (spinepat == spat)) {
							 storeToken(tempout, "*");
						} else {
							getSearchPat(spat, target, "b");
							if ((spinepat == spat) &&
									(*infile.token(line, j) ==  "*v")) {
								// do nothing
								suppress = 1;
							} else {
								storeToken(tempout, *infile.token(line, j));
							}
						}
					}

					break;
				case 'b':

					if (!hre.search(infile.token(line, j)->getSpineInfo(), "\\(")) {
						if (*infile.token(line, j) == "*^") {
							storeToken(tempout, "*");
						} else {
							storeToken(tempout, *infile.token(line, j));
						}
					} else {
						getSearchPat(spat, target, "b");
						spinepat = infile.token(line, j)->getSpineInfo();
						hre.replaceDestructive(spinepat, "\\(", "\\(", "g");
						hre.replaceDestructive(spinepat, "\\)", "\\)", "g");

						if ((*infile.token(line, j) ==  "*v") &&
								(spinepat == spat)) {
							storeToken(tempout, "*");
						} else {
							getSearchPat(spat, target, "a");
							if ((spinepat == spat) &&
									(*infile.token(line, j) == "*v")) {
								// do nothing
								suppress = 1;
							} else {
								storeToken(tempout, *infile.token(line, j));
							}
						}
					}

					break;
				case 'c':
					// work on later
					storeToken(tempout, *infile.token(line, j));
					break;
				default:
					storeToken(tempout, *infile.token(line, j));
				}

				if (suppress) {
					continue;
				}

				if (tempout[(int)tempout.size()-1] == "*x") {
					tval = fieldoccur[t] * 1000 + xmanip[j];
					xserial.push_back(tval);
					xdebug = 1;
				} else {
					tval = 0;
					xserial.push_back(tval);
				}

				if (tempout[(int)tempout.size()-1] == "*v") {
					tval = fieldoccur[t] * 1000 + vmanip[j];
					vserial.push_back(tval);
					vdebug = 1;
				} else {
					tval = 0;
					vserial.push_back(tval);
				}

				fpos.push_back(j);

			}
		}
	}

	if (debugQ && xdebug) {
		m_humdrum_text << "!! *x serials = ";
		for (int ii=0; ii<(int)xserial.size(); ii++) {
			m_humdrum_text << xserial[ii] << " ";
		}
		m_humdrum_text << "\n";
	}

	if (debugQ && vdebug) {
		m_humdrum_text << "!!LINE: " << infile[line] << endl;
		m_humdrum_text << "!! *v serials = ";
		for (int ii=0; ii<(int)vserial.size(); ii++) {
			m_humdrum_text << vserial[ii] << " ";
		}
		m_humdrum_text << "\n";
	}

	// check for proper *x syntax /////////////////////////////////
	for (i=0; i<(int)xserial.size()-1; i++) {
		if (!xserial[i]) {
			continue;
		}
		if (xserial[i] != xserial[i+1]) {
			if (tempout[i] == "*x") {
				xserial[i] = 0;
				tempout[i] = "*";
			}
		} else {
			i++;
		}
	}

	if ((tempout.size() == 1) || (xserial.size() == 1)) {
		// get rid of *x if there is only one spine in output
		if (xserial[0]) {
			xserial[0] = 0;
			tempout[0] = "*";
		}
	} else if ((int)xserial.size() > 1) {
		// check the last item in the list
		int index = (int)xserial.size()-1;
		if (tempout[index] == "*x") {
			if (xserial[index] != xserial[index-1]) {
				xserial[index] = 0;
				tempout[index] = "*";
			}
		}
	}

	// check for proper *v syntax /////////////////////////////////
	vector<int> vsplit;
	vsplit.resize((int)vserial.size());
	fill(vsplit.begin(), vsplit.end(), 0);

	// identify necessary line splits
	for (i=0; i<(int)vserial.size()-1; i++) {
		if (!vserial[i]) {
			continue;
		}
		while ((i<(int)vserial.size()-1) && (vserial[i]==vserial[i+1])) {
			i++;
		}
		if ((i<(int)vserial.size()-1) && vserial[i]) {
			if (vserial.size() > 1) {
				if (vserial[i+1]) {
					vsplit[i+1] = 1;
				}
			}
		}
	}

	// remove single *v spines:

	for (i=0; i<(int)vsplit.size()-1; i++) {
		if (vsplit[i] && vsplit[i+1]) {
			if (tempout[i] == "*v") {
				tempout[i] = "*";
				vsplit[i] = 0;
			}
		}
	}

	if (debugQ) {
		m_humdrum_text << "!!vsplit array: ";
		for (i=0; i<(int)vsplit.size(); i++) {
			m_humdrum_text << " " << vsplit[i];
		}
		m_humdrum_text << endl;
	}

	if (vsplit.size() > 0) {
		if (vsplit[(int)vsplit.size()-1]) {
			if (tempout[(int)tempout.size()-1] == "*v") {
				tempout[(int)tempout.size()-1] = "*";
				vsplit[(int)vsplit.size()-1] = 0;
			}
		}
	}

	int vcount = 0;
	for (i=0; i<(int)vsplit.size(); i++) {
		vcount += vsplit[i];
	}

	if (vcount) {
		printMultiLines(vsplit, vserial, tempout);
	}

	int start = 0;
	for (i=0; i<(int)tempout.size(); i++) {
		if (tempout[i] != "") {
			if (start != 0) {
				m_humdrum_text << "\t";
			}
			m_humdrum_text << tempout[i];
			start++;
		}
	}
	if (start) {
		m_humdrum_text << '\n';
	}
}



//////////////////////////////
//
// Tool_extract::printMultiLines -- print separate *v lines.
//

void Tool_extract::printMultiLines(vector<int>& vsplit, vector<int>& vserial,
		vector<string>& tempout) {
	int i;

	int splitpoint = -1;
	for (i=0; i<(int)vsplit.size(); i++) {
		if (vsplit[i]) {
			splitpoint = i;
			break;
		}
	}

	if (debugQ) {
		m_humdrum_text << "!!tempout: ";
		for (i=0; i<(int)tempout.size(); i++) {
			m_humdrum_text << tempout[i] << " ";
		}
		m_humdrum_text << endl;
	}

	if (splitpoint == -1) {
		return;
	}

	int start = 0;
	int printv = 0;
	for (i=0; i<splitpoint; i++) {
		if (tempout[i] != "") {
			if (start) {
				m_humdrum_text << "\t";
			}
			m_humdrum_text << tempout[i];
			start = 1;
			if (tempout[i] == "*v") {
				if (printv) {
					tempout[i] = "";
				} else {
					tempout[i] = "*";
					printv = 1;
				}
			} else {
				tempout[i] = "*";
			}
		}
	}

	for (i=splitpoint; i<(int)vsplit.size(); i++) {
		if (tempout[i] != "") {
			if (start) {
				m_humdrum_text << "\t";
			}
			m_humdrum_text << "*";
		}
	}

	if (start) {
		m_humdrum_text << "\n";
	}

	vsplit[splitpoint] = 0;

	printMultiLines(vsplit, vserial, tempout);
}



//////////////////////////////
//
// Tool_extract::storeToken --
//

void Tool_extract::storeToken(vector<string>& storage, const string& string) {
	storage.push_back(string);
}

void storeToken(vector<string>& storage, int index, const string& string) {
	storage[index] = string;
}



//////////////////////////////
//
// Tool_extract::isInList -- returns true if first number found in list of numbers.
//     returns the matching index plus one.
//

int Tool_extract::isInList(int number, vector<int>& listofnum) {
	int i;
	for (i=0; i<(int)listofnum.size(); i++) {
		if (listofnum[i] == number) {
			return i+1;
		}
	}
	return 0;

}



//////////////////////////////
//
// Tool_extract::getTraceData --
//

void Tool_extract::getTraceData(vector<int>& startline, vector<vector<int> >& fields,
		const string& tracefile, HumdrumFile& infile) {
	char buffer[1024] = {0};
	HumRegex hre;
	int linenum;
	startline.reserve(10000);
	startline.resize(0);
	fields.reserve(10000);
	fields.resize(0);

	ifstream input;
	input.open(tracefile.c_str());
	if (!input.is_open()) {
		m_error_text << "Error: cannot open file for reading: " << tracefile << endl;
		return;
	}

	string temps;
	vector<int> field;
	vector<int> subfield;
	vector<int> model;

	input.getline(buffer, 1024);
	while (!input.eof()) {
		if (hre.search(buffer, "^\\s*$")) {
			continue;
		}
		if (!hre.search(buffer, "(\\d+)")) {
			continue;
		}
		linenum = hre.getMatchInt(1);
		linenum--;  // adjust so that line 0 is the first line in the file
		temps = buffer;
		hre.replaceDestructive(temps, "", "\\d+");
		hre.replaceDestructive(temps, "", "[^,\\s\\d\\$\\-].*");  // remove any possible comments
		hre.replaceDestructive(temps, "", "\\s", "g");
		if (hre.search(temps, "^\\s*$")) {
			// no field data to process online
			continue;
		}
		startline.push_back(linenum);
		string ttemp = temps;
		fillFieldData(field, subfield, model, ttemp, infile);
		fields.push_back(field);
		input.getline(buffer, 1024);
	}

}



//////////////////////////////
//
// Tool_extract::extractTrace --
//

void Tool_extract::extractTrace(HumdrumFile& infile, const string& tracefile) {
	vector<int> startline;
	vector<vector<int> > fields;
	getTraceData(startline, fields, tracefile, infile);
	int i, j;

	if (debugQ) {
		for (i=0; i<(int)startline.size(); i++) {
			m_humdrum_text << "!!TRACE " << startline[i]+1 << ":\t";
			for (j=0; j<(int)fields[i].size(); j++) {
				m_humdrum_text << fields[i][j] << " ";
			}
			m_humdrum_text << "\n";
		}
	}


	if (startline.size() == 0) {
		for (i=0; i<infile.getLineCount(); i++) {
			if (!infile[i].hasSpines()) {
				m_humdrum_text << infile[i] << '\n';
			}
		}
		return;
	}

	for (i=0; i<startline[0]; i++) {
		if (!infile[i].hasSpines()) {
			m_humdrum_text << infile[i] << '\n';
		}
	}

	int endline;
	for (j=0; j<(int)startline.size(); j++) {
		if (j == (int)startline.size()-1) {
			endline = infile.getLineCount()-1;
		} else {
			endline = startline[j+1]-1;
		}
		for (i=startline[j]; i<endline; i++) {
			if (!infile[i].hasSpines()) {
				m_humdrum_text << infile[i] << '\n';
			} else {
				printTraceLine(infile, i, fields[j]);
			}
		}
	}
}



//////////////////////////////
//
// Tool_extract::printTraceLine --
//

void Tool_extract::printTraceLine(HumdrumFile& infile, int line, vector<int>& field) {
	int j;
	int t;
	int start = 0;
	int target;

	start = 0;
	for (t=0; t<(int)field.size(); t++) {
		target = field[t];
		for (j=0; j<infile[line].getFieldCount(); j++) {
			if (infile[line].token(j)->getTrack() != target) {
				continue;
			}
			if (start != 0) {
				m_humdrum_text << '\t';
			}
			start = 1;
			m_humdrum_text << infile.token(line, j);
		}
	}
	if (start != 0) {
		m_humdrum_text << endl;
	}
}



//////////////////////////////
//
// Tool_extract::example -- example usage of the sonority program
//

void Tool_extract::example(void) {
	m_free_text <<
	"					                                                          \n"
	<< endl;
}



//////////////////////////////
//
// Tool_extract::usage -- gives the usage statement for the sonority program
//

void Tool_extract::usage(const string& command) {
	m_free_text <<
	"					                                                          \n"
	<< endl;
}



//////////////////////////////
//
// Tool_extract::initialize --
//

void Tool_extract::initialize(HumdrumFile& infile) {
	// handle basic options:
	if (getBoolean("author")) {
		m_free_text << "Written by Craig Stuart Sapp, "
			  << "craig@ccrma.stanford.edu, Feb 2008" << endl;
		return;
	} else if (getBoolean("version")) {
		m_free_text << getArg(0) << ", version: Feb 2008" << endl;
		m_free_text << "compiled: " << __DATE__ << endl;
		return;
	} else if (getBoolean("help")) {
		usage(getCommand().c_str());
		return;
	} else if (getBoolean("example")) {
		example();
		return;
	}

	excludeQ    = getBoolean("x");
	interpQ     = getBoolean("i");
	interps     = getString("i");
	kernQ       = getBoolean("k");
	rkernQ      = getBoolean("K");

	interpstate = 1;
	if (!interpQ) {
		interpQ = getBoolean("I");
		interpstate = 0;
		interps = getString("I");
	}
	if (interps.size() > 0) {
		if (interps[0] != '*') {
			// Automatically add ** if not given on exclusive interpretation
			string tstring = "**";
			interps = tstring + interps;
		}
	}

	removerestQ = getBoolean("no-rest");
	noEmptyQ    = getBoolean("no-empty");
	emptyQ      = getBoolean("empty");
	fieldQ      = getBoolean("f");
	debugQ      = getBoolean("debug");
	countQ      = getBoolean("count");
	traceQ      = getBoolean("trace");
	tracefile   = getString("trace");
	reverseQ    = getBoolean("reverse");
	expandQ     = getBoolean("expand") || getBoolean("E");
	submodel    = getString("model").c_str()[0];
	cointerp    = getString("cointerp");
	comodel     = getString("cospine-model").c_str()[0];

	if (getBoolean("no-editoral-rests")) {
		editorialInterpretation = "";
	}

	if (interpQ) {
		fieldQ = true;
	}

	if (emptyQ) {
		fieldQ = true;
	}

	if (noEmptyQ) {
		fieldQ = true;
	}

	if (expandQ) {
		fieldQ = true;
		expandInterp = getString("expand-interp");
	}

	if (!reverseQ) {
		reverseQ = getBoolean("R");
		if (reverseQ) {
			reverseInterp = getString("R");
		}
	}

	if (reverseQ) {
		fieldQ = true;
	}

	if (excludeQ) {
		fieldstring = getString("x");
	} else if (fieldQ) {
		fieldstring = getString("f");
	} else if (kernQ) {
		fieldstring = getString("k");
		fieldQ = true;
	} else if (rkernQ) {
		fieldstring = getString("K");
		fieldQ = true;
		fieldstring = reverseFieldString(fieldstring, infile.getMaxTrack());
	}

	spineListQ = getBoolean("spine-list");
	grepQ      = getBoolean("grep");
	grepString = getString("grep");

	if (getBoolean("name")) {
		blankName = getString("name");
		if (blankName == "") {
			blankName = "**blank";
		} else if (blankName.compare(0, 2, "**") != 0) {
			if (blankName.compare(0, 1, "*") != 0) {
				blankName = "**" + blankName;
			} else {
				blankName = "*" + blankName;
			}
		}
		if (blankName == "**kern") {
			addRestsQ = true;
		}
	}

}


//////////////////////////////
//
// Tool_extract::reverseFieldString --  No dollar expansion for now.
//

string Tool_extract::reverseFieldString(const string& input, int maxval) {
	string output;
	string number;
	for (int i=0; i<(int)input.size(); i++) {
		if (isdigit(input[i])) {
			number += input[i];
			continue;
		} else {
			if (!number.empty()) {
				int value = (int)strtol(number.c_str(), NULL, 10);
				value = maxval - value + 1;
				output += to_string(value);
				output += input[i];
				number.clear();
			}
		}
	}
	if (!number.empty()) {
		int value = (int)strtol(number.c_str(), NULL, 10);
		value = maxval - value + 1;
		output += to_string(value);
	}
	return output;
}



//////////////////////////////
//
// Tool_fb::Tool_fb -- Set the recognized options for the tool.
//

Tool_fb::Tool_fb(void) {
	define("c|compound=b",                               "output reasonable figured bass numbers within octave");
	define("a|accidentals|accid|acc=b",                  "display accidentals in front of the numbers");
	define("b|base|base-track=i:1",                      "number of the base kern track (compare with -k)");
	define("i|intervallsatz=b",                          "display numbers under their voice instead of under the base staff");
	define("o|sort|order=b",                             "sort figured bass numbers by size");
	define("l|lowest=b",                                 "use lowest note as base note");
	define("n|normalize=b",                              "remove number 8 and doubled numbers; adds -co");
	define("r|reduce|abbreviate|abbr=b",                 "use abbreviated figures; adds -nco");
	define("t|ties=b",                                   "hide numbers without attack or changing base (needs -i)");
	define("f|figuredbass=b",                            "shortcut for -acorn3");
	define("3|hide-three=b",                             "hide number 3 if it has an accidental");
	define("m|negative=b",                               "show negative numbers");
	define("above=b",                                    "show numbers above the staff (**fba)");
	define("rate=s:",                                    "rate to display the numbers (use a **recip value, e.g. 4, 4.)");
	define("k|kern-tracks=s",                            "process only the specified kern spines");
	define("s|spine-tracks|spine|spines|track|tracks=s", "Process only the specified spines");
	define("hint=b",                                     "determine harmonic intervals with interval quality");
}



//////////////////////////////
//
// Tool_fb::run -- Do the main work of the tool.
//

bool Tool_fb::run(HumdrumFileSet &infiles) {
	bool status = true;
	for (int i = 0; i < infiles.getCount(); i++) {
		status &= run(infiles[i]);
	}
	return status;
}

bool Tool_fb::run(const string &indata, ostream &out) {
	HumdrumFile infile(indata);
	bool status = run(infile);
	if (hasAnyText()) {
		getAllText(out);
	} else {
		out << infile;
	}
	return status;
}

bool Tool_fb::run(HumdrumFile &infile, ostream &out) {
	bool status = run(infile);
	if (hasAnyText()) {
		getAllText(out);
	} else {
		out << infile;
	}
	return status;
}

bool Tool_fb::run(HumdrumFile &infile) {
	initialize();
	processFile(infile);
	return true;
}



//////////////////////////////
//
// Tool_fb::initialize --
//

void Tool_fb::initialize(void) {
	m_compoundQ      = getBoolean("compound");
	m_accidentalsQ   = getBoolean("accidentals");
	m_baseTrackQ     = getInteger("base");
	m_intervallsatzQ = getBoolean("intervallsatz");
	m_sortQ          = getBoolean("sort");
	m_lowestQ        = getBoolean("lowest");
	m_normalizeQ     = getBoolean("normalize");
	m_reduceQ        = getBoolean("reduce");
	m_attackQ        = getBoolean("ties");
	m_figuredbassQ   = getBoolean("figuredbass");
	m_hideThreeQ     = getBoolean("hide-three");
	m_showNegativeQ  = getBoolean("negative");
	m_aboveQ         = getBoolean("above");
	m_rateQ          = getString("rate");
	m_hintQ          = getBoolean("hint");

	if (getBoolean("spine-tracks")) {
		m_spineTracks = getString("spine-tracks");
	} else if (getBoolean("kern-tracks")) {
		m_kernTracks = getString("kern-tracks");
	}

	if (m_normalizeQ) {
		m_compoundQ = true;
		m_sortQ = true;
	}

	if (m_reduceQ) {
		m_normalizeQ = true;
		m_compoundQ = true;
		m_sortQ = true;
	}

	if (m_figuredbassQ) {
		m_reduceQ = true;
		m_normalizeQ = true;
		m_compoundQ = true;
		m_sortQ = true;
		m_accidentalsQ = true;
		m_hideThreeQ = true;
	}

	if (m_hintQ) {
		m_showNegativeQ = true;
		// m_lowestQ = true;
	}
}



//////////////////////////////
//
// Tool_fb::processFile --
//

void Tool_fb::processFile(HumdrumFile& infile) {

	NoteGrid grid(infile);

	vector<FiguredBassNumber*> numbers;

	vector<HTp> kernspines = infile.getKernSpineStartList();

	int maxTrack = infile.getMaxTrack();

	// Do nothing if base track not withing kern track range
	if (m_baseTrackQ < 1 || m_baseTrackQ > maxTrack) {
		return;
	}

	m_selectedKernSpines.resize(maxTrack + 1); // +1 is needed since track=0 is not used
	// By default, process all tracks:
	fill(m_selectedKernSpines.begin(), m_selectedKernSpines.end(), true);
	// Otherwise, select which **kern track, or spine tracks to process selectively:

	// Calculate which input spines to process based on -s or -k option:
	if (!m_kernTracks.empty()) {
		vector<int> ktracks = Convert::extractIntegerList(m_kernTracks, maxTrack);
		fill(m_selectedKernSpines.begin(), m_selectedKernSpines.end(), false);
		for (int i=0; i<(int)ktracks.size(); i++) {
			int index = ktracks[i] - 1;
			if ((index < 0) || (index >= (int)kernspines.size())) {
				continue;
			}
			int track = kernspines.at(ktracks[i] - 1)->getTrack();
			m_selectedKernSpines.at(track) = true;
		}
	} else if (!m_spineTracks.empty()) {
		infile.makeBooleanTrackList(m_selectedKernSpines, m_spineTracks);
	}

	vector<vector<int>> lastNumbers = {};
	lastNumbers.resize((int)grid.getVoiceCount());
	vector<vector<int>> currentNumbers = {};

	// Interate through the NoteGrid and fill the numbers vector with
	// all generated FiguredBassNumbers
	for (int i=0; i<(int)grid.getSliceCount(); i++) {
		currentNumbers.clear();
		currentNumbers.resize((int)grid.getVoiceCount());

		// Reset usedBaseKernTrack
		int usedBaseKernTrack = m_baseTrackQ;

		// Overwrite usedBaseKernTrack with the lowest voice index of the lowest pitched note
		if (m_lowestQ) {
			int lowestNotePitch = 99999;
			for (int k=0; k<(int)grid.getVoiceCount(); k++) {
				NoteCell* checkCell = grid.cell(k, i);
				HTp currentToken = checkCell->getToken();
				int initialTokenTrack = currentToken->getTrack();

				// Handle spine splits
				do {
					HTp resolvedToken = currentToken->resolveNull();
					int lowest = getLowestBase40Pitch(resolvedToken->getBase40Pitches());

					if (abs(lowest) < lowestNotePitch) {
						lowestNotePitch = abs(lowest);
						usedBaseKernTrack = k + 1;
					}

					HTp nextToken = currentToken->getNextField();
					if (nextToken && (initialTokenTrack == nextToken->getTrack())) {
						currentToken = nextToken;
					} else {
						// Break loop if nextToken is not the same track as initialTokenTrack
						break;
					}
				} while (currentToken);
			}
		}

		NoteCell* baseCell = grid.cell(usedBaseKernTrack - 1, i);

		// Ignore grace notes
		if (baseCell->getToken()->getOwner()->getDuration() == 0) {
			continue;
		}

		string keySignature = getKeySignature(infile, baseCell->getLineIndex());

		// Hide numbers if they do not match rhythmic position of --rate
		if (!m_rateQ.empty()) {
			// Get time signatures
			vector<pair<int, HumNum>> timeSigs;
			infile.getTimeSigs(timeSigs, baseCell->getToken()->getTrack());
			// Ignore numbers if they don't fit
			if (hideNumbersForTokenLine(baseCell->getToken(), timeSigs[baseCell->getLineIndex()])) {
				continue;
			}
		}


		HTp currentToken = baseCell->getToken();
		int initialTokenTrack = baseCell->getToken()->getTrack();
		int lowestBaseNoteBase40Pitch = 9999;

		// Handle spine splits
		do {
			HTp resolvedToken = currentToken->resolveNull();
			int lowest = getLowestBase40Pitch(resolvedToken->getBase40Pitches());

			// Ignore if base is a rest or silent note
			if ((lowest != 0) && (lowest != -1000) && (lowest != -2000)) {
				if(abs(lowest) < lowestBaseNoteBase40Pitch) {
					lowestBaseNoteBase40Pitch = abs(lowest);
				}
			}

			HTp nextToken = currentToken->getNextField();
			if (nextToken && (initialTokenTrack == nextToken->getTrack())) {
				currentToken = nextToken;
			} else {
				// Break loop if nextToken is not the same track as initialTokenTrack
				break;
			}
		} while (currentToken);

		// Ignore if base is a rest or silent note
		if ((lowestBaseNoteBase40Pitch == 0) || (lowestBaseNoteBase40Pitch == -1000) || (lowestBaseNoteBase40Pitch == -2000) || (lowestBaseNoteBase40Pitch == 9999)) {
			continue;
		}

		// Interate through each voice
		for (int j=0; j<(int)grid.getVoiceCount(); j++) {
			NoteCell* targetCell = grid.cell(j, i);

			// Ignore voice if track is not active by --kern-tracks or --spine-tracks
			if (m_selectedKernSpines.at(targetCell->getToken()->getTrack()) == false) {
				continue;
			}

			HTp currentToken = targetCell->getToken();
			int initialTokenTrack = targetCell->getToken()->getTrack();
			vector<FiguredBassNumber*> chordNumbers = {};

			// Handle spine splits
			do {
				HTp resolvedToken = currentToken->resolveNull();
				for (int subtokenBase40: resolvedToken->getBase40Pitches()) {

					// Ignore if target is a rest or silent note
					if ((subtokenBase40 == 0) || (subtokenBase40 == -1000) || (subtokenBase40 == -2000)) {
						continue;
					}

					// Ignore if same pitch as base voice
					if ((abs(lowestBaseNoteBase40Pitch) == abs(subtokenBase40)) && (baseCell->getToken()->getTrack() == initialTokenTrack)) {
						continue;
					}

					// Create FiguredBassNumber
					FiguredBassNumber* number = createFiguredBassNumber(abs(lowestBaseNoteBase40Pitch), abs(subtokenBase40), targetCell->getVoiceIndex(), targetCell->getLineIndex(), targetCell->isAttack(), keySignature);

					currentNumbers[j].push_back(number->m_number);
					chordNumbers.push_back(number);
				}

				HTp nextToken = currentToken->getNextField();
				if (nextToken && (initialTokenTrack == nextToken->getTrack())) {
						currentToken = nextToken;
				} else {
					// Break loop if nextToken is not the same track as initialTokenTrack
					break;
				}
			} while (currentToken);

			// Sort chord numbers by size
			sort(chordNumbers.begin(), chordNumbers.end(), [](FiguredBassNumber* a, FiguredBassNumber* b) -> bool {
				return a->m_number > b->m_number;
			});

			// Then add to numbers vector
			for (FiguredBassNumber*  num: chordNumbers) {
				if (lastNumbers[j].size() != 0) {
					// If a number belongs to a sustained note but the base note did change
					// the new numbers need to be displayable
					num->m_baseOfSustainedNoteDidChange = !num->m_isAttack && std::find(lastNumbers[j].begin(), lastNumbers[j].end(), num->m_number) == lastNumbers[j].end();
				}
				numbers.push_back(num);
			}
		}

		// Set current numbers as the new last numbers
		lastNumbers = currentNumbers;
	}

	string exinterp = m_aboveQ ? "**fba" : "**fb";

	if (m_hintQ) {
		exinterp = "**hint";
	}

	if (m_intervallsatzQ) {
		// Create **fb spine for each voice
		for (int voiceIndex = 0; voiceIndex < grid.getVoiceCount(); voiceIndex++) {
			vector<string> trackData = getTrackDataForVoice(voiceIndex, numbers, infile.getLineCount());
			if (voiceIndex + 1 < grid.getVoiceCount()) {
				int trackIndex = kernspines[voiceIndex + 1]->getTrack();
				infile.insertDataSpineBefore(trackIndex, trackData, ".", exinterp);
			} else {
				infile.appendDataSpine(trackData, ".", exinterp);
			}
		}
	} else {
		// Create **fb spine and bind it to the base voice
		vector<string> trackData = getTrackData(numbers, infile.getLineCount());
		if (m_baseTrackQ < grid.getVoiceCount()) {
			int trackIndex = kernspines[m_baseTrackQ]->getTrack();
			infile.insertDataSpineBefore(trackIndex, trackData, ".", exinterp);
		} else {
			infile.appendDataSpine(trackData, ".", exinterp);
		}
	}

	// Enables usage in verovio (`!!!filter: fb`)
	m_humdrum_text << infile;
}



//////////////////////////////
//
// Tool_fb::hideNumbersForTokenLine -- Checks if rhythmic position of line should display numbers
//

bool Tool_fb::hideNumbersForTokenLine(HTp token, pair<int, HumNum> timeSig) {
	// Get note duration from --rate option
	HumNum rateDuration = Convert::recipToDuration(m_rateQ);
	if (rateDuration.toFloat() != 0) {
		double timeSigBarDuration = timeSig.first * Convert::recipToDuration(to_string(timeSig.second.getInteger())).toFloat();
		double durationFromBarline = token->getDurationFromBarline().toFloat();
		// Handle upbeats
		if (token->getBarlineDuration().toFloat() < timeSigBarDuration) {
			// Fix durationFromBarline when current bar duration is shorter than
			// the bar duration of the time signature
			durationFromBarline = timeSigBarDuration - token->getDurationToBarline().toFloat();
		}
		// Checks if rhythmic position is divisible by rateDuration
		return fmod(durationFromBarline, rateDuration.toFloat()) != 0;
	}
	return false;
}



//////////////////////////////
//
// Tool_fb::getTrackData -- Create **fb spine data with formatted numbers for all voices
//

vector<string> Tool_fb::getTrackData(const vector<FiguredBassNumber*>& numbers, int lineCount) {
	vector<string> trackData;
	trackData.resize(lineCount);

	for (int i = 0; i < lineCount; i++) {
		vector<FiguredBassNumber*> sliceNumbers = filterFiguredBassNumbersForLine(numbers, i);
		if (sliceNumbers.size() > 0) {
			trackData[i] = formatFiguredBassNumbers(sliceNumbers);
		}
	}

	return trackData;
}



//////////////////////////////
//
// Tool_fb::getTrackDataForVoice -- Create **fb spine data with formatted numbers for passed voiceIndex
//

vector<string> Tool_fb::getTrackDataForVoice(int voiceIndex, const vector<FiguredBassNumber*>& numbers, int lineCount) {
	vector<string> trackData;
	trackData.resize(lineCount);

	for (int i = 0; i < lineCount; i++) {
		vector<FiguredBassNumber*> sliceNumbers = filterFiguredBassNumbersForLineAndVoice(numbers, i, voiceIndex);
		if (sliceNumbers.size() > 0) {
			trackData[i] = formatFiguredBassNumbers(sliceNumbers);
		}
	}

	return trackData;
}



//////////////////////////////
//
// Tool_fb::createFiguredBassNumber -- Create FiguredBassNumber from a NoteCell.
//    The figured bass number (num) is calculated with a base and target NoteCell
//    as well as a passed key signature.
//

FiguredBassNumber* Tool_fb::createFiguredBassNumber(int basePitchBase40, int targetPitchBase40, int voiceIndex, int lineIndex, bool isAttack, string keySignature) {

	// Calculate figured bass number
	int baseDiatonicPitch   = Convert::base40ToDiatonic(basePitchBase40);
	int targetDiatonicPitch = Convert::base40ToDiatonic(targetPitchBase40);
	int diff        = abs(targetDiatonicPitch) - abs(baseDiatonicPitch);
	int num;

	if ((baseDiatonicPitch == 0) || (targetDiatonicPitch == 0)) {
		num = 0;
	} else if (diff == 0) {
		num = 1;
	} else if (diff > 0) {
		num = diff + 1;
	} else {
		num = diff - 1;
	}

	// Transform key signature to lower case
	transform(keySignature.begin(), keySignature.end(), keySignature.begin(), [](unsigned char c) {
		return tolower(c);
	});

	char targetPitchName = Convert::kernToDiatonicLC(Convert::base40ToKern(targetPitchBase40));
	int targetAccidNr = Convert::base40ToAccidental(targetPitchBase40);
	string targetAccid;
	for (int i=0; i<abs(targetAccidNr); i++) {
		targetAccid += (targetAccidNr < 0 ? "-" : "#");
	}

	char basePitchName = Convert::kernToDiatonicLC(Convert::base40ToKern(basePitchBase40));
	int baseAccidNr = Convert::base40ToAccidental(basePitchBase40);
	string baseAccid;
	for (int i=0; i<abs(baseAccidNr); i++) {
		baseAccid += (baseAccidNr < 0 ? "-" : "#");
	}

	string accid = targetAccid;
	bool showAccid = false;

	// Show accidentals when they are not included in the key signature
	if ((targetAccidNr != 0) && (keySignature.find(targetPitchName + targetAccid) == std::string::npos)) {
		showAccid = true;
	}

	// Show natural accidentals when they are alterations of the key signature
	if ((targetAccidNr == 0) && (keySignature.find(targetPitchName + targetAccid) != std::string::npos)) {
		accid = "n";
		showAccid = true;
	}

	// Show accidentlas when pitch class of base and target is equal but alteration is different
	if (basePitchName == targetPitchName) {
		if (baseAccidNr == targetAccidNr) {
			showAccid = false;
		} else {
			accid = (targetAccidNr == 0) ? "n" : targetAccid;
			showAccid = true;
		}
	}

	string intervalQuality = getIntervalQuality(basePitchBase40, targetPitchBase40);

	FiguredBassNumber* number = new FiguredBassNumber(num, accid, showAccid, voiceIndex, lineIndex, isAttack, m_intervallsatzQ, intervalQuality, m_hintQ);

	return number;
}



//////////////////////////////
//
// Tool_fb::filterNegativeNumbers -- Hide negative numbers if m_showNegativeQ if not true
//

vector<FiguredBassNumber*> Tool_fb::filterNegativeNumbers(vector<FiguredBassNumber*> numbers) {

	vector<FiguredBassNumber*> filteredNumbers;

	bool mQ = m_showNegativeQ;
	copy_if(numbers.begin(), numbers.end(), back_inserter(filteredNumbers), [mQ](FiguredBassNumber* num) {
		return mQ ? true : (num->m_number > 0);
	});

	return filteredNumbers;
}



//////////////////////////////
//
// Tool_fb::filterFiguredBassNumbersForLine -- Find all FiguredBassNumber objects for a slice (line index) of the music.
//

vector<FiguredBassNumber*> Tool_fb::filterFiguredBassNumbersForLine(vector<FiguredBassNumber*> numbers, int lineIndex) {

	vector<FiguredBassNumber*> filteredNumbers;

	// filter numbers with passed lineIndex
	copy_if(numbers.begin(), numbers.end(), back_inserter(filteredNumbers), [lineIndex](FiguredBassNumber* num) {
		return num->m_lineIndex == lineIndex;
	});

	// sort by voiceIndex
	sort(filteredNumbers.begin(), filteredNumbers.end(), [](FiguredBassNumber* a, FiguredBassNumber* b) -> bool {
		return a->m_voiceIndex > b->m_voiceIndex;
	});

	return filterNegativeNumbers(filteredNumbers);
}



//////////////////////////////
//
// Tool_fb::filterFiguredBassNumbersForLineAndVoice --
//

vector<FiguredBassNumber*> Tool_fb::filterFiguredBassNumbersForLineAndVoice(vector<FiguredBassNumber*> numbers, int lineIndex, int voiceIndex) {

	vector<FiguredBassNumber*> filteredNumbers;

	// filter numbers with passed lineIndex and passed voiceIndex
	copy_if(numbers.begin(), numbers.end(), back_inserter(filteredNumbers), [lineIndex, voiceIndex](FiguredBassNumber* num) {
		return (num->m_lineIndex == lineIndex) && (num->m_voiceIndex == voiceIndex);
	});

	// sort by voiceIndex (probably not needed here)
	sort(filteredNumbers.begin(), filteredNumbers.end(), [](FiguredBassNumber* a, FiguredBassNumber* b) -> bool {
		return a->m_voiceIndex > b->m_voiceIndex;
	});

	return filterNegativeNumbers(filteredNumbers);
}



//////////////////////////////
//
// Tool_fb::formatFiguredBassNumbers -- Create a **fb data record string out of the passed FiguredBassNumber objects
//

string Tool_fb::formatFiguredBassNumbers(const vector<FiguredBassNumber*>& numbers) {

	vector<FiguredBassNumber*> formattedNumbers;

	// Normalize numbers (remove 8 and 1, sort by size, remove duplicate numbers)
	if (m_normalizeQ) {
		bool aQ = m_accidentalsQ;
		// remove 8 and 1 but keep them if they have an accidental
		copy_if(numbers.begin(), numbers.end(), back_inserter(formattedNumbers), [aQ](FiguredBassNumber* num) {
			return ((num->getNumberWithinOctave() != 8) && (num->getNumberWithinOctave() != 1)) || (aQ && num->m_showAccidentals);
		});
		// sort by size
		sort(formattedNumbers.begin(), formattedNumbers.end(), [](FiguredBassNumber* a, FiguredBassNumber* b) -> bool {
			return a->getNumberWithinOctave() < b->getNumberWithinOctave();
		});
		// remove duplicate numbers
		formattedNumbers.erase(unique(formattedNumbers.begin(), formattedNumbers.end(), [](FiguredBassNumber* a, FiguredBassNumber* b) {
			return a->getNumberWithinOctave() == b->getNumberWithinOctave();
		}), formattedNumbers.end());
	} else {
		formattedNumbers = numbers;
	}

	// Hide numbers if they have no attack
	if (m_intervallsatzQ && m_attackQ) {
		vector<FiguredBassNumber*> attackNumbers;
		copy_if(formattedNumbers.begin(), formattedNumbers.end(), back_inserter(attackNumbers), [](FiguredBassNumber* num) {
			return num->m_isAttack || num->m_baseOfSustainedNoteDidChange;
		});
		formattedNumbers = attackNumbers;
	}

	// Analysze before sorting
	if (m_compoundQ) {
		formattedNumbers = analyzeChordNumbers(formattedNumbers);
	}

	// Sort numbers by size
	if (m_sortQ) {
		bool cQ = m_compoundQ;
		sort(formattedNumbers.begin(), formattedNumbers.end(), [cQ](FiguredBassNumber* a, FiguredBassNumber* b) -> bool {
			// sort by getNumberWithinOctave if compoundQ is true otherwise sort by number
			return (cQ) ? a->getNumberWithinOctave() > b->getNumberWithinOctave() : a->m_number > b->m_number;
		});
	}

	if (m_reduceQ) {
		// Overwrite formattedNumbers with abbreviated numbers
		formattedNumbers = getAbbreviatedNumbers(formattedNumbers);
	}

	// join numbers
	string str = "";
	bool first = true;
	for (FiguredBassNumber* number: formattedNumbers) {
		string num = number->toString(m_compoundQ, m_accidentalsQ, m_hideThreeQ);
		if (num.length() > 0) {
			if (!first) str += " ";
			first = false;
			str += num;
		}
	}
	return str;
}



//////////////////////////////
//
// Tool_fb::getAbbreviatedNumbers -- Get abbreviated figured bass numbers
//    If no abbreviation is found all numbers will be shown

vector<FiguredBassNumber*> Tool_fb::getAbbreviatedNumbers(const vector<FiguredBassNumber*>& numbers) {

	vector<FiguredBassNumber*> abbreviatedNumbers;

	string numberString = getNumberString(numbers);

	// Check if an abbreviation exists for passed numbers
	auto it = find_if(FiguredBassAbbreviationMapping::s_mappings.begin(), FiguredBassAbbreviationMapping::s_mappings.end(), [&numberString](const FiguredBassAbbreviationMapping& abbr) {
		return abbr.m_str == numberString;
	});

	if (it != FiguredBassAbbreviationMapping::s_mappings.end()) {
		const FiguredBassAbbreviationMapping& abbr = *it;
		bool aQ = m_accidentalsQ;
		// Store numbers to display by the abbreviation mapping in abbreviatedNumbers
		copy_if(numbers.begin(), numbers.end(), back_inserter(abbreviatedNumbers), [&abbr, aQ](FiguredBassNumber* num) {
			const vector<int>& nums = abbr.m_numbers;
			// Show numbers if they are part of the abbreviation mapping or if they have an accidental
			return (find(nums.begin(), nums.end(), num->getNumberWithinOctave()) != nums.end()) || (num->m_showAccidentals && aQ);
		});

		return abbreviatedNumbers;
	}

	return numbers;
}



//////////////////////////////
//
// Tool_fb::analyzeChordNumbers -- Analyze chord numbers and improve them
//    Set m_convert2To9 to true when a 3 is included in the chord numbers.

vector<FiguredBassNumber*> Tool_fb::analyzeChordNumbers(const vector<FiguredBassNumber*>& numbers) {

	vector<FiguredBassNumber*> analyzedNumbers = numbers;

	// Check if compound numbers 3 is withing passed numbers (chord)
	auto it = find_if(analyzedNumbers.begin(), analyzedNumbers.end(), [](FiguredBassNumber* number) {
		return number->getNumberWithinOctave() == 3;
	});
	if (it != analyzedNumbers.end()) {
		for (auto &number : analyzedNumbers) {
			number->m_convert2To9 = true;
		}
	}

	return analyzedNumbers;
}



//////////////////////////////
//
// Tool_fb::getNumberString -- Get only the numbers (without accidentals) of passed FiguredBassNumbers
//

string Tool_fb::getNumberString(vector<FiguredBassNumber*> numbers) {
	// Sort numbers by size
	sort(numbers.begin(), numbers.end(), [](FiguredBassNumber* a, FiguredBassNumber* b) -> bool {
		return a->getNumberWithinOctave() > b->getNumberWithinOctave();
	});
	// join numbers
	string str = "";
	bool first = true;
	for (FiguredBassNumber* nr: numbers) {
		int num = nr->getNumberWithinOctave();
		if (num > 0) {
			if (!first) str += " ";
			first = false;
			str += to_string(num);
		}
	}
	return str;
}



//////////////////////////////
//
// Tool_fb::getKeySignature -- Get the key signature for a line index of the input file
//

string Tool_fb::getKeySignature(HumdrumFile& infile, int lineIndex) {
	string keySignature = "";
	[&] {
		for (int i = 0; i < infile.getLineCount(); i++) {
			if (i > lineIndex) {
				return;
			}
			HLp line = infile.getLine(i);
			for (int j = 0; j < line->getFieldCount(); j++) {
				if (line->token(j)->isKeySignature()) {
					keySignature = line->getTokenString(j);
				}
			}
		}
	}();
	return keySignature;
}



//////////////////////////////
//
// Tool_fb::getLowestBase40Pitch -- Get lowest base 40 pitch that is not a rest or silent
//    TODO: Handle negative values and sustained notes
//

int Tool_fb::getLowestBase40Pitch(vector<int> base40Pitches) {
	vector<int> filteredBase40Pitches;
	copy_if(base40Pitches.begin(), base40Pitches.end(), std::back_inserter(filteredBase40Pitches), [](int base40Pitch) {
		// Ignore if base is a rest or silent note
		return (base40Pitch != -1000) && (base40Pitch != -2000) && (base40Pitch != 0);
	});

	if (filteredBase40Pitches.size() == 0) {
		return -2000;
	}

	return *min_element(begin(filteredBase40Pitches), end(filteredBase40Pitches));
}



//////////////////////////////
//
// Tool_fb::getIntervalQuality -- Return interval quality prefix string
//

string Tool_fb::getIntervalQuality(int basePitchBase40, int targetPitchBase40) {

	int diff = (targetPitchBase40 - basePitchBase40) % 40;

	diff = diff < -2 ? abs(diff) : diff;

	// See https://wiki.ccarh.org/wiki/Base_40
	string quality;
	switch (diff) {
		// 1
		case -2:
		case 38:
			quality = "dd"; break;
		case -1:
		case 39:
			quality = "d"; break;
		case 0: quality = "P"; break;
		case 1: quality = "A"; break;
		case 2: quality = "AA"; break;

		// 2
		case 3: quality = "dd"; break;
		case 4: quality = "d"; break;
		case 5: quality = "m"; break;
		case 6: quality = "M"; break;
		case 7: quality = "A"; break;
		case 8: quality = "AA"; break;

		// 3
		case 9: quality = "dd"; break;
		case 10: quality = "d"; break;
		case 11: quality = "m"; break;
		case 12: quality = "M"; break;
		case 13: quality = "A"; break;
		case 14: quality = "AA"; break;

		// 4
		case 15: quality = "dd"; break;
		case 16: quality = "d"; break;
		case 17: quality = "P"; break;
		case 18: quality = "A"; break;
		case 19: quality = "AA"; break;

		case 20: quality = "<unused>"; break;

		// 5
		case 21: quality = "dd"; break;
		case 22: quality = "d"; break;
		case 23: quality = "P"; break;
		case 24: quality = "A"; break;
		case 25: quality = "AA"; break;

		// 6
		case 26: quality = "dd"; break;
		case 27: quality = "d"; break;
		case 28: quality = "m"; break;
		case 29: quality = "M"; break;
		case 30: quality = "A"; break;
		case 31: quality = "AA"; break;

		// 7
		case 32: quality = "dd"; break;
		case 33: quality = "d"; break;
		case 34: quality = "m"; break;
		case 35: quality = "M"; break;
		case 36: quality = "A"; break;
		case 37: quality = "AA"; break;

		default: quality = "?"; break;
	}

	return quality;

}



//////////////////////////////
//
// FiguredBassNumber::FiguredBassNumber -- Constructor
//

FiguredBassNumber::FiguredBassNumber(int num, string accid, bool showAccid, int voiceIdx, int lineIdx, bool isAtk, bool intervallsatz, string intervalQuality, bool hint) {
	m_number          = num;
	m_accidentals     = accid;
	m_voiceIndex      = voiceIdx;
	m_lineIndex       = lineIdx;
	m_showAccidentals = showAccid;
	m_isAttack        = isAtk;
	m_intervallsatz   = intervallsatz;
	m_intervalQuality = intervalQuality;
	m_hint            = hint;
}



//////////////////////////////
//
// FiguredBassNumber::toString -- Convert FiguredBassNumber to a string (accidental + number)
//

string FiguredBassNumber::toString(bool compoundQ, bool accidentalsQ, bool hideThreeQ) {
	int num = (compoundQ) ? getNumberWithinOctave() : m_number;
	if (m_hint) {
		return m_intervalQuality + to_string(abs(num));
	}
	string accid = (accidentalsQ && m_showAccidentals) ? m_accidentals : "";
	if (((num == 3) || (num == -3)) && accidentalsQ && m_showAccidentals && hideThreeQ) {
		return accid;
	}
	if (num > 0) {
		return accid + to_string(num);
	}
	if (num < 0) {
		return accid + "~" + to_string(abs(num));
	}
	return "";
}



//////////////////////////////
//
// FiguredBassNumber::getNumberWithinOctave -- Get a reasonable figured bass number
//    Replace 0 with 7 and -7
//    Replace 1 with 8 and -8
//    Replace 2 with 9 if it is a suspension of the ninth
//    Allow 1 (unisono) in intervallsatz

int FiguredBassNumber::getNumberWithinOctave(void) {
	int num = m_number % 7;

	// Replace 0 with 7 and -7
	if ((abs(m_number) > 0) && (m_number % 7 == 0)) {
		return m_number < 0 ? -7 : 7;
	}

	// Replace 1 with 8 and -8
	if (abs(num) == 1) {
		// Allow unisono in intervallsatz
		if (m_intervallsatz || m_hint) {
			if (abs(m_number) == 1) {
				return 1;
			}
		}
		return m_number < 0 ? -8 : 8;
	}

	// Replace 2 with 9 if m_convert2To9 is true (e.g. when a 3 is included in the chord numbers)
	if (m_convert2To9 && (num == 2)) {
		return 9;
	}

	return num;
}



//////////////////////////////
//
// FiguredBassAbbreviationMapping::FiguredBassAbbreviationMapping -- Constructor
//    Helper class to store the mappings for abbreviate figured bass numbers
//

FiguredBassAbbreviationMapping::FiguredBassAbbreviationMapping(string s, vector<int> n) {
	m_str = s;
	m_numbers = n;
}



//////////////////////////////
//
// FiguredBassAbbreviationMapping::s_mappings -- Mapping to abbreviate figured bass numbers
//

const vector<FiguredBassAbbreviationMapping> FiguredBassAbbreviationMapping::s_mappings = {
	FiguredBassAbbreviationMapping("3", {}),
	FiguredBassAbbreviationMapping("5", {}),
	FiguredBassAbbreviationMapping("5 3", {}),
	FiguredBassAbbreviationMapping("6 3", {6}),
	FiguredBassAbbreviationMapping("5 4", {4}),
	FiguredBassAbbreviationMapping("7 5 3", {7}),
	FiguredBassAbbreviationMapping("7 3", {7}),
	FiguredBassAbbreviationMapping("7 5", {7}),
	FiguredBassAbbreviationMapping("6 5 3", {6, 5}),
	FiguredBassAbbreviationMapping("6 4 3", {4, 3}),
	FiguredBassAbbreviationMapping("6 4 2", {4, 2}),
	FiguredBassAbbreviationMapping("9 5 3", {9}),
	FiguredBassAbbreviationMapping("9 5", {9}),
	FiguredBassAbbreviationMapping("9 3", {9}),
};



#define RUNTOOL(NAME, INFILE, COMMAND, STATUS)     \
	Tool_##NAME *tool = new Tool_##NAME;            \
	tool->process(COMMAND);                         \
	tool->run(INFILE);                              \
	if (tool->hasError()) {                         \
		status = false;                              \
		tool->getError(cerr);                        \
		delete tool;                                 \
		break;                                       \
	} else if (tool->hasHumdrumText()) {            \
		INFILE.readString(tool->getHumdrumText());   \
	}                                               \
	delete tool;

#define RUNTOOL2(NAME, INFILE1, INFILE2, COMMAND, STATUS) \
	Tool_##NAME *tool = new Tool_##NAME;            \
	tool->process(COMMAND);                         \
	tool->run(INFILE1, INFILE2);                    \
	if (tool->hasError()) {                         \
		status = false;                              \
		tool->getError(cerr);                        \
		delete tool;                                 \
		break;                                       \
	} else if (tool->hasHumdrumText()) {            \
		INFILE1.readString(tool->getHumdrumText());  \
	}                                               \
	delete tool;

#define RUNTOOLSET(NAME, INFILES, COMMAND, STATUS) \
	Tool_##NAME *tool = new Tool_##NAME;            \
	tool->process(COMMAND);                         \
	tool->run(INFILES);                             \
	if (tool->hasError()) {                         \
		status = false;                              \
		tool->getError(cerr);                        \
		delete tool;                                 \
		break;                                       \
	} else if (tool->hasHumdrumText()) {            \
		INFILES.readString(tool->getHumdrumText());  \
	}                                               \
	delete tool;

#define RUNTOOLSTREAM(NAME, INFILES, COMMAND, STATUS) \
	Tool_##NAME *tool = new Tool_##NAME;               \
	tool->process(COMMAND);                            \
	tool->run(INFILES);                                \
	if (tool->hasError()) {                            \
		status = false;                                 \
		tool->getError(cerr);                           \
		delete tool;                                    \
		break;                                          \
	} else if (tool->hasHumdrumText()) {               \
		INFILES.readString(tool->getHumdrumText());     \
	}                                                  \
	delete tool;



////////////////////////////////
//
// Tool_filter::Tool_filter -- Set the recognized options for the tool.
//

Tool_filter::Tool_filter(void) {
	define("debug=b",      "print debug statement");
	define("v|variant=s:", "Run filters labeled with the given variant");
}



/////////////////////////////////
//
// Tool_filter::run -- Primary interfaces to the tool.
//

bool Tool_filter::run(const string& indata) {
	HumdrumFileSet infiles(indata);
	bool status = run(infiles);
	return status;
}


bool Tool_filter::run(HumdrumFile& infile) {
	HumdrumFileSet infiles;
	infiles.appendHumdrumPointer(&infile);
	bool status = run(infiles);
	infiles.clearNoFree();
	return status;
}

bool Tool_filter::runUniversal(HumdrumFileSet& infiles) {
	bool status = true;
	vector<pair<string, string> > commands;
	getUniversalCommandList(commands, infiles);

	for (int i=0; i<(int)commands.size(); i++) {
		if (commands[i].first == "humdiff") {
			RUNTOOLSET(humdiff, infiles, commands[i].second, status);
		} else if (commands[i].first == "chooser") {
			RUNTOOLSET(chooser, infiles, commands[i].second, status);
		} else if (commands[i].first == "myank") {
			RUNTOOL(myank, infiles, commands[i].second, status);
		}
	}

	removeUniversalFilterLines(infiles);

	return status;
}


//
// In-place processing of file:
//

bool Tool_filter::run(HumdrumFileSet& infiles) {
	if (infiles.getCount() == 0) {
		return false;
	}

	initialize(infiles[0]);

	HumdrumFile& infile = infiles[0];

	#ifdef __EMSCRIPTEN__
	bool optionList = getBoolean("options");
	if (optionList) {
		printEmscripten(m_humdrum_text);
		m_humdrum_text << infile;
	}
	#endif

	bool status = true;
	vector<pair<string, string> > commands;
	getCommandList(commands, infile);
	for (int i=0; i<(int)commands.size(); i++) {
		if (commands[i].first == "addic") {
			RUNTOOL(addic, infile, commands[i].second, status);
		} else if (commands[i].first == "addkey") {
			RUNTOOL(addkey, infile, commands[i].second, status);
		} else if (commands[i].first == "addlabels") {
			RUNTOOL(addlabels, infile, commands[i].second, status);
		} else if (commands[i].first == "addtempo") {
			RUNTOOL(addtempo, infile, commands[i].second, status);
		} else if (commands[i].first == "autoaccid") {
			RUNTOOL(autoaccid, infile, commands[i].second, status);
		} else if (commands[i].first == "autobeam") {
			RUNTOOL(autobeam, infile, commands[i].second, status);
		} else if (commands[i].first == "autocadence") {
			RUNTOOL(autocadence, infile, commands[i].second, status);
		} else if (commands[i].first == "autostem") {
			RUNTOOL(autostem, infile, commands[i].second, status);
		} else if (commands[i].first == "bstyle") {
			RUNTOOL(bstyle, infile, commands[i].second, status);
		} else if (commands[i].first == "binroll") {
			RUNTOOL(binroll, infile, commands[i].second, status);
		} else if (commands[i].first == "chantize") {
			RUNTOOL(chantize, infile, commands[i].second, status);
		} else if (commands[i].first == "chint") {
			RUNTOOL(chint, infile, commands[i].second, status);
		} else if (commands[i].first == "chord") {
			RUNTOOL(chord, infile, commands[i].second, status);
		} else if (commands[i].first == "cint") {
			RUNTOOL(cint, infile, commands[i].second, status);
		} else if (commands[i].first == "cmr") {
			RUNTOOL(cmr, infile, commands[i].second, status);
		} else if (commands[i].first == "composite") {
			RUNTOOL(composite, infile, commands[i].second, status);
		} else if (commands[i].first == "dissonant") {
			RUNTOOL(dissonant, infile, commands[i].second, status);
		} else if (commands[i].first == "double") {
			RUNTOOL(double, infile, commands[i].second, status);
		} else if (commands[i].first == "fb") {
			RUNTOOL(fb, infile, commands[i].second, status);
		} else if (commands[i].first == "flipper") {
			RUNTOOL(flipper, infile, commands[i].second, status);
		} else if (commands[i].first == "filter") {
			RUNTOOL(filter, infile, commands[i].second, status);
		} else if (commands[i].first == "gasparize") {
			RUNTOOL(gasparize, infile, commands[i].second, status);
		} else if (commands[i].first == "half") {
			RUNTOOL(half, infile, commands[i].second, status);
		} else if (commands[i].first == "hands") {
			RUNTOOL(hands, infile, commands[i].second, status);
		} else if (commands[i].first == "homorhythm") {
			RUNTOOL(homorhythm, infile, commands[i].second, status);
		} else if (commands[i].first == "homorhythm2") {
			RUNTOOL(homorhythm2, infile, commands[i].second, status);
		} else if (commands[i].first == "hproof") {
			RUNTOOL(hproof, infile, commands[i].second, status);
		} else if (commands[i].first == "humbreak") {
			RUNTOOL(humbreak, infile, commands[i].second, status);
		} else if (commands[i].first == "humsheet") {
			RUNTOOL(humsheet, infile, commands[i].second, status);
		} else if (commands[i].first == "humtr") {
			RUNTOOL(humtr, infile, commands[i].second, status);
		} else if (commands[i].first == "imitation") {
			RUNTOOL(imitation, infile, commands[i].second, status);
		} else if (commands[i].first == "instinfo") {
			RUNTOOL(instinfo, infile, commands[i].second, status);
		} else if (commands[i].first == "kern2mens") {
			RUNTOOL(kern2mens, infile, commands[i].second, status);
		} else if (commands[i].first == "kernify") {
			RUNTOOL(kernify, infile, commands[i].second, status);
		} else if (commands[i].first == "kernview") {
			RUNTOOL(kernview, infile, commands[i].second, status);
		} else if (commands[i].first == "melisma") {
			RUNTOOL(melisma, infile, commands[i].second, status);
		} else if (commands[i].first == "mens2kern") {
			RUNTOOL(mens2kern, infile, commands[i].second, status);
		} else if (commands[i].first == "meter") {
			RUNTOOL(meter, infile, commands[i].second, status);
		} else if (commands[i].first == "metlev") {
			RUNTOOL(metlev, infile, commands[i].second, status);
		} else if (commands[i].first == "modori") {
			RUNTOOL(modori, infile, commands[i].second, status);
		} else if (commands[i].first == "msearch") {
			RUNTOOL(msearch, infile, commands[i].second, status);
		} else if (commands[i].first == "nproof") {
			RUNTOOL(nproof, infile, commands[i].second, status);
		} else if (commands[i].first == "ordergps") {
			RUNTOOL(ordergps, infile, commands[i].second, status);
		} else if (commands[i].first == "pbar") {
			RUNTOOL(pbar, infile, commands[i].second, status);
		} else if (commands[i].first == "phrase") {
			RUNTOOL(phrase, infile, commands[i].second, status);
		} else if (commands[i].first == "pline") {
			RUNTOOL(pline, infile, commands[i].second, status);
		} else if (commands[i].first == "prange") {
			RUNTOOL(prange, infile, commands[i].second, status);
		} else if (commands[i].first == "recip") {
			RUNTOOL(recip, infile, commands[i].second, status);
		} else if (commands[i].first == "restfill") {
			RUNTOOL(restfill, infile, commands[i].second, status);
		} else if (commands[i].first == "rphrase") {
			RUNTOOL(rphrase, infile, commands[i].second, status);
		} else if (commands[i].first == "sab2gs") {
			RUNTOOL(sab2gs, infile, commands[i].second, status);
		} else if (commands[i].first == "scordatura") {
			RUNTOOL(scordatura, infile, commands[i].second, status);
		} else if (commands[i].first == "semitones") {
			RUNTOOL(semitones, infile, commands[i].second, status);
		} else if (commands[i].first == "shed") {
			RUNTOOL(shed, infile, commands[i].second, status);
		} else if (commands[i].first == "sic") {
			RUNTOOL(sic, infile, commands[i].second, status);
		} else if (commands[i].first == "simat") {
			RUNTOOL2(simat, infile, infile, commands[i].second, status);
		} else if (commands[i].first == "slurcheck") {
			RUNTOOL(slurcheck, infile, commands[i].second, status);
		} else if (commands[i].first == "slur") {
			RUNTOOL(slurcheck, infile, commands[i].second, status);
		} else if (commands[i].first == "spinetrace") {
			RUNTOOL(spinetrace, infile, commands[i].second, status);
		} else if (commands[i].first == "strophe") {
			RUNTOOL(strophe, infile, commands[i].second, status);
		} else if (commands[i].first == "synco") {
			RUNTOOL(synco, infile, commands[i].second, status);
		} else if (commands[i].first == "tabber") {
			RUNTOOL(tabber, infile, commands[i].second, status);
		} else if (commands[i].first == "tandeminfo") {
			RUNTOOL(tandeminfo, infile, commands[i].second, status);
		} else if (commands[i].first == "tassoize") {
			RUNTOOL(tassoize, infile, commands[i].second, status);
		} else if (commands[i].first == "tassoise") {
			RUNTOOL(tassoize, infile, commands[i].second, status);
		} else if (commands[i].first == "tasso") {
			RUNTOOL(tassoize, infile, commands[i].second, status);
		} else if (commands[i].first == "textdur") {
			RUNTOOL(textdur, infile, commands[i].second, status);
		} else if (commands[i].first == "tie") {
			RUNTOOL(tie, infile, commands[i].second, status);
		} else if (commands[i].first == "tspos") {
			RUNTOOL(tspos, infile, commands[i].second, status);
		} else if (commands[i].first == "transpose") {
			RUNTOOL(transpose, infile, commands[i].second, status);
		} else if (commands[i].first == "tremolo") {
			RUNTOOL(tremolo, infile, commands[i].second, status);
		} else if (commands[i].first == "trillspell") {
			RUNTOOL(trillspell, infile, commands[i].second, status);
		} else if (commands[i].first == "vcross") {
			RUNTOOL(vcross, infile, commands[i].second, status);
		} else if (commands[i].first == "1520ify") {
			RUNTOOL(1520ify, infile, commands[i].second, status);

		// filters with aliases:

		} else if (commands[i].first == "colortriads") {
			RUNTOOL(colortriads, infile, commands[i].second, status);
		} else if (commands[i].first == "colourtriads") {
			// British spelling
			RUNTOOL(colortriads, infile, commands[i].second, status);

		} else if (commands[i].first == "colorthirds") {
			RUNTOOL(tspos, infile, commands[i].second, status);
		} else if (commands[i].first == "colourthirds") {
			// British spelling
			RUNTOOL(tspos, infile, commands[i].second, status);

		} else if (commands[i].first == "colorgroups") {
			RUNTOOL(colorgroups, infile, commands[i].second, status);
		} else if (commands[i].first == "colourgroups") { // British spelling
			RUNTOOL(colorgroups, infile, commands[i].second, status);

		} else if (commands[i].first == "deg") { // humlib version of Humdrum Toolkit deg tool
			RUNTOOL(deg, infile, commands[i].second, status);
		} else if (commands[i].first == "degx") { // humlib cli name
			RUNTOOL(deg, infile, commands[i].second, status);

		} else if (commands[i].first == "extract") { // humlib version of Humdrum Toolkit extract tool
			RUNTOOL(extract, infile, commands[i].second, status);
		} else if (commands[i].first == "extractx") { // humlib cli name
			RUNTOOL(extract, infile, commands[i].second, status);

		} else if (commands[i].first == "grep") {
			RUNTOOL(grep, infile, commands[i].second, status);
		} else if (commands[i].first == "humgrep") {
			RUNTOOL(grep, infile, commands[i].second, status);

		} else if (commands[i].first == "myank") { // humlib version of Humdrum Extras myank tool
			RUNTOOL(myank, infile, commands[i].second, status);
		} else if (commands[i].first == "myankx") { // humlib cli name
			RUNTOOL(myank, infile, commands[i].second, status);

		} else if (commands[i].first == "rid") { // humlib version of Humdrum Toolkit deg tool
			RUNTOOL(rid, infile, commands[i].second, status);
		} else if (commands[i].first == "ridx") { // Humdrum Extra cli name
			RUNTOOL(rid, infile, commands[i].second, status);
		} else if (commands[i].first == "ridxx") { // humlib cli name
			RUNTOOL(rid, infile, commands[i].second, status);

		} else if (commands[i].first == "satb2gs") { // humlib version of Humdrum Extras satg2gs tool
			RUNTOOL(satb2gs, infile, commands[i].second, status);
		} else if (commands[i].first == "satb2gsx") { // humlib cli name
			RUNTOOL(satb2gs, infile, commands[i].second, status);

		} else if (commands[i].first == "thru") { // humlib version of Humdrum Toolkit thru tool
			RUNTOOL(thru, infile, commands[i].second, status);
		} else if (commands[i].first == "thrux") { // Humdrum Extras cli name
			RUNTOOL(thru, infile, commands[i].second, status);
		} else if (commands[i].first == "thruxx") { // humlib cli name
			RUNTOOL(thru, infile, commands[i].second, status);

		} else if (commands[i].first == "timebase") { // humlib version of Humdrum Toolkit timebase tool
			RUNTOOL(timebase, infile, commands[i].second, status);
		} else if (commands[i].first == "timebasex") { // humlib cli name
			RUNTOOL(timebase, infile, commands[i].second, status);
		} else {
			cerr << "UNKNOWN FILTER: " << commands[i].first << " OPTIONS: " << commands[i].second << endl;
		}

	}

	removeGlobalFilterLines(infile);

	// Re-load the text for each line from their tokens in case any
	// updates are needed from token changes.
	infile.createLinesFromTokens();
	return status;
}



//////////////////////////////
//
// Tool_filter::removeGlobalFilterLines --
//

void Tool_filter::removeGlobalFilterLines(HumdrumFile& infile) {
	HumRegex hre;
	string text;

	string maintag = "!!!filter:";
	string mainXtag = "!!!Xfilter:";
	string maintagQuery = "^!!!filter:";

	string maintagV;
	string mainXtagV;
	string maintagQueryV;

	if (m_variant.size() > 0) {
		maintagV = "!!!filter-" + m_variant + ":";
		mainXtagV = "!!!Xfilter-" + m_variant + ":";
		maintagQueryV = "^!!!filter-" + m_variant + ":";
	}

	for (int i=0; i<infile.getLineCount(); i++) {
		if (!infile[i].isReference()) {
			continue;
		}

		if (m_variant.size() > 0) {
			if (infile.token(i, 0)->compare(0, maintagV.size(), maintagV) == 0) {
				text = infile.token(i, 0)->getText();
				hre.replaceDestructive(text, mainXtagV, maintagQueryV);
				infile.token(i, 0)->setText(text);
			}
		} else {
			if (infile.token(i, 0)->compare(0, maintag.size(), maintag) == 0) {
				text = infile.token(i, 0)->getText();
				hre.replaceDestructive(text, mainXtag, maintagQuery);
				infile.token(i, 0)->setText(text);
			}
		}
	}
}



//////////////////////////////
//
// Tool_filter::removeUniversalFilterLines --
//

void Tool_filter::removeUniversalFilterLines(HumdrumFileSet& infiles) {
	HumRegex hre;
	string text;

	string maintag = "!!!!filter:";
	string mainXtag = "!!!!Xfilter:";
	string maintagQuery = "^!!!!filter:";

	string maintagV;
	string mainXtagV;
	string maintagQueryV;

	if (m_variant.size() > 0) {
		maintagV = "!!!!filter-" + m_variant + ":";
		mainXtagV = "!!!!Xfilter-" + m_variant + ":";
		maintagQueryV = "^!!!!filter-" + m_variant + ":";
	}

	for (int i=0; i<infiles.getCount(); i++) {
		HumdrumFile& infile = infiles[i];
		for (int j=0; j<infile.getLineCount(); j++) {
			if (!infile[i].isUniversalReference()) {
				continue;
			}
			HTp token = infile.token(j, 0);
			if (m_variant.size() > 0) {
				if (token->compare(0, maintagV.size(), maintagV) == 0) {
					text = token->getText();
					hre.replaceDestructive(text, mainXtagV, maintagQueryV);
					token->setText(text);
					infile[j].createLineFromTokens();
				}
			} else {
				if (token->compare(0, maintag.size(), maintag) == 0) {
					text = token->getText();
					hre.replaceDestructive(text, mainXtag, maintagQuery);
					token->setText(text);
					infile[j].createLineFromTokens();
				}
			}
		}
	}
}



//////////////////////////////
//
// Tool_filter::getCommandList --
//

void Tool_filter::getCommandList(vector<pair<string, string> >& commands,
		HumdrumFile& infile) {

	vector<HLp> refs = infile.getReferenceRecords();
	pair<string, string> entry;
	string tag = "filter";
	if (m_variant.size() > 0) {
		tag += "-";
		tag += m_variant;
	}
	vector<string> clist;
	HumRegex hre;
	for (int i=0; i<(int)refs.size(); i++) {
		string refkey = refs[i]->getGlobalReferenceKey();
		if (refkey != tag) {
			continue;
		}
		string command = refs[i]->getGlobalReferenceValue();
		splitPipeline(clist, command);
		for (int j=0; j<(int)clist.size(); j++) {
			if (hre.search(clist[j], "^\\s*([^\\s]+)")) {
				entry.first  = hre.getMatch(1);
				entry.second = clist[j];
				commands.push_back(entry);
			}
		}
	}
}



//////////////////////////////
//
//  Tool_filter::splitPipeline --
//

void Tool_filter::splitPipeline(vector<string>& clist, const string& command) {
	clist.clear();
	clist.resize(1);
	clist[0] = "";
	int inDoubleQuotes = -1;
	int inSingleQuotes = -1;
	char ch = '\0';
	char lastch;
	for (int i=0; i<(int)command.size(); i++) {
		lastch = ch;
		ch = command[i];

		if (ch == '"') {
			if (lastch == '\\') {
				// escaped double quote, so treat as regular character
				clist.back() += ch;
				continue;
			} else if (inDoubleQuotes >= 0) {
				// turn off previous double quote sequence
				clist.back() += ch;
				inDoubleQuotes = -1;
				continue;
			} else if (inSingleQuotes >= 0) {
				// in an active single quote, so this is not a closing double quote
				clist.back() += ch;
				continue;
			} else {
				// this is the start of a double quote sequence
				clist.back() += ch;
				inDoubleQuotes = i;
				continue;
			}
		}

		if (ch == '\'') {
			if (lastch == '\\') {
				// escaped single quote, so treat as regular character
				clist.back() += ch;
				continue;
			} else if (inSingleQuotes >= 0) {
				// turn off previous single quote sequence
				clist.back() += ch;
				inSingleQuotes = -1;
				continue;
			} else if (inDoubleQuotes >= 0) {
				// in an active double quote, so this is not a closing single quote
				clist.back() += ch;
				continue;
			} else {
				// this is the start of a single quote sequence
				clist.back() += ch;
				inSingleQuotes = i;
				continue;
			}
		}

		if (ch == '|') {
			if ((inSingleQuotes > -1) || (inDoubleQuotes > -1)) {
				// pipe character
				clist.back() += ch;
				continue;
			} else {
				// this is a real pipe
				clist.resize(clist.size() + 1);
				continue;
			}
		}

		if (isspace(ch) && (!(inSingleQuotes > -1)) && (!(inDoubleQuotes > -1))) {
			if (isspace(lastch)) {
				// don't repeat spaces outside of quotes.
				continue;
			}
		}

		// regular character
		clist.back() += ch;
	}

	// remove leading and trailing spaces
	HumRegex hre;
	for (int i=0; i<(int)clist.size(); i++) {
		hre.replaceDestructive(clist[i], "", "^\\s+");
		hre.replaceDestructive(clist[i], "", "\\s+$");
	}

}



//////////////////////////////
//
// Tool_filter::getUniversalCommandList --
//

void Tool_filter::getUniversalCommandList(vector<pair<string, string> >& commands,
		HumdrumFileSet& infiles) {

	vector<HLp> refs = infiles.getUniversalReferenceRecords();
	pair<string, string> entry;
	string tag = "filter";
	if (m_variant.size() > 0) {
		tag += "-";
		tag += m_variant;
	}
	vector<string> clist;
	HumRegex hre;
	for (int i=0; i<(int)refs.size(); i++) {
		if (refs[i]->getUniversalReferenceKey() != tag) {
			continue;
		}
		string command = refs[i]->getUniversalReferenceValue();
		hre.split(clist, command, "\\s*\\|\\s*");
		for (int j=0; j<(int)clist.size(); j++) {
			if (hre.search(clist[j], "^\\s*([^\\s]+)")) {
				entry.first  = hre.getMatch(1);
				entry.second = clist[j];
				commands.push_back(entry);
			}
		}
	}
}



//////////////////////////////
//
// Tool_filter::initialize -- extract time signature lines for
//    each **kern spine in file.
//

void Tool_filter::initialize(HumdrumFile& infile) {
	m_debugQ = getBoolean("debug");
	m_variant.clear();
	if (getBoolean("variant")) {
		m_variant = getString("variant");
	}
}





/////////////////////////////////
//
// Tool_fixps::Tool_fixps -- Set the recognized options for the tool.
//

Tool_fixps::Tool_fixps(void) {
	// define ("n|only-remove-empty-transpositions=b", "Only remove empty transpositions");
}



/////////////////////////////////
//
// Tool_fixps::run -- Primary interfaces to the tool.
//

bool Tool_fixps::run(HumdrumFileSet& infiles) {
	bool status = true;
	for (int i=0; i<infiles.getCount(); i++) {
		status &= run(infiles[i]);
	}
	return status;
}


bool Tool_fixps::run(const string& indata, ostream& out) {
	HumdrumFile infile(indata);
	bool status = run(infile);
	if (hasAnyText()) {
		getAllText(out);
	} else {
		out << infile;
	}
	return status;
}


bool Tool_fixps::run(HumdrumFile& infile, ostream& out) {
	bool status = run(infile);
	if (hasAnyText()) {
		getAllText(out);
	} else {
		out << infile;
	}
	return status;
}

//
// In-place processing of file:
//

bool Tool_fixps::run(HumdrumFile& infile) {
	processFile(infile);
	return true;
}



//////////////////////////////
//
// Tool_fixps::processFile --
//

void Tool_fixps::processFile(HumdrumFile& infile) {
	removeDuplicateDynamics(infile);
	markEmptyVoices(infile);
	vector<vector<HTp>> newlist;
	removeEmpties(newlist, infile);
	outputNewSpining(newlist, infile);
}



//////////////////////////////
//
// Tool_fixps::outputNewSpining --
//

void Tool_fixps::outputNewSpining(vector<vector<HTp>>& newlist, HumdrumFile& infile) {
	for (int i=0; i<infile.getLineCount(); i++) {
		if (!infile[i].hasSpines()) {
			m_humdrum_text << infile[i] << endl;
			continue;
		}
		if ((i > 0) && (!newlist[i].empty()) && newlist[i][0]->isCommentLocal()) {
			if (!newlist[i-1].empty() && newlist[i-1][0]->isCommentLocal()) {
				if (newlist[i].size() == newlist[i-1].size()) {
					bool same = true;
					for (int j=0; j<(int)newlist[i].size(); j++) {
						if (*(newlist[i][j]) != *(newlist[i-1][j])) {
cerr << "GOT HERE " << i << " " << j << endl;
cerr << infile[i-1] << endl;
cerr << infile[i] << endl;
cerr << endl;
							same = false;
							break;
						}
					}
					if (same) {
						continue;
					}
				}
			}
		}
		if (!infile[i].isManipulator()) {
			m_humdrum_text << newlist[i].at(0);
			for (int j=1; j<(int)newlist[i].size(); j++) {
				m_humdrum_text << "\t";
				m_humdrum_text << newlist[i].at(j);
			}
			m_humdrum_text << endl;
			continue;
		}
		if ((i > 0) && !infile[i-1].isManipulator()) {
			printNewManipulator(infile, newlist, i);
		}
	}
}


//////////////////////////////
//
// Tool_fixps::printNewManipulator --
//

void Tool_fixps::printNewManipulator(HumdrumFile& infile, vector<vector<HTp>>& newlist, int line) {
	HTp token = infile.token(line, 0);
	if (*token == "*-") {
		m_humdrum_text << infile[line] << endl;
		return;
	}
	if (token->compare(0, 2, "**") == 0) {
		m_humdrum_text << infile[line] << endl;
		return;
	}
	m_humdrum_text << "++++++++++++++++++++" << endl;
}

//////////////////////////////
//
// Tool_fixps::removeDuplicateDynamics --
//

void Tool_fixps::removeDuplicateDynamics(HumdrumFile& infile) {
	int scount = infile.getStrandCount();
	for (int i=0; i<scount; i++) {
		HTp sstart = infile.getStrandBegin(i);
		if (!sstart->isDataType("**dynam")) {
			continue;
		}
		HTp send   = infile.getStrandEnd(i);
		HTp current = sstart;
		while (current && (current != send)) {
			vector<string> subtoks = current->getSubtokens();
			if (subtoks.size() % 2 == 1) {
				current = current->getNextToken();
				continue;
			}
			bool equal = true;
			int half = (int)subtoks.size() / 2;
			for (int j=0; j<half; j++) {
				if (subtoks[i] != subtoks[i+half]) {
					equal = false;
				}
			}
			if (equal) {
				string newtext = subtoks[0];
				for (int j=1; j<half; j++) {
					newtext += " ";
					newtext += subtoks[j];
				}
				current->setText(newtext);
			}
		}
	}
}



//////////////////////////////
//
// Tool_fixps::removeEmpties --
//

void Tool_fixps::removeEmpties(vector<vector<HTp>>& newlist, HumdrumFile& infile) {
	newlist.resize(infile.getLineCount());
	for (int i=0; i<infile.getLineCount(); i++) {
		if (!infile[i].hasSpines()) {
			continue;
		}
		if (infile[i].isManipulator()) {
			continue;
		}
		for (int j=0; j<infile[i].getFieldCount(); j++) {
			HTp token = infile.token(i, j);
			string value = token->getValue("delete");
			if (value == "true") {
				continue;
			}
			newlist[i].push_back(token);
		}
	}
}



//////////////////////////////
//
// Tool_fixps::markEmptyVoices --
//

void Tool_fixps::markEmptyVoices(HumdrumFile& infile) {
	HLp barline = NULL;
	for (int i=0; i<infile.getLineCount(); i++) {
		if (!infile[i].hasSpines()) {
			continue;
		}
		if (infile[i].isManipulator()) {
			continue;
		}
		if (infile[i].isInterpretation()) {
			if (infile.token(i, 0)->compare(0, 2, "**")) {
				barline = &infile[i];
			}
			continue;
		}
		if (infile[i].isBarline()) {
			barline = &infile[i];
		}
		if (!infile[i].isData()) {
			continue;
		}
		if (!barline) {
			continue;
		}
		// check on the data line if:
		// * it is in the first subspine
		// * it is an invisible rest
		// * it takes the full duration of the measure
		// If so, then mark the tokens for deletion in that layer.
		for (int j=0; j<infile[i].getFieldCount(); j++) {
			HTp token = infile.token(i, j);
			// int track = token->getTrack();
			int subtrack = token->getSubtrack();
			if (subtrack != 1) {
				continue;
			}
			if (token->find("yy") == string::npos) {
				continue;
			}
			if (!token->isRest()) {
				continue;
			}
			HumNum duration = token->getDuration();
			HumNum bardur = token->getDurationToBarline();
			HTp current = token;
			while (current) {
			   subtrack = current->getSubtrack();
				if (subtrack != 1) {
					break;
				}
				current->setValue("delete", "true");
				if (current->isBarline()) {
					break;
				}
				current = current->getNextToken();
			}
			current = token;
			current = current->getPreviousToken();
			while (current) {
				if (current->isManipulator()) {
					break;
				}
				if (current->isBarline()) {
					break;
				}
				subtrack = current->getSubtrack();
				if (subtrack != 1) {
					break;
				}
				current->setValue("delete", "true");
				current = current->getPreviousToken();
			}
		}
	}

}





/////////////////////////////////
//
// Tool_flipper::Tool_flipper -- Set the recognized options for the tool.
//

Tool_flipper::Tool_flipper(void) {
	define("k|keep=b",                      "keep *flip/*Xflip instructions");
	define("a|all=b",                       "flip globally, not just inside *flip/*Xflip regions");
	define("s|strophe=b",                   "flip inside of strophes as well");
	define("S|strophe-only|only-strophe=b", "flip only inside of strophes as well");
	define("i|interp=s:kern",               "flip only in this interpretation");
}



/////////////////////////////////
//
// Tool_flipper::run -- Do the main work of the tool.
//

bool Tool_flipper::run(HumdrumFileSet& infiles) {
	bool status = true;
	for (int i=0; i<infiles.getCount(); i++) {
		status &= run(infiles[i]);
	}
	return status;
}


bool Tool_flipper::run(const string& indata, ostream& out) {
	HumdrumFile infile(indata);
	bool status = run(infile);
	if (hasAnyText()) {
		getAllText(out);
	} else {
		out << infile;
	}
	return status;
}


bool Tool_flipper::run(HumdrumFile& infile, ostream& out) {
	bool status = run(infile);
	if (hasAnyText()) {
		getAllText(out);
	} else {
		out << infile;
	}
	return status;
}


bool Tool_flipper::run(HumdrumFile& infile) {
	initialize();
	processFile(infile);
	return true;
}



//////////////////////////////
//
// Tool_flipper::initialize --  Initializations that only have to be done once
//    for all HumdrumFile segments.
//

void Tool_flipper::initialize(void) {
	m_allQ         = getBoolean("all");
	m_keepQ        = getBoolean("keep");
	m_kernQ        = true;
	m_stropheQ     = getBoolean("strophe");
	m_interp       = getString("interp");
	if (m_interp != "kern") {
		m_kernQ = false;
	}
}



//////////////////////////////
//
// Tool_flipper::processFile --
//

void Tool_flipper::processFile(HumdrumFile& infile) {

	m_fliplines.resize(infile.getLineCount());
	fill(m_fliplines.begin(), m_fliplines.end(), false);

	m_flipState.resize(infile.getMaxTrack()+1);
	if (m_allQ) {
		fill(m_flipState.begin(), m_flipState.end(), true);
	} else {
		fill(m_flipState.begin(), m_flipState.end(), false);
	}

	m_strophe.resize(infile.getMaxTrack()+1);
	fill(m_strophe.begin(), m_strophe.end(), false);

	for (int i=0; i<infile.getLineCount(); i++) {
		processLine(infile, i);
		if (!m_keepQ) {
			if (!m_fliplines[i]) {
				m_humdrum_text << infile[i] << endl;
			}
		}
	}

	if (m_keepQ) {
		m_humdrum_text << infile;
	}
}



//////////////////////////////
//
// Tool_flipper::checkForFlipChanges --
//

void Tool_flipper::checkForFlipChanges(HumdrumFile& infile, int index) {
	if (!infile[index].isInterpretation()) {
		return;
	}

	int track;

	for (int i=0; i<infile[index].getFieldCount(); i++) {
		HTp token = infile.token(index, i);
		if (*token == "*strophe") {
			track = token->getTrack();
			m_strophe[track] = true;
		} else if (*token == "*Xstrophe") {
			track = token->getTrack();
			m_strophe[track] = false;
		}
	}


	if (m_allQ) {
		// state always stays on in this case
		return;
	}

	for (int i=0; i<infile[index].getFieldCount(); i++) {
		HTp token = infile.token(index, i);
		if (*token == "*flip") {
			track = token->getTrack();
			m_flipState[track] = true;
			m_fliplines[i] = true;
		} else if (*token == "*Xflip") {
			track = token->getTrack();
			m_flipState[track] = false;
			m_fliplines[i] = true;
		}
	}

}



//////////////////////////////
//
// Tool_flipper::processLine --
//

void Tool_flipper::processLine(HumdrumFile& infile, int index) {
	if (!infile[index].hasSpines()) {
		return;
	}
	if (infile[index].isInterpretation()) {
		checkForFlipChanges(infile, index);
	}

	vector<vector<HTp>> flipees;
	extractFlipees(flipees, infile, index);
	if (!flipees.empty()) {
		int status = flipSubspines(flipees);
		if (status) {
			infile[index].createLineFromTokens();
		}
	}
}



//////////////////////////////
//
// Tool_flipper::flipSubspines --
//

bool Tool_flipper::flipSubspines(vector<vector<HTp>>& flipees) {
	bool regenerateLine = false;
	for (int i=0; i<(int)flipees.size(); i++) {
		if (flipees[i].size() > 1) {
			flipSpineTokens(flipees[i]);
			regenerateLine = true;
		}
	}
	return regenerateLine;
}


//////////////////////////////
//
// Tool_flipper::flipSpineTokens --
//

void Tool_flipper::flipSpineTokens(vector<HTp>& subtokens) {
	if (subtokens.size() < 2) {
		return;
	}
	int count = (int)subtokens.size();
	count = count / 2;
	HTp tok1;
	HTp tok2;
	string str1;
	string str2;
	for (int i=0; i<count; i++) {
		tok1 = subtokens[i];
		tok2 = subtokens[subtokens.size() - 1 - i];
		str1 = *tok1;
		str2 = *tok2;
		tok1->setText(str2);
		tok2->setText(str1);
	}
}



//////////////////////////////
//
// Tool_flipper::extractFlipees --
//

void Tool_flipper::extractFlipees(vector<vector<HTp>>& flipees,
		 HumdrumFile& infile, int index) {
	flipees.clear();

	HLp line = &infile[index];
	int track;
	int lastInsertTrack = -1;
	for (int i=0; i<line->getFieldCount(); i++) {
		HTp token = line->token(i);
		track = token->getTrack();
		if ((!m_stropheQ) && m_strophe[track]) {
			continue;
		}
		if (!m_flipState[track]) {
			continue;
		}
		if (m_kernQ) {
			if (!token->isKern()) {
				continue;
			}
		} else {
			if (!token->isDataType(m_interp)) {
				continue;
			}
		}
		if (lastInsertTrack != track) {
			flipees.resize(flipees.size() + 1);
			lastInsertTrack = track;
		}
		flipees.back().push_back(token);
	}
}





/////////////////////////////////
//
// Tool_gasparize::Tool_gasparize -- Set the recognized options for the tool.
//

Tool_gasparize::Tool_gasparize(void) {
	define("R|no-reference-records=b",                "do not add reference records");
	define("r|only-add-reference-records=b",          "only add reference records");

	define("B|do-not-delete-breaks=b",                "do not delete system/page break markers");
	define("b|only-delete-breaks=b",                  "only delete breaks");

	define("A|do-not-fix-instrument-abbreviations=b", "do not fix instrument abbreviations");
	define("a|only-fix-instrument-abbreviations=b",   "only fix instrument abbreviations");

	define("E|do-not-fix-editorial-accidentals=b",    "do not fix instrument abbreviations");
	define("e|only-fix-editorial-accidentals=b",      "only fix editorial accidentals");

	define("T|do-not-add-terminal-longs=b",           "do not add terminal long markers");
	define("t|only-add-terminal-longs=b",             "only add terminal longs");

	define("no-ties=b",                               "do not fix tied notes");

	define("N|do-not-remove-empty-transpositions=b",  "do not remove empty transposition instructions");
	define ("n|only-remove-empty-transpositions=b",   "only remove empty transpositions");
}



/////////////////////////////////
//
// Tool_gasparize::run -- Primary interfaces to the tool.
//

bool Tool_gasparize::run(HumdrumFileSet& infiles) {
	bool status = true;
	for (int i=0; i<infiles.getCount(); i++) {
		status &= run(infiles[i]);
	}
	return status;
}


bool Tool_gasparize::run(const string& indata, ostream& out) {
	HumdrumFile infile(indata);
	bool status = run(infile);
	if (hasAnyText()) {
		getAllText(out);
	} else {
		out << infile;
	}
	return status;
}


bool Tool_gasparize::run(HumdrumFile& infile, ostream& out) {
	bool status = run(infile);
	if (hasAnyText()) {
		getAllText(out);
	} else {
		out << infile;
	}
	return status;
}

//
// In-place processing of file:
//

bool Tool_gasparize::run(HumdrumFile& infile) {
	processFile(infile);

	// Re-load the text for each line from their tokens.
	infile.createLinesFromTokens();

	// Need to adjust the line numbers for tokens for later
	// processing.
	m_humdrum_text << infile;
	return true;
}



//////////////////////////////
//
// Tool_gasparize::processFile --
//

void Tool_gasparize::processFile(HumdrumFile& infile) {

	bool mensurationQ    = true;
	bool articulationsQ  = true;
	bool abbreviationsQ  = true;
	bool accidentalsQ    = true;
	bool referencesQ     = true;
	bool terminalsQ      = true;
	bool breaksQ         = true;
	bool transpositionsQ = true;
   bool tieQ            = true;
   bool teditQ          = true;
   bool instrumentQ     = true;
   bool removekeydesigQ = true;
   bool fixbarlinesQ    = true;
   bool parenthesesQ    = true;

	if (getBoolean("no-reference-records")) { referencesQ = false; }
	if (getBoolean("only-add-reference-records")) {
		abbreviationsQ  = false;
		accidentalsQ    = false;
		referencesQ     = true;
		terminalsQ      = false;
		breaksQ         = false;
		transpositionsQ = false;
	}

	if (getBoolean("do-not-delete-breaks")) { breaksQ = false; }
	if (getBoolean("only-delete-breaks")) {
		abbreviationsQ  = false;
		accidentalsQ    = false;
		referencesQ     = false;
		terminalsQ      = false;
		breaksQ         = true;
		transpositionsQ = false;
	}

	if (getBoolean("do-not-fix-instrument-abbreviations")) { abbreviationsQ = false; }
	if (getBoolean("only-fix-instrument-abbreviations")) {
		abbreviationsQ  = true;
		accidentalsQ    = false;
		referencesQ     = false;
		terminalsQ      = false;
		breaksQ         = false;
		transpositionsQ = false;
	}

	if (getBoolean("do-not-fix-editorial-accidentals")) { accidentalsQ = false; }
	if (getBoolean("only-fix-editorial-accidentals")) {
		abbreviationsQ  = false;
		accidentalsQ    = true;
		referencesQ     = false;
		terminalsQ      = false;
		breaksQ         = false;
		transpositionsQ = false;
	}

	if (getBoolean("do-not-add-terminal-longs")) { terminalsQ = false; }
	if (getBoolean("only-add-terminal-longs")) {
		abbreviationsQ  = false;
		accidentalsQ    = false;
		referencesQ     = false;
		terminalsQ      = true;
		breaksQ         = false;
		transpositionsQ = false;
	}

	if (getBoolean("do-not-remove-empty-transpositions")) { transpositionsQ = false; }

	if (getBoolean("no-ties")) { tieQ = false; }

	if (getBoolean("only-remove-empty-transpositions")) {
		abbreviationsQ  = false;
		accidentalsQ    = false;
		referencesQ     = false;
		terminalsQ      = false;
		breaksQ         = false;
		transpositionsQ = true;
	}

	if (articulationsQ)  { removeArticulations(infile); }
	if (fixbarlinesQ)    { fixBarlines(infile); }
	if (tieQ)            { fixTies(infile); }
	if (abbreviationsQ)  { fixInstrumentAbbreviations(infile); }
	if (accidentalsQ)    { fixEditorialAccidentals(infile); }
	if (parenthesesQ)    { createJEditorialAccidentals(infile); }
	if (referencesQ)     { addBibliographicRecords(infile); }
	if (breaksQ)         { deleteBreaks(infile); }
	if (terminalsQ)      { addTerminalLongs(infile); }
	if (transpositionsQ) { deleteDummyTranspositions(infile); }
	if (mensurationQ)    { addMensurations(infile); }
	if (teditQ)          { createEditText(infile); }
   if (instrumentQ)     { adjustIntrumentNames(infile); }
   if (removekeydesigQ) { removeKeyDesignations(infile); }

	adjustSystemDecoration(infile);

	// Input lyrics may contain "=" signs which are to be converted into
	// spaces in **text data, and into elisions when displaying with verovio.
	Tool_shed shed;
	vector<string> argv;
	argv.push_back("shed");
	argv.push_back("-x");     // only apply to **text spines
	argv.push_back("text");
	argv.push_back("-e");
	argv.push_back("s/=/ /g");
	shed.process(argv);
	shed.run(infile);
}



//////////////////////////////
//
// Tool_gasparize::removeArticulations --
//

void Tool_gasparize::removeArticulations(HumdrumFile& infile) {
	HumRegex hre;
	for (int i=0; i<infile.getLineCount(); i++) {
		if (!infile[i].isData()) {
			continue;
		}
		for (int j=0; j<infile[i].getFieldCount(); j++) {
			HTp token = infile.token(i, j);
			if (!token->isKern()) {
				continue;
			}
			if (token->isNull()) {
				continue;
			}
			bool changed = false;
			string text = token->getText();
			if (text.find("'") != string::npos) {
				// remove staccatos
				changed = true;
				hre.replaceDestructive(text, "", "'", "g");
			}
			if (text.find("~") != string::npos) {
				// remove tenutos
				changed = true;
				hre.replaceDestructive(text, "", "~", "g");
			}
			if (changed) {
				token->setText(text);
			}
		}
	}
}



//////////////////////////////
//
// Tool_gasparize::adjustSystemDecoration --
//    !!!system-decoration: [(s1)(s2)(s3)(s4)]
// to:
//    !!!system-decoration: [*]
//

void Tool_gasparize::adjustSystemDecoration(HumdrumFile& infile) {
	for (int i=infile.getLineCount() - 1; i>=0; i--) {
		if (!infile[i].isReference()) {
			continue;
		}
		HTp token = infile.token(i, 0);
		if (token->compare(0, 21, "!!!system-decoration:") == 0) {
			token->setText("!!!system-decoration: [*]");
			break;
		}
	}
}



//////////////////////////////
//
// Tool_gasparize::deleteDummyTranspositions -- Somehow empty
//    transpositions that go to the same pitch can appear in the
//    MusicXML data, so remove them here.  Example:
// 		*Trd0c0
//

void Tool_gasparize::deleteDummyTranspositions(HumdrumFile& infile) {
	vector<int> ldel;
	for (int i=0; i<infile.getLineCount(); i++) {
		if (!infile[i].hasSpines()) {
			continue;
		}
		if (!infile[i].isInterpretation()) {
			continue;
		}
		bool empty = true;
		for (int j=0; j<infile[i].getFieldCount(); j++) {
			HTp token = infile.token(i, j);
			if (*token == "*") {
				continue;
			}
			if (!token->isKern()) {
				empty = false;
				continue;
			}
			if (*token == "*Trd0c0") {
				token->setText("*");
			} else {
				empty = false;
			}
		}
		if (empty) {
			ldel.push_back(i);
		}
	}

	if (ldel.size() == 1) {
		infile.deleteLine(ldel[0]);
	} else if (ldel.size() > 1) {
		cerr << "Warning: multiple transposition lines, not deleting them" << endl;
	}

}


//////////////////////////////
//
// Tool_gasparize::fixEditorialAccidentals -- checkDataLine() does
//       all of the work for this function, which only manages
//       key signature and barline processing.
//    Rules for accidentals in Tasso in Music Project:
//    (1) Only note accidentals printed in the source editions
//        are displayed as regular accidentals.  These accidentals
//        are postfixed with an "X" in the **kern data.
//    (2) Editorial accidentals are given an "i" marker but not
//        a "X" marker in the **kern data.  This editorial accidental
//        is displayed above the note.
//    This algorithm makes adjustments to the input data because
//    Sibelius will drop editorial information after the frist
//    editorial accidental on that pitch in the measure.
//    (3) If a note is the same pitch as a previous note in the
//        measure and the previous note has an editorial accidental,
//        then make the note an editorial note.  However, if the
//        accidental state of the note matches the key-signature,
//        then do not add an editorial accidental, and there will be
//        no accidental displayed on the note.  In that case, add a "y"
//        after the accidental to indicate that it is interpreted
//        and not visible in the original score.
//

void Tool_gasparize::fixEditorialAccidentals(HumdrumFile& infile) {
	removeDoubledAccidentals(infile);

	m_pstates.resize(infile.getMaxTrack() + 1);
	m_estates.resize(infile.getMaxTrack() + 1);
	m_kstates.resize(infile.getMaxTrack() + 1);

	for (int i=0; i<(int)m_pstates.size(); i++) {
		m_pstates[i].resize(70);
		fill(m_pstates[i].begin(), m_pstates[i].end(), 0);
		m_kstates[i].resize(70);
		fill(m_kstates[i].begin(), m_kstates[i].end(), 0);
		m_estates[i].resize(70);
		fill(m_estates[i].begin(), m_estates[i].end(), false);
	}

	for (int i=0; i<infile.getLineCount(); i++) {
		if (infile[i].isInterpretation()) {
			updateKeySignatures(infile, i);
			continue;
		} else if (infile[i].isBarline()) {
			clearStates();
			continue;
		} else if (infile[i].isData()) {
			checkDataLine(infile, i);
		}
	}
}



//////////////////////////////
//
// Tool_gasparize::removeDoubledAccidentals -- Often caused by transposition
//    differences between parts in the MusicXML export from Finale.  Also some
//    strange double sharps appear randomly.
//

void Tool_gasparize::removeDoubledAccidentals(HumdrumFile& infile) {
	HumRegex hre;
	for (int i=0; i<infile.getLineCount(); i++) {
		if (!infile[i].isData()) {
			continue;
		}
		for (int j=0; j<infile[i].getFieldCount(); j++) {
			HTp token = infile.token(i, j);
			if (!token->isKern()) {
				continue;
			}
			if (token->isNull()) {
				continue;
			}
			if (token->isRest()) {
				continue;
			}
			if (token->find("--") != string::npos) {
				string text = *token;
				hre.replaceDestructive(text, "-", "--", "g");
			} else if (token->find("--") != string::npos) {
				string text = *token;
				hre.replaceDestructive(text, "#", "##", "g");
			}
		}
	}
}



//////////////////////////////
//
// Tool_gasparize::addTerminalLongs -- Convert all last notes to terminal longs
//    Also probably add terminal longs before double barlines as in JRP.
//

void Tool_gasparize::addTerminalLongs(HumdrumFile& infile) {
	int scount = infile.getStrandCount();
	for (int i=0; i<scount; i++) {
		HTp cur = infile.getStrandEnd(i);
		if (*cur != "*-") {
			continue;
		}
		if (!cur->isKern()) {
			continue;
		}
		while (cur) {
			if (!cur->isData()) {
				cur = cur->getPreviousToken();
				continue;
			}
			if (cur->isNull()) {
				cur = cur->getPreviousToken();
				continue;
			}
			if (cur->isRest()) {
				cur = cur->getPreviousToken();
				continue;
			}
			if (cur->isSecondaryTiedNote()) {
				cur = cur->getPreviousToken();
				continue;
			}
			if (cur->find("l") != string::npos) {
				// already marked so do not do it again
				break;
			}
			// mark this note with "l"
			string newtext = *cur;
			newtext += "l";
			cur->setText(newtext);
			break;
		}
	}
}



//////////////////////////////
//
// Tool_gasparize::fixInstrumentAbbreviations --
//

void Tool_gasparize::fixInstrumentAbbreviations(HumdrumFile& infile) {
	int iline = -1;
	int aline = -1;

	vector<HTp> kerns = infile.getKernSpineStartList();
	if (kerns.empty()) {
		return;
	}

	HTp cur = kerns[0];
	while (cur) {
		if (cur->isData()) {
			break;
		}
		if (cur->compare(0, 3, "*I\"") == 0) {
			iline = cur->getLineIndex();
		} else if (cur->compare(0, 3, "*I'") == 0) {
			aline = cur->getLineIndex();
		}
		cur = cur->getNextToken();
	}

	if (iline < 0) {
		// no names to create abbreviations for
		return;
	}
	if (aline < 0) {
		// not creating a new abbreviation for now
		// (could add later).
		return;
	}
	if (infile[iline].getFieldCount() != infile[aline].getFieldCount()) {
		// no spine splitting between the two lines.
		return;
	}
	// Maybe also require them to be adjacent to each other.
	HumRegex hre;
	for (int j=0; j<(int)infile[iline].getFieldCount(); j++) {
		if (!infile.token(iline, j)->isKern()) {
			continue;
		}
		if (!hre.search(*infile.token(iline, j), "([A-Za-z][A-Za-z .0-9]+)")) {
			continue;
		}
		string name = hre.getMatch(1);
		string abbr = "*I'";
		if (name == "Basso Continuo") {
			abbr += "BC";
		} else if (name == "Basso continuo") {
			abbr += "BC";
		} else if (name == "basso continuo") {
			abbr += "BC";
		} else {
			abbr += toupper(name[0]);
		}
		// check for numbers after the end of the name and add to abbreviation
		infile.token(aline, j)->setText(abbr);
	}
}



//////////////////////////////
//
// Tool_gasparize::convertBreaks --
//

void Tool_gasparize::convertBreaks(HumdrumFile& infile) {
	HumRegex hre;
	for (int i=infile.getLineCount()-1; i>= 0; i--) {
		if (!infile[i].isGlobalComment()) {
			continue;
		}
		if (hre.search(*infile.token(i, 0), "linebreak\\s*:\\s*original")) {
			string text = "!!LO:LB:g=original";
			infile[i].setText(text);
		}
		else if (hre.search(*infile.token(i, 0), "pagebreak\\s*:\\s*original")) {
			string text = "!!LO:PB:g=original";
			infile[i].setText(text);
		}
	}
}



//////////////////////////////
//
// Tool_gasparize::deleteBreaks --
//

void Tool_gasparize::deleteBreaks(HumdrumFile& infile) {
	HumRegex hre;
	for (int i=infile.getLineCount()-1; i>= 0; i--) {
		if (!infile[i].isGlobalComment()) {
			continue;
		}
		if (hre.search(*infile.token(i, 0), "linebreak\\s*:\\s*original")) {
			infile.deleteLine(i);
		}
		else if (hre.search(*infile.token(i, 0), "pagebreak\\s*:\\s*original")) {
			infile.deleteLine(i);
		}
	}
}


////////////////////////////////
//
// Tool_gasparize::addBibliographicRecords --
//
// !!!COM:
// !!!CDT:
// !!!OTL:
// !!!AGN:
// !!!SCT:
// !!!SCA:
// !!!voices:
//
// At end:
// !!!RDF**kern: l = terminal long
// !!!RDF**kern: i = editorial accidental
// !!!EED:
// !!!EEV: $DATE
//

void Tool_gasparize::addBibliographicRecords(HumdrumFile& infile) {
	vector<HLp> refinfo = infile.getReferenceRecords();
	map<string, HLp> refs;
	for (int i=0; i<(int)refinfo.size(); i++) {
		string key = refinfo[i]->getReferenceKey();
		refs[key] = refinfo[i];
	}

	// header records
	if (refs.find("voices") == refs.end()) {
		if (infile.token(0, 0)->find("!!!OTL") != string::npos) {
			infile.insertLine(1, "!!!voices:");
		} else {
			infile.insertLine(0, "!!!voices:");
		}
	}
	if (refs.find("SCA") == refs.end()) {
		if (infile.token(0, 0)->find("!!!OTL") != string::npos) {
			infile.insertLine(1, "!!!SCA:");
		} else {
			infile.insertLine(0, "!!!SCA:");
		}
	}
	if (refs.find("SCT") == refs.end()) {
		if (infile.token(0, 0)->find("!!!OTL") != string::npos) {
			infile.insertLine(1, "!!!SCT:");
		} else {
			infile.insertLine(0, "!!!SCT:");
		}
	}
	if (refs.find("AGN") == refs.end()) {
		if (infile.token(0, 0)->find("!!!OTL") != string::npos) {
			infile.insertLine(1, "!!!AGN:");
		} else {
			infile.insertLine(0, "!!!AGN:");
		}
	}

	if (refs.find("OTL") == refs.end()) {
		infile.insertLine(0, "!!!OTL:");
	}
	if (refs.find("CDT") == refs.end()) {
		infile.insertLine(0, "!!!CDT: ~1450-~1517");
	}
	if (refs.find("COM") == refs.end()) {
		infile.insertLine(0, "!!!COM: Gaspar van Weerbeke");
	}

	// trailer records
	bool foundi = false;
	bool foundj = false;
	bool foundl = false;
	for (int i=0; i<infile.getLineCount(); i++) {
		if (!infile[i].isReference()) {
			continue;
		}
		HTp token = infile.token(i, 0);
		if (token->find("!!!RDF**kern:") == string::npos) {
			continue;
		}
		if (token->find("terminal breve") != string::npos) {
			foundl = true;
		} else if (token->find("editorial accidental") != string::npos) {
			if (token->find("i =") != string::npos) {
				foundi = true;
			} else if (token->find("j =") != string::npos) {
				foundj = true;
			}
		}
	}
	if (!foundj) {
		infile.appendLine("!!!RDF**kern: j = editorial accidental, optional, paren up");
	}
	if (!foundi) {
		infile.appendLine("!!!RDF**kern: i = editorial accidental");
	}
	if (!foundl) {
		infile.appendLine("!!!RDF**kern: l = terminal long");
	}

	if (refs.find("PTL") == refs.end()) {
		infile.appendLine("!!!PTL: Gaspar van Weerbeke: Collected Works. V. Settings of Liturgical Texts, Songs, and Instrumental Works");
	}
	if (refs.find("PPR") == refs.end()) {
		infile.appendLine("!!!PPR: American Institute of Musicology");
	}
	if (refs.find("PC#") == refs.end()) {
		infile.appendLine("!!!PC#: Corpus Mensurabilis Musicae 106/V");
	}
	if (refs.find("PDT") == refs.end()) {
		infile.appendLine("!!!PDT: {YEAR}");
	}
	if (refs.find("PED") == refs.end()) {
		infile.appendLine("!!!PED: Kolb, Paul");
		infile.appendLine("!!!PED: Pavanello, Agnese");
	}
	if (refs.find("YEC") == refs.end()) {
		infile.appendLine("!!!YEC: Copyright {YEAR}, Kolb, Paul");
		infile.appendLine("!!!YEC: Copyright {YEAR}, Pavanello, Agnese");
	}
	if (refs.find("YEM") == refs.end()) {
		infile.appendLine("!!!YEM: CC-BY-SA 4.0 (https://creativecommons.org/licenses/by-nc/4.0/legalcode)");
	}
	if (refs.find("EED") == refs.end()) {
		infile.appendLine("!!!EED: Zybina, Karina");
		infile.appendLine("!!!EED: Mair-Gruber, Roland");
	}
	if (refs.find("EEV") == refs.end()) {
		string date = getDate();
		string line = "!!!EEV: " + date;
		infile.appendLine(line);
	}
}



////////////////////////////////
//
// Tool_gasparize::checkDataLine --
//

void Tool_gasparize::checkDataLine(HumdrumFile& infile, int lineindex) {
	HumdrumLine& line = infile[lineindex];

	HumRegex hre;
	HTp token;
	bool haseditQ;
	int base7;
	int accid;
	int track;
	bool removeQ;
	for (int i=0; i<line.getFieldCount(); i++) {
		token = line.token(i);
		track = token->getTrack();
		if (!token->isKern()) {
			continue;
		}
		if (token->isNull()) {
			continue;
		}
		if (token->isRest()) {
			continue;
		}
		if (token->find('j') != string::npos) {
			continue;
		}
		if (token->isSecondaryTiedNote()) {
			continue;
		}

		base7 = Convert::kernToBase7(token);
		accid = Convert::kernToAccidentalCount(token);
		haseditQ = false;
		removeQ = false;

		// Hard-wired to "i" as editorial accidental marker
		if (token->find("ni") != string::npos) {
			haseditQ = true;
		} else if (token->find("-i") != string::npos) {
			haseditQ = true;
		} else if (token->find("#i") != string::npos) {
			haseditQ = true;
		} else if (token->find("nXi") != string::npos) {
			haseditQ = true;
			removeQ = true;
		} else if (token->find("-Xi") != string::npos) {
			haseditQ = true;
			removeQ = true;
		} else if (token->find("#Xi") != string::npos) {
			haseditQ = true;
			removeQ = true;
		}

		if (removeQ) {
			string temp = *token;
			hre.replaceDestructive(temp, "", "X");
			token->setText(temp);
		}

		bool explicitQ = false;
		if (token->find("#X") != string::npos) {
			explicitQ = true;
		} else if (token->find("-X") != string::npos) {
			explicitQ = true;
		} else if (token->find("nX") != string::npos) {
			explicitQ = true;
		} else if (token->find("n") != string::npos) {
			// add an explicit accidental marker
			explicitQ = true;
			string text = *token;
			hre.replaceDestructive(text, "nX", "n");
			token->setText(text);
		}

		if (haseditQ) {
			// Store new editorial pitch state.
			m_estates.at(track).at(base7) = true;
			m_pstates.at(track).at(base7) = accid;
			continue;
		}

		if (explicitQ) {
			// No need to make editorial since it is visible.
			m_estates.at(track).at(base7) = false;
			m_pstates.at(track).at(base7) = accid;
			continue;
		}

		if (accid == m_kstates.at(track).at(base7)) {
			// 	!m_estates.at(track).at(base7)) {
			// add !m_estates.at(track).at(base) as a condition if
			// you want editorial accidentals to be added to return the
			// note to the accidental in the key.
			//
			// The accidental matches the key-signature state,
			// so it should not be made editorial eventhough
			// it is not visible.
			m_pstates.at(track).at(base7) = accid;

			// Add a "y" marker of there is an interpreted accidental
			// state (flat or sharp) that is part of the key signature.
			int hasaccid = false;
			if (token->find("#") != string::npos) {
				hasaccid = true;
			} else if (token->find("-") != string::npos) {
				hasaccid = true;
			}
			int hashide = false;
			if (token->find("-y") != string::npos) {
				hashide = true;
			}
			else if (token->find("#y") != string::npos) {
				hashide = true;
			}
			if (hasaccid && !hashide) {
				string text = *token;
				hre.replaceDestructive(text, "#y", "#");
				hre.replaceDestructive(text, "-y", "-");
				token->setText(text);
			}

			continue;
		}

		// At this point the previous note with this pitch class
		// had an editorial accidental, and this note also has the
		// same accidental, or there was a previous visual accidental
		// outside of the key signature that will cause this note to have
		// an editorial accidental mark applied (Sibelius will drop
		// secondary editorial accidentals in a measure when exporting,
		// MusicXML, which is why this function is needed).

		m_estates[track][base7] = true;
		m_pstates[track][base7] = accid;

		string text = token->getText();
		HumRegex hre;
		hre.replaceDestructive(text, "#", "##+", "g");
		hre.replaceDestructive(text, "-", "--+", "g");
		string output = "";
		bool foundQ = false;
		for (int j=0; j<(int)text.size(); j++) {
			if (text[j] == 'n') {
				output += "ni";
				foundQ = true;
			} else if (text[j] == '#') {
				output += "#i";
				foundQ = true;
			} else if (text[j] == '-') {
				output += "-i";
				foundQ = true;
			} else {
				output += text[j];
			}
		}

		if (foundQ) {
			token->setText(output);
			continue;
		}

		// The note is natural, but has no natural sign.
		// add the natural sign and editorial mark.
		for (int j=(int)output.size()-1; j>=0; j--) {
			if ((tolower(output[j]) >= 'a') && (tolower(output[j]) <= 'g')) {
				output.insert(j+1, "ni");
				break;
			}
		}
		token->setText(output);
	}
}



////////////////////////////////
//
// Tool_gasparize::updateKeySignatures -- Fill in the accidental
//    states for each diatonic pitch.
//

void Tool_gasparize::updateKeySignatures(HumdrumFile& infile, int lineindex) {
	HumdrumLine& line = infile[lineindex];
	int track;
	for (int i=0; i<line.getFieldCount(); i++) {
		if (!line.token(i)->isKeySignature()) {
			continue;
		}
		HTp token = line.token(i);
		track = token->getTrack();
		string text = token->getText();
		fill(m_kstates[track].begin(), m_kstates[track].end(), 0);
		for (int j=3; j<(int)text.size()-1; j++) {
			if (text[j] == ']') {
				break;
			}
			switch (text[j]) {
				case 'a': case 'A':
					switch (text[j+1]) {
						case '#': m_kstates[track][5] = +1;
						break;
						case '-': m_kstates[track][5] = -1;
						break;
					}
					break;

				case 'b': case 'B':
					switch (text[j+1]) {
						case '#': m_kstates[track][6] = +1;
						break;
						case '-': m_kstates[track][6] = -1;
						break;
					}
					break;

				case 'c': case 'C':
					switch (text[j+1]) {
						case '#': m_kstates[track][0] = +1;
						break;
						case '-': m_kstates[track][0] = -1;
						break;
					}
					break;

				case 'd': case 'D':
					switch (text[j+1]) {
						case '#': m_kstates[track][1] = +1;
						break;
						case '-': m_kstates[track][1] = -1;
						break;
					}
					break;

				case 'e': case 'E':
					switch (text[j+1]) {
						case '#': m_kstates[track][2] = +1;
						break;
						case '-': m_kstates[track][2] = -1;
						break;
					}
					break;

				case 'f': case 'F':
					switch (text[j+1]) {
						case '#': m_kstates[track][3] = +1;
						break;
						case '-': m_kstates[track][3] = -1;
						break;
					}
					break;

				case 'g': case 'G':
					switch (text[j+1]) {
						case '#': m_kstates[track][4] = +1;
						break;
						case '-': m_kstates[track][4] = -1;
						break;
					}
					break;
			}
			for (int j=0; j<7; j++) {
				if (m_kstates[track][j] == 0) {
					continue;
				}
				for (int k=1; k<10; k++) {
					m_kstates[track][j+k*7] = m_kstates[track][j];
				}
			}
		}
	}

	// initialize m_pstates with contents of m_kstates
	for (int i=0; i<(int)m_kstates.size(); i++) {
		for (int j=0; j<(int)m_kstates[i].size(); j++) {
			m_pstates[i][j] = m_kstates[i][j];
		}
	}

}



////////////////////////////////
//
// Tool_gasparize::clearStates --
//

void Tool_gasparize::clearStates(void) {
	for (int i=0; i<(int)m_pstates.size(); i++) {
		fill(m_pstates[i].begin(), m_pstates[i].end(), 0);
	}
	for (int i=0; i<(int)m_estates.size(); i++) {
		fill(m_estates[i].begin(), m_estates[i].end(), false);
	}
}


//////////////////////////////
//
// Tool_gasparize::getDate --
//

string Tool_gasparize::getDate(void) {
	time_t t = time(NULL);
	tm* timeptr = localtime(&t);
	stringstream ss;
	int year = timeptr->tm_year + 1900;
	int month = timeptr->tm_mon + 1;
	int day = timeptr->tm_mday;
	ss << year << "/";
	if (month < 10) {
		ss << "0";
	}
	ss << month << "/";
	if (day < 10) {
		ss << "0";
	}
	ss << day;
	return ss.str();
}



//////////////////////////////
//
// Tool_gasparize::fixTies --
//    If a tie is unclosed or if a note is followed by an invisible rest, then fix.
//

void Tool_gasparize::fixTies(HumdrumFile& infile) {
	int strands = infile.getStrandCount();
	for (int i=0; i<strands; i++) {
		HTp sstart = infile.getStrandStart(i);
		if (!sstart) {
			continue;
		}
		if (!sstart->isKern()) {
			continue;
		}
		HTp send   = infile.getStrandEnd(i);
		fixTiesForStrand(sstart, send);
	}
	fixTieStartEnd(infile);
}



void Tool_gasparize::fixTieStartEnd(HumdrumFile& infile) {
	int strands = infile.getStrandCount();
	for (int i=0; i<strands; i++) {
		HTp sstart = infile.getStrandStart(i);
		if (!sstart) {
			continue;
		}
		if (!sstart->isKern()) {
			continue;
		}
		HTp send   = infile.getStrandEnd(i);
		fixTiesStartEnd(sstart, send);
	}
}



void Tool_gasparize::fixTiesStartEnd(HTp starts, HTp ends) {
	HTp current = starts;
	HumRegex hre;
	while (current) {
		if (!current->isData()) {
			current = current->getNextToken();
			continue;
		}
		if ((current->find('[') != string::npos) &&
				(current->find(']') != string::npos) &&
				(current->find(' ') == string::npos)) {
			string text = *current;
			hre.replaceDestructive(text, "", "\\[", "g");
			hre.replaceDestructive(text, "_", "\\]", "g");
			current->setText(text);
		}
		current = current->getNextToken();
	}
}


//////////////////////////////
//
// Tool_gasparize::fixTiesForStrand --
//

void Tool_gasparize::fixTiesForStrand(HTp sstart, HTp send) {
	if (!sstart) {
		return;
	}
	HTp current = sstart;
	HTp last = NULL;
	current = current->getNextToken();
	while (current) {
		if (!current->isData()) {
			current = current->getNextToken();
			continue;
		}
		if (current->isNull()) {
			current = current->getNextToken();
			continue;
		}
		if (last == NULL) {
			last = current;
			current = current->getNextToken();
			continue;
		}
		if (current->find("yy") != string::npos) {
			fixTieToInvisibleRest(last, current);
		} else if (((last->find("[") != string::npos) || (last->find("_") != string::npos))
				&& ((current->find("]") == string::npos) && (current->find("_") == string::npos))) {
			fixHangingTie(last, current);
		}
		last = current;
		current = current->getNextToken();
	}
}



//////////////////////////////
//
// Tool_gasparize::fixTieToInvisibleRest --
//

void Tool_gasparize::fixTieToInvisibleRest(HTp first, HTp second) {
	if (second->find("yy") == string::npos) {
		return;
	}
	if ((first->find("[") == string::npos) && (first->find("_") == string::npos)) {
		string ftext = *first;
		ftext = "[" + ftext;
		first->setText(ftext);
	}
	HumRegex hre;
	if (!hre.search(first, "([A-Ga-g#n-]+)")) {
		return;
	}
	string pitch = hre.getMatch(1);
	pitch += "]";
	string text = *second;
	hre.replaceDestructive(text, pitch, "ryy");
	second->setText(text);
}



//////////////////////////////
//
// Tool_gasparize::fixHangingTie -- Not dealing with chain of missing ties.
//

void Tool_gasparize::fixHangingTie(HTp first, HTp second) {
	string text = *second;
	text += "]";
	second->setText(text);
}



//////////////////////////////
//
// Tool_gasparize::addMensurations -- Add mensurations.
//

void Tool_gasparize::addMensurations(HumdrumFile& infile) {
	HumRegex hre;
	for (int i=infile.getLineCount() - 1; i>=0; i--) {
		if (!infile[i].isInterpretation()) {
			continue;
		}
		for (int j=0; j<infile[i].getFieldCount(); j++) {
			HTp token = infile.token(i, 0);
			if (hre.search(token, "^\\*M(\\d+)/(\\d+)")) {
				int value = hre.getMatchInt(1);
				addMensuration(value, infile, i);
			}
		}
	}
}



//////////////////////////////
//
// Tool_gasparize::addMensuration --
//

void Tool_gasparize::addMensuration(int top, HumdrumFile& infile, int index) {
	HTp checktoken = infile[index+1].token(0);
	if (!checktoken) {
		return;
	}
	if (checktoken->find("met") != string::npos) {
		return;
	}
	int fieldcount = infile[index].getFieldCount();
	string line = "*";
	HTp token = infile[index].token(0);
	if (token->isKern()) {
		if (top == 2) {
			line += "met(C|)";
		} else {
			line += "met(O)";
		}
	}
	for (int i=1; i<fieldcount; i++) {
		line += "\t*";
		HTp token = infile[index].token(i);
		if (token->isKern()) {
			if (top == 2) {
				line += "met(C|)";
			} else {
				line += "met(O)";
			}
		}
	}
	infile.insertLine(index+1, line);
}


///////////////////////////////
//
// Tool_gasparize::createEditText -- Convert <i> markers into *edit interps.
//

void Tool_gasparize::createEditText(HumdrumFile& infile) {
	// previous process manipulated the structure so reanalyze here for now:
	infile.analyzeBaseFromTokens();
	infile.analyzeStructureNoRhythm();

	int strands = infile.getStrandCount();
	for (int i=0; i<strands; i++) {
		HTp sstart = infile.getStrandStart(i);
		if (!sstart) {
			continue;
		}
		if (!sstart->isDataType("**text")) {
			continue;
		}
		HTp send   = infile.getStrandEnd(i);
		bool status = addEditStylingForText(infile, sstart, send);
		if (status) {
			infile.analyzeBaseFromTokens();
			infile.analyzeStructureNoRhythm();
		}
	}
}


//////////////////////////////
//
// Tool_gasparize::addEditStylingForText --
//

bool Tool_gasparize::addEditStylingForText(HumdrumFile& infile, HTp sstart, HTp send) {
	HTp current = send->getPreviousToken();
	bool output = false;
	string state = "";
	string laststate = "";
	HumRegex hre;
	HTp lastdata = NULL;
	bool italicQ = false;
	while (current && (current != sstart)) {
		if (!current->isData()) {
			current = current->getPreviousToken();
			continue;
		}
		if (current->isNull()) {
			current = current->getPreviousToken();
			continue;
		}
		italicQ = false;
		string text = current->getText();
		if (text.find("<i>") != string::npos) {
			italicQ = true;
			hre.replaceDestructive(text, "", "<i>", "g");
			hre.replaceDestructive(text, "", "</i>", "g");
			current->setText(text);
		} else {
}
		if (laststate == "") {
			if (italicQ) {
				laststate = "italic";
			} else {
				laststate = "regular";
			}
			current = current->getPreviousToken();
			continue;
		} else {
			if (italicQ) {
				state = "italic";
			} else {
				state = "regular";
			}
		}
		if (state != laststate) {
			if (lastdata && (laststate == "italic")) {
				output = true;
				if (!insertEditText("*edit", infile, lastdata->getLineIndex() - 1, lastdata->getFieldIndex())) {
					string line = getEditLine("*edit", lastdata->getFieldIndex(), lastdata->getOwner());
					infile.insertLine(lastdata->getLineIndex(), line);
				}
			} else if (lastdata && (laststate == "regular")) {
				output = true;
				if (!insertEditText("*Xedit", infile, lastdata->getLineIndex() - 1, lastdata->getFieldIndex())) {
					string line = getEditLine("*Xedit", lastdata->getFieldIndex(), lastdata->getOwner());
					infile.insertLine(lastdata->getLineIndex(), line);
				}
			}
		}
		laststate = state;
		lastdata = current;
		current = current->getPreviousToken();
	}

	if (lastdata && italicQ) {
		// add *edit before first syllable in **text.
		output = true;
		if (!insertEditText("*edit", infile, lastdata->getLineIndex() - 1, lastdata->getFieldIndex())) {
			string line = getEditLine("*edit", lastdata->getFieldIndex(), lastdata->getOwner());
			infile.insertLine(lastdata->getLineIndex(), line);
		}
	}

	return output;
}



//////////////////////////////
//
// Tool_gasparize::insertEditText --
//

bool Tool_gasparize::insertEditText(const string& text, HumdrumFile& infile, int line, int field) {
	if (!infile[line].isInterpretation()) {
		return false;
	}
	HTp token;
	for (int i=0; i<infile[line].getFieldCount(); i++) {
		token = infile.token(line, i);
		if (token->isNull()) {
			continue;
		}
		if (token->find("edit") != string::npos) {
			break;
		}
		return false;
	}
	token = infile.token(line, field);
	token->setText(text);

	return true;
}



/////////////////////
//
// Tool_gasparize::getEditLine --
//

string Tool_gasparize::getEditLine(const string& text, int fieldindex, HLp line) {
	string output;
	for (int i=0; i<fieldindex; i++) {
		output += "*";
		if (i < line->getFieldCount()) {
			output += "\t";
		}
	}
	output += text;
	if (fieldindex < line->getFieldCount()) {
		output += "\t";
	}
	for (int i=fieldindex+1; i<line->getFieldCount(); i++) {
		output += "*";
		if (i < line->getFieldCount()) {
			output += "\t";
		}
	}
	return output;
}



//////////////////////////////
//
// adjustIntrumentNames --
//

void Tool_gasparize::adjustIntrumentNames(HumdrumFile& infile) {
	int instrumentLine = -1;
	int abbrLine = -1;
	for (int i=0; i<infile.getLineCount(); i++) {
		if (infile[i].isData()) {
			break;
		}
		if (!infile[i].isInterpretation()) {
			continue;
		}
		for (int j=0; j<infile[i].getFieldCount(); j++) {
			HTp token = infile.token(i, j);
			if (token->compare(0, 3, "*I\"") == 0) {
				instrumentLine = i;
			}
			if (token->compare(0, 3, "*I'") == 0) {
				abbrLine = i;
			}
		}
	}
	if (instrumentLine < 0) {
		return;
	}
	for (int i=0; i<infile[instrumentLine].getFieldCount(); i++) {
		HTp token = infile.token(instrumentLine, i);
		if (*token == "*I\"CT I") {
			token->setText("*I\"Contratenor 1");
		} else if (*token == "*I\"CTI") {
			token->setText("*I\"Contratenor 1");
		} else if (*token == "*I\"CTII") {
			token->setText("*I\"Contratenor 2");
		} else if (*token == "*I\"CT II") {
			token->setText("*I\"Contratenor 2");
		} else if (*token == "*I\"CT") {
			token->setText("*I\"Contratenor");
		} else if (*token == "*I\"S") {
			token->setText("*I\"Superius");
		} else if (*token == "*I\"A") {
			token->setText("*I\"Altus");
		} else if (*token == "*I\"T") {
			token->setText("*I\"Tenor");
		} else if (*token == "*I\"B") {
			token->setText("*I\"Bassus");
		} else if (*token == "*I\"V") {
			token->setText("*I\"Quintus");
		} else if (*token == "*I\"VI") {
			token->setText("*I\"Sextus");
		}
	}
	if (abbrLine >= 0) {
		return;
	}
	string abbr;
	HumRegex hre;
	for (int i=0; i<infile[instrumentLine].getFieldCount(); i++) {
		HTp token = infile.token(instrumentLine, i);
		string text = *token;
		if (text == "*I\"Quintus") {
			abbr += "*I'V";
		} else if (text == "*I\"Contratenor") {
			abbr += "*I'Ct";
		} else if (text == "*I\"Sextus") {
			abbr += "*I'VI";
		} else if (text == "*I\"Contratenor 1") {
			abbr += "*I'Ct1";
		} else if (text == "*I\"Contratenor 2") {
			abbr += "*I'Ct2";
		} else if (hre.search(text, "^\\*I\"([A-Z])")) {
			abbr += "*I'";
			abbr += hre.getMatch(1);
		} else {
			abbr += "*";
		}
		if (i < infile[instrumentLine].getFieldCount() - 1) {
			abbr += "\t";
		}
	}
	infile.insertLine(instrumentLine+1, abbr);
	infile.analyzeBaseFromTokens();
	infile.analyzeStructureNoRhythm();
}


//////////////////////////////
//
// Tool_gaspar::removeKeyDesignations --
//

void Tool_gasparize::removeKeyDesignations(HumdrumFile& infile) {
	HumRegex hre;
	for (int i=infile.getLineCount() - 1; i>=0; i--) {
		if (!infile[i].isInterpretation()) {
			continue;
		}
		for (int j=0; j<infile[i].getFieldCount(); j++) {
			HTp token = infile.token(i, j);
			if (*token == "*") {
				continue;
			}
			if (!token->isKern()) {
				continue;
			}
			if (hre.search(token, "^\\*[A-Ga-g][#n-]*:$")) {
				// suppress the key desingation
				infile.deleteLine(i);
				break;
			}
		}
	}

}


//////////////////////////////
//
// Tool_gasparize::fixBarlines -- Add final double barline and convert
//    any intermediate final barlines to double barlines.
//

void Tool_gasparize::fixBarlines(HumdrumFile& infile) {
	fixFinalBarline(infile);
	HumRegex hre;

	for (int i=0; i<infile.getLineCount(); i++) {
		if (!infile[i].isBarline()) {
			continue;
		}
		if (infile[i].getDurationToEnd() == 0) {
			break;
		}
		for (int j=0; j<infile[i].getFieldCount(); j++) {
			HTp token = infile.token(i, j);
			if (token->find("==") == string::npos) {
				continue;
			}
			if (hre.search(token, "^==(\\d*)")) {
				string text = "=";
				text += hre.getMatch(1);
				text += "||";
				token->setText(text);
			}
		}
	}
}



//////////////////////////////
//
// Tool_gasparize::fixFinalBarline --
//

void Tool_gasparize::fixFinalBarline(HumdrumFile& infile) {
	for (int i=infile.getLineCount() - 1; i>=0; i--) {
		if (infile[i].isData()) {
			break;
		}
		if (!infile[i].isBarline()) {
			continue;
		}
		for (int j=0; j<infile[i].getFieldCount(); j++) {
			HTp token = infile.token(i, j);
			if (*token != "==") {
				token->setText("==");
			}
		}
	}
}



//////////////////////////////
//
// Tool_gasparize::createJEditorialAccidentals --
// convert
// 	!LO:TX:a:t=(    )
// 	4F#
//

void Tool_gasparize::createJEditorialAccidentals(HumdrumFile& infile) {
	int strands = infile.getStrandCount();
	for (int i=0; i<strands; i++) {
		HTp sstart = infile.getStrandStart(i);
		if (!sstart) {
			continue;
		}
		if (!sstart->isKern()) {
			continue;
		}
		HTp send   = infile.getStrandEnd(i);
		createJEditorialAccidentals(sstart, send);
	}
}

void Tool_gasparize::createJEditorialAccidentals(HTp sstart, HTp send) {
	HTp current = sstart->getNextToken();
	HumRegex hre;
	while (current && (current != send)) {
		if (!current->isCommentLocal()) {
			current = current->getNextToken();
			continue;
		}
		if (hre.search(current, "^!LO:TX:a:t=\\(\\s*\\)$")) {
			current->setText("!");
			convertNextNoteToJAccidental(current);
		}
		current = current->getNextToken();
	}
}

void Tool_gasparize::convertNextNoteToJAccidental(HTp current) {
	current = current->getNextToken();
	HumRegex hre;
	while (current) {
		if (!current->isData()) {
			// Does not handle LO for non-data.
			current = current->getNextToken();
			continue;
		}
		if (current->isNull()) {
			break;
		}
		if (current->isRest()) {
			break;
		}
		string text = *current;
		if (hre.search(text, "i")) {
			hre.replaceDestructive(text, "j", "i");
			current->setText(text);
			break;
		} else if (hre.search(text, "[-#n]")) {
			hre.replaceDestructive(text, "$1j", "(.*[-#n]+)");
			current->setText(text);
			break;
		} else {
			// Need to add a natural sign as well.
			hre.replaceDestructive(text, "$1nj", "(.*[A-Ga-g]+)");
			current->setText(text);
			break;
		}
		break;
	}
	current = current->getNextToken();
}





/////////////////////////////////
//
// Tool_grep::Tool_grep -- Set the recognized options for the tool.
//

Tool_grep::Tool_grep(void) {
	define("v|remove-matching-lines=b",    "remove lines that match regex");
	define("e|regex|regular-expression=s", "regular expression to search with");
}


/////////////////////////////////
//
// Tool_grep::run -- Do the main work of the tool.
//

bool Tool_grep::run(HumdrumFileSet& infiles) {
	bool status = true;
	for (int i=0; i<infiles.getCount(); i++) {
		status &= run(infiles[i]);
	}
	return status;
}


bool Tool_grep::run(const string& indata, ostream& out) {
	HumdrumFile infile(indata);
	bool status = run(infile);
	if (hasAnyText()) {
		getAllText(out);
	} else {
		out << infile;
	}
	return status;
}


bool Tool_grep::run(HumdrumFile& infile, ostream& out) {
	bool status = run(infile);
	if (hasAnyText()) {
		getAllText(out);
	} else {
		out << infile;
	}
	return status;
}


bool Tool_grep::run(HumdrumFile& infile) {
	initialize();
	processFile(infile);
	return true;
}



//////////////////////////////
//
// Tool_grep::initialize --  Initializations that only have to be
//    done one for all HumdrumFile segments.
//

void Tool_grep::initialize(void) {
	m_negateQ = getBoolean("remove-matching-lines");
	m_regex = getString("regular-expression");
}



//////////////////////////////
//
// Tool_grep::processFile --
//

void Tool_grep::processFile(HumdrumFile& infile) {
	HumRegex hre;
	bool match;
	for (int i=0; i<infile.getLineCount(); i++) {
		match = hre.search(infile[i], m_regex);
		if (m_negateQ) {
			if (match) {
				continue;
			}
		} else {
			if (!match) {
				continue;
			}
		}
		m_humdrum_text << infile[i] << "\n";
	}
}




/////////////////////////////////
//
// Tool_half::Tool_half -- Set the recognized options for the tool.
//

Tool_half::Tool_half(void) {
	define("l|lyric-beam-break=b", "Break beams at syllable starts");
}



/////////////////////////////////
//
// Tool_half::run -- Primary interfaces to the tool.
//

bool Tool_half::run(HumdrumFileSet& infiles) {
	bool status = true;
	for (int i=0; i<infiles.getCount(); i++) {
		status &= run(infiles[i]);
	}
	return status;
}


bool Tool_half::run(const string& indata, ostream& out) {
	HumdrumFile infile(indata);
	bool status = run(infile);
	if (hasAnyText()) {
		getAllText(out);
	} else {
		out << infile;
	}
	return status;
}


bool Tool_half::run(HumdrumFile& infile, ostream& out) {
	bool status = run(infile);
	if (hasAnyText()) {
		getAllText(out);
	} else {
		out << infile;
	}
	return status;
}

//
// In-place processing of file:
//

bool Tool_half::run(HumdrumFile& infile) {
	processFile(infile);

	// Re-load the text for each line from their tokens.
	infile.createLinesFromTokens();

	// Need to adjust the line numbers for tokens for later
	// processing.
	m_humdrum_text << infile;
	return true;
}



//////////////////////////////
//
// Tool_half::processFile --
//

void Tool_half::processFile(HumdrumFile& infile) {
	m_lyricBreakQ = getBoolean("lyric-beam-break");
	terminalLongToTerminalBreve(infile);
	halfRhythms(infile);
	adjustBeams(infile);
}



//////////////////////////////
//
// Tool_half::adjustBeams --
//

void Tool_half::adjustBeams(HumdrumFile& infile) {
	Tool_autobeam autobeam;
	vector<string> argv;
	argv.push_back("autobeam");
	if (m_lyricBreakQ) {
		argv.push_back("-l");
	}
	autobeam.process(argv);
	autobeam.run(infile);
}



//////////////////////////////
//
// Tool_half::halfRhythms --
//

void Tool_half::halfRhythms(HumdrumFile& infile) {
	HumRegex hre;
	for (int i=0; i<infile.getLineCount(); i++) {
		if (infile[i].isData()) {
			for (int j=0; j<infile[i].getFieldCount(); j++) {
				HTp token = infile.token(i, j);
				if (!token->isKern()) {
					continue;
				}
				if (token->isNull()) {
					continue;
				}

				string text = *token;
				// extract duration without dot
				HumNum durnodot = Convert::recipToDurationNoDots(text);
				durnodot /= 2;
				string newrhythm = Convert::durationToRecip(durnodot);
				hre.replaceDestructive(text, newrhythm, "\\d+%?\\d*");
				token->setText(text);
			}
		} else if (infile[i].isInterpretation()) {
			// half time signatures
			for (int j=0; j<infile[i].getFieldCount(); j++) {
				HTp token = infile.token(i, j);
				if (hre.search(token, "^\\*M(\\d+)/(\\d+)%(\\d+)")) {
					int bot1 = hre.getMatchInt(2);
					int bot2 = hre.getMatchInt(3);
					if (bot2 % 2) {
						cerr << "Cannot handle conversion of time signature " << token << endl;
						continue;
					}
					bot2 /= 2;
					if (bot2 == 1) {
						string text = *token;
						string replacement = "/" + to_string(bot1);
						hre.replaceDestructive(text, replacement, "/\\d+%\\d+");
						token->setText(text);
					} else {
						string text = *token;
						string replacement = "/" + to_string(bot1);
						replacement += "%" + to_string(bot2);
						hre.replaceDestructive(text, replacement, "/\\d+");
						token->setText(text);
					}
				} else if (hre.search(token, "^\\*M(\\d+)/(\\d+)")) {
					int bot = hre.getMatchInt(2);
					if (bot == 4) {
						bot = 8;
					} else if (bot == 2) {
						bot = 4;
					} else if (bot == 3) {
						bot = 6;
					} else if (bot == 1) {
						bot = 2;
					} else if (bot == 0) {
						bot = 1;
					} else {
						cerr << "Warning: ignored time signature: " << token << endl;
					}
					string text = *token;
					string replacement = "/" + to_string(bot);
					hre.replaceDestructive(text, replacement, "/\\d+");
					token->setText(text);
				}
			}
		}
	}
}



//////////////////////////////
//
// Tool_half::terminalLongToTerminalBreve --
//

void Tool_half::terminalLongToTerminalBreve(HumdrumFile& infile) {
	HumRegex hre;
	for (int i=0; i<infile.getLineCount(); i++) {
		if (!infile[i].isReference()) {
			continue;
		}
		HTp token = infile.token(i, 0);
		if (token->find("terminal long") == string::npos) {
			continue;
		}
		string text = *token;
		hre.replaceDestructive(text, "terminal breve", "terminal long", "g");
		token->setText(text);
	}
}




/////////////////////////////////
//
// Tool_hands::Tool_hands -- Set the recognized options for the tool.
//

Tool_hands::Tool_hands(void) {
	define("c|color=b", "color right-hand notes red and left-hand notes blue");
	define("lcolor|left-color=s:dodgerblue", "color of left-hand notes");
	define("rcolor|right-color=s:crimson",   "color of right-hand notes");
	define("l|left-only=b",                  "remove right-hand notes");
	define("r|right-only=b",                 "remove left-hand notes");
	define("m|mark=b",                       "mark left and right-hand notes");
	define("a|attacks-only=b",               "only mark note attacks and not note sustains");
}



//////////////////////////////
//
// Tool_hands::initialize --  Initializations that only have to be done once
//    for all HumdrumFile segments.
//

void Tool_hands::initialize(void) {
	m_colorQ       = getBoolean("color");
	m_leftColor    = getString("left-color");
	m_rightColor   = getString("right-color");
	m_leftOnlyQ    = getBoolean("left-only");
	m_rightOnlyQ   = getBoolean("right-only");
	m_markQ        = getBoolean("mark");
	m_attacksOnlyQ = getBoolean("attacks-only");
}



/////////////////////////////////
//
// Tool_hands::run -- Do the main work of the tool.
//

bool Tool_hands::run(HumdrumFileSet& infiles) {
	bool status = true;
	for (int i=0; i<infiles.getCount(); i++) {
		status &= run(infiles[i]);
	}
	return status;
}



bool Tool_hands::run(const string& indata, ostream& out) {
	HumdrumFile infile(indata);
	bool status = run(infile);
	if (hasAnyText()) {
		getAllText(out);
	} else {
		out << infile;
	}
	return status;
}


bool Tool_hands::run(HumdrumFile& infile, ostream& out) {
	bool status = run(infile);
	if (hasAnyText()) {
		getAllText(out);
	} else {
		out << infile;
	}
	return status;
}


bool Tool_hands::run(HumdrumFile& infile) {
	initialize();
	processFile(infile);
	return true;
}



//////////////////////////////
//
// Tool_hands::processFile --
//

void Tool_hands::processFile(HumdrumFile& infile) {
	if (m_markQ || m_leftOnlyQ || m_rightOnlyQ) {
		infile.doHandAnalysis(m_attacksOnlyQ);
	}
	if (m_leftOnlyQ) {
		removeNotes(infile, "RH");
	} else if (m_rightOnlyQ) {
		removeNotes(infile, "LH");
	}
	if (m_colorQ) {
		colorHands(infile);
	} else if (m_markQ) {
		markNotes(infile);
	}
	m_humdrum_text << infile;
}



//////////////////////////////
//
// Tool_hands::removeNotes --
//

void Tool_hands::removeNotes(HumdrumFile& infile, const string& htype) {
	int counter = 0;
	int scount = infile.getStrandCount();
	for (int i=0; i<scount; i++) {
		HTp sstart = infile.getStrandStart(i);
		HTp xtok = sstart->getExclusiveInterpretation();
		int hasHandMarkup = xtok->getValueInt("auto", "hand");
		if (!hasHandMarkup) {
			continue;
		}
		HTp send = infile.getStrandEnd(i);
		removeNotes(sstart, send, htype);
		counter++;
	}

	
	if (counter) {
		infile.createLinesFromTokens();
	}
}


void Tool_hands::removeNotes(HTp sstart, HTp send, const string& htype) {
	HTp current = sstart;
	while (current && (current != send)) {
		if (!current->isData() || current->isNull()) {
			current = current->getNextToken();
			continue;
		}

		HumRegex hre;
		string ttype = current->getValue("auto", "hand");
		if (ttype != htype) {
			current = current->getNextToken();
			continue;
		}
		string text = *current;
		hre.replaceDestructive(text, "", "[^0-9.%q ]", "g");
		hre.replaceDestructive(text, "ryy ", " ", "g");
		text += "ryy";
		current->setText(text);
		current = current->getNextToken();
	}
}



//////////////////////////////
//
// Tool_hands::markNotes --
//

void Tool_hands::markNotes(HumdrumFile& infile) {
	HumRegex hre;

	int counter = 0;
	int scount = infile.getStrandCount();
	for (int i=0; i<scount; i++) {
		HTp sstart = infile.getStrandStart(i);
		HTp xtok = sstart->getExclusiveInterpretation();
		int hasHandMarkup = xtok->getValueInt("auto", "hand");
		if (!hasHandMarkup) {
			continue;
		}
		HTp send   = infile.getStrandEnd(i);
		markNotes(sstart, send);
		counter++;
	}

	if (counter) {
		infile.appendLine("!!!RDF**kern: " + m_leftMarker + " = marked note, color=\"" + m_leftColor + "\", left-hand note");
		infile.appendLine("!!!RDF**kern: " + m_rightMarker + " = marked note, color=\"" + m_rightColor + "\", right-hand note");
		infile.createLinesFromTokens();
	}
}


void Tool_hands::markNotes(HTp sstart, HTp send) {
	HTp current = sstart;
	while (current && (current != send)) {
		if (!current->isData() || current->isNull() || current->isRest()) {
			current = current->getNextToken();
			continue;
		}

		HumRegex hre;
		string text = *current;
		string htype = current->getValue("auto", "hand");
		if (htype == "LH") {
			hre.replaceDestructive(text, " " + m_leftMarker, " +", "g");
			text = m_leftMarker + text;
		} else if (htype == "RH") {
			hre.replaceDestructive(text, " " + m_rightMarker, " +", "g");
			text = m_rightMarker + text;
		}
		current->setText(text);
		current = current->getNextToken();
	}
}



//////////////////////////////
//
// Tool_hands::colorHands -- Convert for example *LH into *color:dodgerblue.
//

void Tool_hands::colorHands(HumdrumFile& infile) {
	string left = "*color:" + m_leftColor;
	string right = "*color:" + m_rightColor;
	for (int i=0; i<infile.getLineCount(); i++) {
		if (!infile[i].isInterpretation()) {
			continue;
		}
		bool changed = false;
		for (int j=0; j<infile[i].getFieldCount(); j++) {
			HTp token = infile.token(i, j);
			if (!token->isKern()) {
				continue;
			}
			if (*token == "*LH") {
				token->setText(left);
				changed = true;
			}
			if (*token == "*RH") {
				token->setText(right);
				changed = true;
			}
		}
		if (changed) {
			infile[i].createLineFromTokens();
		}
	}
}




/////////////////////////////////
//
// Tool_homorhythm::Tool_homorhythm -- Set the recognized options for the tool.
//

Tool_homorhythm::Tool_homorhythm(void) {
	define("a|append=b",                 "append analysis to end of input data");
	define("attacks=b",                  "append attack counts for each sonority");
	define("p|prepend=b",                "prepend analysis to end of input data");
	define("r|raw-sonority=b",           "display individual sonority scores only");
	define("raw-score=b",                "display accumulated scores");
	define("M|no-marks=b",               "do not mark homorhythm section notes");
	define("f|fraction=b",               "calculate fraction of music that is homorhythm");
	define("v|voice=b",                  "display voice information or fraction results");
	define("F|filename=b",               "show filename for f option");
	define("n|t|threshold=d:4.0",        "threshold score sum required for homorhythm texture detection");
	define("s|score=d:1.0",              "score assigned to a sonority with three or more attacks");
	define("m|intermediate-score=d:0.5", "score to give sonority between two adjacent attack sonoroties");
	define("l|letter=b",                 "display letter scoress before calculations");
}



/////////////////////////////////
//
// Tool_homorhythm::run -- Do the main work of the tool.
//

bool Tool_homorhythm::run(HumdrumFileSet& infiles) {
	bool status = true;
	for (int i=0; i<infiles.getCount(); i++) {
		status &= run(infiles[i]);
	}
	return status;
}


bool Tool_homorhythm::run(const string& indata, ostream& out) {
	HumdrumFile infile;
	infile.readStringNoRhythm(indata);
	bool status = run(infile);
	if (hasAnyText()) {
		getAllText(out);
	} else {
		out << infile;
	}
	return status;
}


bool Tool_homorhythm::run(HumdrumFile& infile, ostream& out) {
	bool status = run(infile);
	if (hasAnyText()) {
		getAllText(out);
	} else {
		out << infile;
	}
	return status;
}


bool Tool_homorhythm::run(HumdrumFile& infile) {
	initialize();
	infile.analyzeStructure();
	m_voice_count = getExtantVoiceCount(infile);
	m_letterQ = getBoolean("letter");
	processFile(infile);
	infile.createLinesFromTokens();
	return true;
}



//////////////////////////////
//
// Tool_homorhythm::markHomophonicNotes --
//

void Tool_homorhythm::markHomophonicNotes(void) {
	// currently done with a **color spine
}



//////////////////////////////
//
// Tool_homorhythm::initialize --
//

void Tool_homorhythm::initialize(void) {
	m_threshold = getInteger("threshold");
	if (m_threshold < 1.0) {
		m_threshold = 1.0;
	}

	m_score = getDouble("score");
	if (m_score < 1.0) {
		m_score = 1.0;
	}

	m_intermediate_score = getDouble("intermediate-score");
	if (m_intermediate_score < 0.0) {
		m_intermediate_score = 0.0;
	}

	if (m_intermediate_score > m_score) {
		m_intermediate_score = m_score;
	}

}



//////////////////////////////
//
// Tool_homorhythm::processFile --
//

void Tool_homorhythm::processFile(HumdrumFile& infile) {
	vector<int> data;
	data.reserve(infile.getLineCount());

	m_homorhythm.clear();
	m_homorhythm.resize(infile.getLineCount());

	m_notecount.clear();
	m_notecount.resize(infile.getLineCount());
	fill(m_notecount.begin(), m_notecount.end(), 0);

	m_attacks.clear();
	m_attacks.resize(infile.getLineCount());
	fill(m_attacks.begin(), m_attacks.end(), 0);

	m_notes.clear();
	m_notes.resize(infile.getLineCount());

	for (int i=0; i<infile.getLineCount(); i++) {
		if (!infile[i].isData()) {
			continue;
		}
		data.push_back(i);
		analyzeLine(infile, i);
	}

	// change Y N Y patterns to Y Y Y
	for (int i=1; i<(int)data.size() - 1; i++) {
		if (m_homorhythm[data[i]] == "Y") {
			continue;
		}
		if (m_homorhythm[data[i+1]] == "N") {
			continue;
		}
		if (m_homorhythm[data[i-1]] == "N") {
			continue;
		}
	  	m_homorhythm[data[i]] = "NY";  // not homphonic by will get intermediate score.
	}

	vector<double> score(infile.getLineCount(), 0);
	vector<double> raw(infile.getLineCount(), 0);

	double sum = 0.0;
	for (int i=0; i<(int)data.size(); i++) {
		if (m_homorhythm[data[i]].find("Y") != string::npos) {
			if (m_homorhythm[data[i]].find("N") != string::npos) {
				// sonority between two homorhythm-like sonorities.
				// maybe also differentiate based on metric position.
				sum += m_intermediate_score;
				raw[data[i]] = m_intermediate_score;
			} else {
				sum += m_score;
				raw[data[i]] = m_score;
			}
		} else {
			sum = 0.0;
		}
		score[data[i]] = sum;
	}

	for (int i=(int)data.size()-2; i>=0; i--) {
		if (score[data[i]] == 0) {
			continue;
		}
		if (score[data[i+1]] > score[data[i]]) {
			score[data[i]] = score[data[i+1]];
		}
	}

	if (getBoolean("raw-score")) {
		addAccumulatedScores(infile, score);
	}

	if (getBoolean("raw-sonority")) {
		addRawAnalysis(infile, raw);
	}
	if (getBoolean("raw-score")) {
		addAccumulatedScores(infile, score);
	}

	if (getBoolean("fraction")) {
		addFractionAnalysis(infile, score);
	}

	if (getBoolean("attacks")) {
		addAttacks(infile, m_attacks);
	}

	if (!getBoolean("fraction")) {
		// Color the notes within homorhythm textures.
		// mark homorhythm regions in red,
		// non-homorhythm sonorities within these regions in green
		// and non-homorhythm regions in black.
		if (m_letterQ) {
			infile.appendDataSpine(m_homorhythm, "", "**hp");
		}
		for (int i=0; i<(int)data.size(); i++) {
			if (score[data[i]] >= m_threshold) {
				if (m_attacks[data[i]] < (int)m_notes[data[i]].size() - 1) {
					m_homorhythm[data[i]] = "dodgerblue";
				} else {
					m_homorhythm[data[i]] = "red";
				}
			} else {
				m_homorhythm[data[i]] = "black";
			}
		}
		infile.appendDataSpine(m_homorhythm, "", "**color");

		// problem with **color spine in javascript, so output via humdrum text
		m_humdrum_text << infile;
	}

}



//////////////////////////////
//
// Tool_homorhythm::addAccumulatedScores --
//

void Tool_homorhythm::addAccumulatedScores(HumdrumFile& infile, vector<double>& score) {
	infile.appendDataSpine(score, "", "**score", false);
}



//////////////////////////////
//
// Tool_homorhythm::addRawAnalysis --
//

void Tool_homorhythm::addRawAnalysis(HumdrumFile& infile, vector<double>& raw) {
	infile.appendDataSpine(raw, "", "**raw", false);
}



//////////////////////////////
//
// Tool_homorhythm::addAttacks --
//

void Tool_homorhythm::addAttacks(HumdrumFile& infile, vector<int>& attacks) {
	infile.appendDataSpine(attacks, "", "**atks");
}



//////////////////////////////
//
// Tool_homorhythm::addFractionAnalysis --
//

void Tool_homorhythm::addFractionAnalysis(HumdrumFile& infile, vector<double>& score) {
	double sum = 0.0;
	for (int i=0; i<infile.getLineCount(); i++) {
		if (!infile[i].isData()) {
			continue;
		}
		if (score[i] > m_threshold) {
			sum += infile[i].getDuration().getFloat();
		}
	}
	double total = infile.getScoreDuration().getFloat();
	int ocount = getOriginalVoiceCount(infile);
	double fraction = sum / total;
	double percent = int(fraction * 1000.0 + 0.5)/10.0;
	if (getBoolean("filename")) {
		m_free_text << infile.getFilename() << "\t";
	}
	if (getBoolean("voice")) {
		m_free_text << ocount;
		m_free_text << "\t";
		m_free_text << m_voice_count;
		m_free_text << "\t";
		if (ocount == m_voice_count) {
			m_free_text << "complete" << "\t";
		} else {
			m_free_text << "incomplete" << "\t";
		}
	}
	if (m_voice_count < 2) {
		m_free_text << -1;
	} else {
		m_free_text << percent;
	}
	m_free_text << endl;
}



//////////////////////////////
//
// Tool_homorhythm::getOriginalVoiceCount --
//

int Tool_homorhythm::getOriginalVoiceCount(HumdrumFile& infile) {
	HumRegex hre;
	for (int i=0; i<infile.getLineCount(); i++) {
		if (!infile[i].isReference()) {
			continue;
		}
		HTp token = infile.token(i, 0);
		if (hre.search(token, "^\\!\\!\\!voices\\s*:\\s*(\\d+)")) {
			int count = hre.getMatchInt(1);
			if (hre.search(token, "bc", "i")) {
				// add one for basso-continuo
				count++;
			}
			return count;
		}
	}
	return 0;
}



//////////////////////////////
//
// Tool_homorhythm::getExtantVoiceCount --
//

int Tool_homorhythm::getExtantVoiceCount(HumdrumFile& infile) {
	vector<HTp> spines = infile.getKernSpineStartList();
	return (int)spines.size();
}



//////////////////////////////
//
// Tool_homorhythm::analyzeLine --
//

void Tool_homorhythm::analyzeLine(HumdrumFile& infile, int line) {
	m_notes[line].reserve(10);
	HPNote note;
	if (!infile[line].isData()) {
		return;
	}
	int nullQ = 0;
	for (int i=0; i<infile[line].getFieldCount(); i++) {
		HTp token = infile.token(line, i);
		if (!token->isKern()) {
			continue;
		}
		if (token->isRest()) {
			continue;
		}
		if (token->isNull()) {
			nullQ = 1;
			token = token->resolveNull();
			if (!token) {
				continue;
			}
			if (token->isRest()) {
				continue;
			}
		} else {
			nullQ = 0;
		}
		int track = token->getTrack();
		vector<string> subtokens = token->getSubtokens();
		for (int j=0; j<(int)subtokens.size(); j++) {
			note.track = track;
			note.line = token->getLineIndex();
			note.field = token->getFieldIndex();
			note.subfield = j;
			note.token = token;
			note.text = subtokens[j];
			note.duration = Convert::recipToDuration(note.text);
			if (nullQ) {
				note.attack = false;
				note.nullQ = true;
			} else {
				note.nullQ = false;
				if ((note.text.find("_") != string::npos) ||
				    (note.text.find("]") != string::npos)) {
					note.attack = false;
				} else {
					note.attack = true;
				}
			}
			m_notes[line].push_back(note);
		}
	}

	// There must be at least three attacks to be considered homorhythm
	// maybe adjust to N-1 or three voices, or a similar rule.
	vector<HumNum> adurs;
	for (int i=0; i<(int)m_notes[line].size(); i++) {
		if (m_notes[line][i].attack) {
			adurs.push_back(m_notes[line][i].duration);
			m_attacks[line]++;
		}
	}
	// if ((int)m_attacks[line] >= (int)m_notes[line].size() - 1) {
	if ((int)m_attacks[line] >= 3) {
		string value = "Y";
		// value += to_string(m_attacks[line]);
		m_homorhythm[line] = value;
	} else if ((m_voice_count == 3) && (m_attacks[line] == 2)) {
		if ((adurs.size() >= 2) && (adurs[0] == adurs[1])) {
			m_homorhythm[line] = "Y";
		} else {
			m_homorhythm[line] = "N";
		}
	} else {
		string value = "N";
		// value += to_string(m_attacks[line]);
		m_homorhythm[line] = value;
	}
	// redundant or three-or-more case:
	if (m_notes[line].size() <= 2) {
		m_homorhythm[line] = "N";
	}
}




/////////////////////////////////
//
// Tool_homorhythm2::Tool_homorhythm -- Set the recognized options for the tool.
//

Tool_homorhythm2::Tool_homorhythm2(void) {
	define("t|threshold=d:1.6",  "threshold score sum required for homorhythm texture detection");
	define("u|threshold2=d:1.3", "threshold score sum required for semi-homorhythm texture detection");
	define("s|score=b",          "show numeric scores");
	define("n|length=i:4",       "sonority length to calculate");
	define("f|fraction=b",       "report fraction of music that is homorhythm");
}



/////////////////////////////////
//
// Tool_homorhythm2::run -- Do the main work of the tool.
//

bool Tool_homorhythm2::run(HumdrumFileSet& infiles) {
	bool status = true;
	for (int i=0; i<infiles.getCount(); i++) {
		status &= run(infiles[i]);
	}
	return status;
}


bool Tool_homorhythm2::run(const string& indata, ostream& out) {
	HumdrumFile infile;
	infile.read(indata);
	bool status = run(infile);
	if (hasAnyText()) {
		getAllText(out);
	} else {
		out << infile;
	}
	return status;
}


bool Tool_homorhythm2::run(HumdrumFile& infile, ostream& out) {
	bool status = run(infile);
	if (hasAnyText()) {
		getAllText(out);
	} else {
		out << infile;
	}
	return status;
}


bool Tool_homorhythm2::run(HumdrumFile& infile) {
	initialize();
	processFile(infile);
	return true;
}



//////////////////////////////
//
// Tool_homorhythm2::initialize --
//

void Tool_homorhythm2::initialize(void) {
	m_threshold = getDouble("threshold");
	if (m_threshold < 0.0) {
		m_threshold = 0.0;
	}
	m_threshold2 = getDouble("threshold2");
	if (m_threshold2 < 0.0) {
		m_threshold2 = 0.0;
	}
	if (m_threshold < m_threshold2) {
		double temp = m_threshold;
		m_threshold = m_threshold2;
		m_threshold2 = temp;
	}

}



//////////////////////////////
//
// Tool_homorhythm2::processFile --
//

void Tool_homorhythm2::processFile(HumdrumFile& infile) {
	infile.analyzeStructure();
	NoteGrid grid(infile);
	m_score.resize(infile.getLineCount());
	fill(m_score.begin(), m_score.end(), 0.0);

	double score;
	int count;
	int wsize = getInteger("length");

	for (int i=0; i<grid.getSliceCount()-wsize; i++) {
		score = 0;
		count = 0;
		for (int j=0; j<grid.getVoiceCount(); j++) {
			for (int k=j+1; k<grid.getVoiceCount(); k++) {
				for (int m=0; m<wsize; m++) {
					NoteCell* cell1 = grid.cell(j, i+m);
					if (cell1->isRest()) {
						continue;
					}
					NoteCell* cell2 = grid.cell(k, i+m);
					if (cell2->isRest()) {
						continue;
					}
					count++;
					if (cell1->isAttack() && cell2->isAttack()) {
						score += 1.0;
					}
				}
			}
		}
		int index = grid.getLineIndex(i);
		m_score[index] = score / count;
	}

	for (int i=grid.getSliceCount()-1; i>=wsize; i--) {
		score = 0;
		count = 0;
		for (int j=0; j<grid.getVoiceCount(); j++) {
			for (int k=j+1; k<grid.getVoiceCount(); k++) {
				for (int m=0; m<wsize; m++) {
					NoteCell* cell1 = grid.cell(j, i-m);
					if (cell1->isRest()) {
						continue;
					}
					NoteCell* cell2 = grid.cell(k, i-m);
					if (cell2->isRest()) {
						continue;
					}
					count++;
					if (cell1->isAttack() && cell2->isAttack()) {
						score += 1.0;
					}
				}
			}
		}
		int index = grid.getLineIndex(i);
		m_score[index] += score / count;
	}


	for (int i=0; i<(int)m_score.size(); i++) {
		m_score[i] = int(m_score[i] * 100.0 + 0.5) / 100.0;
	}


	vector<string> color(infile.getLineCount());;
	for (int i=0; i<infile.getLineCount(); i++) {
		if (!infile[i].isData()) {
			continue;
		}
		if (m_score[i] >= m_threshold) {
			color[i] = "red";
		} else if (m_score[i] >= m_threshold2) {
			color[i] = "orange";
		} else {
			color[i] = "black";
		}
	}

	if (getBoolean("fraction")) {
		HumNum sum = 0;
		HumNum total = infile.getScoreDuration();
		for (int i=0; i<(int)m_score.size(); i++) {
			if (m_score[i] >= m_threshold2) {
				sum += infile[i].getDuration();
			}
		}
		HumNum fraction = sum / total;
		m_free_text << int(fraction.getFloat() * 1000.0 + 0.5) / 10.0 << endl;
	} else {
		if (getBoolean("score")) {
			infile.appendDataSpine(m_score, ".", "**cdata", false);
		}
		infile.appendDataSpine(color, ".", "**color", true);
		infile.createLinesFromTokens();

		// problem within emscripten-compiled version, so force to output as string:
		m_humdrum_text << infile;
	}

}






/////////////////////////////////
//
// Tool_gridtest::Tool_hproof -- Set the recognized options for the tool.
//

Tool_hproof::Tool_hproof(void) {
	// put option definitions here
}



///////////////////////////////
//
// Tool_hproof::run -- Primary interfaces to the tool.
//

bool Tool_hproof::run(HumdrumFileSet& infiles) {
	bool status = true;
	for (int i=0; i<infiles.getCount(); i++) {
		status &= run(infiles[i]);
	}
	return status;
}

bool Tool_hproof::run(const string& indata, ostream& out) {
	HumdrumFile infile(indata);
	return run(infile, out);
}


bool Tool_hproof::run(HumdrumFile& infile, ostream& out) {
	bool status = run(infile);
	out << infile;
	return status;
}


bool Tool_hproof::run(HumdrumFile& infile) {
	markNonChordTones(infile);
	infile.appendLine("!!!RDF**kern: N = marked note, color=chocolate (non-chord tone)");
	infile.appendLine("!!!RDF**kern: Z = marked note, color=black (chord tone)");
	infile.createLinesFromTokens();
	return true;
}



//////////////////////////////
//
// Tool_hproof::markNonChordTones -- Mark
//

void Tool_hproof::markNonChordTones(HumdrumFile& infile) {
	vector<HTp> list;
	infile.getSpineStartList(list);
	vector<HTp> hlist;
	for (auto it : list) {
		if (*it == "**harm") {
			hlist.push_back(it);
		}
		if (*it == "**rhrm") {
			hlist.push_back(it);
		}
	}
	if (hlist.empty()) {
		cerr << "Warning: No **harm or **rhrm spines in data" << endl;
		return;
	}

	processHarmSpine(infile, hlist[0]);
}



//////////////////////////////
//
// processHarmSpine --
//

void Tool_hproof::processHarmSpine(HumdrumFile& infile, HTp hstart) {
	string key = "*C:";  // assume C major if no key designation
	HTp token = hstart;
	HTp ntoken = token->getNextNNDT();
	while (token) {
		markNotesInRange(infile, token, ntoken, key);
		if (!ntoken) {
			break;
		}
		if (ntoken && token) {
			getNewKey(token, ntoken, key);
		}
		token = ntoken;
		ntoken = ntoken->getNextNNDT();
	}
}



//////////////////////////////
//
// Tool_hproof::getNewKey --
//

void Tool_hproof::getNewKey(HTp token, HTp ntoken, string& key) {
	token = token->getNextToken();
	while (token && (token != ntoken)) {
		if (token->isKeyDesignation()) {
			key = *token;
		}
		token = token->getNextToken();
	}
}



//////////////////////////////
//
// Tool_hproof::markNotesInRange --
//

void Tool_hproof::markNotesInRange(HumdrumFile& infile, HTp ctoken, HTp ntoken, const string& key) {
	if (!ctoken) {
		return;
	}
	int startline = ctoken->getLineIndex();
	int stopline = infile.getLineCount();
	if (ntoken) {
		stopline = ntoken->getLineIndex();
	}
	vector<int> cts;
	cts = Convert::harmToBase40(ctoken, key);
	for (int i=startline; i<stopline; i++) {
		if (!infile[i].isData()) {
			continue;
		}
		for (int j=0; j<infile[i].getFieldCount(); j++) {
			if (!infile.token(i, j)->isKern()) {
				continue;
			}
			HTp tok = infile.token(i, j);
			if (tok->isNull()) {
				continue;
			}
			if (tok->isRest()) {
				continue;
			}
			markHarmonicTones(tok, cts);
		}
	}

// cerr << "TOK\t" << ctoken << "\tLINES\t" << startline << "\t" << stopline << "\t";
// for (int i=0; i<cts.size(); i++) {
// cerr << " " << Convert::base40ToKern(cts[i]);
// }
// cerr << endl;

}



//////////////////////////////
//
// Tool_hproof::markHarmonicTones --
//

void Tool_hproof::markHarmonicTones(HTp tok, vector<int>& cts) {
	int count = tok->getSubtokenCount();
	vector<int> notes = cts;
	string output;
	for (int i=0; i<count; i++) {
		string subtok = tok->getSubtoken(i);
		int pitch = Convert::kernToBase40(subtok);
		if (i > 0) {
			output += " ";
		}
		bool found = false;
		for (int j=0; j<(int)cts.size(); j++) {
			if (pitch % 40 == cts[j] % 40) {
				output += subtok;
				output += "Z";
				found = true;
				break;
			}
		}
		if (!found) {
			output += subtok;
			output += "N";
		}
	}
	tok->setText(output);
}





/////////////////////////////////
//
// Tool_humbreak::Tool_humbreak -- Set the recognized options for the tool.
//

Tool_humbreak::Tool_humbreak(void) {
	define("m|measures=s",             "measures numbers to place linebreaks before");
	define("p|page-breaks=s",          "measure numbers to place page breaks before");
	define("g|group=s:original",       "line/page break group");
	define("r|remove|remove-breaks=b", "remove line/page breaks");
	define("l|page-to-line-breaks=b",  "convert page breaks to line breaks");
}



/////////////////////////////////
//
// Tool_humbreak::run -- Do the main work of the tool.
//

bool Tool_humbreak::run(HumdrumFileSet& infiles) {
	bool status = true;
	for (int i=0; i<infiles.getCount(); i++) {
		status &= run(infiles[i]);
	}
	return status;
}


bool Tool_humbreak::run(const string& indata, ostream& out) {
	HumdrumFile infile(indata);
	bool status = run(infile);
	if (hasAnyText()) {
		getAllText(out);
	} else {
		out << infile;
	}
	return status;
}


bool Tool_humbreak::run(HumdrumFile& infile, ostream& out) {
	bool status = run(infile);
	if (hasAnyText()) {
		getAllText(out);
	} else {
		out << infile;
	}
	return status;
}


bool Tool_humbreak::run(HumdrumFile& infile) {
	processFile(infile);
	return true;
}



//////////////////////////////
//
// Tool_humbreak::initialize --  Initializations that only have to be done once
//    for all HumdrumFile segments.
//

void Tool_humbreak::initialize(void) {
	string systemMeasures = getString("measures");
	string pageMeasures = getString("page-breaks");
	m_group = getString("group");
	m_removeQ = getBoolean("remove-breaks");
	m_page2lineQ = getBoolean("page-to-line-breaks");

	vector<string> lbs;
	vector<string> pbs;
	HumRegex hre;
	hre.split(lbs, systemMeasures, "[^\\da-z]+");
	hre.split(pbs, pageMeasures, "[^\\da-z]+");

	for (int i=0; i<(int)lbs.size(); i++) {
		if (hre.search(lbs[i], "^(p?)(\\d+)([a-z]?)")) {
			int number = hre.getMatchInt(2);
			if (!hre.getMatch(1).empty()) {
				m_pageMeasures[number] = 1;
				int offset = 0;
				string letter;
				if (!hre.getMatch(3).empty()) {
					letter = hre.getMatch(3);
					offset = letter.at(0) - 'a';
				}
				m_pageOffset[number] = offset;
			} else {
				m_lineMeasures[number] = 1;
				int offset = 0;
				if (!hre.getMatch(3).empty()) {
					string letter = hre.getMatch(3);
					offset = letter.at(0) - 'a';
				}
				m_lineOffset[number] = offset;
			}
		}
	}

	for (int i=0; i<(int)pbs.size(); i++) {
		if (hre.search(pbs[i], "^(\\d+)([a-z]?)")) {
			int number = hre.getMatchInt(1);
			m_pageMeasures[number] = 1;
			int offset = 0;
			if (!hre.getMatch(2).empty()) {
				string letter = hre.getMatch(2);
				offset = letter.at(0) - 'a';
			}
			m_pageOffset[number] = offset;
		}
	}
}



//////////////////////////////
//
// Tool_humbreak::markLineBreakMeasures --
//

void Tool_humbreak::markLineBreakMeasures(HumdrumFile& infile) {
	vector<HLp> pbreak;
	vector<HLp> lbreak;
	HumRegex hre;
	map<int, int> used;

	for (int i=0; i<infile.getLineCount(); i++) {
		if (infile[i].isCommentGlobal()) {
			HTp token = infile[i].token(0);
			if (hre.search(token, "^!!LO:LB:")) {
				lbreak.push_back(&infile[i]);
			} else if (hre.search(token, "^!!LO:PB:")) {
				pbreak.push_back(&infile[i]);
			}
		}

		if (!infile[i].isBarline()) {
			continue;
		}

		int barnum = infile[i].getBarNumber();
		if (barnum < 0) {
			lbreak.clear();
			pbreak.clear();
			continue;
		}

		int status = m_lineMeasures[barnum];
		if (status) {
			HLp line = &infile[i];
			int offset = m_lineOffset[barnum];
			if (offset && (used[barnum] == 0)) {
				used[barnum] = offset;
				int ocounter = 0;
				lbreak.clear();
				pbreak.clear();
				for (int j=i+1; j<infile.getLineCount(); j++) {
					if (infile[i].isCommentGlobal()) {
						HTp token = infile.token(i, 0);
						if (hre.search(token, "^!!LO:LB:")) {
							lbreak.push_back(&infile[i]);
						}
						if (hre.search(token, "^!!LO:PB:")) {
							pbreak.push_back(&infile[i]);
						}
					}
					if (!infile[j].isBarline()) {
						continue;
					}
					ocounter++;
					if (ocounter == offset) {
						line = &infile[j];
					}
				}
				if (!lbreak.empty()) {
					lbreak.back()->setValue("auto", "barnum", barnum + 1);
				} else {
					line->setValue("auto", "barnum", barnum + 1);
				}
			} else {
				line->setValue("auto", "barnum", barnum + 1);
			}
		}

		status = m_pageMeasures[barnum];
		if (status) {
			HLp line = &infile[i];
			int offset = m_pageOffset[barnum];
			if (offset) {
				int ocounter = 0;
				lbreak.clear();
				pbreak.clear();
				for (int j=i+1; j<infile.getLineCount(); j++) {
					if (infile[i].isCommentGlobal()) {
						HTp token = infile.token(i, 0);
						if (hre.search(token, "^!!LO:LB:")) {
							lbreak.push_back(&infile[i]);
						}
						if (hre.search(token, "^!!LO:PB:")) {
							pbreak.push_back(&infile[i]);
						}
					}
					if (!infile[j].isBarline()) {
						continue;
					}
					ocounter++;
					if (ocounter == offset) {
						line = &infile[j];
					}
				}
				if (!pbreak.empty()) {
					pbreak.back()->setValue("auto", "barnum", barnum + 1);
					pbreak.back()->setValue("auto", "page", 1);
				}
			} else {
				line->setValue("auto", "barnum", barnum + 1);
				line->setValue("auto", "page", 1);
			}
		}
	}
}



//////////////////////////////
//
// Tool_humbreak::addBreaks --
//

void Tool_humbreak::addBreaks(HumdrumFile& infile) {
	markLineBreakMeasures(infile);

	HumRegex hre;
	for (int i=0; i<infile.getLineCount(); i++) {
		if (!(infile[i].isBarline() || infile[i].isComment())) {
			m_humdrum_text << infile[i] << endl;
			continue;
		}

		int barnum = infile[i].getValueInt("auto", "barnum");
		if (barnum < 1) {
			m_humdrum_text << infile[i] << endl;
			continue;
		}
		barnum--;
		int pageQ = infile[i].getValueInt("auto", "page");

		if (pageQ && infile[i].isComment()) {
			HTp token = infile.token(i, 0);
			if (hre.search(token, "^!!LO:PB:")) {
				// Add group to existing LO:PB:
				HTp token = infile.token(i, 0);
				HTp barToken = infile.token(i+1, 0);
				if (barToken->isBarline()) {
					int measure = infile[i+1].getBarNumber();
					int pbStatus = m_pageMeasures[measure];
					if (pbStatus) {
						string query = "\\b" + m_group + "\\b";
						if (!hre.match(token, query)) {
							m_humdrum_text << token << ", " << m_group << endl;
						} else {
							m_humdrum_text << token << endl;
						}
					} else {
						m_humdrum_text << token << endl;
					}
					m_humdrum_text << infile[i+1] << endl;
					i++;
					continue;
				}
			} else if (hre.search(token, "^!!LO:LB:")) {
				// Add group to existing LO:LB:
				HTp token = infile.token(i, 0);
				HTp barToken = infile.token(i+1, 0);
				if (barToken->isBarline()) {
					int measure = infile[i+1].getBarNumber();
					int lbStatus = m_lineMeasures[measure];
					if (lbStatus) {
						string query = "\\b" + m_group + "\\b";
						if (!hre.match(token, query)) {
							m_humdrum_text << token << ", " << m_group << endl;
						} else {
							m_humdrum_text << token << endl;
						}
					} else {
						m_humdrum_text << token << endl;
					}
					m_humdrum_text << infile[i+1] << endl;
					i++;
					continue;
				}
			}
		}

		if (pageQ) {
			m_humdrum_text << "!!LO:PB:g=" << m_group << endl;
		} else {
			m_humdrum_text << "!!LO:LB:g=" << m_group << endl;
		}
		m_humdrum_text << infile[i] << endl;
	}
}



//////////////////////////////
//
// Tool_humbreak::processFile --
//

void Tool_humbreak::processFile(HumdrumFile& infile) {
	initialize();
	if (m_removeQ) {
		removeBreaks(infile);
	} else if (m_page2lineQ) {
		convertPageToLine(infile);
	} else {
		addBreaks(infile);
	}
}



//////////////////////////////
//
// Tool_humbreak::removeBreaks --
//

void Tool_humbreak::removeBreaks(HumdrumFile& infile) {
	for (int i=0; i<infile.getLineCount(); i++) {
		if (infile[i].token(0)->compare(0, 7, "!!LO:LB") == 0) {
			continue;
		}
		if (infile[i].token(0)->compare(0, 7, "!!LO:PB") == 0) {
			continue;
		}
		m_humdrum_text << infile[i] << endl;
	}
}



//////////////////////////////
//
// Tool_humbreak::convertPageToLine --
//

void Tool_humbreak::convertPageToLine(HumdrumFile& infile) {
	HumRegex hre;
	for (int i=0; i<infile.getLineCount(); i++) {
		if (infile[i].token(0)->compare(0, 7, "!!LO:PB") == 0) {
			string text = *infile[i].token(0);
			hre.replaceDestructive(text, "!!LO:LB", "!!LO:PB");
			m_humdrum_text << text << endl;
			continue;
		}
		m_humdrum_text << infile[i] << endl;
	}
}




/////////////////////////////////
//
// Tool_humdiff::Tool_humdiff -- Set the recognized options for the tool.
//

Tool_humdiff::Tool_humdiff(void) {
	define("r|reference=i:1",     "sequence number of reference score");
	define("report=b",            "display report of differences");
	define("time-points|times=b", "display timepoint lists for each file");
	define("note-points|notes=b", "display notepoint lists for each file");
	define("c|color=s:red",       "color for difference markers");
}



//////////////////////////////
//
// Tool_humdiff::run --
//

bool Tool_humdiff::run(HumdrumFileSet& infiles) {
	int reference = getInteger("reference") - 1;
	if (reference < 0) {
		cerr << "Error: reference has to be 1 or higher" << endl;
		return false;
	}
	if (reference > infiles.getCount()) {
		cerr << "Error: reference number is too large: " << reference << endl;
		cerr << "Maximum is " << infiles.getCount() << endl;
		return false;
	}

	if (infiles.getSize() == 0) {
		cerr << "Usage: " << getCommand() << " files" << endl;
		return false;
	} else if (infiles.getSize() < 2) {
		cerr << "Error: requires two or more files" << endl;
		cerr << "Usage: " << getCommand() << " files" << endl;
		return false;
	} else {
		HumNum targetdur = infiles[0].getScoreDuration();
		for (int i=1; i<infiles.getSize(); i++) {
			HumNum dur = infiles[i].getScoreDuration();
			if (dur != targetdur) {
				cerr << "Error: all files must have the same duration" << endl;
				return false;
			}
		}

		for (int i=0; i<infiles.getCount(); i++) {
			if (i == reference) {
				continue;
			}
			compareFiles(infiles[reference], infiles[i]);
		}

		if (!getBoolean("report")) {
			infiles[reference].createLinesFromTokens();
			m_humdrum_text << infiles[reference];
			if (m_marked) {
				m_humdrum_text << "!!!RDF**kern: @ = marked note";
				if (getBoolean("color")) {
					m_humdrum_text << "color=\"" << getString("color") << "\"";
				}
				m_humdrum_text << endl;
			}
		}
	}

	return true;
}



//////////////////////////////
//
// Tool_humdiff::compareFiles --
//

void Tool_humdiff::compareFiles(HumdrumFile& reference, HumdrumFile& alternate) {
	vector<vector<TimePoint>> timepoints(2);
	extractTimePoints(timepoints.at(0), reference);
	extractTimePoints(timepoints.at(1), alternate);

	if (getBoolean("time-points")) {
		printTimePoints(timepoints[0]);
		printTimePoints(timepoints[1]);
	}

	compareTimePoints(timepoints, reference, alternate);
}



//////////////////////////////
//
// Tool_humdiff::printTimePoints --
//

void Tool_humdiff::printTimePoints(vector<TimePoint>& timepoints) {
	for (int i=0; i<(int)timepoints.size(); i++) {
		m_free_text << "TIMEPOINT " << i << ":" << endl;
		m_free_text << timepoints[i] << endl;
	}
}



//////////////////////////////
//
// Tool_humdiff::compareTimePoints --
//

void Tool_humdiff::compareTimePoints(vector<vector<TimePoint>>& timepoints,
		HumdrumFile& reference, HumdrumFile& alternate) {
	vector<int> indexes(timepoints.size(), 0);
	HumNum minval;
	HumNum value;
	int found;

	vector<HumdrumFile*> infiles(2, NULL);
	infiles[0] = &reference;
	infiles[1] = &alternate;

	vector<int> increment(timepoints.size(), 0);

	while ((1)) {
		if (indexes.at(0) >= (int)timepoints.at(0).size()) {
			// at the end of the list of notes for the first file.
			// break from the comparison for now and figure out how
			// to report differences of added notes in the other file(s)
			// later.
			break;
		}
		timepoints.at(0).at(indexes.at(0)).index.resize(timepoints.size());
		for (int i=1; i<(int)timepoints.size(); i++) {
			timepoints.at(0).at(indexes.at(0)).index.at(i) = -1;
		}
		minval = timepoints.at(0).at(indexes.at(0)).timestamp;
		for (int i=1; i<(int)timepoints.size(); i++) {
			if (indexes.at(i) >= (int)timepoints.at(i).size()) {
				continue;
			}
			value = timepoints.at(i).at(indexes.at(i)).timestamp;
			if (value < minval) {
				minval = value;
			}
		}
		found = 0;
		fill(increment.begin(), increment.end(), 0);

		for (int i=0; i<(int)timepoints.size(); i++) {
			if (indexes.at(i) >= (int)timepoints.at(i).size()) {
				// index is too large for file, so skip checking it.
				continue;
			}
			found = 1;
			value = timepoints.at(i).at(indexes.at(i)).timestamp;

			if (value == minval) {
				timepoints.at(0).at(indexes.at(0)).index.at(i) = timepoints.at(i).at(indexes.at(i)).index.at(0);
				increment.at(i)++;
			}
		}
		if (!found) {
			break;
		} else {
			compareLines(minval, indexes, timepoints, infiles);
		}
		for (int i=0; i<(int)increment.size(); i++) {
			indexes.at(i) += increment.at(i);
		}
	}
}



//////////////////////////////
//
// Tool_humdiff::printNotePoints --
//

void Tool_humdiff::printNotePoints(vector<NotePoint>& notelist) {
	m_free_text << "vvvvvvvvvvvvvvvvvvvvvvvvv" << endl;
	for (int i=0; i<(int)notelist.size(); i++) {
		m_free_text << "NOTE " << i << endl;
		m_free_text << notelist.at(i) << endl;
	}
	m_free_text << "^^^^^^^^^^^^^^^^^^^^^^^^^" << endl;
	m_free_text << endl;
}



//////////////////////////////
//
// Tool_humdiff::markNote -- mark the note (since it does not have a match in other edition(s).
//

void Tool_humdiff::markNote(NotePoint& np) {
	m_marked = 1;
	HTp token = np.token;
	if (!token) {
		return;
	}
	if (!token->isChord()) {
		string contents = *token;
		contents += "@";
		token->setText(contents);
		return;
	}
	vector<string> tokens = token->getSubtokens();
	tokens[np.subindex] += "@";
	string output = tokens[0];
	for (int i=1; i<(int)tokens.size(); i++) {
		output += " ";
		output += tokens[i];
	}
	token->setText(output);
}



//////////////////////////////
//
// Tool_humdiff::compareLines --
//

void Tool_humdiff::compareLines(HumNum minval, vector<int>& indexes,
		vector<vector<TimePoint>>& timepoints, vector<HumdrumFile*> infiles) {

	bool reportQ = getBoolean("report");

	// cerr << "COMPARING LINES ====================================" << endl;
	vector<vector<NotePoint>> notelist(indexes.size());

	// Note: timepoints size must be 2
	// and infiles size must be 2
	for (int i=0; i<(int)timepoints.size(); i++) {
		if (indexes.at(i) >= (int)timepoints.at(i).size()) {
			continue;
		}
		if (timepoints.at(i).at(indexes.at(i)).timestamp != minval) {
			// not at the same time
			continue;
		}

		getNoteList(notelist.at(i), *infiles[i],
			timepoints.at(i).at(indexes.at(i)).index[0],
			timepoints.at(i).at(indexes.at(i)).measure, i, indexes.at(i));


	}
	for (int i=0; i<(int)notelist.at(0).size(); i++) {
		notelist.at(0).at(i).matched.resize(notelist.size());
		fill(notelist.at(0).at(i).matched.begin(), notelist.at(0).at(i).matched.end(), -1);
		notelist.at(0).at(i).matched.at(0) = i;
		for (int j=1; j<(int)notelist.size(); j++) {
			int status = findNoteInList(notelist.at(0).at(i), notelist.at(j));
			notelist.at(0).at(i).matched.at(j) = status;
			if ((status < 0) && !reportQ) {
				markNote(notelist.at(0).at(i));
			}
		}
	}

	if (getBoolean("notes")) {
		for (int i=0; i<(int)notelist.size(); i++) {
			cerr << "========== NOTES FOR I=" << i << endl;
			printNotePoints(notelist.at(i));
			cerr << endl;
		}
	}

	if (!reportQ) {
		return;
	}

	// report
	for (int i=0; i<(int)notelist.at(0).size(); i++) {
		for (int j=1; j<(int)notelist.at(0).at(i).matched.size(); j++) {
			if (notelist.at(0).at(i).matched.at(j) < 0) {
				cout << "NOTE " << notelist.at(0).at(i).subtoken
				     << " DOES NOT HAVE EXACT MATCH IN SOURCE " << j << endl;
				int humindex = notelist.at(0).at(i).token->getLineIndex();
				cout << "\tREFERENCE MEASURE\t: " << notelist.at(0).at(i).measure << endl;
				cout << "\tREFERENCE LINE NO.\t: " << humindex+1 << endl;
				cout << "\tREFERENCE LINE TEXT\t: " << (*infiles[0])[humindex] << endl;

				cout << "\tTARGET  " << j << " LINE NO. ";
				if (j < 10) {
					cout << " ";
				}
				cout << ":\t" << "X" << endl;

				cout << "\tTARGET  " << j << " LINE TEXT";
				if (j < 10) {
					cout << " ";
				}
				cout << ":\t" << "X" << endl;

				cout << endl;
			}
		}
	}
}



//////////////////////////////
//
// Tool_humdiff::findNoteInList --
//

int Tool_humdiff::findNoteInList(NotePoint& np, vector<NotePoint>& nps) {
	for (int i=0; i<(int)nps.size(); i++) {
		// cerr << "COMPARING " << np.token << " (" << np.b40 << ") TO " << nps.at(i).token << " (" << nps.at(i).b40 << ") " << endl;
		if (nps.at(i).processed) {
			continue;
		}
		if (nps.at(i).b40 != np.b40) {
			continue;
		}
		if (nps.at(i).duration != np.duration) {
			continue;
		}
		return i;
	}
	// cerr << "\tCannot find note " << np.token << " on line " << np.token->getLineIndex() << " in other work" << endl;
	return -1;
}




//////////////////////////////
//
// Tool_humdiff::getNoteList --
//

void Tool_humdiff::getNoteList(vector<NotePoint>& notelist, HumdrumFile& infile, int line, int measure, int sourceindex, int tpindex) {
	for (int i=0; i<infile[line].getFieldCount(); i++) {
		HTp token = infile.token(line, i);
		if (!token->isKern()) {
			continue;
		}
		if (token->isNull()) {
			continue;
		}
		if (token->isRest()) {
			continue;
		}
		int scount = token->getSubtokenCount();
		int track = token->getTrack();
		int layer = token->getSubtrack();
		for (int j=0; j<scount; j++) {
			string subtok = token->getSubtoken(j);
			if (subtok.find("]") != string::npos) {
				continue;
			}
			if (subtok.find("_") != string::npos) {
				continue;
			}
			// found a note to store;
			notelist.resize(notelist.size() + 1);
			notelist.back().token = token;
			notelist.back().subtoken = subtok;
			notelist.back().subindex = j;
			notelist.back().measurequarter = token->getDurationFromBarline();
			notelist.back().measure =
			notelist.back().track = track;
			notelist.back().layer = layer;
			notelist.back().sourceindex = sourceindex;
			notelist.back().tpindex = tpindex;
			notelist.back().duration = token->getTiedDuration();
			notelist.back().b40 = Convert::kernToBase40(subtok);
		}
	}
}



//////////////////////////////
//
// Tool_humdiff::extractTimePoints -- Extract a list of the timestamps in a file.
//

void Tool_humdiff::extractTimePoints(vector<TimePoint>& points, HumdrumFile& infile) {
	TimePoint tp;
	points.clear();
	HumRegex hre;
	points.reserve(infile.getLineCount());
	int measure = -1;
	for (int i=0; i<infile.getLineCount(); i++) {
		if (infile[i].isBarline()) {
			if (hre.search(infile.token(i, 0), "(\\d+)")) {
				measure = hre.getMatchInt(1);
			}
		}
		if (!infile[i].isData()) {
			continue;
		}
		if (infile[i].getDuration() == 0) {
			// ignore grace notes for now
			continue;
		}
		tp.clear();
		tp.file.push_back(&infile);
		tp.index.push_back(i);
		tp.timestamp = infile[i].getDurationFromStart();
		tp.measure = measure;
		points.push_back(tp);
	}
}



//////////////////////////////
//
// operator<< == print a TimePoint
//

ostream& operator<<(ostream& out, TimePoint& tp) {
	out << "\ttimestamp:\t" << tp.timestamp.getFloat() << endl;
	out << "\tmeasure:\t" << tp.measure << endl;
	out << "\tindexes:\t" << endl;
	for (int i=0; i<(int)tp.index.size(); i++) {
		out << "\t\tindex " << i << " is:\t" << tp.index[i] << "\t" << (*tp.file[i])[tp.index[i]] << endl;
	}
	return out;
}



//////////////////////////////
//
// operator<< == print a NotePoint
//

ostream& operator<<(ostream& out, NotePoint& np) {
	if (np.token) {
		out << "\ttoken:\t\t" << np.token << endl;
	}
	out << "\ttoken index:\t" << np.subindex << endl;
	if (!np.subtoken.empty()) {
		out << "\tsubtoken:\t" << np.subtoken << endl;
	}
	out << "\tmeasure:\t" << np.measure << endl;
	out << "\tmquarter:\t" << np.measurequarter << endl;
	out << "\ttrack:\t\t" << np.track << endl;
	out << "\tlayer:\t\t" << np.layer << endl;
	out << "\tduration:\t" << np.duration << endl;
	out << "\tb40:\t\t" << np.b40 << endl;
	out << "\tprocessed:\t" << np.processed << endl;
	out << "\tsourceindex:\t" << np.sourceindex << endl;
	out << "\ttpindex:\t" << np.tpindex << endl;
	out << "\tmatched:\t" << endl;
	for (int i=0; i<(int)np.matched.size(); i++) {
		out << "\t\tindex " << i << " is:\t" << np.matched[i] << endl;
	}
	return out;
}





/////////////////////////////////
//
// Tool_humsheet::Tool_humsheet -- Set the recognized options for the tool.
//

Tool_humsheet::Tool_humsheet(void) {
	define("h|H|html|HTML=b",       "output table in HTML wrapper");
	define("i|id|ID=b",             "include ID for each cell");
	define("z|zebra=b",             "add zebra striping by spine to style");
	define("y|z2|zebra2|zebra-2=b", "zebra striping by data type");
	define("t|tab-index=b",         "vertical tab indexing");
	define("X|no-exinterp=b",       "do not embed exclusive interp data");
	define("J|no-javascript=b",     "do not embed javascript code");
	define("S|no-style=b",          "do not embed CSS style element");
}



/////////////////////////////////
//
// Tool_humsheet::run -- Do the main work of the tool.
//

bool Tool_humsheet::run(HumdrumFileSet& infiles) {
	bool status = true;
	for (int i=0; i<infiles.getCount(); i++) {
		status &= run(infiles[i]);
	}
	return status;
}


bool Tool_humsheet::run(const string& indata, ostream& out) {
	HumdrumFile infile(indata);
	bool status = run(infile);
	if (hasAnyText()) {
		getAllText(out);
	} else {
		out << infile;
	}
	return status;
}


bool Tool_humsheet::run(HumdrumFile& infile, ostream& out) {
	bool status = run(infile);
	if (hasAnyText()) {
		getAllText(out);
	} else {
		out << infile;
	}
	return status;
}


bool Tool_humsheet::run(HumdrumFile& infile) {
	initialize();
	processFile(infile);
	return true;
}



//////////////////////////////
//
// Tool_humsheet::initialize --  Initializations that only have to be done once
//    for all HumdrumFile segments.
//

void Tool_humsheet::initialize(void) {
	m_idQ         = getBoolean("id");
	m_htmlQ       = getBoolean("html");
	m_zebraQ      = getBoolean("zebra");
	m_zebra2Q     = getBoolean("zebra2");
	m_exinterpQ   = !getBoolean("no-exinterp");
	m_javascriptQ = !getBoolean("no-javascript");
	m_tabindexQ   = getBoolean("tab-index");
}



//////////////////////////////
//
// Tool_humsheet::processFile --
//

void Tool_humsheet::processFile(HumdrumFile& infile) {
	analyzeTracks(infile);
	if (m_htmlQ) {
		printHtmlHeader();
		printStyle(infile);
	}
	if (m_tabindexQ) {
		analyzeTabIndex(infile);
	}
	m_free_text << "<table class=\"humdrum\"";
	m_free_text << " data-spine-count=\"" << infile.getMaxTrack() << "\"";
	m_free_text << ">\n";
	for (int i=0; i<infile.getLineCount(); i++) {
		m_free_text << "<tr";
		printRowClasses(infile, i);
		printRowData(infile, i);
		printTitle(infile, i);
		m_free_text << ">";
		printRowContents(infile, i);
		m_free_text << "</tr>\n";
	}
	m_free_text << "</table>";
	if (m_htmlQ) {
		if (m_javascriptQ) {
			printJavascript();
		}
		printHtmlFooter();
	}
}



//////////////////////////////
//
// Tool_humsheet::printTitle --
//

void Tool_humsheet::printTitle(HumdrumFile& infile, int line) {
	if (!infile[line].isReference()) {
		return;
	}
	string meaning = Convert::getReferenceKeyMeaning(infile[line].token(0));
	if (!meaning.empty()) {
		m_free_text << " title=\"" << meaning << "\"";
	}
}



//////////////////////////////
//
// Tool_humsheet::printRowData --
//

void Tool_humsheet::printRowData(HumdrumFile& infile, int line) {
	m_free_text << " data-line=\"" << line << "\"";
}



///////////////////////////////
//
// printHtmlHeader --
//

void Tool_humsheet::printHtmlHeader(void) {
	m_free_text << "<html>\n";
	m_free_text << "<head>\n";
	m_free_text << "<title>\n";
	m_free_text << "UNTITLED\n";
	m_free_text << "</title>\n";
	m_free_text << "</head>\n";
	m_free_text << "<body>\n";
}



///////////////////////////////
//
// printHtmlFooter --
//

void Tool_humsheet::printHtmlFooter(void) {
	m_free_text << "</body>\n";
	m_free_text << "</html>\n";
}



///////////////////////////////
//
// printRowClasses --
//

void Tool_humsheet::printRowClasses(HumdrumFile& infile, int row) {
	string classes;
	HLp hl = &infile[row];
	if (hl->hasSpines()) {
		classes += "spined ";
	}
	if (hl->isEmpty()) {
		classes += "empty ";
	}
	if (hl->isData()) {
		classes += "data ";
	}
	if (hl->isInterpretation()) {
		classes += "interp ";
		HTp token = hl->token(0);
		if (token->compare(0, 2, "*>") == 0) {
			classes += "label ";
		}
	}
	if (hl->isLocalComment()) {
		classes += "lcomment ";
		if (isLayout(hl)) {
			classes += "layout ";
		}
	}
	HTp token = hl->token(0);
	if (token->compare(0, 2, "!!") == 0) {
		if ((token->size() == 2) || (token->at(3) != '!')) {
			classes += "gcommet ";
		}
	}

	if (hl->isUniversalReference()) {
		if (token->compare(0, 11, "!!!!filter:") == 0) {
			classes += "ufilter ";
		} else if (token->compare(0, 12, "!!!!Xfilter:") == 0) {
			classes += "usedufilter ";
		} else {
			classes += "ureference ";
			if (token->compare(0, 12, "!!!!SEGMENT:") == 0) {
				classes += "segment ";
			}
		}
	} else if (hl->isCommentUniversal()) {
		classes += "ucomment ";
	} else if (hl->isReference()) {
		classes += "reference ";
	} else if (hl->isGlobalComment()) {
		HTp token = hl->token(0);
		if (token->compare(0, 10, "!!!filter:") == 0) {
			classes += "filter ";
		} else if (token->compare(0, 11, "!!!Xfilter:") == 0) {
			classes += "usedfilter ";
		} else {
			classes += "gcomment ";
			if (isLayout(hl)) {
				classes += "layout ";
			}
		}
	}

	if (hl->isBarline()) {
		classes += "barline ";
	}
	if (hl->isManipulator()) {
		HTp token = hl->token(0);
		if (token->compare(0, 2, "**") == 0) {
			classes += "exinterp ";
		} else {
			classes += "manip ";
		}
	}
	if (!classes.empty()) {
      // remove space.
		classes.resize((int)classes.size() - 1);
		m_free_text << " class=\"" << classes << "\"";
	}
}



//////////////////////////////
//
// Tool_humsheet::isLayout -- check to see if any cell
//    starts with "!LO:".
//

bool Tool_humsheet::isLayout(HLp line) {
	if (line->hasSpines()) {
		if (!line->isCommentLocal()) {
			return false;
		}
		for (int i=0; i<line->getFieldCount(); i++) {
			HTp token = line->token(i);
			if (token->compare(0, 4, "!LO:") == 0) {
				return true;
			}
		}
	} else {
		HTp token = line->token(0);
		if (token->compare(0, 5, "!!LO:") == 0) {
			return true;
		}
	}
	return false;
}



///////////////////////////////
//
// Tool_humsheet::printRowContents --
//

void Tool_humsheet::printRowContents(HumdrumFile& infile, int row) {
	int fieldcount = infile[row].getFieldCount();
	for (int i=0; i<fieldcount; i++) {
		HTp token = infile.token(row, i);
		m_free_text << "<td";
		if (m_idQ) {
			printId(token);
		}
		printCellClasses(token);
		printCellData(token);
		if (m_tabindexQ) {
			printTabIndex(token);
		}
		printColSpan(token);
		if (!infile[row].isManipulator()) {
			// do not allow manipulators to be edited
			m_free_text << " contenteditable=\"true\"";
		} else if (infile[row].isExclusive()) {
			// but allow exclusive interpretation to be edited
			m_free_text << " contenteditable=\"true\"";
		}
		m_free_text << ">";
		printToken(token);
		m_free_text << "</td>";
	}
}



//////////////////////////////
//
// Tool_humsheet::printCellData --
//

void Tool_humsheet::printCellData(HTp token) {
	int field = token->getFieldIndex();
	m_free_text << " data-field=\"" << field << "\"";


	if (token->getOwner()->hasSpines()) {
		int spine = token->getTrack() - 1;
		m_free_text << " data-spine=\"" << spine << "\"";

		int subspine = token->getSubtrack();
		if (subspine > 0) {
			m_free_text << " data-subspine=\"" << subspine << "\"";
		}

		string exinterp = token->getDataType().substr(2);
		if (m_exinterpQ && !exinterp.empty()) {
			m_free_text << " data-x=\"" << exinterp << "\"";
		}
	}
}



//////////////////////////////
//
// Tool_humsheet::printToken --
//

void Tool_humsheet::printToken(HTp token) {
	for (int i=0; i<(int)token->size(); i++) {
		switch (token->at(i)) {
			case '>':
				m_free_text << "&gt;";
				break;
			case '<':
				m_free_text << "&lt;";
				break;
			default:
				m_free_text << token->at(i);
		}
	}
}



///////////////////////////////
//
// Tool_humsheet::printId --
//

void Tool_humsheet::printId(HTp token) {
	int line = token->getLineNumber();
	int field = token->getFieldNumber();
	string id = "tok-L";
	id += to_string(line);
	id += "F";
	id += to_string(field);
	m_free_text << " id=\"" << id << "\"";
}



///////////////////////////////
//
// Tool_humsheet::printTabIndex --
//

void Tool_humsheet::printTabIndex(HTp token) {
	string number = token->getValue("auto", "tabindex");
	if (number.empty()) {
		return;
	}
	m_free_text << " tabindex=\"" << number << "\"";
}



//////////////////////////////
//
// Tool_humsheet::printColspan -- print any necessary colspan values for
//    token (to align by primary spines)
//

void Tool_humsheet::printColSpan(HTp token) {
	if (!token->getOwner()->hasSpines()) {
		m_free_text << " colspan=\"" << m_max_field << "\"";
		return;
	}
	int track = token->getTrack() - 1;
	int scount = m_max_subtrack.at(track);
	int subtrack = token->getSubtrack();
	if (subtrack > 1) {
		subtrack--;
	}
	HTp nexttok = token->getNextFieldToken();
	int ntrack = -1;
	if (nexttok) {
		ntrack = nexttok->getTrack() - 1;
	}
	if ((ntrack < 0) || (ntrack != track)) {
		// at the end of a primary spine, so do a colspan with the remaining subtracks
		if (subtrack < scount-1) {
			int colspan = scount - subtrack;
			m_free_text << " colspan=\"" << colspan << "\"";
		}
	} else {
		// do nothing
	}
}



///////////////////////////////
//
// printCellClasses --
//

void Tool_humsheet::printCellClasses(HTp token) {
	int track = token->getTrack();
	string classlist;

	if (m_zebraQ) {
		if (track % 2 == 0) {
			classlist = "zebra ";
		}
	}

	if (token->getOwner()->hasSpines()) {
		int length = (int)token->size();
		if (length > 20) {
			classlist += "long ";
		}
	}

	if (!classlist.empty()) {
		classlist.resize((int)classlist.size() - 1);
		m_free_text << " class=\"" << classlist << "\"";
	}

}



//////////////////////////////
//
// Tool_humsheet::printStyle --
//

void Tool_humsheet::printStyle(HumdrumFile& infile) {

	m_free_text << "<style>\n";
	m_free_text << "body {\n";
	m_free_text << "	padding: 20px;\n";
	m_free_text << "}\n";
	m_free_text << "table.humdrum {\n";
	m_free_text << "	border-collapse: collapse;\n";
	m_free_text << "}\n";
	m_free_text << "table.humdrum td:focus {\n";
	m_free_text << "	background: #ff000033 !important;\n";
	m_free_text << "}\n";
	m_free_text << "table.humdrum td {\n";
	m_free_text << "	outline: none;\n";
	m_free_text << "}\n";
	m_free_text << "table.humdrum td[data-subspine='1'],\n";
	m_free_text << "table.humdrum td[data-subspine='2'],\n";
	m_free_text << "table.humdrum td[data-subspine='3'],\n";
	m_free_text << "table.humdrum td[data-subspine='4'],\n";
	m_free_text << "table.humdrum td[data-subspine='5'],\n";
	m_free_text << "table.humdrum td[data-subspine='6'],\n";
	m_free_text << "table.humdrum td[data-subspine='7'],\n";
	m_free_text << "table.humdrum td[data-subspine='8'],\n";
	m_free_text << "table.humdrum td[data-subspine='9'] {\n";
	m_free_text << "	border-right: solid #0000000A 1px;\n";
	m_free_text << "	padding-left: 3px;\n";
	m_free_text << "}\n";
	m_free_text << "table.humdrum tr.ucomment {\n";
	m_free_text << "	color: chocolate;\n";
	m_free_text << "}\n";
	m_free_text << "table.humdrum tr.segment {\n";
	m_free_text << "	color: chocolate;\n";
	m_free_text << "	background: rgb(255,99,71,0.25);\n";
	m_free_text << "}\n";
	m_free_text << "table.humdrum tr.ureference {\n";
	m_free_text << "	color: chocolate;\n";
	m_free_text << "}\n";
	m_free_text << "table.humdrum tr.reference {\n";
	m_free_text << "	color: green;\n";
	m_free_text << "}\n";
	m_free_text << "table.humdrum tr.gcomment {\n";
	m_free_text << "	color: blue;\n";
	m_free_text << "}\n";
	m_free_text << "table.humdrum tr.ucomment {\n";
	m_free_text << "	color: violet;\n";
	m_free_text << "}\n";
	m_free_text << "table.humdrum tr.lcomment {\n";
	m_free_text << "	color: $#2fc584;\n";
	m_free_text << "}\n";
	m_free_text << "table.humdrum tr.interp.manip {\n";
	m_free_text << "	color: magenta;\n";
	m_free_text << "}\n";
	m_free_text << "table.humdrum tr.interp.exinterp {\n";
	m_free_text << "	color: red;\n";
	m_free_text << "}\n";
	m_free_text << "table.humdrum tr.interp {\n";
	m_free_text << "	color: darkviolet;\n";
	m_free_text << "}\n";
	m_free_text << "table.humdrum tr.filter {\n";
	m_free_text << "	color: limegreen;\n";
	m_free_text << "}\n";
	m_free_text << "table.humdrum tr.usedfilter {\n";
	m_free_text << "	color: olive;\n";
	m_free_text << "}\n";
	m_free_text << "table.humdrum tr.ufilter {\n";
	m_free_text << "	color: limegreen;\n";
	m_free_text << "	background: rgba(0,0,aa,0.3);\n";
	m_free_text << "}\n";
	m_free_text << "table.humdrum tr.usedufilter {\n";
	m_free_text << "	color: olive;\n";
	m_free_text << "	background: rgba(0,0,aa,0.3);\n";
	m_free_text << "}\n";
	m_free_text << "table.humdrum tr.interp.label {\n";
	m_free_text << "	background: rgba(75,0,130,0.3);\n";
	m_free_text << "}\n";
	m_free_text << "table.humdrum tr.layout {\n";
	m_free_text << "	color: orange;\n";
	m_free_text << "}\n";
	m_free_text << "table.humdrum tr.barline {\n";
	m_free_text << "	color: gray;\n";
	m_free_text << "	background: rgba(0, 0, 0, 0.06);\n";
	m_free_text << "}\n";
	m_free_text << "table.humdrum td.long {\n";
	m_free_text << "	white-space: nowrap;\n";
	m_free_text << "	max-width: 200px;\n";
	m_free_text << "	background-image: linear-gradient(to right, cornsilk 95%, crimson 100%);\n";
	m_free_text << "	overflow: scroll;\n";
	m_free_text << "}\n";

	if (m_zebraQ) {
		m_free_text << "table.humdrum .zebra {\n";
		m_free_text << "	background: #ccccff33;\n";
		m_free_text << "}\n";
	} else if (m_zebra2Q) {
		m_free_text << "table.humdrum td[data-x='kern'] {\n";
		m_free_text << "	background: #ffcccc33;\n";
		m_free_text << "}\n";
		m_free_text << "table.humdrum td[data-x='dynam'] {\n";
		m_free_text << "	background: #ccccff33;\n";
		m_free_text << "}\n";
		m_free_text << "table.humdrum td[data-x='text'] {\n";
		m_free_text << "	background: #ccffcc33;\n";
		m_free_text << "}\n";
	}

	m_free_text << "</style>\n";
}



//////////////////////////////
//
// Tool_humsheet::printJavascript --
//

void Tool_humsheet::printJavascript(void) {

	m_free_text << "<script>\n";
	m_free_text << "\n";
	m_free_text << "var AKey      = 65;\n";
	m_free_text << "var BKey      = 66;\n";
	m_free_text << "var CKey      = 67;\n";
	m_free_text << "var DKey      = 68;\n";
	m_free_text << "var EKey      = 69;\n";
	m_free_text << "var FKey      = 70;\n";
	m_free_text << "var GKey      = 71;\n";
	m_free_text << "var HKey      = 72;\n";
	m_free_text << "var IKey      = 73;\n";
	m_free_text << "var JKey      = 74;\n";
	m_free_text << "var KKey      = 75;\n";
	m_free_text << "var LKey      = 76;\n";
	m_free_text << "var MKey      = 77;\n";
	m_free_text << "var NKey      = 78;\n";
	m_free_text << "var OKey      = 79;\n";
	m_free_text << "var PKey      = 80;\n";
	m_free_text << "var QKey      = 81;\n";
	m_free_text << "var RKey      = 82;\n";
	m_free_text << "var SKey      = 83;\n";
	m_free_text << "var TKey      = 84;\n";
	m_free_text << "var UKey      = 85;\n";
	m_free_text << "var VKey      = 86;\n";
	m_free_text << "var WKey      = 87;\n";
	m_free_text << "var XKey      = 88;\n";
	m_free_text << "var YKey      = 89;\n";
	m_free_text << "var ZKey      = 90;\n";
	m_free_text << "var ZeroKey   = 48;\n";
	m_free_text << "var OneKey    = 49;\n";
	m_free_text << "var TwoKey    = 50;\n";
	m_free_text << "var ThreeKey  = 51;\n";
	m_free_text << "var FourKey   = 52;\n";
	m_free_text << "var FiveKey   = 53;\n";
	m_free_text << "var SixKey    = 54;\n";
	m_free_text << "var SevenKey  = 55;\n";
	m_free_text << "var EightKey  = 56;\n";
	m_free_text << "var NineKey   = 57;\n";
	m_free_text << "var PgUpKey   = 33;\n";
	m_free_text << "var PgDnKey   = 34;\n";
	m_free_text << "var EndKey    = 35;\n";
	m_free_text << "var HomeKey   = 36;\n";
	m_free_text << "var LeftKey   = 37;\n";
	m_free_text << "var UpKey     = 38;\n";
	m_free_text << "var RightKey  = 39;\n";
	m_free_text << "var DownKey   = 40;\n";
	m_free_text << "var EnterKey  = 13;\n";
	m_free_text << "var SpaceKey  = 32;\n";
	m_free_text << "var SlashKey  = 191;\n";
	m_free_text << "var EscKey    = 27;\n";
	m_free_text << "var BackKey   = 8;\n";
	m_free_text << "var CommaKey  = 188;\n";
	m_free_text << "var MinusKey  = 189;\n";
	m_free_text << "var DotKey    = 190;\n";
	m_free_text << "var SemiColonKey = 186;\n";
	m_free_text << "var BackQuoteKey   = 192;\n";
	m_free_text << "var SingleQuoteKey = 222;\n";
	m_free_text << "\n";
	m_free_text << "var TARGET_SPINE    = 0;\n";
	m_free_text << "var TARGET_SUBSPINE = 0;\n";
	m_free_text << "\n";
	m_free_text << "window.addEventListener('keydown', processKey, true);\n";
	m_free_text << "\n";
	m_free_text << "function processKey(event) {\n";
	m_free_text << "	var target;\n";
	m_free_text << "	var spine;\n";
	m_free_text << "	var subspine;\n";
	m_free_text << "	var rent;\n";
	m_free_text << "	var nextline;\n";
	m_free_text << "	var line;\n";
	m_free_text << "	var nexttr;\n";
	m_free_text << "	var newtd;\n";
	m_free_text << "\n";
	m_free_text << "	if (!event.preventDefault) {\n";
	m_free_text << "		event.preventDefault = function() { };\n";
	m_free_text << "	}\n";
	m_free_text << "\n";
	m_free_text << "	if (event.metaKey) {\n";
	m_free_text << "		return;\n";
	m_free_text << "	}\n";
	m_free_text << "\n";
	m_free_text << "	switch (event.keyCode) {\n";
	m_free_text << "\n";
	m_free_text << "		case EnterKey: // Move to next lower row in same spine\n";
	m_free_text << "			if (event.shiftKey) {\n";
	m_free_text << "				moveLine(event.target, -1);\n";
	m_free_text << "			} else {\n";
	m_free_text << "				moveLine(event.target, +1);\n";
	m_free_text << "			}\n";
	m_free_text << "			event.preventDefault();\n";
	m_free_text << "			break;\n";
	m_free_text << "\n";
	m_free_text << "		case DownKey:  // Move to next lower row in same spine\n";
	m_free_text << "			moveLine(event.target, +1);\n";
	m_free_text << "			event.preventDefault();\n";
	m_free_text << "			break;\n";
	m_free_text << "\n";
	m_free_text << "		case UpKey:\n";
	m_free_text << "			moveLine(event.target, -1);\n";
	m_free_text << "			event.preventDefault();\n";
	m_free_text << "			break;\n";
	m_free_text << "\n";
	m_free_text << "		case RightKey:\n";
	m_free_text << "			if (event.shiftKey) {\n";
	m_free_text << "				moveField(event.target, +1);\n";
	m_free_text << "				event.preventDefault();\n";
	m_free_text << "			}\n";
	m_free_text << "			 break;\n";
	m_free_text << "\n";
	m_free_text << "		case LeftKey:\n";
	m_free_text << "			if (event.shiftKey) {\n";
	m_free_text << "				moveField(event.target, -1);\n";
	m_free_text << "				event.preventDefault();\n";
	m_free_text << "			}\n";
	m_free_text << "			break;\n";
	m_free_text << "\n";
	m_free_text << "	}\n";
	m_free_text << "}\n";
	m_free_text << "\n";
	m_free_text << "\n";
	m_free_text << "//////////////////////////////\n";
	m_free_text << "//\n";
	m_free_text << "// moveField -- Move the to the next or previous td on a row.\n";
	m_free_text << "//\n";
	m_free_text << "\n";
	m_free_text << "function moveField(target, direction) {\n";
	m_free_text << "	if (target.nodeName !== 'TD') {\n";
	m_free_text << "		console.log('TARGET IS NOT TD');\n";
	m_free_text << "		return;\n";
	m_free_text << "	}\n";
	m_free_text << "	rent = target.parentNode;\n";
	m_free_text << "	if (rent.nodeName !== 'TR') {\n";
	m_free_text << "		console.log('PARENT IS NOT TR');\n";
	m_free_text << "		return;\n";
	m_free_text << "	}\n";
	m_free_text << "	var tds = rent.querySelectorAll('TD');\n";
	m_free_text << "	for (var i=0; i<tds.length; i++) {\n";
	m_free_text << "		if (target !== tds[i]) {\n";
	m_free_text << "			console.log('TARGET', target, 'NOT THE SAME AS', tds[i]);\n";
	m_free_text << "			continue;\n";
	m_free_text << "		}\n";
	m_free_text << "		var newi = i + direction;\n";
	m_free_text << "		if (newi < 0) {\n";
	m_free_text << "			// Don't do anything since there are no more\n";
	m_free_text << "			// cells to the left of the current cell.\n";
	m_free_text << "			return;\n";
	m_free_text << "		}\n";
	m_free_text << "		if (newi >= tds.length) {\n";
	m_free_text << "			// Don't do anything since there are no more\n";
	m_free_text << "			// cells to the right of the current cell.\n";
	m_free_text << "			return;\n";
	m_free_text << "		}\n";
	m_free_text << "		var newtd = tds[newi];\n";
	m_free_text << "		newtd.focus();\n";
	m_free_text << "		return;\n";
	m_free_text << "	}\n";
	m_free_text << "}\n";
	m_free_text << "\n";
	m_free_text << "\n";
	m_free_text << "\n";
	m_free_text << "//////////////////////////////\n";
	m_free_text << "//\n";
	m_free_text << "// moveLine --\n";
	m_free_text << "//\n";
	m_free_text << "\n";
	m_free_text << "function moveLine(target, direction) {\n";
	m_free_text << "	if (target.nodeName !== 'TD') {\n";
	m_free_text << "		console.log('TARGET IS NOT TD');\n";
	m_free_text << "		return;\n";
	m_free_text << "	}\n";
	m_free_text << "	spine = parseInt(target.dataset.spine || -1);\n";
	m_free_text << "	subspine = parseInt(target.dataset.subspine || -1);\n";
	m_free_text << "	rent = target.parentNode;\n";
	m_free_text << "	if (rent.nodeName !== 'TR') {\n";
	m_free_text << "		console.log('PARENT IS NOT TR');\n";
	m_free_text << "		return;\n";
	m_free_text << "	}\n";
	m_free_text << "	line = parseInt(rent.dataset.line || -1);\n";
	m_free_text << "	nextline = line + direction;\n";
	m_free_text << "\n";
	m_free_text << "	nexttr = document.querySelector('tr[data-line=\"' + nextline + '\"]');\n";
	m_free_text << "\n";
	m_free_text << "	if (nexttr && nexttr.className.match(/\\bmanip\\b/)) {\n";
	m_free_text << "		nextline = line + direction * 2;\n";
	m_free_text << "		nexttr = document.querySelector('tr[data-line=\"' + nextline + '\"]');\n";
	m_free_text << "	}\n";
	m_free_text << "	if (nexttr && nexttr.className.match(/\\bmanip\\b/)) {\n";
	m_free_text << "		nextline = line + direction * 3;\n";
	m_free_text << "		nexttr = document.querySelector('tr[data-line=\"' + nextline + '\"]');\n";
	m_free_text << "	}\n";
	m_free_text << "	if (nexttr && nexttr.className.match(/\\bmanip\\b/)) {\n";
	m_free_text << "		nextline = line + direction * 4;\n";
	m_free_text << "		nexttr = document.querySelector('tr[data-line=\"' + nextline + '\"]');\n";
	m_free_text << "	}\n";
	m_free_text << "\n";
	m_free_text << "	if (!nexttr) {\n";
	m_free_text << "		// nexttr does not exist so do nothing\n";
	m_free_text << "		return;\n";
	m_free_text << "	}\n";
	m_free_text << "	newtd = getNewTd(nexttr, spine, subspine);\n";
	m_free_text << "	if (!newtd) {\n";
	m_free_text << "		console.log('CANNOT FIND NEW TD');\n";
	m_free_text << "		return;\n";
	m_free_text << "	}\n";
	m_free_text << "	newtd.focus();\n";
	m_free_text << "}\n";
	m_free_text << "\n";
	m_free_text << "\n";
	m_free_text << "\n";
	m_free_text << "//////////////////////////////\n";
	m_free_text << "//\n";
	m_free_text << "// getNewTd -- Find the td element with the matching spine and subspine\n";
	m_free_text << "//        numbers as the starting cell.  If they do not match, then find\n";
	m_free_text << "//        one that has a matching spine.  If that cannot be found, then\n";
	m_free_text << "//        store the spine and subspine for recovering the spine position\n";
	m_free_text << "//        after passing through a global comment.\n";
	m_free_text << "//\n";
	m_free_text << "\n";
	m_free_text << "function getNewTd(tr, spine, subspine) {\n";
	m_free_text << "	if (spine < 0) {\n";
	m_free_text << "		spine = TARGET_SPINE;\n";
	m_free_text << "		subspine = TARGET_SUBSPINE;\n";
	m_free_text << "	}\n";
	m_free_text << "	var tds = tr.querySelectorAll('td');\n";
	m_free_text << "	if (tds.length == 1) {\n";
	m_free_text << "		return tds[0];\n";
	m_free_text << "	} else if (tds.length == 0) {\n";
	m_free_text << "		console.log('DID NOT FIND ANY TDS');\n";
	m_free_text << "		return null;\n";
	m_free_text << "	}\n";
	m_free_text << "	var list = [];\n";
	m_free_text << "	var obj;\n";
	m_free_text << "	var i;\n";
	m_free_text << "	for (i=0; i<tds.length; i++) {\n";
	m_free_text << "		obj = {};\n";
	m_free_text << "		obj.spine = parseInt(tds[i].dataset.spine);\n";
	m_free_text << "		obj.subspine = parseInt(tds[i].dataset.subspine || -1);\n";
	m_free_text << "		obj.td = tds[i];\n";
	m_free_text << "		list.push(obj);\n";
	m_free_text << "	}\n";
	m_free_text << "	for (i=0; i<list.length; i++) {\n";
	m_free_text << "		if (list[i].spine != spine) {\n";
	m_free_text << "			continue;\n";
	m_free_text << "		}\n";
	m_free_text << "		if (list[i].subspine != subspine) {\n";
	m_free_text << "			continue;\n";
	m_free_text << "		}\n";
	m_free_text << "		return list[i].td;\n";
	m_free_text << "	}\n";
	m_free_text << "	// did not find the exact spine/subspine, so go to the first \n";
	m_free_text << "	// spine that matches (backwards if subspine is not 0).\n";
	m_free_text << "	if (subspine < 0) {\n";
	m_free_text << "		for (i=0; i<list.length - 1; i++) {\n";
	m_free_text << "			if (list[i].spine != spine) {\n";
	m_free_text << "				continue;\n";
	m_free_text << "			}\n";
	m_free_text << "			return list[i].td;\n";
	m_free_text << "		}\n";
	m_free_text << "	} else {\n";
	m_free_text << "		for (i=list.length - 1; i>=0; i--) {\n";
	m_free_text << "			if (list[i].spine != spine) {\n";
	m_free_text << "				continue;\n";
	m_free_text << "			}\n";
	m_free_text << "			return list[i].td;\n";
	m_free_text << "		}\n";
	m_free_text << "	}\n";
	m_free_text << "	if (list.length == 1) {\n";
	m_free_text << "		return list[0].td;\n";
	m_free_text << "	}\n";
	m_free_text << "	console.log('DID NOT FIND NEW TD FOR', spine, subspine);\n";
	m_free_text << "	return null;\n";
	m_free_text << "}\n";
	m_free_text << "\n";
	m_free_text << "\n";
	m_free_text << "\n";
	m_free_text << "//////////////////////////////\n";
	m_free_text << "//\n";
	m_free_text << "// focusin eventListener -- When a cell is focused on, and the cell\n";
	m_free_text << "//     is spined, then store its spine/subspine values in TARGET_SPINE\n";
	m_free_text << "//     and TARGET_SUBSPINE for navigating through global records.\n";
	m_free_text << "//\n";
	m_free_text << "\n";
	m_free_text << "document.addEventListener('focusin', function(event) {\n";
	m_free_text << "	var target = event.target;\n";
	m_free_text << "	if (target.nodeName !== 'TD') {\n";
	m_free_text << "		return;\n";
	m_free_text << "	}\n";
	m_free_text << "	moveCursorToEndOfText(target);\n";
	m_free_text << "\n";
	m_free_text << "	var tr = target.parentNode;\n";
	m_free_text << "	if (!tr) {\n";
	m_free_text << "		return;\n";
	m_free_text << "	}\n";
	m_free_text << "	if (tr.nodeName !== 'TR') {\n";
	m_free_text << "		return;\n";
	m_free_text << "	}\n";
	m_free_text << "	if (!tr.className.match(/\\bspined\\b/)) {\n";
	m_free_text << "		return;\n";
	m_free_text << "	}\n";
	m_free_text << "	var spine = parseInt(target.dataset.spine || -1);\n";
	m_free_text << "	var subspine = parseInt(target.dataset.subspine || -1);\n";
	m_free_text << "	TARGET_SPINE = spine;\n";
	m_free_text << "	TARGET_SUBSPINE = subspine;\n";
	m_free_text << "});\n";
	m_free_text << "\n";
	m_free_text << "\n";
	m_free_text << "\n";
	m_free_text << "//////////////////////////////\n";
	m_free_text << "//\n";
	m_free_text << "// focusout eventListener -- When leaving a cell, check that\n";
	m_free_text << "//    its contents are syntactically correct.\n";
	m_free_text << "//\n";
	m_free_text << "\n";
	m_free_text << "document.addEventListener('focusout', function(event) {\n";
	m_free_text << "	var target = event.target;\n";
	m_free_text << "	if (target.nodeName !== 'TD') {\n";
	m_free_text << "		return;\n";
	m_free_text << "	}\n";
	m_free_text << "\n";
	m_free_text << "	var tr = target.parentNode;\n";
	m_free_text << "	if (!tr) {\n";
	m_free_text << "		return;\n";
	m_free_text << "	}\n";
	m_free_text << "	if (tr.nodeName !== 'TR') {\n";
	m_free_text << "		return;\n";
	m_free_text << "	}\n";
	m_free_text << "	var empty = '.';\n";
	m_free_text << "	var classes = tr.className;\n";
	m_free_text << "	if (classes.match(/\\bmanip\\b/)) {\n";
	m_free_text << "		empty = '*';\n";
	m_free_text << "	} if (classes.match(/\\binterp\\b/)) {\n";
	m_free_text << "		empty = '*';\n";
	m_free_text << "	} else if (classes.match(/\\blcomment\\b/)) {\n";
	m_free_text << "		empty = '!';\n";
	m_free_text << "	} else if (classes.match(/comment\\b/)) {\n";
	m_free_text << "		empty = '!!';\n";
	m_free_text << "	} else if (classes.match(/reference\\b/)) {\n";
	m_free_text << "		empty = '!';\n";
	m_free_text << "	} else if (classes.match(/\\bbarline\\b/)) {\n";
	m_free_text << "		empty = '=';\n";
	m_free_text << "	}\n";
	m_free_text << "	\n";
	m_free_text << "	var contents = target.textContent;\n";
	m_free_text << "	if (contents === '') {\n";
	m_free_text << "		target.textContent = empty;\n";
	m_free_text << "		return;\n";
	m_free_text << "	}\n";
	m_free_text << "	\n";
	m_free_text << "	var firstchar = contents.charAt(0);\n";
	m_free_text << "	if ((empty === '!') && (firstchar !== '!')) {\n";
	m_free_text << "		target.textContent = '!' + contents;\n";
	m_free_text << "		return;\n";
	m_free_text << "	}\n";
	m_free_text << "	else if ((empty === '*') && (firstchar !== '*')) {\n";
	m_free_text << "		target.textContent = '*' + contents;\n";
	m_free_text << "		return;\n";
	m_free_text << "	}\n";
	m_free_text << "	else if ((empty === '=') && (firstchar !== '=')) {\n";
	m_free_text << "		target.textContent = '=' + contents;\n";
	m_free_text << "		return;\n";
	m_free_text << "	}\n";
	m_free_text << "	else if ((empty === '.') && (firstchar === '!')) {\n";
	m_free_text << "		contents = contents.replace(/^[!*=]*/, '');\n";
	m_free_text << "		target.textContent = contents;\n";
	m_free_text << "		return;\n";
	m_free_text << "	}\n";
	m_free_text << "	else if ((empty === '.') && (firstchar === '*')) {\n";
	m_free_text << "		contents = contents.replace(/^[!*=]*/, '');\n";
	m_free_text << "		target.textContent = contents;\n";
	m_free_text << "		return;\n";
	m_free_text << "	}\n";
	m_free_text << "	else if ((empty === '.') && (firstchar === '=')) {\n";
	m_free_text << "		contents = contents.replace(/^[!*=]*/, '');\n";
	m_free_text << "		target.textContent = contents;\n";
	m_free_text << "		return;\n";
	m_free_text << "	}\n";
	m_free_text << "});\n";
	m_free_text << "\n";
	m_free_text << "\n";
	m_free_text << "\n";
	m_free_text << "//////////////////////////////\n";
	m_free_text << "//\n";
	m_free_text << "// moveCursorToEndOfText --\n";
	m_free_text << "//\n";
	m_free_text << "\n";
	m_free_text << "function moveCursorToEndOfText(element) {\n";
	m_free_text << "	var range;\n";
	m_free_text << "	var selection;\n";
	m_free_text << "	if (document.createRange) {\n";
	m_free_text << "		range = document.createRange();\n";
	m_free_text << "		range.selectNodeContents(element);\n";
	m_free_text << "		range.collapse(false);\n";
	m_free_text << "		selection = window.getSelection();\n";
	m_free_text << "		selection.removeAllRanges();\n";
	m_free_text << "		selection.addRange(range);\n";
	m_free_text << "	}\n";
	m_free_text << "}\n";
	m_free_text << "\n";
	m_free_text << "</script>\n";

}



//////////////////////////////
//
// Tool_humsheet::analyzeTracks --
//

void Tool_humsheet::analyzeTracks(HumdrumFile& infile) {
	m_max_track = infile.getMaxTrack();
	m_max_subtrack.resize(m_max_track);
	std::fill(m_max_subtrack.begin(), m_max_subtrack.end(), 0);
	vector<int> current(m_max_track, 0);
	for (int i=0; i<infile.getLineCount(); i++) {
		if (!infile[i].hasSpines()) {
			continue;
		}
		fill(current.begin(), current.end(), 0);
		for (int j=0; j<infile[i].getFieldCount(); j++) {
			HTp token = infile.token(i, j);
			int track = token->getTrack();
			track--;  // 0-indexing tracks
			current.at(track)++;
			if (current.at(track) > m_max_subtrack.at(track)) {
				m_max_subtrack[track] = current[track];
			}
		}
	}

	m_max_field = 0;
	for (int i=0; i<(int)m_max_subtrack.size(); i++) {
		m_max_field += m_max_subtrack[i];
	}
}



//////////////////////////////
//
// Tool_humsheet::analyzeTabIndex --
//

void Tool_humsheet::analyzeTabIndex(HumdrumFile& infile) {
	int scount = infile.getStrandCount();
	int counter = 1;
	for (int i=0; i<scount; i++) {
		HTp start = infile.getStrandStart(i);
		HTp stop = infile.getStrandEnd(i);
		HTp current = start;
		while (current && (current != stop)) {
			string number = to_string(counter++);
			current->setValue("auto", "tabindex", number);
			current = current->getNextToken();
		}
	}
}





/////////////////////////////////
//
// Tool_humsort::Tool_humsort -- Set the recognized options for the tool.
//

Tool_humsort::Tool_humsort(void) {
	// add options here
	define("n|numeric=b",                             "sort numerically");
	define("r|reverse=b",                             "sort in reversed order");
	define("s|spine=i:1",                             "spine to sort (1-indexed)");
	define("I|do-not-ignore-case=b",                  "do not ignore case when sorting alphabetically");
	define("i|e|x|interp|exclusive-interpretation=s", "exclusive interpretation to sort");
}



/////////////////////////////////
//
// Tool_humsort::run -- Do the main work of the tool.
//

bool Tool_humsort::run(HumdrumFileSet& infiles) {
	bool status = true;
	for (int i=0; i<infiles.getCount(); i++) {
		status &= run(infiles[i]);
	}
	return status;
}


bool Tool_humsort::run(const string& indata, ostream& out) {
	HumdrumFile infile(indata);
	bool status = run(infile);
	if (hasAnyText()) {
		getAllText(out);
	} else {
		out << infile;
	}
	return status;
}


bool Tool_humsort::run(HumdrumFile& infile, ostream& out) {
	bool status = run(infile);
	if (hasAnyText()) {
		getAllText(out);
	} else {
		out << infile;
	}
	return status;
}


bool Tool_humsort::run(HumdrumFile& infile) {
	processFile(infile);
	return true;
}



//////////////////////////////
//
// Tool_humsort::processFile --
//

void Tool_humsort::processFile(HumdrumFile& infile) {
	vector<HTp> sstarts;
	infile.getSpineStartList(sstarts);
	int spine = getInteger("spine");
	if (getBoolean("exclusive-interpretation")) {
		string datatype = getString("exclusive-interpretation");
		if (datatype.compare(0, 2, "**")) {
			datatype = "**" + datatype;
		} else if (datatype.compare(0, 1, "*")) {
			datatype = "*" + datatype;
		}
		for (int i=0; i<(int)sstarts.size(); i++) {
			if (sstarts[i]->isDataType(datatype)) {
				spine = sstarts[i]->getTrack();
				break;
			}
		}
	}
	vector<HTp> data;
	data.reserve(infile.getLineCount());
	HTp current = sstarts.at(spine-1);
	current = current->getNextToken();
	while (current) {
		if (current->isData()) {
			data.push_back(current);
		}
		current = current->getNextToken();
	}

	if (getBoolean("numeric")) {
		std::sort(data.begin(), data.end(),
			[](HTp a, HTp b) {
				if (*a == *b) {
					return 0;
				}
				if (*a == ".") {
					return -1;
				}
				if (*b == ".") {
					return 0;
				}
				char cha = a->at(0);
				char chb = b->at(0);
				if ((isdigit(cha) || cha == '-' || cha == '+' || cha == '.') &&
				    (isdigit(chb) || chb == '-' || chb == '+' || chb == '.')) {
					int A = stod(*a);
					int B = stod(*b);
					if (A < B) {
						return -1;
					} else {
						return 0;
					}
				}
				// one value is not a number for some reason, so compare as string
				return *a < *b ? -1 : 0;
			});
	} else {
		// alphabetic sorting
		if (!getBoolean("do-not-ignore-case")) {
			std::sort(data.begin(), data.end(), [](HTp a, HTp b) {
					string A = *a;
					string B = *b;
					std::transform(A.begin(), A.end(), A.begin(), ::tolower);
					std::transform(B.begin(), B.end(), B.begin(), ::tolower);
					return A < B;
			});
		} else {
			std::sort(data.begin(), data.end(),
				[](HTp a, HTp b) { return *a < *b; });
		}
	}

	for (int i=0; i<infile.getLineCount(); i++) {
		if (infile[i].hasSpines()) {
			m_humdrum_text << infile[i] << endl;
			break;
		}
		m_humdrum_text << infile[i] << endl;
	}
	if (getBoolean("reverse")) {
		for (int i=(int)data.size()-1; i>=0; i--) {
			m_humdrum_text << data[i]->getOwner() << endl;
		}
	} else {
		for (int i=0; i<(int)data.size(); i++) {
			m_humdrum_text << data[i]->getOwner() << endl;
		}
	}
	for (int i=0; i<infile.getLineCount(); i++) {
		if (*infile[i].token(0) != "*-") {
			continue;
		}
		for (int j=i; j<infile.getLineCount(); j++) {
			m_humdrum_text << infile[j] << endl;
		}
	}
}







/////////////////////////////////
//
// Tool_humtr::Tool_humtr -- Set the recognized options for the tool.
//

Tool_humtr::Tool_humtr(void) {
	define("T|no-text|no-lyrics=b",     "do not convert lyrics in **text spines.");
	define("L|no-local=b",              "do not convert local LO t parameters.");
	define("G|no-global=b",             "do not convert global LO t parameters.");
	define("R|no-reference=b",          "do not convert reference record values.");

	define("t|text-only|lyrics-only=b", "convert only lyrics in **text spines.");
	define("l|local-only=b",            "convert only local LO t parameters.");
	define("g|global-only=b",           "convert only global LO t parameters.");
	define("r|reference-only=b",        "convert only reference record values.");

	define("d|data-type=s",             "process only given exclusive interpretations");
	define("s|spines=s",                "spines to process");

	define("i|input=s",                 "input characters to change");
	define("o|output=s",                "output characters to change to");

	define("m|replace-map=s",           "characters to change from and to");
	define("M|display-mapping=b",       "display character transliterations mappings");
	define("p|popc|popc2=b",            "add POPC2 character substitutions");
}



//////////////////////////////
//
// Tool_humtr::initialize --  Initializations that only have to be done once
//    for all HumdrumFile segments.
//

void Tool_humtr::initialize(void) {
	m_lyricsQ    = !getBoolean("T");
	m_localQ     = !getBoolean("L");
	m_globalQ    = !getBoolean("G");
	m_referenceQ = !getBoolean("R");

	m_lyricsOnlyQ    = getBoolean("t");
	m_localOnlyQ     = getBoolean("l");
	m_globalOnlyQ    = getBoolean("g");
	m_referenceOnlyQ = getBoolean("r");

	if (m_lyricsOnlyQ || m_localOnlyQ || m_globalOnlyQ || m_referenceOnlyQ) {
		m_lyricsQ = false;
		m_localQ = false;
		m_globalQ = false;
		m_referenceQ = false;
	}
	if (m_lyricsOnlyQ) {
		m_lyricsQ = true;
	}
	if (m_localOnlyQ) {
		m_localQ = true;
	}
	if (m_globalOnlyQ) {
		m_globalQ = true;
	}
	if (m_referenceOnlyQ) {
		m_referenceQ = true;
	}

	m_from.clear();
	m_to.clear();

	if (!getBoolean("replace-map")) {
		string replace = getString("replace-map");
		addFromToCombined(replace);
	}

	if (getBoolean("input") && getBoolean("output")) {
		string fromString = getString("input");
		string toString = getString("output");
		fillFromToPair(fromString, toString);
	}

	if (getBoolean("popc")) {
		addFromToCombined("ſ:s ʃ:s &#383;:s ν:u ί:í α:a ť:k ᴣ:z ʓ:z̨ ʒ̇:ż ʒ́:ź Ʒ̇:Ż Ʒ́:Ź ӡ:z Ʒ:Z Ӡ:Z æ:ae");
	}
}



/////////////////////////////////
//
// Tool_humtr::run -- Do the main work of the tool.
//

bool Tool_humtr::run(HumdrumFileSet& infiles) {
	bool status = true;
	for (int i=0; i<infiles.getCount(); i++) {
		status &= run(infiles[i]);
	}
	return status;
}



bool Tool_humtr::run(const string& indata, ostream& out) {
	HumdrumFile infile(indata);
	bool status = run(infile);
	if (hasAnyText()) {
		getAllText(out);
	} else {
		out << infile;
	}
	return status;
}


bool Tool_humtr::run(HumdrumFile& infile, ostream& out) {
	bool status = run(infile);
	if (hasAnyText()) {
		getAllText(out);
	} else {
		out << infile;
	}
	return status;
}


bool Tool_humtr::run(HumdrumFile& infile) {
	initialize();
	processFile(infile);
	if (getBoolean("display-mapping")) {
		int lengthF = (int)m_from.size();
		int lengthT = (int)m_to.size();
		int length = lengthF;
		if (length > lengthT) {
			length = lengthT;
		}
		for (int i=0; i<length; i++) {
			m_free_text << "FROM\t" << m_from[i] << "\tTO\t" << m_to[i] << endl;
		}
		return true;
	} else {
		infile.createLinesFromTokens();
		m_humdrum_text << infile;
	}
	return true;
}



//////////////////////////////
//
// Tool_humtr::processFile --
//

void Tool_humtr::processFile(HumdrumFile& infile) {
	if (m_lyricsQ) {
		convertTextSpines(infile);
	}
	if (m_localQ) {
		convertLocalLayoutText(infile);
	}
	if (m_globalQ) {
		convertGlobalLayoutText(infile);
	}
	if (m_referenceQ) {
		convertReferenceText(infile);
	}
}



//////////////////////////////
//
// Tool_humtr::convertTextSpines --
//

void Tool_humtr::convertTextSpines(HumdrumFile& infile) {
	int scount = infile.getStrandCount();
	for (int i=0; i<scount; i++) {
		HTp stok = infile.getStrandStart(i);
		if (!stok->isDataType("**text")) {
			continue;
		}
		HTp etok = infile.getStrandEnd(i);
		processTextStrand(stok, etok);
	}
}



//////////////////////////////
//
// Tool_humtr::processTextStrand --
//

void Tool_humtr::processTextStrand(HTp stok, HTp etok) {
	HTp current = stok;
	while (current && (current != etok)) {
		if (!current->isData()) {
			current = current->getNextToken();
			continue;
		}
		if (current->isNull()) {
			current = current->getNextToken();
			continue;
		}

		string text = transliterateText(*current);
		if (text != *current) {
			current->setText(text);
		}

		current = current->getNextToken();
	}
}



//////////////////////////////
//
// Tool_humtr::convertReferenceText --
//

void Tool_humtr::convertReferenceText(HumdrumFile& infile) {
	HumRegex hre;
	for (int i=0; i<infile.getLineCount(); i++) {
		if (!infile[i].isGlobalReference()) {
			continue;
		}

		HTp token = infile.token(i, 0);
		if (!hre.search(token, "^!!![^:]+:(.*)$")) {
			continue;
		}
		string oldcontents = hre.getMatch(1);
		if (oldcontents == "") {
			return;
		}
		string newcontents = transliterateText(oldcontents);
		if (oldcontents != newcontents) {
			string text = *token;
			hre.replaceDestructive(text, ":" + newcontents, ":" + oldcontents);
			token->setText(text);
		}
	}
}



//////////////////////////////
//
// Tool_humtr::convertGlobalLayoutText --
//

void Tool_humtr::convertGlobalLayoutText(HumdrumFile& infile) {
	HumRegex hre;
	for (int i=0; i<infile.getLineCount(); i++) {
		if (!infile[i].isCommentGlobal()) {
			continue;
		}

		HTp token = infile.token(i, 0);
		if (!hre.search(token, "^!!LO:.*:t=([^:]+)")) {
			continue;
		}
		string oldcontents = hre.getMatch(1);
		string newcontents = transliterateText(oldcontents);
		if (oldcontents != newcontents) {
			string text = *token;
			hre.replaceDestructive(text, ":t=" + newcontents, ":t=" + oldcontents);
			token->setText(text);
		}
	}
}



//////////////////////////////
//
// Tool_humtr::convertLocalLayoutText --
//

void Tool_humtr::convertLocalLayoutText(HumdrumFile& infile) {
	HumRegex hre;

	for (int i=0; i<infile.getLineCount(); i++) {
		if (!infile[i].isCommentLocal()) {
			continue;
		}
		for (int j=0; j<infile[i].getFieldCount(); j++) {
			HTp token = infile.token(i, j);
			if (*token == "!") {
				continue;
			}
			if (!hre.search(token, "^!LO:.*:t=([^:]+)")) {
				continue;
			}
			string oldcontents = hre.getMatch(1);
			string newcontents = transliterateText(oldcontents);
			if (oldcontents != newcontents) {
				string text = *token;
				hre.makeSafeDestructive(oldcontents);
				hre.replaceDestructive(text, ":t=" + newcontents, ":t=" + oldcontents);
				token->setText(text);
			}
		}
	}
}



//////////////////////////////
//
// Tool_humtr::addFromToCombined -- Add additional translations to
//     the from / to substitutions array.
//

void Tool_humtr::addFromToCombined(const string& value) {
	HumRegex hre;
	vector<string> entries;
	hre.split(entries, value, m_sep1);
	for (int i=0; i<(int)entries.size(); i++) {
		vector<string> mapping;
		hre.split(mapping, entries[i], m_sep2);
		if (mapping.size() != 2) {
			cerr << "Warning: ignoring bad character mapping: " << entries[i] << endl;
			continue;
		}
		m_from.push_back(mapping[0]);
		m_to.push_back(mapping[1]);
	}
}



//////////////////////////////
//
// Tool_humtr::fillFromToPair
//

void Tool_humtr::fillFromToPair(const string& from, const string& to) {
	vector<string> fromList = getUtf8CharacterArray(from);
	vector<string> toList   = getUtf8CharacterArray(to);
	if (fromList.size() != toList.size()) {
		cerr << "Error: String lengths to not match for " << from << "\tAND\t" << to << endl;
		cerr << "FROM LIST count: " << fromList.size() << endl;
		for (int i=0; i<(int)fromList.size(); i++) {
			cerr << "\t" << fromList[i] << endl;
		}
		cerr << endl;
		cerr << "TO LIST count: " << toList.size() << endl;
		for (int i=0; i<(int)toList.size(); i++) {
			cerr << "\t" << toList[i] << endl;
		}
		return;
	}
	for (int i=0; i<(int)fromList.size(); i++) {
		m_from.push_back(fromList[i]);
	}
	for (int i=0; i<(int)toList.size(); i++) {
		m_to.push_back(toList[i]);
	}
}



//////////////////////////////
//
// Tool_humtr::getUtf8CharacterArray --
//

vector<string> Tool_humtr::getUtf8CharacterArray(const string& value) {
	vector<string> output;
	string current;
	for (int i=0; i<(int)value.size(); i++) {
		current = "";
		char v = value[i];
		current.push_back(v);
		unsigned char u = (unsigned char)v;
		if (u < 0x80) {
			output.push_back(current);
			continue;
		}
		int count = 0;
		if (u >> 5 == 6) {
			count = 1;
		} else if (u >> 4 == 14) {
			count = 2;
		} else if (u >> 3 == 30) {
			count = 3;
		} else {
			cerr << "Error reading UTF-8 character in string " << value << endl;
			output.clear();
			return output;
		}
		for (int j=0; j<count; j++) {
			v = value[i+j];
			u = (unsigned char)v;
			if (v >> 6 != 2) {
				cerr << "Error in reading UTF-8 character of string " << endl;
				output.clear();
				return output;
			}
			current.push_back(v);
		}
		output.push_back(current);
	}

	if (output.empty()) {
		return output;
	}

	// Check for ASCII character ranges:
	vector<string> out2;
	out2.push_back(output[0]);
	for (int i=1; i<(int)output.size() - 1; i++) {
		if (output[i] != "-") {
			out2.push_back(output[i]);
			continue;
		}
		if ((output[i-1].size() > 1) || (output[i+1].size() > 1)) {
			// One or both of the adjacent characters are UTF-8, so
			// treat dash as regular character rather than range operator.
			out2.push_back(output[i]);
			continue;
		}

		// Insert a range of characters:
		unsigned int starting = (unsigned char)output[i-1][0];
		unsigned int ending   = (unsigned char)output[i+1][0];
		if (starting > 0xff) {
			cerr << "Strange error here " << starting << endl;
		}
		if (ending > 0xff) {
			cerr << "Strange error here " << starting << endl;
		}
		if (starting == ending) {
			continue;
		}
		int direction;
		if (starting > ending) {
			direction = -1;
			starting--;
			ending++;
		} else {
			direction = 1;
			starting++;
			ending--;
		}
		if (direction > 0) {
			for (unsigned int j=starting; j<=ending; j++) {
				string current = "";
				current.push_back((unsigned char)j);
				out2.push_back(current);
			}
		} else {
			for (unsigned int j=starting; j>=ending; j--) {
				string current = "";
				current.push_back((unsigned char)j);
				out2.push_back(current);
			}
		}
	}
	if (output.size() > 1) {
		out2.push_back(output.back());
	}

	return out2;
}



//////////////////////////////
//
// Tool_humtr::transliterateText --
//

string Tool_humtr::transliterateText(const string& input) {
	return transliterateTextNonOverlapping(input);
}



//////////////////////////////
//
// Tool_humtr::transliterateTextNonOverlapping --
//

string Tool_humtr::transliterateTextNonOverlapping(const string& input) {
	string output = input;
	HumRegex hre;
	for (int i=0; i<(int)m_from.size(); i++) {
		hre.replaceDestructive(output, m_to.at(i), m_from.at(i), "g");
	}
	return output;
}



//////////////////////////////
//
// Tool_humtr::transliterateTextOverlapping -- Only single-character mappings
//     are allowed (used particularly for character ranges).
//

string Tool_humtr::transliterateTextOverlapping(const string& input) {
	// not implemented yet
	return input;
}





int Tool_imitation::Enumerator = 0;


/////////////////////////////////
//
// Tool_imitation::Tool_imitation -- Set the recognized options for the tool.
//

Tool_imitation::Tool_imitation(void) {
	define("debug=b",                   "print grid cell information");
	define("e|exinterp=s:**vvdata",     "specify exinterp for **vvdata spine");

	define("n|t|threshold=i:7",         "minimum number of notes to match");
	define("f|first=b",                 "only give info for first sequence of matched pair");

	define("q|quiet|no-info=b",         "do not add spines giving information about matches");

	define("N|no-enumeration=b",        "do not display enumeration number");
	define("C|no-count=b",              "do not display note-count number");
	define("D|no-distance=b",           "do not display distance between first notes of sequences");
	define("I|no-interval=b",           "do not display interval transposite between sequences");

	define("NN|no-enumeration2=b",      "do not display enumeration number on second sequence");
	define("CC|no-count2=b",            "do not display note-count number on second sequence");
	define("DD|no-distance2=b",         "do not display distance between first notes of sequences on second sequence");
	define("II|no-interval2=b",         "do not display interval transposition between sequences on second sequence");
	define("2|enumerate-second-only=b", "display enumeration number on second sequence only (no count, distance, or interval");

	define("p|no-duration=b",            "pitch only when matching: do not consider duration");
	define("d|max-distance=d",           "maximum distance in quarter notes between imitations");
	define("s|single-mark=b",            "place a single mark on matched notes (not one for each match pair");
	define("r|rest=b",                   "require match trigger to follow a rest");
	define("R|rest2=b",                  "require match target to also follow a rest");
	define("i|intervals=s",              "require given interval sequence in imitation");
	define("M|no-mark=b",                "do not mark matched sequences");
	define("Z|no-zero=b",                "do not mark imitation starting at the same time");
	define("z|only-zero=b",              "mark only imitation starting at the same time (parallel motion)");
	define("m|measure=b",                "include measure number in imitation information");
	define("b|beat=b",                   "include beat number (really quarter-note number) in imitation information");
	define("l|length=b",                 "include length of imitation (in quarter-note units)");

	define("a|add=b",                    "add inversions, retrograde, etc. if specified to normal search");
	define("v|inversion=b",              "match inversions");
	define("g|retrograde=b",             "match retrograde");
}



/////////////////////////////////
//
// Tool_imitation::run -- Do the main work of the tool.
//

bool Tool_imitation::run(HumdrumFileSet& infiles) {
	bool status = true;
	for (int i=0; i<infiles.getCount(); i++) {
		status &= run(infiles[i]);
	}
	return status;
}


bool Tool_imitation::run(const string& indata, ostream& out) {
	HumdrumFile infile(indata);
	bool status = run(infile);
	if (hasAnyText()) {
		getAllText(out);
	} else {
		out << infile;
	}
	return status;
}


bool Tool_imitation::run(HumdrumFile& infile, ostream& out) {
	bool status = run(infile);
	if (hasAnyText()) {
		getAllText(out);
	} else {
		out << infile;
	}
	return status;
}


bool Tool_imitation::run(HumdrumFile& infile) {
	Enumerator = 0;

	NoteGrid grid(infile);

	if (getBoolean("debug")) {
		grid.printGridInfo(cerr);
		// return 1;
	}

	Enumerator = 0;
	m_threshold = getInteger("threshold") + 1;
	if (m_threshold < 3) {
		m_threshold = 3;
	}

	m_nozero       = getBoolean("no-zero");
	m_onlyzero     = getBoolean("only-zero");
	if (m_nozero && m_onlyzero) {
		// only one of these two options can be used at the same time
		m_nozero = false;
	}
	m_maxdistanceQ = getBoolean("max-distance");
	m_maxdistance  = getDouble("max-distance");
	m_duration     = !getBoolean("no-duration");
	m_measure      = getBoolean("measure");
	m_length       = getBoolean("length");
	m_beat         = getBoolean("beat");
	if (m_measure) {
		m_barlines = infile.getMeasureNumbers();
	}

	m_noInfo = getBoolean("quiet");

	m_noN    = getBoolean("N");
	m_noC    = getBoolean("C");
	m_noD    = getBoolean("D");
	m_noI    = getBoolean("I");

	m_noNN   = getBoolean("NN");
	m_noCC   = getBoolean("CC");
	m_noDD   = getBoolean("DD");
	m_noII   = getBoolean("II");

	m_inversion  = getBoolean("inversion");
	m_retrograde = getBoolean("retrograde");

	m_addsearches = false;
	if (getBoolean("add")) {
		m_inversion = false;
		m_retrograde = false;
		m_addsearches = true;
	}

	if (getBoolean("enumerate-second-only")) {
		m_noNN = false;
		m_noCC = true;
		m_noDD = true;
		m_noII = true;
	}

	m_first   = getBoolean("first");
	m_mark    = !getBoolean("no-mark");
	m_rest    = getBoolean("rest");
	m_rest2   = getBoolean("rest2");
	m_single  = getBoolean("single-mark");

	if (getBoolean("intervals")) {
		vector<string> values;
		HumRegex hre;
		string intstring = getString("intervals");
		hre.split(values, intstring.c_str(), "[^0-9+-]+");
		m_intervals.resize(values.size());
		for (int i=0; i<(int)values.size(); i++) {
			m_intervals.at(i) = stoi(values.at(i));
			// subtract one since intervals in caluculations are zero-indexed:
			if (m_intervals.at(i) > 0) {
				m_intervals.at(i)--;
			} else if (m_intervals.at(i) < 0) {
				m_intervals.at(i)++;
			}
		}
	}

	vector<vector<string>>    results;
	vector<vector<NoteCell*>> attacks;
	vector<vector<double>>    intervals;

	doAnalysis(results, grid, attacks, intervals, infile, getBoolean("debug"));
	char originalMarker = m_marker;
	if (m_addsearches && getBoolean("inversion")) {
		m_inversion = true;
		m_marker = 'N';
		doAnalysis(results, grid, attacks, intervals, infile, getBoolean("debug"));
	}
	m_marker = originalMarker;

	if (!getBoolean("no-info")) {
		string exinterp = getString("exinterp");
		vector<HTp> kernspines = infile.getKernSpineStartList();
		infile.appendDataSpine(results.back(), "", exinterp);
		for (int i = (int)results.size()-1; i>0; i--) {
			int track = kernspines.at(i)->getTrack();
			infile.insertDataSpineBefore(track, results.at(i-1), "", exinterp);
		}
	}
	if (m_mark && Enumerator) {
		string rdfline = "!!!RDF**kern: ";
		rdfline += m_marker;
		rdfline += " = marked note (color=\"chocolate\")";
		infile.appendLine(rdfline);
		if (getBoolean("add") && getBoolean("inversion")) {
			rdfline = "!!!RDF**kern: ";
			rdfline += "N";
			rdfline += " = marked note (color=\"limegreen\"), inversion match";
			infile.appendLine(rdfline);
		}
	}
	infile.createLinesFromTokens();
	// new data spines not showing up after createLinesFromTokens(), so force to text for now:
	m_humdrum_text << infile;
	return true;
}



//////////////////////////////
//
// Tool_imitation::doAnalysis -- do a basic melodic analysis of all parts.
//

void Tool_imitation::doAnalysis(vector<vector<string> >& results,
		NoteGrid& grid, vector<vector<NoteCell*> >& attacks,
		vector<vector<double>>& intervals, HumdrumFile& infile,
		bool debug) {

	results.resize(grid.getVoiceCount());
	for (int i=0; i<(int)results.size(); i++) {
		results.at(i).resize(infile.getLineCount());
	}

	attacks.resize(grid.getVoiceCount());
	for (int i=0; i<(int)attacks.size(); i++) {
		grid.getNoteAndRestAttacks(attacks.at(i), i);
	}

	intervals.resize(grid.getVoiceCount());
	for (int i=0; i<(int)intervals.size(); i++) {
		intervals.at(i).resize(attacks.at(i).size());
		getIntervals(intervals.at(i), attacks.at(i));
	}

	for (int i=0; i<(int)attacks.size(); i++) {
		for (int j=i+1; j<(int)attacks.size(); j++) {
			analyzeImitation(results, attacks, intervals, i, j);
		}
	}
}



///////////////////////////////
//
// Tool_imitation::getIntervals --
//

void Tool_imitation::getIntervals(vector<double>& intervals,
		vector<NoteCell*>& attacks) {
	for (int i=0; i<(int)attacks.size() - 1; i++) {
		intervals.at(i) = *attacks.at(i+1) - *attacks.at(i);
	}
	intervals.back() = NAN;

	if (getBoolean("debug")) {
		cout << endl;
		for (int i=0; i<(int)intervals.size(); i++) {
			cout << "INTERVAL " << i << "\t=\t" << intervals.at(i) << "\tATK "
			     << attacks.at(i)->getSgnDiatonicPitch() << "\t" << attacks.at(i)->getToken() << endl;
		}
	}

}



//////////////////////////////
//
// Tool_imitation::analyzeImitation -- do imitation analysis between two voices.
//

void Tool_imitation::analyzeImitation(vector<vector<string>>& results,
		vector<vector<NoteCell*>>& attacks, vector<vector<double>>& intervals,
		int v1, int v2) {

	vector<NoteCell*>& v1a = attacks.at(v1);
	vector<NoteCell*>& v2a = attacks.at(v2);
	vector<double>& v1i = intervals.at(v1);
	vector<double>& v2i = intervals.at(v2);

	int min = m_threshold - 1;
	int count;

	vector<int> enum1(v1a.size(), 0);
	vector<int> enum2(v2a.size(), 0);

	for (int i=0; i<(int)v1i.size() - 1; i++) {
		count = 0;
		for (int j=0; j<(int)v2i.size() - 1; j++) {
			if (m_rest || m_rest2) {
				if ((i > 0) && (!Convert::isNaN(attacks.at(v1).at(i-1)->getSgnDiatonicPitch()))) {
					// match initiator must be preceded by a rest (or start of music)
					continue;
				}
			}
			if (m_rest2) {
				if ((j > 0) && (!Convert::isNaN(attacks.at(v2).at(j-1)->getSgnDiatonicPitch()))) {
					// match target must be preceded by a rest (or start of music)
					continue;
				}
			}
			if ((enum1.at(i) != 0) && (enum1.at(i) == enum2.at(j))) {
				// avoid re-matching an existing match as a submatch
				continue;
			}
			count = compareSequences(v1a, v1i, i, v2a, v2i, j);
			if ((count >= min) && (m_intervals.size() > 0)) {
				count = checkForIntervalSequence(m_intervals, v1i, i, count);
			}
			if (count < min) {
				j += count;
				continue;
			}

			// cout << "Match length count " << count << endl;
			HTp token1 = attacks.at(v1).at(i)->getToken();
			HTp token2 = attacks.at(v2).at(j)->getToken();
			HumNum time1 = token1->getDurationFromStart();
			HumNum time2 = token2->getDurationFromStart();
			HumNum distance1 = time2 - time1;
			HumNum distance2 = time1 - time2;

			if (m_maxdistanceQ && (distance1.getAbs().getFloat() > m_maxdistance)) {
				j += count;
				continue;
			}

			Enumerator++;
			for (int k=0; k<count; k++) {
				enum1.at(i+k) = Enumerator;
				enum2.at(j+k) = Enumerator;
			}

			int interval = int(*attacks.at(v2).at(j) - *attacks.at(v1).at(i));

			if (!m_noInfo) {
				if (!(m_first && (distance1 < 0))) {
					int line1 = attacks.at(v1).at(i)->getLineIndex();
					if (!results.at(v1).at(line1).empty()) {
						results.at(v1).at(line1) += " ";
					}

					bool data = false;

					if (!m_noN) {
						data = true;
						if (m_inversion) {
							results.at(v1).at(line1) += "v";
						} else if (m_retrograde) {
							results.at(v1).at(line1) += "r";
						} else {
							results.at(v1).at(line1) += "n";
						}
						results.at(v1).at(line1) += to_string(Enumerator);
					}

					if (m_measure) {
						if (data) {
							results.at(v1).at(line1) += ":";
						}
						data = true;
						results.at(v1).at(line1) += "m";
						int line = attacks.at(v1).at(i)->getToken()->getLineIndex();
						results.at(v1).at(line1) += to_string(m_barlines[line]);
					}

					if (m_beat) {
						if (data) {
							results.at(v1).at(line1) += ":";
						}
						data = true;
						results.at(v1).at(line1) += "b";
						HLp humline = attacks.at(v1).at(i)->getToken()->getOwner();
						stringstream ss;
						ss.str("");
						ss << humline->getBeat().getFloat();
						results.at(v1).at(line1) += ss.str();
					}

					if (m_length) {
						if (data) {
							results.at(v1).at(line1) += ":";
						}
						data = true;
						results.at(v1).at(line1) += "L";
						// time1 is the starttime
						HumNum endtime;
						HTp endtoken = NULL;
						if (i+count < (int)attacks.at(v1).size()) {
							endtoken = attacks.at(v1).at(i+count)->getToken();
							endtime = endtoken->getDurationFromStart();
						} else {
							endtime = token1->getOwner()->getOwner()->getScoreDuration();
						}
						HumNum duration = endtime - time1;
						stringstream ss;
						ss.str("");
						ss << duration.getFloat();
						results.at(v1).at(line1) += ss.str();
					}

					if (!m_noC) {
						if (data) {
							results.at(v1).at(line1) += ":";
						}
						data = true;
						results.at(v1).at(line1) += "c";
						results.at(v1).at(line1) += to_string(count);
					}

					if (!m_noD) {
						if (data) {
							results.at(v1).at(line1) += ":";
						}
						data = true;
						results.at(v1).at(line1) += "d";
						// maybe allow fractions?
						results.at(v1).at(line1) += to_string(distance1.getNumerator());
					}

					if (!m_noI) {
						if (data) {
							results.at(v1).at(line1) += ":";
						}
						data = true;
						if (distance1.getDenominator() != 1) {
							results.at(v1).at(line1) += '/';
							results.at(v1).at(line1) += to_string(distance1.getNumerator());
						}
						results.at(v1).at(line1) += "i";
						if (interval > 0) {
							results.at(v1).at(line1) += to_string(interval + 1);
						} else {
							int newinterval = -(interval + 1);
							if (newinterval == -1) {
								newinterval = 1; // unison (no sign)
							}
							results.at(v1).at(line1) += to_string(newinterval);
						}
					}
				}

				if (!(m_first && (distance2 <= 0))) {
					int line2 = attacks.at(v2).at(j)->getLineIndex();

					if (!results.at(v2).at(line2).empty()) {
						results.at(v2).at(line2) += " ";
					}

					bool data2 = false;

					if ((!m_noN) && (!m_noNN)) {
						data2 = true;
						if (m_inversion) {
							results.at(v2).at(line2) += "v";
						} else if (m_retrograde) {
							results.at(v2).at(line2) += "r";
						} else {
							results.at(v2).at(line2) += "n";
						}
						results.at(v2).at(line2) += to_string(Enumerator);
					}

					if (m_measure) {
						if (data2) {
							results.at(v2).at(line2) += ":";
						}
						data2 = true;
						results.at(v2).at(line2) += "m";
						int line = attacks.at(v2).at(j)->getToken()->getLineIndex();
						results.at(v2).at(line2) += to_string(m_barlines[line]);
					}

					if (m_beat) {
						if (data2) {
							results.at(v2).at(line2) += ":";
						}
						data2 = true;
						results.at(v2).at(line2) += "b";
						HLp humline = attacks.at(v2).at(j)->getToken()->getOwner();
						stringstream ss;
						ss.str("");
						ss << humline->getBeat().getFloat();
						results.at(v2).at(line2) += ss.str();
					}

					if (m_length) {
						if (data2) {
							results.at(v2).at(line2) += ":";
						}
						data2 = true;
						results.at(v2).at(line2) += "L";
						// time1 is the starttime
						HumNum endtime;
						HTp endtoken = NULL;
						if (j+count < (int)attacks.at(v2).size()) {
							endtoken = attacks.at(v2).at(j+count)->getToken();
							endtime = endtoken->getDurationFromStart();
						} else {
							endtime = token2->getOwner()->getOwner()->getScoreDuration();
						}
						HumNum duration = endtime - time2;
						stringstream ss;
						ss.str("");
						ss << duration.getFloat();
						results.at(v2).at(line2) += ss.str();
					}

					if ((!m_noC) && (!m_noCC)) {
						if (data2) {
							results.at(v2).at(line2) += ":";
						}
						data2 = true;
						results.at(v2).at(line2) += "c";
						results.at(v2).at(line2) += to_string(count);
					}

					if ((!m_noD) && (!m_noDD)) {
						if (data2) {
							results.at(v2).at(line2) += ":";
						}
						data2 = true;
						results.at(v2).at(line2) += "d";
						results.at(v2).at(line2) += to_string(distance2.getNumerator());
					}

					if ((!m_noI) && (!m_noII)) {
						if (data2) {
							results.at(v2).at(line2) += ":";
						}
						data2 = true;
						if (distance2.getDenominator() != 1) {
							results.at(v2).at(line2) += '/';
							results.at(v2).at(line2) += to_string(distance2.getNumerator());
						}
						results.at(v2).at(line2) += "i";
						if (interval > 0) {
							int newinterval = -(interval + 1);
							if (newinterval == -1) {
								newinterval = 1; // unison (no sign)
							}
							results.at(v2).at(line2) += to_string(newinterval);
						} else {
							results.at(v2).at(line2) += to_string(interval + 1);
						}
					}
				}
			}

			if (m_mark) {
				for (int z=0; z<count; z++) {
					if (i+z >= (int)attacks.at(v1).size()) {
						break;
					}
					token1 = attacks.at(v1).at(i+z)->getToken();
					if (j+z >= (int)attacks.at(v2).size()) {
						break;
					}
					token2 = attacks.at(v2).at(j+z)->getToken();
					if (m_single) {
						if (token1->find(m_marker) == string::npos) {
							token1->setText(*token1 + m_marker);
						}
						if (token2->find(m_marker) == string::npos) {
							token2->setText(*token2 + m_marker);
						}
					} else {
						token1->setText(*token1 + m_marker);
						token2->setText(*token2 + m_marker);
					}

               if (attacks.at(v1).at(i+z)->isRest() && (z < count - 1) ) {
						markedTiedNotes(attacks.at(v1).at(i+z)->m_tiedtokens);
					} else if (!attacks.at(v1).at(i+z)->isRest()) {
						markedTiedNotes(attacks.at(v1).at(i+z)->m_tiedtokens);
					}

               if (attacks.at(v2).at(j+z)->isRest() && (z < count - 1) ) {
						markedTiedNotes(attacks.at(v2).at(j+z)->m_tiedtokens);
					} else if (!attacks.at(v2).at(j+z)->isRest()) {
						markedTiedNotes(attacks.at(v2).at(j+z)->m_tiedtokens);
					}

				}
			}

			// skip over match (need to do in i as well somehow)
			j += count;
		} // j loop
	} // i loop
}



//////////////////////////////
//
// Tool_imitation::markedTiedNotes --
//

void Tool_imitation::markedTiedNotes(vector<HTp>& tokens) {
	for (int i=0; i<(int)tokens.size(); i++) {
		if (m_single) {
			if (tokens.at(i)->find(m_marker) == string::npos) {
				tokens.at(i)->setText(*tokens.at(i) + m_marker);
			}
		} else {
			tokens.at(i)->setText(*tokens.at(i) + m_marker);
		}
	}
}



//////////////////////////////
//
// Tool_imitation::checkForIntervalSequence --
//

int Tool_imitation::checkForIntervalSequence(vector<int>& m_intervals,
		vector<double>& v1i, int starti, int count) {

	int endi = starti + count - (int)m_intervals.size();
	for (int i=starti; i<endi; i++) {
		for (int j=0; j<(int)m_intervals.size(); j++) {
			if (m_intervals.at(j) != v1i.at(i+j)) {
				break;
			}
			if (j == (int)m_intervals.size() - 1) {
				// successfully found the interval pattern in imitation
				return count;
			}
		}
	}

	// pattern was not found so say that there was no match
	return 0;
}



///////////////////////////////
//
// Tool_imitation::compareSequences -- Returns the number of notes that
//     match between the two sequences (which is one more than the
//     interval count).
//

int Tool_imitation::compareSequences(vector<NoteCell*>& attack1,
		vector<double>& seq1, int i1, vector<NoteCell*>& attack2,
		vector<double>& seq2, int i2) {
	int count = 0;
	// sequences cannot start with rests
	if (Convert::isNaN(seq1.at(i1)) || Convert::isNaN(seq2.at(i2))) {
		return count;
	}
	if (m_nozero) {
		// exclude matches that start at the same time.
		if (attack1.at(i1)->getToken()->getDurationFromStart() == attack2.at(i2)->getToken()->getDurationFromStart()) {
			return count;
		}
	} else if (m_onlyzero) {
		// exclude matches that do not start at the same time (parallel motion).
		if (attack1.at(i1)->getToken()->getDurationFromStart() != attack2.at(i2)->getToken()->getDurationFromStart()) {
			return count;
		}
	}

	HumNum dur1;
	HumNum dur2;

	while ((i1+count < (int)seq1.size()) && (i2+count < (int)seq2.size())) {

		if (m_duration) {
			dur1 = attack1.at(i1+count)->getDuration();
			dur2 = attack2.at(i2+count)->getDuration();
			if (dur1 != dur2) {
				break;
			}
		}

		if (Convert::isNaN(seq1.at(i1+count))) {
			// the first voice's interval is to/from a rest
			if (Convert::isNaN(seq2.at(i2+count))) {
				// The seoncd voice's interval is also to/from a rest,
				// so increment count and continue.
				count++;
				continue;
			} else {
				// The second voice's interval is not to/from a rest,
				// so return the current count.
				if (count) {
					return count + 1;
				} else {
					return count;
				}
			}
		} else if (Convert::isNaN(seq2.at(i2+count))) {
			// The second voice's interval is to/from a rest
			// but already know that the first one is not, so return
			// current count;
			if (count) {
				return count + 1;
			} else {
				return count;
			}
		} else if (m_inversion && (seq1.at(i1+count) == -seq2.at(i2+count))) {
         // The two sequences match as inversions at this point, so continue.
			count++;
		} else if ((!m_inversion) && (seq1.at(i1+count) == seq2.at(i2+count))) {
         // The two sequences match at this point, so continue.
			count++;
			continue;
		} else {
			// The sequences do not match so return the current count.
			if (count) {
				return count + 1;
			} else {
				return count;
			}
		}
	}

	if (count) {
		// don't add one for some reaason (this will cause out-of-bounds)
		return count;
	} else {
		return count;
	}
}




/////////////////////////////////
//
// Tool_instinfo::Tool_instinfo -- Set the recognized options for the tool.
//

Tool_instinfo::Tool_instinfo(void) {
	define("c|instrument-class=s",        "instrument class by kern spine");
	define("i|instrument-code=s",         "instrument codes by kern spine");
	define("m|instrument-number=s",       "instrument number by kern spine");
	define("n|instrument-name=s",         "instrument name by kern spine");
	define("a|instrument-abbreviation=s", "instrument class by kern spine");
}



/////////////////////////////////
//
// Tool_instinfo::run -- Do the main work of the tool.
//

bool Tool_instinfo::run(HumdrumFileSet& infiles) {
	bool status = true;
	for (int i=0; i<infiles.getCount(); i++) {
		status &= run(infiles[i]);
	}
	return status;
}


bool Tool_instinfo::run(const string& indata, ostream& out) {
	HumdrumFile infile(indata);
	bool status = run(infile);
	if (hasAnyText()) {
		getAllText(out);
	} else {
		out << infile;
	}
	return status;
}


bool Tool_instinfo::run(HumdrumFile& infile, ostream& out) {
	bool status = run(infile);
	if (hasAnyText()) {
		getAllText(out);
	} else {
		out << infile;
	}
	return status;
}


bool Tool_instinfo::run(HumdrumFile& infile) {
	processFile(infile);
	return true;
}



//////////////////////////////
//
// Tool_instinfo::initialize --  Initializations that only have to be done once
//    for all HumdrumFile segments.
//

void Tool_instinfo::initialize(HumdrumFile& infile) {
	HumRegex hre;

	vector<HTp> kernstarts;
	kernstarts = infile.getKernSpineStartList();
	int ksize = (int)kernstarts.size();


	// Store instrument class, such as *Iww
	// Separated by semicolons and/or spaces
	string iclass = Convert::trimWhiteSpace(getString("instrument-class"));
	vector<string> pieces;
	hre.split(pieces, iclass, "[\\s:;,]+");
	if ((iclass.find(":") == string::npos) && ((int)pieces.size() == ksize)) {
		for (int i=0; i<ksize; i++) {
			m_iclass[i] = pieces[i];
		}
	} else {
		while (hre.search(iclass, "\\s*k\\s*(\\d+)\\s*:\\s*([^\\s]+)\\s*;?\\s*")) {
			int k = hre.getMatchInt(1);
			string iiclass = hre.getMatch(2);
			m_iclass[k-1] = iiclass;
			hre.replaceDestructive(iclass, "", "\\s*k\\s*(\\d+)\\s*:\\s*([^\\s]+)\\s*;?\\s*");
		}
	}

	// Store instrument codes, such as *Iflt
	// Separated by semicolons and/or spaces
	string icode = Convert::trimWhiteSpace(getString("instrument-code"));
	hre.split(pieces, icode, "[\\s:;,]+");
	if ((icode.find(":") == string::npos) && ((int)pieces.size() == ksize)) {
		for (int i=0; i<ksize; i++) {
			m_icode[i] = pieces[i];
		}
	} else {
		while (hre.search(icode, "\\s*k\\s*(\\d+)\\s*:\\s*([^\\s]+)\\s*;?\\s*")) {
			int k = hre.getMatchInt(1);
			string code = hre.getMatch(2);
			m_icode[k-1] = code;
			hre.replaceDestructive(icode, "", "\\s*k\\s*(\\d+)\\s*:\\s*([^\\s]+)\\s*;?\\s*");
		}
	}

	// Store instrument number, such as *I#2
	// The # before number is optional (and will be added automatically)
	// Separated by semicolons and/or spaces
	string inum = Convert::trimWhiteSpace(getString("instrument-number"));
	hre.split(pieces, inum, "[\\s:;,]+");
	if ((inum.find(":") == string::npos) && ((int)pieces.size() == ksize)) {
		for (int i=0; i<ksize; i++) {
			string value = pieces[i];
			if (!value.empty()) {
				if (value[0] != '#') {
					value = "#" + value;
				}
			}
			m_inum[i] = value;
		}
	} else {
		while (hre.search(inum, "\\s*k\\s*(\\d+)\\s*:\\s*#?\\s*([\\d]+)\\s*;?\\s*")) {
			int k = hre.getMatchInt(1);
			string num = "#" + hre.getMatch(2);
			m_inum[k-1] = num;
			hre.replaceDestructive(inum, "", "\\s*k\\s*(\\d+)\\s*:\\s*#?\\s*([\\d]+)\\s*;?\\s*");
		}
	}

	// Store instrument name, such as *I"flt
	// The names must be separated by semicolons.
	string iname = Convert::trimWhiteSpace(getString("instrument-name"));
	hre.split(pieces, iname, "\\s*;\\s*");
	if ((!hre.search(iname, "k\\s*\\d+\\s*:")) && ((int)pieces.size() == ksize)) {
		for (int i=0; i<ksize; i++) {
			m_iname[i] = pieces[i];
		}
	} else {
		while (hre.search(iname, "\\s*k\\s*(\\d+)\\s*:\\s*([^;]+)\\s*(;|$)\\s*")) {
			int k = hre.getMatchInt(1);
			string name = hre.getMatch(2);
			m_iname[k-1] = name;
			hre.replaceDestructive(iname, "", "\\s*k\\s*(\\d+)\\s*:\\s*([^;]+)");
		}
	}

	// Store instrument abbreviation, such as *I'fl.
	// The abbreviations must be separated by semicolons.
	string iabbr = Convert::trimWhiteSpace(getString("instrument-abbreviation"));
	hre.split(pieces, iabbr, "\\s*;\\s*");
	if ((!hre.search(iabbr, "k\\s*\\d+\\s*:")) && ((int)pieces.size() == ksize)) {
		for (int i=0; i<ksize; i++) {
			m_iabbr[i] = pieces[i];
		}
	} else {
		while (hre.search(iabbr, "\\s*k\\s*(\\d+)\\s*:\\s*([^;]+)\\s*(;|$)\\s*")) {
			int k = hre.getMatchInt(1);
			string abbr = hre.getMatch(2);
			m_iabbr[k-1] = abbr;
			hre.replaceDestructive(iabbr, "", "\\s*k\\s*(\\d+)\\s*:\\s*([^;]+)");
		}
	}

}



//////////////////////////////
//
// Tool_instinfo::processFile --
//

void Tool_instinfo::processFile(HumdrumFile& infile) {
	initialize(infile);
	vector<HTp> kspines;
	kspines = infile.getKernSpineStartList();
	vector<int> ktracks(kspines.size(), -1);;
	for (int i=0; i<(int)kspines.size(); i++) {
		ktracks[i] = kspines[i]->getTrack();
	}
	map<int, int> track2kindex;
	for (int i=0; i<(int)ktracks.size(); i++) {
		track2kindex[ktracks[i]] = i+1;
	}

	int gpsIndex      = -1;
	int exinterpIndex = -1;
	int iclassIndex   = -1;
	int icodeIndex    = -1;
	int inameIndex    = -1;
	int iabbrIndex    = -1;
	int inumIndex     = -1;

	HumRegex hre;

	for (int i=0; i<infile.getLineCount(); i++) {
		if (!infile[i].hasSpines()) {
			continue;
		}
		if (infile[i].isExclusiveInterpretation()) {
			exinterpIndex = i;
			continue;
		}
		if (infile[i].isData()) {
			break;
		}
		if (infile[i].isBarline()) {
			break;
		}
		if (!infile[i].isInterpretation()) {
			continue;
		}
		for (int j=0; j<infile[i].getFieldCount(); j++) {
			HTp token = infile.token(i, j);
			if (!token->isKern()) {
				continue;
			}
			if (hre.search(token, "^\\*staff\\d")) {
				gpsIndex = i;
			}
			if (hre.search(token, "^\\*part\\d")) {
				gpsIndex = i;
			}
			if (hre.search(token, "^\\*group\\d")) {
				gpsIndex = i;
			}
			if (token->isInstrumentClass()) {
				iclassIndex = i;
			}
			if (token->isInstrumentCode()) {
				icodeIndex = i;
			}
			if (token->isInstrumentName()) {
				inameIndex = i;
			}
			if (token->isInstrumentAbbreviation()) {
				iabbrIndex = i;
			}
			if (token->isInstrumentNumber()) {
				inumIndex = i;
			}
		}
	}

	if ((iclassIndex > 0) && !m_iclass.empty()) {
		updateInstrumentLine(infile, iclassIndex, m_iclass, track2kindex, "*IC");
	}
	if ((icodeIndex > 0) && !m_icode.empty()) {
		updateInstrumentLine(infile, icodeIndex, m_icode, track2kindex, "*I");
	}
	if ((inumIndex > 0) && !m_inum.empty()) {
		updateInstrumentLine(infile, inumIndex, m_inum, track2kindex, "*I");
	}
	if ((inameIndex > 0) && !m_iname.empty()) {
		updateInstrumentLine(infile, inameIndex, m_iname, track2kindex, "*I\"");
	}
	if ((iabbrIndex > 0) && !m_iabbr.empty()) {
		updateInstrumentLine(infile, iabbrIndex, m_iabbr, track2kindex, "*I'");
	}

	// Insertion line of instrument info after given line;
	// Add above given index:
	int aclassIndex = -1;
	int acodeIndex  = -1;
	int anumIndex   = -1;
	int anameIndex  = -1;
	int aabbrIndex  = -1;
	// Or add below given index:
	int bclassIndex = -1;
	int bcodeIndex  = -1;
	int bnumIndex   = -1;
	int bnameIndex  = -1;
	int babbrIndex  = -1;

	// Where to place instrument class:
	if ((iclassIndex < 0) && !m_iclass.empty()) {
		if (icodeIndex > 0) {
			aclassIndex = icodeIndex;
		} else if (inumIndex > 0) {
			aclassIndex = inumIndex;
		} else if (inameIndex > 0) {
			aclassIndex = inameIndex;
		} else if (iabbrIndex > 0) {
			aclassIndex = iabbrIndex;
		} else if (gpsIndex > 0) {
			bclassIndex = gpsIndex;
		} else if (exinterpIndex >= 0) {
			bclassIndex = exinterpIndex;
		}
	}

	// Where to place instrument code:
	if ((icodeIndex < 0) && !m_icode.empty()) {
		if (iclassIndex > 0) {
			bcodeIndex = iclassIndex;
		} else if (inumIndex > 0) {
			acodeIndex = inumIndex;
		} else if (inameIndex > 0) {
			acodeIndex = inameIndex;
		} else if (iabbrIndex > 0) {
			acodeIndex = iabbrIndex;
		} else if (gpsIndex > 0) {
			bcodeIndex = gpsIndex;
		} else if (exinterpIndex >= 0) {
			bcodeIndex = exinterpIndex;
		}
	}

	// Where to place instrument number:
	if ((inumIndex < 0) && !m_inum.empty()) {
		if (icodeIndex > 0) {
			bnumIndex = icodeIndex;
		} else if (iclassIndex > 0) {
			bnumIndex = iclassIndex;
		} else if (inameIndex > 0) {
			anumIndex = inameIndex;
		} else if (iabbrIndex > 0) {
			anumIndex = iabbrIndex;
		} else if (gpsIndex > 0) {
			bnumIndex = gpsIndex;
		} else if (exinterpIndex >= 0) {
			bnumIndex = exinterpIndex;
		}
	}

	// Where to place instrument name:
	if ((inameIndex < 0) && !m_iname.empty()) {
		if (inumIndex > 0) {
			bnameIndex = inumIndex;
		} else if (icodeIndex > 0) {
			bnameIndex = icodeIndex;
		} else if (iclassIndex > 0) {
			bnameIndex = iclassIndex;
		} else if (iabbrIndex > 0) {
			anameIndex = iabbrIndex;
		} else if (gpsIndex > 0) {
			bnameIndex = gpsIndex;
		} else if (exinterpIndex >= 0) {
			bnameIndex = exinterpIndex;
		}
	}

	// Where to place instrument abbreviation:
	if ((iabbrIndex < 0) && !m_iabbr.empty()) {
		if (inameIndex > 0) {
			babbrIndex = inameIndex;
		} else if (inumIndex > 0) {
			babbrIndex = inumIndex;
		} else if (icodeIndex > 0) {
			babbrIndex = icodeIndex;
		} else if (iclassIndex > 0) {
			babbrIndex = iclassIndex;
		} else if (gpsIndex > 0) {
			babbrIndex = gpsIndex;
		} else if (exinterpIndex >= 0) {
			babbrIndex = exinterpIndex;
		}
	}

	if (aclassIndex > 0) {
		insertInstrumentInfo(infile, aclassIndex, m_iclass, "*IC", "above-class", track2kindex);
	} else if (bclassIndex >= 0) {
		insertInstrumentInfo(infile, bclassIndex, m_iclass, "*IC", "below-class", track2kindex);
	}

	if (acodeIndex > 0) {
		insertInstrumentInfo(infile, acodeIndex, m_icode, "*I", "above-code", track2kindex);
	} else if (bcodeIndex >= 0) {
		insertInstrumentInfo(infile, bcodeIndex, m_icode, "*I", "below-code", track2kindex);
	}

	if (anumIndex > 0) {
		insertInstrumentInfo(infile, anumIndex, m_inum, "*I", "above-num", track2kindex);
	} else if (bnumIndex >= 0) {
		insertInstrumentInfo(infile, bnumIndex, m_inum, "*I", "below-num", track2kindex);
	}

	if (anameIndex > 0) {
		insertInstrumentInfo(infile, anameIndex, m_iname, "*I\"", "above-name", track2kindex);
	} else if (bnameIndex >= 0) {
		insertInstrumentInfo(infile, bnameIndex, m_iname, "*I\"", "below-name", track2kindex);
	}

	if (aabbrIndex > 0) {
		insertInstrumentInfo(infile, aabbrIndex, m_iabbr, "*I'", "above-abbr", track2kindex);
	} else if (babbrIndex >= 0) {
		insertInstrumentInfo(infile, babbrIndex, m_iabbr, "*I'", "below-abbr", track2kindex);
	}

	infile.createLinesFromTokens();

	bool dataQ = false;
	for (int i=0; i<infile.getLineCount(); i++) {
		if (!infile[i].hasSpines()) {
			m_humdrum_text << infile[i] << endl;
			continue;
		}
		if (infile[i].isData()) {
			dataQ = true;
		}
		if (infile[i].isBarline()) {
			dataQ = true;
		}
		if (dataQ) {
			m_humdrum_text << infile[i] << endl;
			continue;
		}
		printLine(infile, i);
	}
}



/////////////////////////////
//
// Tool_instinfo::insertInstrumentInfo --
//

void Tool_instinfo::insertInstrumentInfo(HumdrumFile& infile, int index,
	map<int, string>& info, const string& prefix, const string& key, map<int, int>& track2kindex) {

	for (int j=0; j<infile[index].getFieldCount(); j++) {
		HTp token = infile.token(index, j);
		if (!token->isKern()) {
			token->setValue("auto", key, "*");
			continue;
		}
		int track = token->getTrack();
		int kindex = track2kindex[track] - 1;
		if (kindex < 0) {
			token->setValue("auto", key, "*");
			continue;
		}
		if (((key == "above-class") || (key == "below-class")) && info[kindex].empty()) {
			token->setValue("auto", key, "*");
			continue;
		}
		if (((key == "above-code") || (key == "below-code")) && info[kindex].empty()) {
			token->setValue("auto", key, "*");
			continue;
		}
		if (((key == "above-num") || (key == "below-num")) && info[kindex].empty()) {
			token->setValue("auto", key, "*");
			continue;
		}
		string newtext = prefix + info[kindex];
		token->setValue("auto", key, newtext);
	}
}



//////////////////////////////
//
// Tool_instinfo::printLine --
//

void Tool_instinfo::printLine(HumdrumFile& infile, int index) {
	HTp first = infile.token(index, 0);

	if (!first->getValue("auto", "above-class").empty()) {
		printLine(infile, index, "above-class");
	}
	if (!first->getValue("auto", "above-code").empty()) {
		printLine(infile, index, "above-code");
	}
	if (!first->getValue("auto", "above-num").empty()) {
		printLine(infile, index, "above-num");
	}
	if (!first->getValue("auto", "above-name").empty()) {
		printLine(infile, index, "above-name");
	}
	if (!first->getValue("auto", "above-abbr").empty()) {
		printLine(infile, index, "above-abbr");
	}

	m_humdrum_text << infile[index] << endl;

	if (!first->getValue("auto", "below-class").empty()) {
		printLine(infile, index, "below-class");
	}
	if (!first->getValue("auto", "below-code").empty()) {
		printLine(infile, index, "below-code");
	}
	if (!first->getValue("auto", "below-num").empty()) {
		printLine(infile, index, "below-num");
	}
	if (!first->getValue("auto", "below-name").empty()) {
		printLine(infile, index, "below-name");
	}
	if (!first->getValue("auto", "below-abbr").empty()) {
		printLine(infile, index, "below-abbr");
	}

}



//////////////////////////////
//
// Tool_instinfo::printLine --
//

void Tool_instinfo::printLine(HumdrumFile& infile, int index, const string& key) {
	for (int j=0; j<infile[index].getFieldCount(); j++) {
		HTp token = infile.token(index, j);
		string value = token->getValue("auto", key);
		if (value.empty()) {
			value = "*";
		}
		m_humdrum_text << value;
		if (j < infile[index].getFieldCount() - 1) {
			m_humdrum_text << "\t";
		}
	}
	m_humdrum_text << endl;
}



//////////////////////////////
//
// Tool_instinfo::updateInstrumentLine --
//

void Tool_instinfo::updateInstrumentLine(HumdrumFile& infile, int index,
		map<int, string>& values, map<int, int>& track2kindex,
		const string& prefix) {
	for (int j = 0; j<infile[index].getFieldCount(); j++) {
		HTp token = infile.token(index, j);
		if (!token->isKern()) {
			continue;
		}
		int track = token->getTrack();
		int kindex = track2kindex[track] - 1;
		if (kindex < 0) {
			continue;
		}
		string value = values[kindex];
		if (value.empty()) {
			continue;
		}
		value = prefix + value;
		token->setText(value);
	}
}




/////////////////////////////////
//
// Tool_kern2mens::Tool_kern2mens -- Set the recognized options for the tool.
//

Tool_kern2mens::Tool_kern2mens(void) {
	define("N|no-measure-numbers=b",                "remove measure numbers");
	define("M|no-measures=b",                       "remove measures ");
	define("I|not-invisible=b",                     "keep measures visible");
	define("D|no-double-bar=b",                     "keep thick final barlines");
	define("c|clef=s",                              "clef to use in mensural notation");
	define("V|no-verovio=b",                        "don't add verovio styling");
	define("e|evenNoteSpacing|even-note-spacing=b", "add evenNoteSpacing option");
}



/////////////////////////////////
//
// Tool_kern2mens::run -- Do the main work of the tool.
//

bool Tool_kern2mens::run(HumdrumFileSet& infiles) {
	bool status = true;
	for (int i=0; i<infiles.getCount(); i++) {
		status &= run(infiles[i]);
	}
	return status;
}

bool Tool_kern2mens::run(const string& indata, ostream& out) {
	HumdrumFile infile(indata);
	bool status = run(infile);
	if (hasAnyText()) {
		getAllText(out);
	} else {
		out << infile;
	}
	return status;
}


bool Tool_kern2mens::run(HumdrumFile& infile, ostream& out) {
	bool status = run(infile);
	if (hasAnyText()) {
		getAllText(out);
	} else {
		out << infile;
	}
	return status;
}


bool Tool_kern2mens::run(HumdrumFile& infile) {
	m_numbersQ   = !getBoolean("no-measure-numbers");
	m_measuresQ  = !getBoolean("no-measures");
	m_invisibleQ = !getBoolean("not-invisible");
	m_doublebarQ = !getBoolean("no-double-bar");
	m_noverovioQ =  getBoolean("no-verovio");
	m_clef       =  getString("clef");
	m_evenNoteSpacingQ = getBoolean("even-note-spacing");
	storeKernEditorialAccidental(infile);
	storeKernTerminalLong(infile);
	convertToMens(infile);
	return true;
}



//////////////////////////////
//
// Tool_kern2mens::convertToMens --
//

void Tool_kern2mens::convertToMens(HumdrumFile& infile) {
	analyzeColoration(infile);
	int maxtrack = infile.getMaxTrack();
	for (int i=0; i<infile.getLineCount(); i++) {
		if (infile[i].isBarline()) {
			printBarline(infile, i);
			continue;
		}
		if (!infile[i].hasSpines()) {
			if (i == m_kernEdAccLineIndex) {
				m_humdrum_text << m_mensEdAccLine << endl;
			} else if (i == m_kernTerminalLongIndex) {
				m_humdrum_text << m_mensTerminalLongLine << endl;
			} else {
				m_humdrum_text << infile[i] << "\n";
			}
			continue;
		}
		if ((maxtrack == 1) && infile[i].isAllNull()) {
			continue;
		}
		for (int j=0; j<infile[i].getFieldCount(); j++) {
			HTp token = infile.token(i, j);
			m_humdrum_text << convertKernTokenToMens(token);
			if (j < infile[i].getFieldCount() - 1) {
				m_humdrum_text << "\t";
			}
		}
		m_humdrum_text << "\n";
	}
	if (!m_noverovioQ) {
		addVerovioStyling(infile);
	}
}



//////////////////////////////
//
// Tool_kern2mens::addVerovioStyling --  Add a spacing of 0.3, 0.5 if no
//   spacing is already found in the data.  Use the -V option to no add any
//   spacing options (to use the defaults in verovio).
//

void Tool_kern2mens::addVerovioStyling(HumdrumFile& infile) {
	HumRegex hre;
	for (int i=0; i<infile.getLineCount(); i++) {
		if (infile[i].hasSpines()) {
			continue;
		}
		HTp token = infile[i].token(0);
		if (hre.search(token, "!!!verovio:\\s*evenNoteSpacing")) {
			return;
		}
		if (!m_evenNoteSpacingQ) {
			if (hre.search(token, "!!!verovio:\\s*spacingLinear")) {
				return;
			}
			if (hre.search(token, "!!!verovio:\\s*spacingNonLinear")) {
				return;
			}
		}
	}
	if (m_evenNoteSpacingQ) {
		m_humdrum_text << "!!!verovio: evenNoteSpacing\n";
	} else {
		m_humdrum_text << "!!!verovio: spacingLinear 0.3\n";
		m_humdrum_text << "!!!verovio: spacingNonLinear 0.5\n";
	}
}



//////////////////////////////
//
// Tool_kern2mens::convertKernTokenToMens --
//

string Tool_kern2mens::convertKernTokenToMens(HTp token) {
	string data;
	HumRegex hre;
	if (!token->isKern()) {
		return *token;
	}
	if (token->isNull()) {
		return *token;
	}
	if (token->isExclusiveInterpretation()) {
		return "**mens";
	}
	if (token->isInterpretation()) {
		if (!m_clef.empty()) {
			if (hre.search(token, "^\\*clef")) {
				data = "*clef";
				data += m_clef;
				return data;
			}
		} else if (hre.search(token, "^\\*[mo]?clef")) {
			string value = getClefConversion(token);
			return value;
		}
	}
	if (!token->isData()) {
		return *token;
	}
	if (token->isSecondaryTiedNote()) {
		return ".";
	}
	data = *token;
	// remove uninteresting characters (beams, articulations, etc).
	// keeping pitches, accidentals, rests, slurs, durations, ties
	hre.replaceDestructive(data, "", "[^A-Gnra-g#\\(\\)\\[\\]0-9%.-]", "g");
	// but keep editorial accidental (probably i)
	HumNum dur;
	if (token->find("[") != std::string::npos) {
		dur = token->getTiedDuration();
		hre.replaceDestructive(data, "", "\\[");
	} else {
		dur = token->getDuration();
	}
	string rhythm = Convert::durationToRecip(dur);
	bool perfect = false;
	if (rhythm.find('.') != std::string::npos) {
		perfect = true;
	}
	hre.replaceDestructive(data, rhythm, "\\d+%?\\d*\\.*");
	hre.replaceDestructive(data, "S", "3\\%4");
	hre.replaceDestructive(data, "s", "3\\%2");
	hre.replaceDestructive(data, "M", "3");
	hre.replaceDestructive(data, "X", "000");
	hre.replaceDestructive(data, "L", "00");
	hre.replaceDestructive(data, "S", "0");
	hre.replaceDestructive(data, "u", "16");
	hre.replaceDestructive(data, "M", "2");
	hre.replaceDestructive(data, "m", "4");
	hre.replaceDestructive(data, "U", "8");
	hre.replaceDestructive(data, "s", "1");
	hre.replaceDestructive(data, ":", "\\.");
	if (perfect) {
		hre.replaceDestructive(data, "$1p", "([XLSsMmUu]+)");
	} else {
		hre.replaceDestructive(data, "$1i", "([XLSsMmUu]+)");
	}

	// transfer editorial accidental
	if (!m_kernEditorialAccidental.empty()) {
		if (token->find(m_kernEditorialAccidental) != string::npos) {
			hre.replaceDestructive(data, "$1z", "([#-n]+)", "g");
		}
	}

	// transfer terminal longs
	if (!m_kernTerminalLong.empty()) {
		string searchTerm = "(" + m_kernTerminalLong + "+)";
		if (hre.search(token, searchTerm)) {
			data += hre.getMatch(1);
		}
	}

	bool coloration = token->getValueBool("auto", "coloration");
	if (coloration) {
		data += "~";
	}

	return data;
}



//////////////////////////////
//
// Tool_kern2mens::printBarline --
//

void Tool_kern2mens::printBarline(HumdrumFile& infile, int line) {
	bool doubleQ = false;
	// keeping double barlines and final barlines.
	if (infile.token(line, 0)->find("==") != std::string::npos) {
		doubleQ = true;
	} else if (infile.token(line, 0)->find("||") != std::string::npos) {
		doubleQ = true;
	} else if (!m_measuresQ) {
		return;
	}

	HumRegex hre;
	int dataline = line+1;
	while (dataline < infile.getLineCount()) {
		if (infile[dataline].isData()) {
			break;
		}
		dataline++;
	}
	if (dataline >= infile.getLineCount()) {
		dataline = infile.getLineCount() - 1;
	}
	if (infile[dataline].isData()) {
		int attacks = true;
		for (int j=0; j<infile[dataline].getFieldCount(); j++) {
			HTp token = infile.token(dataline, j);
			if (!token->isKern()) {
				continue;
			}
			if (token->isSecondaryTiedNote()) {
				attacks = false;
				break;
			}
		}
		if ((!doubleQ) && (!attacks)) {
			return;
		}
	}

	for (int j=0; j<infile[line].getFieldCount(); j++) {
		doubleQ = false;
		string token = *infile.token(line, j);
		if (m_doublebarQ && (token.find("==") != std::string::npos)) {
			hre.replaceDestructive(token, "=||", "=+");
			doubleQ = true;
		}
		if (m_doublebarQ && (token.find("||") != std::string::npos)) {
			doubleQ = true;
		}
		if (!m_numbersQ) {
			hre.replaceDestructive(token, "", "\\d+");
		}
		if (token.find("-") != std::string::npos) {
			m_humdrum_text << token;
		} else {
			if ((!doubleQ) && m_invisibleQ) {
				m_humdrum_text << token << "-";
			} else {
				m_humdrum_text << token;
			}
		}
		if (j < infile[line].getFieldCount() - 1) {
			m_humdrum_text << "\t";
		}
	}
	m_humdrum_text << "\n";
}



//////////////////////////////
//
// Tool_kern2mens::getClefConversion --
//    If token is *oclef and there is an adjacent *clef,
//        convert *oclef to *clef and *clef to *mclef; otherwise,
//        return the given clef.
//
//

string Tool_kern2mens::getClefConversion(HTp token) {

	vector<HTp> clefs;
	vector<HTp> oclefs;
	vector<HTp> mclefs;

	HumRegex hre;

	HTp current = token->getNextToken();
	while (current) {
		if (current->isData()) {
			break;
		}
		if (current->compare(0, 5, "*clef") == 0) {
			clefs.push_back(current);
		}
		if (current->compare(0, 6, "*oclef") == 0) {
			oclefs.push_back(current);
		}
		if (current->compare(0, 6, "*mclef") == 0) {
			mclefs.push_back(current);
		}
		current = current->getNextToken();
	}

	current = token->getPreviousToken();
	while (current) {
		if (current->isData()) {
			break;
		}
		if (current->compare(0, 5, "*clef") == 0) {
			clefs.push_back(current);
		}
		if (current->compare(0, 6, "*oclef") == 0) {
			oclefs.push_back(current);
		}
		if (current->compare(0, 6, "*mclef") == 0) {
			mclefs.push_back(current);
		}
		current = current->getPreviousToken();
	}

	if (token->compare(0, 5, "*clef") == 0) {
		if (oclefs.size() > 0) {
			string value = *token;
			hre.replaceDestructive(value, "mclef", "clef");
			return value;
		}
	}

	if (token->compare(0, 6, "*oclef") == 0) {
		if (clefs.size() > 0) {
			string value = *token;
			hre.replaceDestructive(value, "clef", "oclef");
			return value;
		}
	}

	return *token;
}



//////////////////////////////
//
// Tool_kern2mens::storeKernEditorialAccidental --
//

void Tool_kern2mens::storeKernEditorialAccidental(HumdrumFile& infile) {
	for (int i=infile.getLineCount() - 1; i>= 0; i--) {
		if (infile[i].hasSpines()) {
			continue;
		}
		if (!infile[i].isReferenceRecord()) {
			continue;
		}
		string key = infile[i].getReferenceKey();
		if (key != "RDF**kern") {
			continue;
		}
		HumRegex hre;
		string value = infile[i].getReferenceValue();
		if (hre.search(value, "^\\s*([^\\s]+)\\s*=\\s*(.*)\\s*$")) {
			string signifier = hre.getMatch(1);
			string definition = hre.getMatch(2);
			if (hre.search(definition, "editorial\\s+accidental")) {
				m_kernEditorialAccidental = signifier;
				m_kernEdAccLineIndex = i;
				m_mensEdAccLine = "!!!RDF**mens: z = ";
				m_mensEdAccLine += definition;
				break;
			}
		}
	}
}



//////////////////////////////
//
// Tool_kern2mens::storeKernTerminalLong --
//

void Tool_kern2mens::storeKernTerminalLong(HumdrumFile& infile) {
	for (int i=infile.getLineCount() - 1; i>= 0; i--) {
		if (infile[i].hasSpines()) {
			continue;
		}
		if (!infile[i].isReferenceRecord()) {
			continue;
		}
		string key = infile[i].getReferenceKey();
		if (key != "RDF**kern") {
			continue;
		}
		HumRegex hre;
		string value = infile[i].getReferenceValue();
		if (hre.search(value, "^\\s*([^\\s]+)\\s*=\\s*(.*)\\s*$")) {
			string signifier = hre.getMatch(1);
			string definition = hre.getMatch(2);

			if (hre.search(definition, "terminal\\s+long")) {
				m_kernTerminalLong = signifier;
				m_kernTerminalLongIndex = i;
				m_mensTerminalLongLine = "!!!RDF**mens: " + signifier + " = ";
				m_mensTerminalLongLine += definition;
				break;
			} else if (hre.search(definition, "long\\s+note")) {
				m_kernTerminalLong = signifier;
				m_kernTerminalLongIndex = i;
				m_mensTerminalLongLine = "!!!RDF**mens: " + signifier + " = ";
				m_mensTerminalLongLine += definition;
				break;
			}

		}
	}
}



//////////////////////////////
//
// Tool_kern2mens::analyzeColoration --
//

void Tool_kern2mens::analyzeColoration(HumdrumFile& infile) {
	vector<HTp> spinestarts = infile.getKernSpineStartList();
	for (int i=0; i<(int)spinestarts.size(); i++) {
		analyzeColoration(spinestarts[i]);
	}
}

void Tool_kern2mens::analyzeColoration(HTp stok) {
	HTp current = stok->getNextToken();
	bool coloration = false;
	while (current) {
		if (current->isInterpretation()) {
			if (*current == "*col") {
				coloration = true;
			} else if (*current == "*Xcol") {
				coloration = false;
			}
		}
		if (!current->isData()) {
			current = current->getNextToken();
			continue;
		}
		if (current->isNull()) {
			current = current->getNextToken();
			continue;
		}
		if (coloration) {
			current->setValue("auto", "coloration", 1);
		}
		current = current->getNextToken();
	}
}





/////////////////////////////////
//
// Tool_kernify::Tool_kernify -- Set the recognized options for the tool.
//

Tool_kernify::Tool_kernify(void) {
	define("f|force=b", "force staff-like spines to be displayed as text");
}



/////////////////////////////////
//
// Tool_kernify::run -- Do the main work of the tool.
//

bool Tool_kernify::run(HumdrumFileSet& infiles) {
	bool status = true;
	for (int i=0; i<infiles.getCount(); i++) {
		status &= run(infiles[i]);
	}
	return status;
}


bool Tool_kernify::run(const string& indata, ostream& out) {
	HumdrumFile infile(indata);
	bool status = run(infile);
	if (hasAnyText()) {
		getAllText(out);
	} else {
		out << infile;
	}
	return status;
}


bool Tool_kernify::run(HumdrumFile& infile, ostream& out) {
	bool status = run(infile);
	if (hasAnyText()) {
		getAllText(out);
	} else {
		out << infile;
	}
	return status;
}


bool Tool_kernify::run(HumdrumFile& infile) {
	initialize();
	m_hasDataInterpretations = prepareDataSpines(infile);
	processFile(infile);
	return true;
}



//////////////////////////////
//
// Tool_kernify::initialize -- Setup to do before processing a file.
//

void Tool_kernify::initialize(void) {
	if (getBoolean("force")) {
		m_forceQ = true;
	}
	// do nothing
}



//////////////////////////////
//
// Tool_kernify::processFile -- Analyze an input file.
//

void Tool_kernify::processFile(HumdrumFile& infile) {
	generateDummyKernSpine(infile);
}



//////////////////////////////
//
// Tool_kernify::prepareDataSpines --
// 

bool Tool_kernify::prepareDataSpines(HumdrumFile& infile) {
	vector<HTp> spinestarts;
	infile.getSpineStartList(spinestarts);
	bool output = false;
	for (int i=0; i<(int)spinestarts.size(); i++) {
		output |= prepareDataSpine(spinestarts[i]);
	}
	return output;
}



//////////////////////////////
//
// Tool_kernify::prepareDataSpine -- check to see if *[abcv]data interpretation and if so process
//    and return true;
//

bool Tool_kernify::prepareDataSpine(HTp spinestart) {
	HumRegex hre;
	if (hre.search(spinestart, "^\\*\\*[abcv]data-")) {
		return false;
	}

	string prefix;
	HTp current = spinestart->getNextToken();
	while (current && !current->isData()) {
		if (current->isInterpretation()) {
			if (*current == "*adata") {
				prefix = "adata";
				break;
			} else if (*current == "*bdata") {
				prefix = "bdata";
				break;
			} else if (*current == "*cdata") {
				prefix = "cdata";
				break;
			} else if (*current == "*vdata") {
				prefix = "vdata";
				break;
			}
		}
		current = current->getNextToken();
	}

	if (prefix.empty()) {
		return false;
	}

	string text = "**";
	text += prefix;
	text += "-";
	text += spinestart->substr(2);
	spinestart->setText(text);

	return true;
}



//////////////////////////////
//
// Tool_kernify::generateDummyKernSpine --
//

void Tool_kernify::generateDummyKernSpine(HumdrumFile& infile) {
	vector<HTp> spineStarts;
	infile.getSpineStartList(spineStarts);
	bool hasRecip = false;
	if (spineStarts.empty()) {
		// no spines, so nothing to do
		return;
	}
	for (int i=0; i<(int)spineStarts.size(); i++) {
		if (spineStarts[i]->isStaffLike()) {
			if (!m_forceQ) {
				// No need for a dummy kern spine, so do nothing.
				// later an option can be used to force a dummy
				// kern spine even if there already exists
				return;
			}
		}
		if (spineStarts[i]->isDataType("**recip")) {
			hasRecip = true;
		}
	}

	int striaIndex = -1;
	int clefIndex = -1;
	for (int i=0; i<infile.getLineCount(); i++) {
		if (!infile[i].hasSpines()) {
			continue;
		}
		if (infile[i].isData()) {
			break;
		}
		if (!infile[i].isInterpretation()) {
			continue;
		}
		for (int j=0; j<infile[i].getFieldCount(); j++) {
			HTp token = infile.token(i, j);
			if (striaIndex < 0) {
				if (token->compare(0, 6, "*stria") == 0) {
					striaIndex = i;
				}
			}
			if (clefIndex < 0) {
				if (token->compare(0, 6, "*clef") == 0) {
					clefIndex = i;
				}
			}
		}
	}
	if (striaIndex == clefIndex) {
		// Don't show on the same data line.
		striaIndex = -1;
	}

	bool hasDuration = infile.getScoreDuration() > 0 ? true : false;

	HumRegex hre;
	for (int i=0; i<infile.getLineCount(); i++) {
		if (!infile[i].hasSpines()) {
			m_humdrum_text << infile[i];
		} else if (infile[i].isExclusiveInterpretation()) {
			m_humdrum_text << "**kern";
			for (int j=infile[i].getFieldCount()-1; j>=0; j--) {
				HTp token = infile.token(i, j);
				if (*token == "**recip") {
					m_humdrum_text << "\t**xrecip";
				} else if (token->find("**kern") != std::string::npos) {
					string value = *token;
					hre.replaceDestructive(value, "nrek", "kern", "g");
					hre.replaceDestructive(value, "**zcdata-", "^\\*\\*");
					m_humdrum_text << "\t" << value;
				} else if (token->find("**mens") != std::string::npos) {
					string value = *token;
					hre.replaceDestructive(value, "snem", "mens", "g");
					hre.replaceDestructive(value, "**ycdata-", "^\\*\\*");
					m_humdrum_text << "\t" << value;
				} else if (token->find("**cdata") == std::string::npos) {
					if (!m_hasDataInterpretations) {
						string value = token->substr(2);
						hre.replaceDestructive(value, "nrek", "kern", "g");
						hre.replaceDestructive(value, "snem", "snem", "g");
						m_humdrum_text << "\t**xcdata-" << value;
					} else {
						m_humdrum_text << "\t" << token;
					}
				} else {
					m_humdrum_text << "\t" << token;
				}
			}
			if (striaIndex < 0) {
				m_humdrum_text << endl << "*stria0" << "\t" << makeNullLine(infile[i]);
			}
			if (clefIndex < 0) {
				m_humdrum_text << endl << "*clefXyy" << "\t" << makeNullLine(infile[i]);
			}
		} else if (infile[i].isManipulator()) {
			if (*infile[i].token(0) == "*-") {
				m_humdrum_text << "*-";
			} else {
				m_humdrum_text << "*";
			}
			m_humdrum_text << "\t" << makeReverseLine(infile[i]);
		} else if (infile[i].isBarline()) {
			m_humdrum_text << infile[i].token(0) << "\t" << makeReverseLine(infile[i]);
		} else if (infile[i].isData()) {
			if (hasRecip) {
				for (int j=0; j<infile[i].getFieldCount(); j++) {
					HTp token = infile.token(i, j);
					if (!token->isDataType("**recip")) {
						continue;
					}
					m_humdrum_text << token << "ryy\t" << makeReverseLine(infile[i]);
					break;
				}
			} else {
				if (!hasDuration) {
					m_humdrum_text << "4ryy" << "\t" << makeReverseLine(infile[i]);
				} else {
					HumNum duration = infile[i].getDuration();
					string recip;
					if (duration == 0) {
						recip = "q";
					} else {
						recip = Convert::durationToRecip(duration);
					}

					m_humdrum_text << recip << "ryy\t" << makeReverseLine(infile[i]);
				}
			}
		} else if (infile[i].isCommentLocal()) {
				m_humdrum_text << "!" << "\t" << makeReverseLine(infile[i]);
		} else if (infile[i].isInterpretation()) {
				if (striaIndex == i) {
					m_humdrum_text << "*stria0" << "\t" << makeReverseLine(infile[i]);
					striaIndex = -1;
				} else if (clefIndex == i) {
					m_humdrum_text << "*clefXyy" << "\t" << makeReverseLine(infile[i]);
					clefIndex = -1;
				} else {
					HTp token = infile[i].token(0);
					if (token->compare(0, 2, "*>") == 0) {
						m_humdrum_text << token << "\t" << makeReverseLine(infile[i]);
					} else {
						m_humdrum_text << "*" << "\t" << makeReverseLine(infile[i]);
					}
				}
		} else {
			m_humdrum_text << "!!UNKNONWN LINE TYPE FOR LINE " << i+1 << ":\t" << infile[i];
		}
		m_humdrum_text << endl;
	}
}



//////////////////////////////
//
// Tool_kernify::makeNullLine --
//

string Tool_kernify::makeNullLine(HumdrumLine& line) {
	string output;
	for (int i=0; i<line.getFieldCount(); i++) {
		output += "*";
		if (i < line.getFieldCount() - 1) {
			output += "\t";
		}
	}
	return output;
}



//////////////////////////////
//
// Tool_kernify::makeReverseLine --
//

string Tool_kernify::makeReverseLine(HumdrumLine& line) {
	string output;
	for (int i=line.getFieldCount() - 1; i>= 0; i--) {
		output += *line.token(i);
		if (i > 0) {
			output += "\t";
		}
	}
	return output;
}





/////////////////////////////////
//
// Tool_kernview::Tool_kernview -- Set the recognized options for the tool.
//

Tool_kernview::Tool_kernview(void) {
	define("v|view|s|show=s",   "view the list of spines");
	define("g=s",               "regular expression of kern spines to view");
	define("G=s",               "regular expression of kern spines to hide");
	define("h|hide|r|remove=s", "hide the list of spines");
}



/////////////////////////////////
//
// Tool_kernview::run -- Do the main work of the tool.
//

bool Tool_kernview::run(HumdrumFileSet& infiles) {
	bool status = true;
	for (int i=0; i<infiles.getCount(); i++) {
		status &= run(infiles[i]);
	}
	return status;
}


bool Tool_kernview::run(const string& indata, ostream& out) {
	HumdrumFile infile(indata);
	bool status = run(infile);
	if (hasAnyText()) {
		getAllText(out);
	} else {
		out << infile;
	}
	return status;
}


bool Tool_kernview::run(HumdrumFile& infile, ostream& out) {
	bool status = run(infile);
	if (hasAnyText()) {
		getAllText(out);
	} else {
		out << infile;
	}
	return status;
}


bool Tool_kernview::run(HumdrumFile& infile) {
	initialize(infile);
	processFile(infile);
	return true;
}



//////////////////////////////
//
// Tool_kernview::initialize --  Initializations that only have to be done once
//    for all HumdrumFile segments.
//

void Tool_kernview::initialize(HumdrumFile& infile) {
	m_view_string = getString("view");
	m_hide_string = getString("hide");
	if (getBoolean("g")) {
		m_view_string = getKernString(infile, getString("g"));
	}
	if (getBoolean("G")) {
		m_hide_string = getKernString(infile, getString("G"));
	}
}



//////////////////////////////
//
// Tool_kernview::getKernString -- Return a list of the **kern spines that match to the given
//    comman-separated list of patterns.
//

string Tool_kernview::getKernString(HumdrumFile& infile, const string& list) {
	HumRegex hre;
	vector<string> pieces;
	hre.split(pieces, list, "\\s*,\\s*");
	string output;
	vector<HTp> starts = infile.getKernSpineStartList();
	vector<bool> targets(starts.size(), false);
	for (int i=0; i<(int)pieces.size(); i++) {
		if (pieces.empty()) {
			continue;
		}
		for (int j=0; j<(int)starts.size(); j++) {
			if (targets[j]) {
				continue;
			}
			HTp current = starts[j];
			while (current) {
				if (current->isData()) {
					break;
				}
				if (hre.search(current, pieces[i])) {
					targets[j] = true;
					break;
				}
				current = current->getNextToken();
			}
		}
	}

	for (int i=0; i<(int)targets.size(); i++) {
		if (targets[i]) {
			if (output.empty()) {
				output += to_string(i+1);
			} else {
				output += "," + to_string(i+1);
			}
		}
	}

	return output;
}



//////////////////////////////
//
// Tool_kernview::processFile --
//

void Tool_kernview::processFile(HumdrumFile& infile) {
	if (m_view_string.empty() && m_hide_string.empty()) {
		return;
	}

	int count = 0;
	vector<HTp> spines;
	infile.getSpineStartList(spines);
	vector<HTp> kernish;
	for (int i=0; i<(int)spines.size(); i++) {
		string exinterp = spines[i]->getDataType();
		if (exinterp.find("kern") != string::npos) {
			count++;
			kernish.push_back(spines[i]);
		}
	}

	if (kernish.empty()) {
		return;
	}

	vector<int> viewlist;
	vector<int> hidelist;
	if (!m_view_string.empty()) {
		viewlist = Convert::extractIntegerList(m_view_string, count);

		// First hide every kernish spine:
		for (int i=0; i<(int)kernish.size(); i++) {
			kernish[i]->setText("**kernyy");
		}
		// Then show given kernish spines:
		for (int i=0; i<(int)viewlist.size(); i++) {
			int value = viewlist[i];
			value--;
			if (value < 0) {
				continue;
			}
			if (value >= (int)kernish.size()) {
				// invalid: too large of a spine number
				continue;
			}
			kernish[value]->setText("**kern");
		}

	} else if (!m_hide_string.empty()) {
		hidelist = Convert::extractIntegerList(m_hide_string, count);

		// First show every kernish spine:
		for (int i=0; i<(int)kernish.size(); i++) {
			kernish[i]->setText("**kern");
		}
		// Then hide given kernish spines:
		for (int i=0; i<(int)hidelist.size(); i++) {
			int value = hidelist[i];
			value--;
			if (value < 0) {
				continue;
			}
			if (value >= (int)kernish.size()) {
				// invalid: too large of a spine number
				continue;
			}
			kernish[value]->setText("**kernyy");
		}
	}

	int line = kernish[0]->getLineIndex();
	infile[line].createLineFromTokens();

}





// #define QUARTER_CONVERT * 4
#define QUARTER_CONVERT

#define ELEMENT_DEBUG_STATEMENT(X)
//#define ELEMENT_DEBUG_STATEMENT(X)  cerr << #X << endl;

#define NODE_VERIFY(ELEMENT, RETURNVALUE)        \
	if (!ELEMENT) {                               \
		return RETURNVALUE;                        \
	}                                             \
	if (strcmp(ELEMENT.name(), #ELEMENT) != 0) {  \
		return RETURNVALUE;                        \
	}                                             \
	ELEMENT_DEBUG_STATEMENT(ELEMENT)

#define MAKE_CHILD_LIST(VARNAME, ELEMENT)        \
	vector<xml_node> VARNAME;                     \
	getChildrenVector(VARNAME, ELEMENT);


#define DKHTP "Don't know how to process "

#define CURRLOC " in measure " << m_currentMeasure


//////////////////////////////
//
// Tool_mei2hum::Tool_mei2hum --
//

Tool_mei2hum::Tool_mei2hum(void) {
	define("app|app-label=s", "app label to follow");
	define("r|recip=b",       "output **recip spine");
	define("s|stems=b",       "include stems in output");
	define("x|xmlids=b",      "include xmlids in output");
	define("P|no-place=b",    "do not convert placement attribute");

	m_maxverse.resize(m_maxstaff);
	fill(m_maxverse.begin(), m_maxverse.end(), 0);

	m_measureDuration.resize(m_maxstaff);
	fill(m_measureDuration.begin(), m_measureDuration.end(), 0);

	m_currentMeterUnit.resize(m_maxstaff);
	fill(m_currentMeterUnit.begin(), m_currentMeterUnit.end(), 4);

	m_hasDynamics.resize(m_maxstaff);
	fill(m_hasDynamics.begin(), m_hasDynamics.end(), false);

	m_hasXmlids.resize(m_maxstaff);
	fill(m_hasXmlids.begin(), m_hasXmlids.end(), false);

	m_hasHarm.resize(m_maxstaff);
	fill(m_hasHarm.begin(), m_hasHarm.end(), false);
}



//////////////////////////////
//
// Tool_mei2hum::convert -- Convert an MEI file into
//     Humdrum content.
//

bool Tool_mei2hum::convertFile(ostream& out, const char* filename) {
	xml_document doc;
	auto result = doc.load_file(filename);
	if (!result) {
		cerr << "\nXML file [" << filename << "] has syntax errors\n";
		cerr << "Error description:\t" << result.description() << "\n";
		cerr << "Error offset:\t" << result.offset << "\n\n";
		exit(1);
	}

	return convert(out, doc);
}


bool Tool_mei2hum::convert(ostream& out, istream& input) {
	string s(istreambuf_iterator<char>(input), {});
	return convert(out, s.c_str());
}


bool Tool_mei2hum::convert(ostream& out, const char* input) {
	xml_document doc;
	auto result = doc.load_string(input);
	if (!result) {
		cout << "\nXML content has syntax errors\n";
		cout << "Error description:\t" << result.description() << "\n";
		cout << "Error offset:\t" << result.offset << "\n\n";
		exit(1);
	}

	return convert(out, doc);
}



bool Tool_mei2hum::convert(ostream& out, xml_document& doc) {
	initialize();

	bool status = true; // for keeping track of problems in conversion process.

	buildIdLinkMap(doc);

	auto score = doc.select_node("/mei/music/body/mdiv/score").node();

	if (!score) {
		cerr << "Cannot find score, so cannot convert MEI file to Humdrum";
		cerr << endl;
		cerr << "Perhaps there is a problem in the XML structure of the file.";
		cerr << endl;
		return false;
	}

	m_staffcount = extractStaffCountByFirstMeasure(score);
	if (m_staffcount == 0) {
		// probably mensural music
		m_staffcount = extractStaffCountByScoreDef(score);
		if (m_staffcount == 0) {
			cerr << "error: no music detected in <score>" << endl;
		}
	}

	if (m_recipQ) {
		m_outdata.enableRecipSpine();
	}

	HumNum systemstamp = 0;  // timestamp for music.
	systemstamp = parseScore(score, systemstamp);

	m_outdata.removeRedundantClefChanges();

	processHairpins();

	// set the duration of the last slice

	HumdrumFile outfile;

	// Report verse counts for each staff to HumGrid:
	for (int i=0; i<(int)m_maxverse.size(); i++) {
		if (m_maxverse[i] == 0) {
			continue;
		}
		m_outdata.setVerseCount(i, 0, m_maxverse[i]);
	}

	// Report dynamic presence for each staff to HumGrid:
	for (int i=0; i<(int)m_hasDynamics.size(); i++) {
		if (m_hasDynamics[i] == false) {
			continue;
		}
		m_outdata.setDynamicsPresent(i);
	}

	// Report <harm> presence for each staff to HumGrid:
	for (int i=0; i<(int)m_hasHarm.size(); i++) {
		if (m_hasHarm[i] == false) {
			continue;
		}
		m_outdata.setHarmonyPresent(i);
	}

	// Report xmlid presence for each staff to HumGrid:
	for (int i=0; i<(int)m_hasXmlids.size(); i++) {
		if (m_hasXmlids[i] == false) {
			continue;
		}
		m_outdata.setXmlidsPresent(i);
	}

	auto measure = doc.select_node("/mei/music/body/mdiv/score/section/measure").node();
	auto number = measure.attribute("n");
	int measurenumber = 0;

	if (number) {
		measurenumber = number.as_int();
	} else {
		measurenumber = 0;
	}

	string interp = "**kern";
	if (m_mensuralQ) {
		interp = "**mens";
	}
	if (measurenumber > 1) {
		m_outdata.transferTokens(outfile, measurenumber, interp);
	} else {
		m_outdata.transferTokens(outfile, 0, interp);
	}

	addHeaderRecords(outfile, doc);
	addExtMetaRecords(outfile, doc);
	addFooterRecords(outfile, doc);

	for (int i=0; i<outfile.getLineCount(); i++) {
		outfile[i].createLineFromTokens();
	}
	out << outfile;

	return status;
}



//////////////////////////////
//
// Tool_mei2hum::processHairpins --
//

void Tool_mei2hum::processHairpins(void) {
	for (int i=0; i<(int)m_hairpins.size(); i++) {
		processHairpin(m_hairpins[i]);
	}
}



//////////////////////////////
//
// Tool_mei2hum::processHairpin -- Currently does not create lines to refine position of hairpin.
//
//    <hairpin tstamp="1" form="dim" place="below" staff="8" opening="0" endto="4" tstamp2="0m+4.667"/>
//

void Tool_mei2hum::processHairpin(hairpin_info& info) {
	xml_node hairpin = info.hairpin;
	GridMeasure *gm = info.gm;
	int mindex = info.mindex;

	string tstamp  = hairpin.attribute("tstamp").value();
	string tstamp2 = hairpin.attribute("tstamp2").value();
	string form    = hairpin.attribute("form").value();
	string staff   = hairpin.attribute("staff").value();
	if (staff == "") {
		cerr << "Error: hairpin requires a staff number" << endl;
		return;
	}

	auto myit = m_outdata.begin();
	while (myit != m_outdata.end()) {
		if (*myit == gm) {
			break;
		}
		myit++;
	}

	int staffnum = stoi(staff);
	string hairopen = "<";
	string hairclose = "[";
	if (form == "dim") {
		hairopen = ">";
		hairclose = "]";
	}
	double starttime = stod(tstamp) - 1.0;
	double measure = 0.0;
	auto loc = tstamp2.find("m+");
	if (loc != string::npos) {
		string mnum = tstamp2.substr(0, loc);
		measure = stod(mnum);
		tstamp2 = tstamp2.substr(loc+2, string::npos);
	}
	double endtime = stod(tstamp2) - 1;

	HumNum measurestart = gm->getTimestamp();
	HumNum timestamp;   // timestamp of event in measure
	HumNum mtimestamp;  // starttime of the measure
	double threshold = 0.001;
	auto it = gm->begin();
	GridSlice *lastgs = NULL;
	// bool found = false;

	while (it != gm->end()) {
		if (!(*it)->isDataSlice()) {
			it++;
			continue;
		}
		timestamp = (*it)->getTimestamp();
		mtimestamp = (timestamp - measurestart) * 4;
      mtimestamp /= m_currentMeterUnit[mindex];
		double diff = starttime - mtimestamp.getFloat();
		if (diff < threshold) {
			// found = true;
			lastgs = *it;
			break;
		} else if (diff < 0.0) {
			// found = true;
			lastgs = *it;
			break;
		}
		lastgs = *it;
		it++;
	}

	if (lastgs) {
		GridPart* part = lastgs->at(staffnum-1);
		part->setDynamics(hairopen);
		m_outdata.setDynamicsPresent(staffnum-1);
	}

	myit += (int)measure;
	mindex += (int)measure;
	gm = *myit;
	it = gm->begin();
	lastgs = NULL;
	// found = false;
	while (it != gm->end()) {
		if (!(*it)->isDataSlice()) {
			it++;
			continue;
		}
		timestamp = (*it)->getTimestamp();
		mtimestamp = (timestamp - measurestart) * 4;
		mtimestamp /=  m_currentMeterUnit[mindex];
		double diff = endtime - mtimestamp.getFloat();
		if (diff < threshold) {
			// found = true;
			lastgs = *it;
			break;
		} else if (diff < 0.0) {
			// found = true;
			lastgs = *it;
			break;
		}
		lastgs = *it;
		it++;
	}
	if (lastgs) {
		GridPart* part = lastgs->at(staffnum-1);
		part->setDynamics(hairclose);
		m_outdata.setDynamicsPresent(staffnum-1);
	}

}



//////////////////////////////
//
// Tool_mei2hum::addExtMetaRecords --
//

void Tool_mei2hum::addExtMetaRecords(HumdrumFile& outfile, xml_document& doc) {
	pugi::xpath_node_set metaframes = doc.select_nodes("/mei/meiHead/extMeta/frames/metaFrame");
	double starttime;
	string starttimevalue;
	string token;
	xml_node node;
	xml_node timenode;

	// place header reference records, assumed to be time sorted
	for (int i=(int)metaframes.size()-1; i>=0; i--) {
		node = metaframes[i].node();
		timenode = node.select_node("./frameInfo/startTime").node();
		starttimevalue = timenode.attribute("float").value();
		if (starttimevalue == "") {
			starttime = 0.0;
		} else {
			starttime = stof(starttimevalue);
		}
		if (starttime > 0.0) {
			continue;
		}
		token = node.attribute("token").value();
		if (token.empty()) {
			continue;
		}
		outfile.insertLine(0, token);
		if (token.find("!!!RDF**kern: < = below") != string::npos) {
			m_belowQ = false;
		}
		if (token.find("!!!RDF**kern: > = above") != string::npos) {
			m_aboveQ = false;
		}
	}

	// place footer reference records, assumed to be time sorted
	for (int i=0; i<(int)metaframes.size(); i++) {
		node = metaframes[i].node();
		timenode = node.select_node("./frameInfo/startTime").node();
		starttimevalue = timenode.attribute("float").value();
		if (starttimevalue == "") {
			starttime = 0.0;
		} else {
			starttime = stof(starttimevalue);
		}
		if (starttime == 0.0) {
			continue;
		}
		token = node.attribute("token").value();
		if (token.empty()) {
			continue;
		}
		outfile.appendLine(token);
		if (token.find("!!!RDF**kern: < = below") != string::npos) {
			m_belowQ = false;
		}
		if (token.find("!!!RDF**kern: > = above") != string::npos) {
			m_aboveQ = false;
		}
	}

}



//////////////////////////////
//
// Tool_mei2hum::addHeaderRecords --
//

void Tool_mei2hum::addHeaderRecords(HumdrumFile& outfile, xml_document& doc) {

	// title is at /mei/meiHead/fileDesc/titleStmt/title
	string title = cleanReferenceRecordText(doc.select_node("/mei/meiHead/fileDesc/titleStmt/title").node().child_value());

	// composer is at /mei/meiHead/fileDesc/titleStmt/respStmt/persName@role="creator"
	string composer = cleanReferenceRecordText(doc.select_node("/mei/meiHead/fileDesc/titleStmt/respStmt/persName[@role='creator']").node().child_value());

	// lyricist is at /mei/meiHead/fileDesc/titleStmt/respStmt/persName@role="lyricist"
	string lyricist = cleanReferenceRecordText(doc.select_node("/mei/meiHead/fileDesc/titleStmt/respStmt/persName[@role='lyricist']").node().child_value());

	if (!m_systemDecoration.empty()) {
		outfile.insertLine(0, "!!!system-decoration: " + m_systemDecoration);
	}

	if (!title.empty()) {
		outfile.insertLine(0, "!!!OTL: " + title);
	}
	if (!lyricist.empty()) {
		outfile.insertLine(0, "!!!LYR: " + lyricist);
	}
	if (!composer.empty()) {
		outfile.insertLine(0, "!!!COM: " + composer);
	}

}



//////////////////////////////
//
// Tool_mei2hum::addFooterRecords --
//

void Tool_mei2hum::addFooterRecords(HumdrumFile& outfile, xml_document& doc) {
	if (m_aboveQ) {
		outfile.appendLine("!!!RDF**kern: > = above");
	}
	if (m_belowQ) {
		outfile.appendLine("!!!RDF**kern: < = below");
	}
	if (m_editorialAccidentalQ) {
		outfile.appendLine("!!!RDF**kern: i = editorial accidental");
	}
}



//////////////////////////////
//
// Tool_mei2hum::extractStaffCountByFirstMeasure -- Count the number of staves
//    in the score by looking at the first measure of the score.  Input is the
//    <score> element.
//

int Tool_mei2hum::extractStaffCountByFirstMeasure(xml_node element) {
	auto measure = element.select_node("//measure").node();
	if (!measure) {
		return 0;
	}

	int count = 0;
	for (xml_node child : measure.children()) {
		string nodename = child.name();
		if (nodename == "staff") {
			count++;
		}
	}
	return count;
}



//////////////////////////////
//
// Tool_mei2hum::extractStaffCountByScoreDef -- Count the number of staves
//    in the score by counting <staffDef> entries in the first <scoreDef>.
//     Input is the <score> element.
//

int Tool_mei2hum::extractStaffCountByScoreDef(xml_node element) {
	xml_node scoredef = element.select_node("//scoreDef").node();
	if (!scoredef) {
		return 0;
	}

	pugi::xpath_node_set staffdefs = element.select_nodes(".//staffDef");
	return (int)staffdefs.size();
}



///////////////////////////////////
//
// Tool_mei2hum::parseScore -- Convert an MEI <score> element into Humdrum data.
//

HumNum Tool_mei2hum::parseScore(xml_node score, HumNum starttime) {
	NODE_VERIFY(score, starttime)
	MAKE_CHILD_LIST(children, score);

	for (xml_node item : children) {
		string nodename = item.name();
		if (nodename == "scoreDef") {
			parseScoreDef(item, starttime);
		} else if (nodename == "section") {
			starttime = parseSection(item, starttime);
		} else {
			cerr << DKHTP << score.name() << "/" << nodename << CURRLOC << endl;
		}
	}

	return starttime;
}



//////////////////////////////
//
// Tool_mei2hum::parseScoreDef -- Process a <scoreDef> element in an MEI file.
//

void Tool_mei2hum::parseScoreDef(xml_node scoreDef, HumNum starttime) {
	NODE_VERIFY(scoreDef, )
	MAKE_CHILD_LIST(children, scoreDef);

	if (m_scoreDef.global.timestamp == starttime QUARTER_CONVERT) {
		m_scoreDef.clear();
	}
	m_scoreDef.global.timestamp = starttime QUARTER_CONVERT;

	fillWithStaffDefAttributes(m_scoreDef.global, scoreDef);

	for (xml_node item : children) {
		string nodename = item.name();
		if (nodename == "staffGrp") {
		    parseStaffGrp(item, starttime);
		} else if (nodename == "staffDef") {
		    parseStaffDef(item, starttime);
		} else if (nodename == "pgHead") {
		    processPgHead(item, starttime);
		} else if (nodename == "pgFoot") {
		   processPgFoot(item, starttime);
		} else if (nodename == "keySig") { // drizo
			processKeySig(m_scoreDef.global, item, starttime); // drizo
		} else {
			cerr << DKHTP << scoreDef.name() << "/" << nodename << CURRLOC << endl;
		}
	}

	if (!children.empty()) {
		string deco = prepareSystemDecoration(scoreDef);
		if (!deco.empty()) {
			m_systemDecoration = prepareSystemDecoration(scoreDef);
		}
	}
}



//////////////////////////////
//
// Tool_mei2hum::prepareSystemDecoration --
//

string Tool_mei2hum::prepareSystemDecoration(xml_node scoreDef) {
	NODE_VERIFY(scoreDef, "")
	MAKE_CHILD_LIST(children, scoreDef);

	if (children.empty()) {
		return "";
	}

	string output;
	for (int i=0; i<(int)children.size(); i++) {
		getRecursiveSDString(output, children[i]);
	}
	string newoutput;
	int counter = 0;
	for (int i=0; i<(int)output.size(); i++) {
		newoutput += output[i];
		if (i < (int)output.size() - 1) {
			if (std::isdigit(output[i]) && (output[i+1] == 's')) {
				newoutput += ',';
				counter++;
			}
		}
	}
	if (counter <= 1) {
		return "";
	} else {
		return newoutput;
	}
}



//////////////////////////////
//
// Tool_mei2hum::getRecursiveSDString --
//    <scoreDef key.sig="0" key.mode="minor" meter.count="4" meter.unit="4" meter.sym="common">
//       <staffGrp n="1" barthru="false" symbol="bracket">
//          <staffDef n="1" lines="5" clef.line="2" clef.shape="G"/>
//          <staffDef n="2" lines="5" clef.line="4" clef.shape="F"/>
//       </staffGrp>
//    </scoreDef>
//

void Tool_mei2hum::getRecursiveSDString(string& output, xml_node current) {
	string name = current.name();

	if (name == "staffDef") {
		xml_attribute natt = current.attribute("n");
		if (!natt) {
			cerr << "Error: unknown staff number for staffDef" << endl;
			return;
		}
		int n = natt.as_int();
		if (n < 1) {
			cerr << "Staff number " << n << " must be positive" << endl;
			return;
		}
		output += "s" + to_string(n);
		return;
	} else if (name == "staffGrp") {
		vector<xml_node> children;
		getChildrenVector(children, current);
		if (children.empty()) {
			// strange: no children in a staffGrp...
			return;
		}

		bool barthru = true;
		xml_attribute barthruatt = current.attribute("barthru");
		if (barthruatt) {
			string value = barthruatt.value();
			if (value == "false")  {
				barthru = false;
			}
		}
		string prestring = "";
		string poststring = "";
		xml_attribute symbolattr = current.attribute("symbol");
		if (symbolattr) {
			string value = symbolattr.value();
			if (value == "bracket") {
				prestring = "[";
				poststring = "]";
			} else if (value == "brace") {
				prestring = "{";
				poststring = "}";
			}
		}
		if (barthru) {
			prestring += "(";
			poststring.insert(0, ")");
		}
		output += prestring;
		for (int i=0; i<(int)children.size(); i++) {
			getRecursiveSDString(output, children[i]);
		}
		output += poststring;
	} else if (name == "pgHead") {
		return;
	} else if (name == "pgFoot") {
		return;
	} else if (name == "keySig") { // drizo
		return;
	} else {
		cerr << "Unknown element in scoreDef descendant: " << name << endl;
	}
}



//////////////////////////////
//
// Tool_mei2hum::processPgFoot -- Dummy function since scoreDef/pgFoot is currently ignored.
//

void Tool_mei2hum::processPgFoot(xml_node pgFoot, HumNum starttime) {
	NODE_VERIFY(pgFoot, )
	return;
}



//////////////////////////////
//
// Tool_mei2hum::processPgHead -- Dummy function since scoreDef/pgHead is currently ignored.
//

void Tool_mei2hum::processPgHead(xml_node pgHead, HumNum starttime) {
	NODE_VERIFY(pgHead, )
	return;
}



//////////////////////////////
//
// Tool_mei2hum::processKeySig -- Convert MEI key signature to Humdrum.
//

void Tool_mei2hum::processKeySig(mei_staffDef& staffinfo, xml_node keysig, HumNum starttime) {
	MAKE_CHILD_LIST(children, keysig);
	string token = "*k[";
	for (xml_node item : children) {
		string pname = item.attribute("pname").value();
		string accid = item.attribute("accid").value();
		if (pname.empty()) {
			continue;
		}
		token += pname;
		if (accid == "s") {
			token += "#";
		} else if (accid == "f") {
			token += "-";
		} else if (accid.empty() || accid == "n") {
			token += "n";
		} else if (accid == "ss") {
			token += "##";
		} else if (accid == "x") {
			token += "##";
		} else if (accid == "ff") {
			token += "--";
		} else {
			token += "?";
		}
	}
	token += "]";

	staffinfo.keysig = token;
}



//////////////////////////////
//
// Tool_mei2hum::parseStaffGrp -- Process a <staffGrp> element in an MEI file.
//

void Tool_mei2hum::parseStaffGrp(xml_node staffGrp, HumNum starttime) {
	NODE_VERIFY(staffGrp, )
	MAKE_CHILD_LIST(children, staffGrp);

	for (xml_node item : children) {
		string nodename = item.name();
		if (nodename == "staffGrp") {
		    parseStaffGrp(item, starttime);
		} else if (nodename == "staffDef") {
		    parseStaffDef(item, starttime);
		} else {
			cerr << DKHTP << staffGrp.name() << "/" << nodename << CURRLOC << endl;
		}
	}

}



//////////////////////////////
//
// Tool_mei2hum::parseStaffDef -- Process a <staffDef> element in an MEI file.
//
// Also see instrument abbreviations:
//     https://www.loc.gov/standards/valuelist/marcmusperf.html
//
// Stored in mei/meiHead/workDesc/work/perfMedium/perfResList/perfRes@codeval:
//
// <mei>
//     <meiHead>
//         <workDesc>
//             <work>
//                <perfMedium>
//                    <perfResList>
//                        <perfRes codedval="wa">Flute</perfRes>
//                        <perfRes codedval="wb">Oboe 1</perfRes>
//                        <perfRes codedval="wb">Oboe 2</perfRes>
//                        <perfRes codedval="wc">Clarinet in B flat 1</perfRes>
//                        <perfRes codedval="wc">Clarinet in B flat 2</perfRes>
//                        <perfRes codedval="bc">Horn in F 1</perfRes>
//                        <perfRes codedval="bc">Horn F 2</perfRes>
//                        <perfRes codedval="bb" solo="true">Solo Trumpet in C</perfRes>
//                        <perfRes codedval="pa">Timpani</perfRes>
//                        <perfRes codedval="sa">Violin I</perfRes>
//                        <perfRes codedval="sa">Violin II</perfRes>
//                        <perfRes codedval="sb">Viola</perfRes>
//                        <perfRes codedval="sc">Violoncello</perfRes>
//                        <perfRes codedval="sd">Contrabass</perfRes>
//                    </perfResList>
//                </perfMedium>
//

void Tool_mei2hum::parseStaffDef(xml_node staffDef, HumNum starttime) {
	NODE_VERIFY(staffDef, )

	string staffnum = staffDef.attribute("n").value();
	if (staffnum.empty()) {
		// no staffDef@n so cannot process.
		return;
	}

	int num = stoi(staffnum);
	if (num < 1) {
		// to small
		return;
	}
	if (num > 1000) {
		// too large
		return;
	}

	m_scoreDef.minresize(num);
	m_scoreDef.staves.at(num-1).clear();
	m_scoreDef.staves.at(num-1) = m_scoreDef.global;

	fillWithStaffDefAttributes(m_scoreDef.staves.at(num-1), staffDef);

	// see leaky memory note below for why there are separate
	// variables for clef, keysig, etc.
	mei_staffDef& staffdef = m_scoreDef.staves.at(num-1);
	string clef         = staffdef.clef;
	string keysig       = staffdef.keysig;
	string timesig      = staffdef.timesig;
	string midibpm      = staffdef.midibpm;
	string transpose    = staffdef.transpose;
	string label        = staffdef.label;
	string labelabbr    = staffdef.labelabbr;
	int maximodus       = staffdef.maximodus;
	int modus           = staffdef.modus;
	int tempus          = staffdef.tempus;
	int prolatio        = staffdef.prolatio;
	int hasMensuration  = maximodus | modus | tempus | prolatio;

	// Incorporate label into HumGrid:
	if (label.empty()) {
		label = m_scoreDef.global.label;
	}
	if (!label.empty()) {
		// Need to store in next measure (fix later).  If there are no measures then
		// create one.
		if (m_outdata.empty()) {
			/* GridMeasure* gm = */ m_outdata.addMeasureToBack();
		}
		// m_scoreDef.staves[num-1].keysig disappears after this line, so some
		// leaky memory is likey to happen here.
		m_outdata.back()->addLabelToken(label, starttime QUARTER_CONVERT, num-1,
				0, 0, m_staffcount, 1);
	}

	// Incorporate labelabbr into HumGrid:
	if (labelabbr.empty()) {
		labelabbr = m_scoreDef.global.labelabbr;
	}
	if (!labelabbr.empty()) {
		// Need to store in next measure (fix later).  If there are no measures then
		// create one.
		if (m_outdata.empty()) {
			/* GridMeasure* gm = */ m_outdata.addMeasureToBack();
		}
		// m_scoreDef.staves[num-1].keysig disappears after this line, so some
		// leaky memory is likey to happen here.
		m_outdata.back()->addLabelAbbrToken(labelabbr, starttime QUARTER_CONVERT, num-1,
				0, 0, m_staffcount, 1);
	}

	// Incorporate clef into HumGrid:
	if (clef.empty()) {
		clef = m_scoreDef.global.clef;
	}
	if (!clef.empty()) {
		// Need to store in next measure (fix later).  If there are no measures then
		// create one.
		if (m_outdata.empty()) {
			/* GridMeasure* gm = */ m_outdata.addMeasureToBack();
		}
		// m_scoreDef.staves[num-1].keysig disappears after this line, so some
		// leaky memory is likey to happen here.
		m_outdata.back()->addClefToken(clef, starttime QUARTER_CONVERT, num-1,
				0, 0, m_staffcount);
	}

	// Incorporate transposition into HumGrid:
	if (transpose.empty()) {
		transpose = m_scoreDef.global.transpose;
	}
	if (!transpose.empty()) {
		// Need to store in next measure (fix later).  If there are no measures then
		// create one.
		if (m_outdata.empty()) {
			/* GridMeasure* gm = */ m_outdata.addMeasureToBack();
		}
		m_outdata.back()->addTransposeToken(transpose, starttime QUARTER_CONVERT, num-1,
				0, 0, m_staffcount);
	}

	// Incorporate key signature into HumGrid:
	if (keysig.empty()) {
		keysig = m_scoreDef.global.keysig;
	}
	if (!keysig.empty()) {
		// Need to store in next measure (fix later).  If there are no measures then
		// create one.
		if (m_outdata.empty()) {
			/* GridMeasure* gm = */ m_outdata.addMeasureToBack();
		}
		m_outdata.back()->addKeySigToken(keysig, starttime QUARTER_CONVERT, num-1,
				0, 0, m_staffcount);
	}

	// Incorporate time signature into HumGrid:
	if (timesig.empty()) {
		timesig = m_scoreDef.global.timesig;
	}
	if (!timesig.empty()) {
		// Need to store in next measure (fix later).  If there are no measures then
		// create one.
		if (m_outdata.empty()) {
			/* GridMeasure* gm = */ m_outdata.addMeasureToBack();
		}
		m_outdata.back()->addTimeSigToken(timesig, starttime QUARTER_CONVERT, num-1,
				0, 0, m_staffcount);
	}

	// Add metric signature/mensuration sign here.

	if (hasMensuration) {
		if (m_outdata.empty()) {
			m_outdata.addMeasureToBack();
		}
		string metsigtok = "*";
		metsigtok += "met()_";
		metsigtok += to_string(maximodus);
		metsigtok += to_string(modus);
		metsigtok += to_string(tempus);
		metsigtok += to_string(prolatio);

		m_outdata.back()->addMeterSigToken(metsigtok, starttime QUARTER_CONVERT,
		      num-1, 0, 0, m_staffcount);
	}

	// Incorporate tempo into HumGrid:
	if (midibpm.empty()) {
		midibpm = m_scoreDef.global.midibpm;
	}
	if (!midibpm.empty()) {
		// Need to store in next measure (fix later).  If there are no measures then
		// create one.
		if (m_outdata.empty()) {
			/* GridMeasure* gm = */ m_outdata.addMeasureToBack();
		}
		m_outdata.back()->addTempoToken(midibpm, starttime QUARTER_CONVERT, num-1,
				0, 0, m_staffcount);
	}

}



//////////////////////////////
//
// Tool_mei2hum::fillWithStaffDefAttributes --
//

void Tool_mei2hum::fillWithStaffDefAttributes(mei_staffDef& staffinfo, xml_node element) {

	string clefshape;
	string clefline;
	string clefdis;
	string clefdisplace;
	string metercount;
	string meterunit;
	string staffnum;
	string keysig;
	string midibpm;
	string label;
	string labelabbr;
	string notationtype;
	int maximodus = 0;
	int modus     = 0;
	int tempus    = 0;
	int prolatio  = 0;
	int transsemi = 0;
	int transdiat = 0;

	string nodename = element.name();

	int nnum = 0;  // For staffnumber of element is staffDef.

	for (auto atti = element.attributes_begin(); atti != element.attributes_end(); atti++) {
		string attname = atti->name();
		if (attname == "clef.shape") {
			clefshape = atti->value();
		} else if (attname == "clef.line") {
			clefline = atti->value();
		} else if (attname == "clef.dis") {
			clefdis = atti->value();
		} else if (attname == "clef.displace") {
			clefdisplace = atti->value();
		} else if (attname == "meter.count") {
			metercount = atti->value();
		} else if (attname == "meter.unit") {
			meterunit = atti->value();
		} else if (attname == "key.sig") {
			keysig = atti->value();
		} else if (attname == "label") {
			label = atti->value();
		} else if (attname == "label.abbr") {
			labelabbr = atti->value();
		} else if (attname == "midi.bpm") {
			midibpm = atti->value();
		} else if (attname == "trans.semi") {
			transsemi = atti->as_int();
		} else if (attname == "trans.diat") {
			transdiat = atti->as_int();
		} else if (attname == "notationtype") {
			notationtype = atti->value();
		} else if (attname == "prolatio") {
			prolatio = atti->as_int();
		} else if (attname == "tempus") {
			tempus = atti->as_int();
		} else if (attname == "modusminor") {
			modus = atti->as_int();
		} else if (attname == "modusmaior") {
			maximodus = atti->as_int();
		} else if (attname == "n") {
			nnum = atoi(atti->value());
		}
	}
	if (nnum < 1) {
		nnum = 1;
	}

	// Fill in possible child element attributes:

	// staffDef/mensur
	xml_node mensurNode = element.select_node(".//mensur").node();
	if (mensurNode) {
		for (auto atti = mensurNode.attributes_begin(); atti != mensurNode.attributes_end(); atti++) {
			string attname = atti->name();
			if (attname == "prolatio") {
				prolatio = atti->as_int();
			} else if (attname == "tempus") {
				tempus = atti->as_int();
			} else if (attname == "modusminor") {
				modus = atti->as_int();
			} else if (attname == "modusmaior") {
				maximodus = atti->as_int();
			}
		}
	}

	// staffDef/label
	xml_node labelNode = element.select_node(".//label").node();
	if (labelNode) {
		string testlabel = labelNode.child_value();
		if (!testlabel.empty()) {
			label = testlabel;
		}
	}

	// staffDef/labelAbbr
	xml_node labelAbbrNode = element.select_node(".//labelAbbr").node();
	if (labelAbbrNode) {
		string testlabelabbr = labelAbbrNode.child_value();
		if (!testlabelabbr.empty()) {
			labelabbr = testlabelabbr;
		}
	}


// ggg


	if ((transsemi != 0) || (transdiat != 0)) {
		// Fix octave transposition problems:
		if ((transsemi ==  12) && (transdiat ==  0)) { transdiat =   7; }
		if ((transsemi == -12) && (transdiat ==  0)) { transdiat =  -7; }
		if ((transsemi ==   0) && (transdiat ==  7)) { transsemi =  12; }
		if ((transsemi ==   0) && (transdiat == -7)) { transsemi = -12; }
		// transposition needed to get to transposed score:
		staffinfo.transpose = "*ITrd" + to_string(-transdiat) + "c" + to_string(-transsemi);
		// transposition needed to get to C score:
		staffinfo.base40 = -Convert::transToBase40(staffinfo.transpose);
	}

	if ((!clefshape.empty()) && (!clefline.empty())) {
		staffinfo.clef = makeHumdrumClef(clefshape, clefline, clefdis, clefdisplace);
	}
	if ((!metercount.empty()) && (!meterunit.empty())) {
		HumNum meterduration = stoi(metercount) * 4 / stoi(meterunit);
		if (nodename == "scoreDef") {
			for (int i=0; i<(int)m_measureDuration.size(); i++) {
				m_measureDuration.at(i) = meterduration;
				m_currentMeterUnit.at(i) = stoi(meterunit);
			}
		} else if (nodename == "staffDef") {
			if (nnum > 0) {
				m_measureDuration.at(nnum-1) = meterduration;
				m_currentMeterUnit.at(nnum-1) = stoi(meterunit);
			}
		} else {
			cerr << DKHTP << element.name() << "@meter.count/@meter.unit" << CURRLOC << endl;
		}

		staffinfo.timesig = "*M" + metercount + "/" + meterunit;
	}
	if (!keysig.empty()) {
		int count = stoi(keysig);
		int accid = 0;
		if (keysig.find("s") != string::npos) {
			accid = +1;
		} else if (keysig.find("f") != string::npos) {
			accid = -1;
		}


		// Also check for enharmonic transpositions...
		int adjust = 0;
		if (staffinfo.base40 != 0) {
			adjust = Convert::base40IntervalToLineOfFifths(staffinfo.base40);
		}
		count += adjust;

		// Adjust for transposition to C score here.

		if (accid > 0) {
			switch (count) {
				case 1: staffinfo.keysig = "*k[f#]";             break;
				case 2: staffinfo.keysig = "*k[f#c#]";           break;
				case 3: staffinfo.keysig = "*k[f#c#g#]";         break;
				case 4: staffinfo.keysig = "*k[f#c#g#d#]";       break;
				case 5: staffinfo.keysig = "*k[f#c#g#d#a#]";     break;
				case 6: staffinfo.keysig = "*k[f#c#g#d#a#e#]";   break;
				case 7: staffinfo.keysig = "*k[f#c#g#d#a#e#b#]"; break;
			}
		} else if (accid < 0) {
			switch (count) {
				case 1: staffinfo.keysig = "*k[b-]";             break;
				case 2: staffinfo.keysig = "*k[b-e-]";           break;
				case 3: staffinfo.keysig = "*k[b-e-a-]";         break;
				case 4: staffinfo.keysig = "*k[b-e-a-d-]";       break;
				case 5: staffinfo.keysig = "*k[b-e-a-d-g-]";     break;
				case 6: staffinfo.keysig = "*k[b-e-a-d-g-c-]";   break;
				case 7: staffinfo.keysig = "*k[b-e-a-d-g-c-f-]"; break;
			}
		}
	}
	if (!midibpm.empty()) {
		staffinfo.midibpm = "*MM" + midibpm;
	}
	if (!label.empty()) {
		staffinfo.label = "*I\"" + label;
	}
	if (!labelabbr.empty()) {
		staffinfo.labelabbr = "*I'" + labelabbr;
	}
	if (notationtype.empty()) {
		staffinfo.mensural = false;
		staffinfo.black = false;
	} else {
		if (notationtype == "mensural") {
			staffinfo.mensural = true;
			staffinfo.black = false;
		} else if (notationtype == "mensural.white") {
			staffinfo.mensural = true;
			staffinfo.black = false;
		} else if (notationtype == "mensural.black") {
			staffinfo.mensural = true;
			staffinfo.black = true;
		}
		if (staffinfo.mensural) {
			m_mensuralQ = true; // used to print **mens later
			if (maximodus > 0) { staffinfo.maximodus = maximodus; }
			if (modus > 0)     { staffinfo.modus = modus; }
			if (tempus > 0)    { staffinfo.tempus = tempus; }
			if (prolatio > 0)  { staffinfo.prolatio = prolatio; }
		}
		// deal with mensuration sign, or calculate from maximodus/modus/tempus/prolatio
	}
}



//////////////////////////////
//
// Tool_mei2hum::parseSection -- Process a <section> element in an MEI file.
//

HumNum Tool_mei2hum::parseSection(xml_node section, HumNum starttime) {
	NODE_VERIFY(section, starttime);
	MAKE_CHILD_LIST(children, section);

	for (int i=0; i<(int)children.size(); i++) {
		string nodename = children[i].name();
		if (nodename == "section") {
			starttime = parseSection(children[i], starttime);
		} else if (nodename == "measure") {
			starttime = parseMeasure(children[i], starttime);
		} else if (nodename == "app") {
			starttime = parseApp(children[i], starttime);
		} else if (nodename == "sb") {   // system break
			parseSb(children[i], starttime);
		} else if (nodename == "pb") {   // page break;
			parseSb(children[i], starttime);
		} else if (nodename == "scoreDef") {   // usually page size information
			parseScoreDef(children[i], starttime);
		} else if (nodename == "staffDef") {   // will this have any useful info?
		   // ignore for now
		} else if (nodename == "staff") {
			// section/staff is possible in mensural music.
			parseStaff_mensural(children[i], starttime);
		} else {
			cerr << DKHTP << section.name() << "/" << nodename << CURRLOC << endl;
		}
	}

	return starttime;
}



//////////////////////////////
//
// Tool_mei2hum::parseSectionScoreDef --  This is used for page layout information.  Have to look
//   later at if ever has musical content.
//
// Example:
// 	<scoreDef page.height="1973" page.width="1524" page.leftmar="179"
//                 page.rightmar="90" page.topmar="118" page.botmar="112"/>
//

void Tool_mei2hum::parseSectionScoreDef(xml_node scoreDef, HumNum starttime) {
	NODE_VERIFY(scoreDef, );
	MAKE_CHILD_LIST(children, scoreDef);

	for (int i=0; i<(int)children.size(); i++) {
		string nodename = children[i].name();
		cerr << DKHTP << scoreDef.name() << "/" << nodename << CURRLOC << endl;
	}
}



//////////////////////////////
//
// Tool_mei2hum::parsePb -- Page break in the music.  Currently treating the same
//   as <sb>.
//

void Tool_mei2hum::parsePb(xml_node pb, HumNum starttime) {
	NODE_VERIFY(pb, );
	MAKE_CHILD_LIST(children, pb);

	// There should be no children of pb (at least any that are currently known)
	for (int i=0; i<(int)children.size(); i++) {
		string nodename = children[i].name();
		cerr << DKHTP << pb.name() << "/" << nodename << CURRLOC << endl;
	}

	m_outdata.back()->appendGlobalLayout("!!LO:LB", starttime QUARTER_CONVERT);
}



//////////////////////////////
//
// Tool_mei2hum::parseSb -- System (line) break in the music.
//

void Tool_mei2hum::parseSb(xml_node sb, HumNum starttime) {
	NODE_VERIFY(sb, );
	MAKE_CHILD_LIST(children, sb);

	// There should be no children of sb (at least any that are currently known)
	for (int i=0; i<(int)children.size(); i++) {
		string nodename = children[i].name();
		cerr << DKHTP << sb.name() << "/" << nodename << CURRLOC << endl;
	}

	m_outdata.back()->appendGlobalLayout("!!LO:LB", starttime QUARTER_CONVERT);
}



//////////////////////////////
//
// Tool_mei2hum::parseApp --
//

HumNum Tool_mei2hum::parseApp(xml_node app, HumNum starttime) {
	NODE_VERIFY(app, starttime);
	MAKE_CHILD_LIST(children, app);

	if (children.empty()) {
		return starttime;
	}

	xml_node target = children[0];
	if (!m_appLabel.empty()) {
		string testlabel;
		for (int i=0; i<(int)children.size(); i++) {
			testlabel = children[i].attribute("label").value();
			if (testlabel == m_appLabel) {
				target = children[i];
				break;
			}
		}
	}

	// Only following the first element in app list for now.
	string nodename = target.name();
	if (nodename == "lem") {
		starttime = parseLem(target, starttime);
	} else if (nodename == "rdg") {
		starttime = parseRdg(target, starttime);
	} else {
		cerr << DKHTP << app.name() << "/" << nodename << CURRLOC << endl;
	}

	return starttime;
}



//////////////////////////////
//
// Tool_mei2hum::parseLem -- Process a <lem> element in an MEI file.
//

HumNum Tool_mei2hum::parseLem(xml_node lem, HumNum starttime) {
	NODE_VERIFY(lem, starttime);
	MAKE_CHILD_LIST(children, lem);

	for (int i=0; i<(int)children.size(); i++) {
		string nodename = children[i].name();
		if (nodename == "section") {
			starttime = parseSection(children[i], starttime);
		} else if (nodename == "measure") {
			starttime = parseMeasure(children[i], starttime);
		} else {
			cerr << DKHTP << lem.name() << "/" << nodename << CURRLOC << endl;
		}
	}

	return starttime;
}



//////////////////////////////
//
// Tool_mei2hum::parseRdg -- Process a <rdg> element in an MEI file.
//

HumNum Tool_mei2hum::parseRdg(xml_node rdg, HumNum starttime) {
	NODE_VERIFY(rdg, starttime);
	MAKE_CHILD_LIST(children, rdg);

	for (int i=0; i<(int)children.size(); i++) {
		string nodename = children[i].name();
		if (nodename == "section") {
			starttime = parseSection(children[i], starttime);
		} else if (nodename == "measure") {
			starttime = parseMeasure(children[i], starttime);
		} else {
			cerr << DKHTP << rdg.name() << "/" << nodename << CURRLOC << endl;
		}
	}

	return starttime;
}



//////////////////////////////
//
// Tool_mei2hum::parseMeasure -- Process an MEI measure element.
//

HumNum Tool_mei2hum::parseMeasure(xml_node measure, HumNum starttime) {
	NODE_VERIFY(measure, starttime);
	MAKE_CHILD_LIST(children, measure);

	string n = measure.attribute("n").value();
	int nnum = 0;
	if (n.empty()) {
		// cerr << "Warning: no measure number on measure element" << endl;
	} else {
		nnum = stoi(n);
	}
	if (nnum < 0) {
		cerr << "Error: invalid measure number: " << nnum << endl;
	}
	m_currentMeasure = nnum;

	GridMeasure* gm = m_outdata.addMeasureToBack();
	gm->setTimestamp(starttime QUARTER_CONVERT);

	vector<HumNum> durations;

	for (int i=0; i<(int)children.size(); i++) {
		string nodename = children[i].name();
		if (nodename == "staff") {
			durations.push_back(parseStaff(children[i], starttime) - starttime);
		} else if (nodename == "fermata") {
			// handled in process processNodeStartLinks()
		} else if (nodename == "slur") {
			// handled in process processNode(Start|Stop)Links()
		} else if (nodename == "tie") {
			// handled in process processNode(Start|Stop)Links()
		} else if (nodename == "arpeg") {
			// handled in process processNode(Start|Stop)Links()
		} else if (nodename == "tupletSpan") {
			// handled in process processNode(Start|Stop)Links()
		} else if (nodename == "trill") {
			// handled in process processNode(Start|Stop)Links()
		} else if (nodename == "dynam") {
			parseDynam(children[i], starttime);
		} else if (nodename == "hairpin") {
			parseHairpin(children[i], starttime);
		} else if (nodename == "harm") {
			parseHarm(children[i], starttime);
		} else if (nodename == "tempo") {
			parseTempo(children[i], starttime);
		} else if (nodename == "dir") {
			parseDir(children[i], starttime);
		} else if (nodename == "reh") {
			parseReh(children[i], starttime);
		} else {
			cerr << DKHTP << measure.name() << "/" << nodename << CURRLOC << endl;
		}
	}

	// Check that the duration of each layer is the same here.

	if (durations.empty()) {
		return starttime;
	}

	bool allequal = true;
	for (int i=1; i<(int)durations.size(); i++) {
		if (durations[i] != durations[0]) {
			allequal = false;
			break;
		}
	}

	HumNum measuredur = durations[0];
	HumNum targetDur = m_measureDuration.at(0) / 4;
	HumNum maxdur = 0;
	bool overfilledQ = false;
	if (!allequal) {
		measuredur = targetDur;
		for (int i=0; i<(int)durations.size(); i++) {
			if (durations[i] > maxdur) {
				maxdur = durations[i];
			}
			if (durations[i] == targetDur) {
				continue;
			}
			if (durations[i] < targetDur) {

				HumNum difference = targetDur - durations[i];
				string spacer = Convert::durationToRecip(difference QUARTER_CONVERT);
				spacer += "ryy";

				std::ostringstream message;
				message << "Error: measure " << m_currentMeasure;
				message << ", staff " << i+1 << " is underfilled: ";
				message << "adding token " << spacer;
				message << " at end of measure to complete its duration.";
				cerr << message.str() << endl;

				// Add an invisible rest to fill in the problem spot.
				// staff with multiple layers will have to be addressed as well...
				m_outdata.back()->addDataToken(spacer, starttime QUARTER_CONVERT +
						durations[i] QUARTER_CONVERT, i, 0, 0, m_staffcount);

				// put an error message at the start of the measure warning about being underfilled
				m_outdata.back()->addGlobalComment("!!" + message.str(), starttime QUARTER_CONVERT);

			} else if (durations[i] > targetDur) {
				std::ostringstream message;
				message << "Error: measure " << m_currentMeasure;
				message << " staff " << i+1 << " is overfilled: ";
				message << (durations[i] QUARTER_CONVERT).getFloat();
				message << " quarter notes instead of ";
				message << targetDur.getFloat() * 4 << ".";
				cerr << message.str() << endl;
				m_outdata.back()->addGlobalComment("!!" + message.str(), starttime QUARTER_CONVERT);

				overfilledQ = true;
			}
		}
	}

	if (overfilledQ) {
		// pad measures that are not under filled so that all
		// parts have the same maximum overfilling.
		for (int i=0; i<(int)durations.size(); i++) {
			if (durations[i] == maxdur) {
				continue;
			}
			HumNum difference = maxdur - durations[i];
			string spacer = Convert::durationToRecip(difference QUARTER_CONVERT);
			spacer += "ryy";

			std::ostringstream message;
			message << "Warning: measure " << m_currentMeasure;
			message << ", staff " << i+1 << " padded. ";
			message << "adding token " << spacer;
			message << " at end ot measure to extend its duration.";
			// cerr << message.str() << endl;

			// Add an invisible rest to fill in the problem spot.
			// staff with multiple layers will have to be addressed as well...
			m_outdata.back()->addDataToken(spacer, starttime QUARTER_CONVERT +
					durations[i] QUARTER_CONVERT, i, 0, 0, m_staffcount);

			// put an error message at the start of the measure warning about being underfilled
			m_outdata.back()->addGlobalComment("!!" + message.str(), starttime QUARTER_CONVERT);

		}
	}

	gm->setTimestamp(starttime QUARTER_CONVERT);
	gm->setDuration(measuredur QUARTER_CONVERT);
	gm->setTimeSigDur(m_measureDuration[0]);

	string rightstyle = measure.attribute("right").value();
	if (rightstyle == "") {
		// do nothing
	} else if (rightstyle == "end") {
		gm->setFinalBarlineStyle();
	} else if (rightstyle == "rptend") {
		gm->setRepeatBackwardStyle();
	} else if (rightstyle == "invis") {
		gm->setInvisibleBarline();
	}

	if (overfilledQ) {
		return starttime + maxdur;
	} else {
		return starttime + measuredur;
	}
}



//////////////////////////////
//
// Tool_mei2hum::parseHairpin -- Process crescendo or dimuendo.
//
//    <hairpin tstamp="1" form="dim" place="below" staff="8" opening="0" endto="4" tstamp2="0m+4.667"/>
//

void Tool_mei2hum::parseHairpin(xml_node hairpin, HumNum starttime) {
	NODE_VERIFY(hairpin, );
	MAKE_CHILD_LIST(children, hairpin);

	for (int i=0; i<(int)children.size(); i++) {
		string nodename = children[i].name();
		cerr << DKHTP << hairpin.name() << "/" << nodename << CURRLOC << endl;
	}

	// Store the hairpin for later parsing when more of the
	// score is known:
	auto it = m_outdata.end();
	it--;
	if (it != m_outdata.end()) {
		m_hairpins.resize(m_hairpins.size() + 1);
		m_hairpins.back().hairpin = hairpin;
		m_hairpins.back().gm = *it;
		m_hairpins.back().mindex = ((int)m_currentMeterUnit.size()) - 1;
	}
}



//////////////////////////////
//
// Tool_mei2hum::parseReh -- Rehearsal markings (ignored for now)
//

void Tool_mei2hum::parseReh(xml_node reh, HumNum starttime) {
	NODE_VERIFY(reh, );
	MAKE_CHILD_LIST(children, reh);

	for (int i=0; i<(int)children.size(); i++) {
		string nodename = children[i].name();
		if (nodename == "rend") {
			// deal with reh/rend here.
		} else {
			cerr << DKHTP << reh.name() << "/" << nodename << CURRLOC << endl;
		}
	}

}



//////////////////////////////
//
// Tool_mei2hum::parseStaff -- Process an MEI staff element.
//

HumNum Tool_mei2hum::parseStaff(xml_node staff, HumNum starttime) {
	NODE_VERIFY(staff, starttime);
	MAKE_CHILD_LIST(children, staff);

	string n = staff.attribute("n").value();
	int nnum = 0;
	if (n.empty()) {
		cerr << "Warning: no staff number on staff element in measure " << m_currentMeasure << endl;
	} else {
		nnum = stoi(n);
	}
	if (nnum < 1) {
		cerr << "Error: invalid staff number: " << nnum << endl;
		m_currentStaff++;
	} else {
		m_currentStaff = nnum;
	}

	if (m_maxStaffInFile < m_currentStaff) {
		m_maxStaffInFile = m_currentStaff;
	}

	vector<bool> layerPresent;
	vector<HumNum> durations;

	for (int i=0; i<(int)children.size(); i++) {
		string nodename = children[i].name();
		if (nodename == "layer") {
			durations.push_back(parseLayer(children[i], starttime, layerPresent) - starttime);
		} else {
			cerr << DKHTP << staff.name() << "/" << nodename << CURRLOC << endl;
		}
	}

// ggg
// if ((m_currentMeasure == 12) && (m_currentStaff == 6)) {
// cerr << "============= LAYER COUNT " << layerPresent.size() << endl;
// }

	bool complete = true;
	for (int i=0; i<(int)layerPresent.size(); i++) {
// ggg
// if ((m_currentMeasure == 12) && (m_currentStaff == 6)) {
// cerr << "============= LAYER " << i+1 << " : " << layerPresent[i] << endl;
// }
		complete &= layerPresent[i];
	}
	if (!complete) {
		// need to add invisible rests in un-specified layers.
		cerr << "INCOMPLETE LAYERS IN STAFF" << endl;
	}

	// Check that the duration of each layer is the same here.

	if (durations.empty()) {
		return starttime;
	}

	// bool allequal = true;
	for (int i=1; i<(int)durations.size(); i++) {
		if (durations[i] != durations[0]) {
			// allequal = false;
			break;
		}
	}

	HumNum staffdur = durations[0];
	m_currentStaff = 0;

	return starttime + staffdur;
}




//////////////////////////////
//
// Tool_mei2hum::parseStaff_mensural -- Process an MEI staff element for mensural
//   music.
//

HumNum Tool_mei2hum::parseStaff_mensural(xml_node staff, HumNum starttime) {
	NODE_VERIFY(staff, starttime);
	MAKE_CHILD_LIST(children, staff);

	string n = staff.attribute("n").value();
	int nnum = 0;
	if (n.empty()) {
		cerr << "Warning: no staff number on staff element in measure " << m_currentMeasure << endl;
	} else {
		nnum = stoi(n);
	}
	if (nnum < 1) {
		cerr << "Error: invalid staff number: " << nnum << endl;
		m_currentStaff++;
	} else {
		m_currentStaff = nnum;
	}

	if (m_maxStaffInFile < m_currentStaff) {
		m_maxStaffInFile = m_currentStaff;
	}

	vector<bool> layerPresent;
	vector<HumNum> durations;

	for (int i=0; i<(int)children.size(); i++) {
		string nodename = children[i].name();
		if (nodename == "layer") {
			durations.push_back(parseLayer_mensural(children[i], starttime, layerPresent) - starttime);
		} else {
			cerr << DKHTP << staff.name() << "/" << nodename << CURRLOC << endl;
		}
	}

	bool complete = true;
	for (int i=0; i<(int)layerPresent.size(); i++) {
		complete &= layerPresent[i];
	}
	if (!complete) {
		// need to add invisible rests in un-specified layers.
		cerr << "INCOMPLETE LAYERS IN STAFF" << endl;
	}

	// Check that the duration of each layer is the same here.

	if (durations.empty()) {
		return starttime;
	}

	// bool allequal = true;
	for (int i=1; i<(int)durations.size(); i++) {
		if (durations[i] != durations[0]) {
			// allequal = false;
			break;
		}
	}

	HumNum staffdur = durations[0];
	m_currentStaff = 0;

	return starttime + staffdur;
}



//////////////////////////////
//
// Tool_mei2hum::parseLigature -- Process an MEI ligature element.
//

HumNum Tool_mei2hum::parseLigature(xml_node ligature, HumNum starttime) {
	NODE_VERIFY(ligature, starttime);
	MAKE_CHILD_LIST(children, ligature);

	string dummy;

	// Same parsing as layer:
	xml_node layer = ligature;
	for (int i=0; i<(int)children.size(); i++) {
		string nodename = children[i].name();
		if (nodename == "note") {
			starttime = parseNote_mensural(children[i], xml_node(NULL), dummy, starttime, 0);
		} else if (nodename == "ligature") {
			starttime = parseLigature(children[i], starttime);
		} else if (nodename == "choice") {
			starttime = parseChoice_mensural(children[i], starttime);
		} else if (nodename == "chord") {
			// starttime = parseChord(children[i], starttime, 0);
			cerr << DKHTP << layer.name() << "/" << nodename << CURRLOC << endl;
		} else if (nodename == "rest") {
			starttime = parseRest_mensural(children[i], starttime);
		} else if (nodename == "space") {
			starttime = parseRest_mensural(children[i], starttime);
		} else if (nodename == "mRest") {
			// starttime = parseMRest(children[i], starttime);
			cerr << DKHTP << layer.name() << "/" << nodename << CURRLOC << endl;
		} else if (nodename == "beam") {
			// starttime = parseBeam(children[i], starttime);
			cerr << DKHTP << layer.name() << "/" << nodename << CURRLOC << endl;
		} else if (nodename == "tuplet") {
			// starttime = parseTuplet(children[i], starttime);
			cerr << DKHTP << layer.name() << "/" << nodename << CURRLOC << endl;
		} else if (nodename == "clef") {
			parseClef(children[i], starttime);
		} else if (nodename == "barLine") {
			parseBarline(children[i], starttime);
		} else if (nodename == "dot") {
			// dot is processed in parseNote_mensural;
		} else {
			cerr << DKHTP << layer.name() << "/" << nodename << CURRLOC << endl;
		}
	}

	return starttime;
}



//////////////////////////////
//
// Tool_mei2hum::parseLayer --
//

HumNum Tool_mei2hum::parseLayer(xml_node layer, HumNum starttime, vector<bool>& layerPresent) {
	NODE_VERIFY(layer, starttime)
	MAKE_CHILD_LIST(children, layer);

	int nnum = 0;
	xml_attribute nattr = layer.attribute("n");
	if (!nattr) {
		// No number on layer, assuming next available number should be used.
		m_currentLayer++;
		nnum = m_currentLayer;
	} else {
		nnum = nattr.as_int();
	}
	if (nnum < 1) {
		cerr << "Error: Ignoring layer with invalid number: " << nnum
		     << " in measure " << m_currentMeasure
		     << ", staff " << m_currentStaff << endl;
		return starttime;
	}
	if (nnum > 8) {
		cerr << "Error: Ignoring layer with ridiculous number: " << nnum
		     << " in measure " << m_currentMeasure
		     << ", staff " << m_currentStaff << endl;
		return starttime;
	}
	m_currentLayer = nnum;

// ggg
// if ((m_currentMeasure == 12) && (m_currentStaff == 6)) {
// cerr << "CURRENT LAYER " << m_currentLayer << endl;
// }

	// grow Layer array if necessary:
	if ((int)layerPresent.size() < m_currentLayer) {
		int oldsize = (int)layerPresent.size();
		layerPresent.resize(m_currentLayer);
		for (int i=oldsize; i<m_currentLayer; i++) {
			layerPresent.at(i) = false;
		}
   }
// ggg
// if ((m_currentMeasure == 12) && (m_currentStaff == 6)) {
// cerr << "LAYER CHECKER SIZE IS " << layerPresent.size() << endl;
// }


	if (layerPresent.at(m_currentLayer - 1)) {
		cerr << "Error: measure " << m_currentMeasure
		     << ", staff " << m_currentStaff
		     << ": layer " << m_currentLayer << " is duplicated on staff: "
		     << m_currentStaff << ". Ignoring duplicate layer." << endl;
		return starttime;
	} else {
		layerPresent.at(m_currentLayer - 1) = true;
	}

	HumNum  starting = starttime;
	string dummy;

	for (int i=0; i<(int)children.size(); i++) {
		string nodename = children[i].name();
		if (nodename == "note") {
			starttime = parseNote(children[i], xml_node(NULL), dummy, starttime, 0);
		} else if (nodename == "chord") {
			starttime = parseChord(children[i], starttime, 0);
		} else if (nodename == "rest") {
			starttime = parseRest(children[i], starttime);
		} else if (nodename == "space") {
			starttime = parseRest(children[i], starttime);
		} else if (nodename == "mRest") {
			starttime = parseMRest(children[i], starttime);
		} else if (nodename == "beam") {
			starttime = parseBeam(children[i], starttime);
		} else if (nodename == "tuplet") {
			starttime = parseTuplet(children[i], starttime);
		} else if (nodename == "clef") {
			parseClef(children[i], starttime);
		} else {
			cerr << DKHTP << layer.name() << "/" << nodename << CURRLOC << endl;
		}
	}

	if (!m_gracenotes.empty()) {
		processGraceNotes(starttime);
	}

	m_currentLayer = 0;
	return starttime;
}



//////////////////////////////
//
// Tool_mei2hum::parseLayer_mensural --
//

HumNum Tool_mei2hum::parseLayer_mensural(xml_node layer, HumNum starttime, vector<bool>& layerPresent) {
	NODE_VERIFY(layer, starttime)
	MAKE_CHILD_LIST(children, layer);

	int nnum = 0;
	xml_attribute nattr = layer.attribute("n");
	if (!nattr) {
		// No number on layer, assuming next available number should be used.
		m_currentLayer++;
		nnum = m_currentLayer;
	} else {
		nnum = nattr.as_int();
	}
	if (nnum < 1) {
		cerr << "Error: Ignoring layer with invalid number: " << nnum
		     << " in measure " << m_currentMeasure
		     << ", staff " << m_currentStaff << endl;
		return starttime;
	}
	if (nnum > 8) {
		cerr << "Error: Ignoring layer with ridiculous number: " << nnum
		     << " in measure " << m_currentMeasure
		     << ", staff " << m_currentStaff << endl;
		return starttime;
	}
	m_currentLayer = nnum;

	// grow Layer array if necessary:
	if ((int)layerPresent.size() < m_currentLayer) {
		int oldsize = (int)layerPresent.size();
		layerPresent.resize(m_currentLayer);
		for (int i=oldsize; i<m_currentLayer; i++) {
			layerPresent.at(i) = false;
		}
   }

	if (layerPresent.at(m_currentLayer - 1)) {
		cerr << "Error: measure " << m_currentMeasure
		     << ", staff " << m_currentStaff
		     << ": layer " << m_currentLayer << " is duplicated on staff: "
		     << m_currentStaff << ". Ignoring duplicate layer." << endl;
		return starttime;
	} else {
		layerPresent.at(m_currentLayer - 1) = true;
	}

	HumNum  starting = starttime;
	string dummy;

	for (int i=0; i<(int)children.size(); i++) {
		string nodename = children[i].name();
		if (nodename == "note") {
			starttime = parseNote_mensural(children[i], xml_node(NULL), dummy, starttime, 0);
		} else if (nodename == "ligature") {
			starttime = parseLigature(children[i], starttime);
		} else if (nodename == "choice") {
			starttime = parseChoice_mensural(children[i], starttime);
		} else if (nodename == "chord") {
			// starttime = parseChord(children[i], starttime, 0);
			cerr << DKHTP << layer.name() << "/" << nodename << CURRLOC << endl;
		} else if (nodename == "rest") {
			starttime = parseRest_mensural(children[i], starttime);
		} else if (nodename == "space") {
			starttime = parseRest_mensural(children[i], starttime);
		} else if (nodename == "mRest") {
			// starttime = parseMRest(children[i], starttime);
			cerr << DKHTP << layer.name() << "/" << nodename << CURRLOC << endl;
		} else if (nodename == "beam") {
			// starttime = parseBeam(children[i], starttime);
			cerr << DKHTP << layer.name() << "/" << nodename << CURRLOC << endl;
		} else if (nodename == "tuplet") {
			// starttime = parseTuplet(children[i], starttime);
			cerr << DKHTP << layer.name() << "/" << nodename << CURRLOC << endl;
		} else if (nodename == "clef") {
			parseClef(children[i], starttime);
		} else if (nodename == "barLine") {
			parseBarline(children[i], starttime);
		} else if (nodename == "dot") {
			// dot is processed in parseNote_mensural;
		} else {
			cerr << DKHTP << layer.name() << "/" << nodename << CURRLOC << endl;
		}
	}

	//if (!m_gracenotes.empty()) {
	//	processGraceNotes(starttime);
	//}

	m_currentLayer = 0;
	return starttime;
}



//////////////////////////////
//
// Tool_mei2hum::parseChoice_mensural -- Process an MEI choice element (within mensural music).
//

HumNum Tool_mei2hum::parseChoice_mensural(xml_node choice, HumNum starttime) {
	NODE_VERIFY(choice, starttime);
	MAKE_CHILD_LIST(children, choice);
	for (int i=0; i<(int)children.size(); i++) {
		string nodename = children[i].name();
		if (nodename == "corr") {
			starttime = parseCorr_mensural(children[i], starttime);
		} else if (nodename == "sic") {
			// Ignoring sic in conversion (at least for now)
		} else {
			cerr << DKHTP << choice.name() << "/" << nodename << CURRLOC << endl;
		}
	}
	return starttime;
}



//////////////////////////////
//
// Tool_mei2hum::parseCorr_mensural -- Process an MEI corr element (within mensural music).
//

HumNum Tool_mei2hum::parseCorr_mensural(xml_node corr, HumNum starttime) {
	NODE_VERIFY(corr, starttime);
	MAKE_CHILD_LIST(children, corr);

	string dummy;

	// Same parsing as layer:
	xml_node layer = corr;
	for (int i=0; i<(int)children.size(); i++) {
		string nodename = children[i].name();
		if (nodename == "note") {
			starttime = parseNote_mensural(children[i], xml_node(NULL), dummy, starttime, 0);
		} else if (nodename == "ligature") {
			starttime = parseLigature(children[i], starttime);
		} else if (nodename == "choice") {
			starttime = parseChoice_mensural(children[i], starttime);
		} else if (nodename == "chord") {
			// starttime = parseChord(children[i], starttime, 0);
			cerr << DKHTP << layer.name() << "/" << nodename << CURRLOC << endl;
		} else if (nodename == "rest") {
			starttime = parseRest_mensural(children[i], starttime);
		} else if (nodename == "space") {
			starttime = parseRest_mensural(children[i], starttime);
		} else if (nodename == "mRest") {
			// starttime = parseMRest(children[i], starttime);
			cerr << DKHTP << layer.name() << "/" << nodename << CURRLOC << endl;
		} else if (nodename == "beam") {
			// starttime = parseBeam(children[i], starttime);
			cerr << DKHTP << layer.name() << "/" << nodename << CURRLOC << endl;
		} else if (nodename == "tuplet") {
			// starttime = parseTuplet(children[i], starttime);
			cerr << DKHTP << layer.name() << "/" << nodename << CURRLOC << endl;
		} else if (nodename == "clef") {
			parseClef(children[i], starttime);
		} else if (nodename == "barLine") {
			parseBarline(children[i], starttime);
		} else if (nodename == "dot") {
			// dot is processed in parseNote_mensural;
		} else {
			cerr << DKHTP << layer.name() << "/" << nodename << CURRLOC << endl;
		}
	}

	return starttime;
}



//////////////////////////////
//
// Tool_mei2hum::parseBarline --
//

void Tool_mei2hum::parseBarline(xml_node barLine, HumNum starttime) {
	NODE_VERIFY(barLine, )

	// Check to see if there is another barline following this one, and if so
	// do not insert this barline.
	xml_node nextsibling = barLine.next_sibling();
	if (strcmp(nextsibling.name(), "barLine") == 0) {
		return;
	}

	m_outdata.back()->addBarlineToken("=", starttime QUARTER_CONVERT,
 		m_currentStaff-1, 0, 0, m_staffcount);
}



//////////////////////////////
//
// Tool_mei2hum::parseTuplet --
//

HumNum Tool_mei2hum::parseTuplet(xml_node tuplet, HumNum starttime) {
	NODE_VERIFY(tuplet, starttime)
	MAKE_CHILD_LIST(children, tuplet);

	string num = tuplet.attribute("num").value();
	string numbase = tuplet.attribute("numbase").value();

	HumNum newfactor = 1;

	if (numbase == "") {
		cerr << "Warning: tuplet@numbase is empty" << endl;
	} else {
		newfactor = stoi(numbase);
	}

	if (num == "") {
		cerr << "Warning: tuplet@num is empty" << endl;
	} else {
		newfactor /= stoi(num);
	}

	m_tupletfactor *= newfactor;

	string stored_beamPostfix;
	if (m_beamPostfix != "") {
		stored_beamPostfix = m_beamPostfix;
		m_beamPostfix.clear();
	}

	xml_node lastnoterestchord;
	for (int i=(int)children.size() - 1; i>=0; i--) {
		string nodename = children[i].name();
		if (nodename == "note") {
			lastnoterestchord = children[i];
			break;
		} else if (nodename == "rest") {
			lastnoterestchord = children[i];
			break;
		} else if (nodename == "chord") {
			lastnoterestchord = children[i];
			break;
		}
	}

	string dummy;
	for (int i=0; i<(int)children.size(); i++) {
		if (children[i] == lastnoterestchord) {
			m_beamPostfix = stored_beamPostfix;
		}
		string nodename = children[i].name();
		if (nodename == "note") {
			starttime = parseNote(children[i], xml_node(NULL), dummy, starttime, 0);
		} else if (nodename == "rest") {
			starttime = parseRest(children[i], starttime);
		} else if (nodename == "chord") {
			starttime = parseChord(children[i], starttime, 0);
		} else if (nodename == "beam") {
			starttime = parseBeam(children[i], starttime);
		} else {
			cerr << DKHTP << tuplet.name() << "/" << nodename << CURRLOC << endl;
		}
	}

	m_tupletfactor /= newfactor;

	return starttime;
}



//////////////////////////////
//
// Tool_mei2hum::parseBeam --
//

HumNum Tool_mei2hum::parseBeam(xml_node beam, HumNum starttime) {
	NODE_VERIFY(beam, starttime)
	MAKE_CHILD_LIST(children, beam);

	bool isvalid = beamIsValid(children);
	// bool isgrace = beamIsGrace(children);

	if (isvalid) {
		m_beamPrefix = "L";
	}

	xml_node lastnoterestchord;
	for (int i=(int)children.size()-1; i>=0; i--) {
		string nodename = children[i].name();
		if (nodename == "note") {
			lastnoterestchord = children[i];
			break;
		} else if (nodename == "rest") {
			lastnoterestchord = children[i];
			break;
		} else if (nodename == "chord") {
			lastnoterestchord = children[i];
			break;
		} else if (nodename == "tuplet") {
			lastnoterestchord = children[i];
			break;
		}
	}

	string dummy;
	for (int i=0; i<(int)children.size(); i++) {
		if (children[i] == lastnoterestchord) {
			if (isvalid) {
				m_beamPostfix = "J";
			}
		}
		string nodename = children[i].name();
		if (nodename == "note") {
			starttime = parseNote(children[i], xml_node(NULL), dummy, starttime, 0);
		} else if (nodename == "rest") {
			starttime = parseRest(children[i], starttime);
		} else if (nodename == "chord") {
			starttime = parseChord(children[i], starttime, 0);
		} else if (nodename == "tuplet") {
			starttime = parseTuplet(children[i], starttime);
		} else if (nodename == "clef") { //drizo
			parseClef(children[i], starttime);
		} else {
			cerr << DKHTP << beam.name() << "/" << nodename << CURRLOC << endl;
		}
	}

	return starttime;
}



//////////////////////////////
//
// Tool_mei2hum::beamIsGrace -- beam only contains grace notes.
//

bool Tool_mei2hum::beamIsGrace(vector<xml_node>& beamlist) {
	for (int i=0; i<(int)beamlist.size(); i++) {
		string nodename = beamlist[i].name();
		if (nodename != "note") {
			continue;
		}
		string grace = beamlist[i].attribute("grace").value();
		if (grace.empty()) {
			return false;
		}
	}

	return true;
}



//////////////////////////////
//
// Tool_mei2hum::beamIsValid --
//

bool Tool_mei2hum::beamIsValid(vector<xml_node>& beamlist) {
	for (int i=0; i<(int)beamlist.size(); i++) {
		string nodename = beamlist[i].name();
		if (nodename != "note") {
			continue;
		}
		string grace = beamlist[i].attribute("grace").value();
		if (!grace.empty()) {
			continue;
		}
		string dur = beamlist[i].attribute("dur").value();
		if (dur.empty()) {
			// strange, but skip
			continue;
		}
		if (isdigit(dur[0])) {
			if (stoi(dur) <= 4) {
				return false;
			}
		} else {
			// "breve", "long", "maxima", junk, etc.
			return false;
		}
	}

	return true;
}



//////////////////////////////
//
// Tool_mei2hum::processGraceNotes -- write the grace notes into the score
//   in reverse order before any note at the given timestamp.
//

void Tool_mei2hum::processGraceNotes(HumNum timestamp) {
	int size = (int)m_gracenotes.size();
	int counter = 1;
	string output;
	for (int i=size-1; i>=0; i--) {
		string nodename = m_gracenotes[i].node.name();
		if (nodename == "note") {
			m_beamPrefix = m_gracenotes[i].beamprefix;
			m_beamPostfix = m_gracenotes[i].beampostfix;
			parseNote(m_gracenotes[i].node, xml_node(NULL), output, m_gracetime, counter);
		} else if (nodename == "chord") {
			m_beamPrefix = m_gracenotes[i].beamprefix;
			m_beamPostfix = m_gracenotes[i].beampostfix;
			parseChord(m_gracenotes[i].node, m_gracetime, counter);
		} else {
			cerr << "STRANGE THING HAPPENED HERE, node name is " << nodename << endl;
		}
		counter++;
	}

	m_gracenotes.clear();
}



//////////////////////////////
//
// Tool_mei2hum::parseNote --
//

HumNum Tool_mei2hum::parseNote(xml_node note, xml_node chord, string& output,
		HumNum starttime, int gracenumber) {
	NODE_VERIFY(note, starttime)
	MAKE_CHILD_LIST(children, note);

	HumNum duration;
	int dotcount;

	string grace = note.attribute("grace").value();
	bool graceQ = !grace.empty();

	if (gracenumber == 0) {
		if (graceQ) {
			if (!m_gracenotes.empty()) {
				if (starttime != m_gracetime) {
					// Grace notes at end of previous measure which
					// should have been processed before coming into the
					// next measure.
					cerr << "STRANGE ERROR IN GRACE NOTE PARSING" << endl;
					cerr << "\tSTARTTIME: " << starttime << endl;
					cerr << "\tGRACETIME: " << m_gracetime << endl;
				}
			} else {
				m_gracetime = starttime;
			}
			if (chord) {
				grace_info ginfo;
				ginfo.node = chord;
				ginfo.beamprefix = m_beamPrefix;
				ginfo.beampostfix = m_beamPostfix;
				m_beamPrefix.clear();
				m_beamPostfix.clear();
				m_gracenotes.push_back(ginfo);
			} else {
				grace_info ginfo;
				ginfo.node = note;
				ginfo.beamprefix = m_beamPrefix;
				ginfo.beampostfix = m_beamPostfix;
				m_beamPrefix.clear();
				m_beamPostfix.clear();
				m_gracenotes.push_back(ginfo);
			}
			// grace notes processed after knowing how many of them
			// are before a real note (or at the end of the measure).
 			return starttime;
		}

	}

	processPreliminaryLinkedNodes(note);

	if (chord) {
		duration = getDuration(chord);
		dotcount = getDotCount(chord);
		// maybe allow different durations on notes in a chord if the
		// first one is the same as the duration of the chord.
	} else {
		duration = getDuration(note);
		dotcount = getDotCount(note);
	}

	string recip = getHumdrumRecip(duration, dotcount);
	string humpitch = getHumdrumPitch(note, children);
	string editorial = getEditorialAccidental(children);
	string cautionary = getCautionaryAccidental(children);
	if (!editorial.empty()) {
		humpitch += editorial;
	}
	if (!cautionary.empty()) {
		humpitch += cautionary;
	}

	string articulations = getNoteArticulations(note, chord);

	string stemdir = note.attribute("stem.dir").value();

	if (!m_stemsQ) {
		// suppress note stems
		stemdir = "";
	}

	if (stemdir == "up") {
		stemdir = "/";
	} else if (stemdir == "down") {
		stemdir = "\\";
	} else {
		stemdir = "";
	}
	string gracelabel = "";
	if (graceQ) {
		gracelabel = "q";
	}

	string tok = recip + gracelabel + humpitch + articulations + stemdir
			+ m_beamPrefix + m_beamPostfix;
	m_beamPrefix.clear();
	m_beamPostfix.clear();

	m_fermata = false;
	processLinkedNodes(tok, note);
	if (!m_fermata) {
		processFermataAttribute(tok, note);
	}

	GridSlice* dataslice = NULL;

	if (!chord) {
		if (gracenumber == 0) {
			dataslice = m_outdata.back()->addDataToken(tok, starttime QUARTER_CONVERT,
					m_currentStaff-1, 0, m_currentLayer-1, m_staffcount);
		} else {
			dataslice = m_outdata.back()->addGraceToken(tok, starttime QUARTER_CONVERT,
					m_currentStaff-1, 0, m_currentLayer-1, m_staffcount, gracenumber);
		}
	} else {
		output += tok;
	}

	if (m_xmlidQ) {
		GridStaff* staff = dataslice->at(m_currentStaff-1)->at(0);
		// not keeping track of overwriting ids at the moment:
		string xmlid = note.attribute("xml:id").value();
		if (!xmlid.empty()) {
			staff->setXmlid(xmlid);
			m_outdata.setXmlidsPresent(m_currentStaff-1);
		}
	}

	bool hasverse = false;

	for (int i=0; i<(int)children.size(); i++) {
		string nodename = children[i].name();
		if ((nodename == "verse") && (dataslice != NULL)) {
			hasverse = true;
			parseVerse(children[i], dataslice->at(m_currentStaff-1)->at(0));
		} else if ((nodename == "syl") && (dataslice != NULL)) {
			hasverse = true;
			parseBareSyl(children[i], dataslice->at(m_currentStaff-1)->at(0));
		} else if (nodename == "artic") {
			// handled elsewhere: don't do anything
		} else if (nodename == "accid") {
			// handled elsewhere: don't do anything
		} else {
			cerr << DKHTP << note.name() << "/" << nodename << CURRLOC << endl;
		}
	}

	if (!hasverse) {
		string attsyl = note.attribute("syl").value();
		if (!attsyl.empty()) {
			parseSylAttribute(attsyl, dataslice->at(m_currentStaff-1)->at(0));
		}
	}

	if ((!graceQ) && (!m_gracenotes.empty())) {
		processGraceNotes(starttime);
	}

	if (graceQ) {
		return starttime;
	} else {
		return starttime + duration;
	}
}



//////////////////////////////
//
// Tool_mei2hum::parseNote_mensural --
//

HumNum Tool_mei2hum::parseNote_mensural(xml_node note, xml_node chord, string& output,
		HumNum starttime, int gracenumber) {
	NODE_VERIFY(note, starttime)
	MAKE_CHILD_LIST(children, note);

	HumNum duration;
	int dotcount;

	string grace = note.attribute("grace").value();
	bool graceQ = !grace.empty();
	if (graceQ) {
		// cannot have grace notes in mensural music
		return starttime;
	}

	processPreliminaryLinkedNodes(note);

	if (chord) {
		duration = getDuration_mensural(chord, dotcount);
		// maybe allow different durations on notes in a chord if the
		// first one is the same as the duration of the chord.
	} else {
		duration = getDuration_mensural(note, dotcount);
	}

	string meidur = note.attribute("dur").value();
	string mensrhy;
	if      (meidur == "maxima")      { mensrhy = "X"; }
	else if (meidur == "longa")       { mensrhy = "L"; }
	else if (meidur == "brevis")      { mensrhy = "S"; }
	else if (meidur == "semibrevis")  { mensrhy = "s"; }
	else if (meidur == "minima")      { mensrhy = "M"; }
	else if (meidur == "semiminima")  { mensrhy = "m"; }
	else if (meidur == "fusa")        { mensrhy = "U"; }
	else if (meidur == "semifusa")    { mensrhy = "u"; }
	else { mensrhy = "?"; }

	string recip      = getHumdrumRecip(duration/4, dotcount);
	string humpitch   = getHumdrumPitch(note, children);
	string editorial  = getEditorialAccidental(children);
	string cautionary = getCautionaryAccidental(children);

	xml_attribute dur_qual = note.attribute("dur.quality");
	string durquality = dur_qual.value();
	string quality;
	if (durquality == "perfecta") {
		quality = "p";
	} else if (durquality == "imperfecta") {
		quality = "i";
	} else if (durquality == "altera") {
		quality = "+";
	}

	humpitch = mensrhy + quality + humpitch;

	if (!editorial.empty()) {
		humpitch += editorial;
	}
	if (!cautionary.empty()) {
		humpitch += cautionary;
	}

	// string articulations = getNoteArticulations(note, chord);
	string articulations;

	string stemdir = note.attribute("stem.dir").value();

	// if (!m_stemsQ) {
	// 	// suppress note stems
	// 	stemdir = "";
	// }

	if (stemdir == "up") {
		stemdir = "/";
	} else if (stemdir == "down") {
		stemdir = "\\";
	} else {
		stemdir = "";
	}
	string gracelabel = "";
	if (graceQ) {
		gracelabel = "q";
	}

	string mensdot = "";
	xml_node nextsibling = note.next_sibling();
	if (strcmp(nextsibling.name(), "barLine") == 0) {
		nextsibling = nextsibling.next_sibling();
	}
	if (strcmp(nextsibling.name(), "dot") == 0) {
		mensdot = ":";
	}
	string tok = /* recip + */ gracelabel + humpitch + articulations + stemdir
			+ m_beamPrefix + m_beamPostfix + mensdot;
	m_beamPrefix.clear();
	m_beamPostfix.clear();

	m_fermata = false;
	processLinkedNodes(tok, note);
	if (!m_fermata) {
		processFermataAttribute(tok, note);
	}

	GridSlice* dataslice = NULL;

	if (!chord) {
		if (gracenumber == 0) {
			dataslice = m_outdata.back()->addDataToken(tok, starttime QUARTER_CONVERT,
					m_currentStaff-1, 0, m_currentLayer-1, m_staffcount);
		} else {
			dataslice = m_outdata.back()->addGraceToken(tok, starttime QUARTER_CONVERT,
					m_currentStaff-1, 0, m_currentLayer-1, m_staffcount, gracenumber);
		}
	} else {
		output += tok;
	}

	if (m_xmlidQ) {
		GridStaff* staff = dataslice->at(m_currentStaff-1)->at(0);
		// not keeping track of overwriting ids at the moment:
		string xmlid = note.attribute("xml:id").value();
		if (!xmlid.empty()) {
			staff->setXmlid(xmlid);
			m_outdata.setXmlidsPresent(m_currentStaff-1);
		}
	}

	bool hasverse = false;

	for (int i=0; i<(int)children.size(); i++) {
		string nodename = children[i].name();
		if ((nodename == "verse") && (dataslice != NULL)) {
			hasverse = true;
			parseVerse(children[i], dataslice->at(m_currentStaff-1)->at(0));
		} else if ((nodename == "syl") && (dataslice != NULL)) {
			hasverse = true;
			parseBareSyl(children[i], dataslice->at(m_currentStaff-1)->at(0));
		} else if (nodename == "artic") {
			// handled elsewhere: don't do anything
		} else if (nodename == "accid") {
			// handled elsewhere: don't do anything
		} else {
			cerr << DKHTP << note.name() << "/" << nodename << CURRLOC << endl;
		}
	}

	if (!hasverse) {
		string attsyl = note.attribute("syl").value();
		if (!attsyl.empty()) {
			parseSylAttribute(attsyl, dataslice->at(m_currentStaff-1)->at(0));
		}
	}

	return starttime + duration;
}



//////////////////////////////
//
// Tool_mei2hum::parseSylAttribute --
//

void Tool_mei2hum::parseSylAttribute(const string& attsyl, GridStaff* staff) {
	vector<string> pieces(1);
	int length = (int)attsyl.size();
	if (length == 0) {
		return;
	}
	if (length == 1) {
		pieces[0] += attsyl;
	} else {
		for (int i=0; i<length-2; i++) {
			if ((attsyl[i] == '/') && (attsyl[i+1] == '/')) {
				pieces.emplace_back("");
				i++;
			} else {
				pieces.back() += attsyl[i];
			}
		}
		if ((attsyl[length-1] != '/') && (attsyl[length-2] != '/')) {
			pieces.back() += attsyl[length-2];
			pieces.back() += attsyl.back();
		}
	}

	if ((pieces.size() == 1) && (pieces[0].empty())) {
		return;
	}

	for (int i=0; i<(int)pieces.size(); i++) {
		pieces[i] = cleanVerseText(pieces[i]);
	}

	for (int i=0; i<(int)pieces.size(); i++) {
		if (pieces[i].empty()) {
			continue;
		}
		staff->setVerse(i, pieces[i]);
		reportVerseNumber(i+1, m_currentStaff-1);
	}
}



//////////////////////////////
//
// Tool_mei2hum::getEditorialAccidental --
//

string Tool_mei2hum::getEditorialAccidental(vector<xml_node>& children) {
	string output;
	if (children.empty()) {
		return output;
	}

	for (int i=0; i<(int)children.size(); i++) {
		string nodename = children[i].name();
		if (nodename != "accid") {
			continue;
		}
		string function = children[i].attribute("func").value();
		if (function != "edit") {
			continue;
		}
		string accid = children[i].attribute("accid").value();
		if (accid.empty()) {
			continue;
		}
		output = accidToKern(accid);
		if (!output.empty()) {
			output += "i";
		}
		m_editorialAccidentalQ = true;
		break;
	}

	return output;
}



//////////////////////////////
//
// Tool_mei2hum::getCautionaryAccidental --
// Such as:
//     <accid accid="n" func="caution" />
//

string Tool_mei2hum::getCautionaryAccidental(vector<xml_node>& children) {
	string output;
	if (children.empty()) {
		return output;
	}

	for (int i=0; i<(int)children.size(); i++) {
		string nodename = children[i].name();
		if (nodename != "accid") {
			continue;
		}
		string function = children[i].attribute("func").value();
		if (function != "caution") {
			continue;
		}
		string accid = children[i].attribute("accid").value();
		if (accid.empty()) {
			continue;
		}
		output = accidToKern(accid);
		if ((!output.empty()) && (output != "n")) {
			output += "X";
		}
		break;
	}

	return output;
}



//////////////////////////////
//
// Tool_mei2hum::accidToKern -- Convert accid string into **kern accidental.
//

string Tool_mei2hum::accidToKern(const string& accid) {
	string output;
	if (accid == "n") {
		output = "n";
	} else if (accid == "s") {
		output = "#";
	} else if (accid == "f") {
		output = "-";
	} else if (accid == "ff") {
		output = "--";
	} else if (accid == "ss") {
		output = "##";
	} else if (accid == "x") {
		output = "##";
	} else if (accid == "nf") {
		output = "-";
	} else if (accid == "ns") {
		output = "#";
	} else if (accid == "xs") {
		output = "###";
	} else if (accid == "sx") {
		output = "###";
	} else if (accid == "tf") {
		output = "---";
	} else if (accid == "ts") {
		output = "###";
	} else {
		cerr << "Don't know how to interpret " << accid << " accidental" << endl;
	}
	return output;
}



//////////////////////////////
//
// Tool_mei2hum::parseMRest -- Full-measure rest.
//

HumNum Tool_mei2hum::parseMRest(xml_node mrest, HumNum starttime) {
	HumNum duration = m_measureDuration.at(m_currentStaff-1);
	duration /= 4;
	int dotcount = 0;
	string recip = getHumdrumRecip(duration, dotcount);
	if (recip.find('%') != string::npos) {
		string recip2 = getHumdrumRecip(duration, 1);
		if (recip2.find('%') == string::npos) {
			recip = recip2;
			dotcount = 1;
		} else {
			string recip3 = getHumdrumRecip(duration, 2);
			if (recip2.find('%') == string::npos) {
				recip = recip3;
				dotcount = 2;
			}
		}
	}
	string tok = recip + "r";
	// Add fermata on whole-measure rest if needed.

	// Deal here with calculating number of dots needed for
	// measure duration.

	m_outdata.back()->addDataToken(tok, starttime QUARTER_CONVERT,
			m_currentStaff-1, 0, m_currentLayer-1, m_staffcount);

	return starttime + duration; // convert to whole-note units
}



//////////////////////////////
//
// Tool_mei2hum::parseRest --
//

HumNum Tool_mei2hum::parseRest(xml_node rest, HumNum starttime) {
	if (!rest) {
		return starttime;
	}
	string nodename = rest.name();
	if (!((nodename == "rest") || (nodename == "space"))) {
		return starttime;
	}
	if (nodename == "rest") {
		ELEMENT_DEBUG_STATEMENT(rest)
	} else if (nodename == "space") {
		ELEMENT_DEBUG_STATEMENT(space)
	}

	processPreliminaryLinkedNodes(rest);

	HumNum duration = getDuration(rest);
	int dotcount = getDotCount(rest);
	string recip = getHumdrumRecip(duration, dotcount);
	string invisible;
	if (nodename == "space") {
		invisible = "yy";
	}

	string output = recip + "r" + invisible + m_beamPrefix + m_beamPostfix;
	m_beamPrefix.clear();
	m_beamPostfix.clear();

	processLinkedNodes(output, rest);
	processFermataAttribute(output, rest);

	m_outdata.back()->addDataToken(output, starttime QUARTER_CONVERT,
			m_currentStaff-1, 0, m_currentLayer-1, m_staffcount);

	return starttime + duration;
}



//////////////////////////////
//
// Tool_mei2hum::parseRest_mensural --
//

HumNum Tool_mei2hum::parseRest_mensural(xml_node rest, HumNum starttime) {
	if (!rest) {
		return starttime;
	}
	string nodename = rest.name();
	if (!((nodename == "rest") || (nodename == "space"))) {
		return starttime;
	}
	if (nodename == "rest") {
		ELEMENT_DEBUG_STATEMENT(rest)
	} else if (nodename == "space") {
		ELEMENT_DEBUG_STATEMENT(space)
	}

	MAKE_CHILD_LIST(children, rest);

	processPreliminaryLinkedNodes(rest);

	string meidur = rest.attribute("dur").value();
	string mensrhy;
	if      (meidur == "maxima")      { mensrhy = "X"; }
	else if (meidur == "longa")       { mensrhy = "L"; }
	else if (meidur == "brevis")      { mensrhy = "S"; }
	else if (meidur == "semibrevis")  { mensrhy = "s"; }
	else if (meidur == "minima")      { mensrhy = "M"; }
	else if (meidur == "semiminima")  { mensrhy = "m"; }
	else if (meidur == "fusa")        { mensrhy = "U"; }
	else if (meidur == "semifusa")    { mensrhy = "u"; }
	else { mensrhy = "?"; }

	int dotcount = 0;
	HumNum duration = getDuration_mensural(rest, dotcount);

	string invisible;
	if (nodename == "space") {
		invisible = "yy";
	}

	string recip = getHumdrumRecip(duration/4, dotcount);
	string humpitch   = ""; // getHumdrumPitch(rest, children);
	string editorial  = getEditorialAccidental(children);
	string cautionary = getCautionaryAccidental(children);

	xml_attribute dur_qual = rest.attribute("dur.quality");
	string durquality = dur_qual.value();
	string quality;
	if (durquality == "perfecta") {
		quality = "p";
	} else if (durquality == "imperfecta") {
		quality = "i";
	} else if (durquality == "altera") {
		quality = "+";
	}

	humpitch = mensrhy + quality + humpitch;

	string output = mensrhy + "r" + invisible + m_beamPrefix + m_beamPostfix;
	m_beamPrefix.clear();
	m_beamPostfix.clear();

	processLinkedNodes(output, rest);
	processFermataAttribute(output, rest);

	m_outdata.back()->addDataToken(output, starttime QUARTER_CONVERT,
			m_currentStaff-1, 0, m_currentLayer-1, m_staffcount);

	return starttime + duration;
}



//////////////////////////////
//
// Tool_mei2hum::getNoteArticulations --
//

string Tool_mei2hum::getNoteArticulations(xml_node note, xml_node chord) {

	string attribute_artic = note.attribute("artic").value();

	vector<xml_node> element_artic;
	for (pugi::xml_node artic : note.children("artic")) {
		element_artic.push_back(artic);
	}

	string chord_attribute_artic;
	vector<xml_node> chord_element_artic;

	if (chord) {
		chord_attribute_artic = chord.attribute("artic").value();
	}
	for (pugi::xml_node artic : chord.children("artic")) {
		chord_element_artic.push_back(artic);
	}

	string output;

	output += getHumdrumArticulation("\\bstacc\\b", "'", attribute_artic, element_artic,
			chord_attribute_artic, chord_element_artic);
	output += getHumdrumArticulation("\\bacc\\b", "^", attribute_artic, element_artic,
			chord_attribute_artic, chord_element_artic);
	output += getHumdrumArticulation("\\bmarc\\b", "^^", attribute_artic, element_artic,
			chord_attribute_artic, chord_element_artic);
	output += getHumdrumArticulation("\\bstacciss\\b", "`", attribute_artic, element_artic,
			chord_attribute_artic, chord_element_artic);
	output += getHumdrumArticulation("\\bten\\b", "~", attribute_artic, element_artic,
			chord_attribute_artic, chord_element_artic);

	return output;
}



///////////////////////////////
//
// Tool_mei2hum::getHumdrumArticulation --
//

string Tool_mei2hum::getHumdrumArticulation(const string& tag, const string& humdrum,
		const string& attribute_artic, vector<xml_node>& element_artic,
		const string& chord_attribute_artic, vector<xml_node>& chord_element_artic) {
	HumRegex hre;
	string output;

	// First check artic attributes:
	if (hre.search(attribute_artic, tag)) {
		output = humdrum;
		return output;
	}

	// If the note is attached to a chord, search the chord:
	if (hre.search(chord_attribute_artic, tag)) {
		output = humdrum;
		return output;
	}

	// Now search for artic elements in the note:
	for (int i=0; i<(int)element_artic.size(); i++) {
		string nodename = element_artic[i].name();
		if (nodename != "artic") {
			continue;
		}
		string artic = element_artic[i].attribute("artic").value();
		if (hre.search(artic, tag)) {
			output = humdrum;
			output += setPlacement(element_artic[i].attribute("place").value());
			return output;
		}
	}

	// And then in the chord:
	for (int i=0; i<(int)chord_element_artic.size(); i++) {
		string nodename = chord_element_artic[i].name();
		if (nodename != "artic") {
			continue;
		}
		string artic = chord_element_artic[i].attribute("artic").value();
		if (hre.search(artic, tag)) {
			output = humdrum;
			output += setPlacement(chord_element_artic[i].attribute("place").value());
			return output;
		}
	}

	return output;
}



//////////////////////////////
//
// Tool_mei2hum::setPlacement --
//

string Tool_mei2hum::setPlacement(const string& placement) {
	if (!m_placeQ) {
		return "";
	}
	if (placement == "above") {
		m_aboveQ = true;
		return ">";
	} else if (placement == "below") {
		m_belowQ = true;
		return "<";
	}
	return "";
}



//////////////////////////////
//
// Tool_mei2hum::processFermataAttribute -- @fermata="(above|below)"
//

void Tool_mei2hum::processFermataAttribute(string& output, xml_node node) {
	string fermata = node.attribute("fermata").value();
	if (fermata.empty()) {
		return;
	}
	if (fermata == "above") {
		output += ';';
	} else if (fermata == "below") {
		output += ";<";
		m_belowQ = true;
	}
}



//////////////////////////////
//
// Tool_mei2hum::processPreliminaryLinkedNodes -- Process tupletSpan
//      before rhythm of linked notes are processed.
//

void Tool_mei2hum::processPreliminaryLinkedNodes(xml_node node) {
	string id = node.attribute("xml:id").value();
	if (!id.empty()) {
		auto found = m_startlinks.find(id);
		if (found != m_startlinks.end()) {
			processNodeStartLinks2(node, (*found).second);
		}
	}
}



//////////////////////////////
//
// Tool_mei2hum::processLinkedNodes --
//

void Tool_mei2hum::processLinkedNodes(string& output, xml_node node) {
	string id = node.attribute("xml:id").value();
	if (!id.empty()) {
		auto found = m_startlinks.find(id);
		if (found != m_startlinks.end()) {
			processNodeStartLinks(output, node, (*found).second);
		}
		found = m_stoplinks.find(id);
		if (found != m_stoplinks.end()) {
			processNodeStopLinks(output, node, (*found).second);
		}
	}
}



//////////////////////////////
//
// Tool_mei2hum::getDotCount --  Get dot count from first note in chord if no @dur on chord.
//

int Tool_mei2hum::getDotCount(xml_node node) {
	string name = node.name();
	if (name == "chord") {
		if (!node.attribute("dur")) {
			node = node.select_node(".//note").node();
		}
	}

	string dots = node.attribute("dots").value();
	int dotcount = 0;
	if (dots != "") {
		dotcount = stoi(dots);
	}
	return dotcount;
}



//////////////////////////////
//
// Tool_mei2hum::processNodeStartLinks --
//

void Tool_mei2hum::processNodeStartLinks(string& output, xml_node node,
		vector<xml_node>& nodelist) {
	for (int i=0; i<(int)nodelist.size(); i++) {
		string nodename = nodelist[i].name();
		if (nodename == "fermata") {
			m_fermata = true; // used to disable note@fermata duplications
			parseFermata(output, node, nodelist[i]);
		} else if (nodename == "slur") {
			parseSlurStart(output, node, nodelist[i]);
		} else if (nodename == "tie") {
			parseTieStart(output, node, nodelist[i]);
		} else if (nodename == "trill") {
			parseTrill(output, node, nodelist[i]);
		} else if (nodename == "arpeg") {
			parseArpeg(output, node, nodelist[i]);
		} else if (nodename == "tupletSpan") {
			// handled in processNodeStartLinks2
		} else {
			cerr << DKHTP << nodename
			     << " element in processNodeStartLinks()" << endl;
		}
	}
}



//////////////////////////////
//
// Tool_mei2hum::processNodeStartLinks2 -- process tupletSpan before the
//     duration of the note/rest/chord is calculated.
//

void Tool_mei2hum::processNodeStartLinks2(xml_node node,
		vector<xml_node>& nodelist) {
	for (int i=0; i<(int)nodelist.size(); i++) {
		string nodename = nodelist[i].name();
		if (nodename == "tupletSpan") {
			parseTupletSpanStart(node, nodelist[i]);
		}
	}
}



//////////////////////////////
//
// Tool_mei2hum::parseTupletSpanStart --
//     Such as:
//          <tupletSpan staff="10" num="3" numbase="2" num.visible="true"
//                num.place="below" num.format="count" startid="#4235235"
//                endid="#532532"/>
//

void Tool_mei2hum::parseTupletSpanStart(xml_node node,
		xml_node tupletSpan) {
	NODE_VERIFY(tupletSpan, )

	if (strcmp(tupletSpan.attribute("endid").value(), "") == 0) {
		cerr << "Warning: <tupletSpan> requires endid attribute (at least ";
		cerr << "for this parser)" << endl;
		return;
	}

	if (strcmp(tupletSpan.attribute("startid").value(), "") == 0) {
		cerr << "Warning: <tupletSpan> requires startid attribute (at least ";
		cerr << "for this parser)" << endl;
		return;
	}

	string num = tupletSpan.attribute("num").value();
	string numbase = tupletSpan.attribute("numbase").value();

	HumNum newfactor = 1;

	if (numbase == "") {
		cerr << "Warning: tuplet@numbase is empty" << endl;
	} else {
		newfactor = stoi(numbase);
	}

	if (num == "") {
		cerr << "Warning: tuplet@num is empty" << endl;
	} else {
		newfactor /= stoi(num);
	}

	m_tupletfactor *= newfactor;

}



//////////////////////////////
//
// Tool_mei2hum::parseTupletSpanStop --
//     Such as:
//          <tupletSpan staff="10" num="3" numbase="2" num.visible="true"
//                num.place="below" num.format="count" startid="#4235235"
//                endid="#532532"/>
//

void Tool_mei2hum::parseTupletSpanStop(string& output, xml_node node,
		xml_node tupletSpan) {
	NODE_VERIFY(tupletSpan, )

	if (strcmp(tupletSpan.attribute("endid").value(), "") == 0) {
		return;
	}
	if (strcmp(tupletSpan.attribute("startid").value(), "") == 0) {
		return;
	}

	string num = tupletSpan.attribute("num").value();
	string numbase = tupletSpan.attribute("numbase").value();

	HumNum newfactor = 1;

	if (numbase == "") {
		cerr << "Warning: tuplet@numbase is empty" << endl;
	} else {
		newfactor = stoi(numbase);
	}

	if (num == "") {
		cerr << "Warning: tuplet@num is empty" << endl;
	} else {
		newfactor /= stoi(num);
	}

	// undo the tuplet factor:
	m_tupletfactor /= newfactor;

}



//////////////////////////////
//
// Tool_mei2hum::parseArpeg -- Only handles single chord arpeggiation for now
//    (ignores @endid).
//

void Tool_mei2hum::parseArpeg(string& output, xml_node node, xml_node arpeg) {
	NODE_VERIFY(arpeg, )

	if (strcmp(arpeg.attribute("endid").value(), "") != 0) {
		cerr << "Warning: multi-note arpeggios are not yet handled in the converter." << endl;
	}

	string nodename = node.name();
	if (nodename == "note") {
		output += ':';
	} else if (nodename == "chord") {
		string temp = output;
		output.clear();
		for (int i=0; i<(int)temp.size(); i++) {
			if (temp[i] == ' ') {
				output += ": ";
			} else {
				output += temp[i];
			}
		}
		output += ':';
	} else {
		cerr << DKHTP << "an arpeggio attached to a "
		     << nodename << " element" << endl;
		return;
	}

}



//////////////////////////////
//
// Tool_mei2hum::processNodeStopLinks --
//

void Tool_mei2hum::processNodeStopLinks(string& output, xml_node node,
		vector<xml_node>& nodelist) {
	for (int i=0; i<(int)nodelist.size(); i++) {
		string nodename = nodelist[i].name();
		if (nodename == "slur") {
			parseSlurStop(output, node, nodelist[i]);
		} else if (nodename == "tie") {
			parseTieStop(output, node, nodelist[i]);
		} else if (nodename == "tupletSpan") {
			parseTupletSpanStop(output, node, nodelist[i]);
		} else {
			cerr << DKHTP << nodename
			     << " element in processNodeStopLinks()" << endl;
		}
	}
}



//////////////////////////////
//
// Tool_mei2hum::parseSlurStart --
//

void Tool_mei2hum::parseSlurStart(string& output, xml_node node, xml_node slur) {
	NODE_VERIFY(slur, )
	string nodename = node.name();
	if (nodename == "note") {
		output = "(" + setPlacement(slur.attribute("curvedir").value()) + output;
	} else if (nodename == "chord") {
		output = "(" + setPlacement(slur.attribute("curvedir").value()) + output;
	} else {
		cerr << DKHTP << "a slur start attached to a "
		     << nodename << " element" << endl;
		return;
	}

}



//////////////////////////////
//
// Tool_mei2hum::parseSlurStop --
//

void Tool_mei2hum::parseSlurStop(string& output, xml_node node, xml_node slur) {
	NODE_VERIFY(slur, )
	string nodename = node.name();
	if (nodename == "note") {
		output += ")";
	} else if (nodename == "chord") {
		output += ")";
	} else {
		cerr << DKHTP << "a tie end attached to a "
		     << nodename << " element" << endl;
		return;
	}
}



//////////////////////////////
//
// Tool_mei2hum::parseTieStart -- Need to deal with chords later.
//

void Tool_mei2hum::parseTieStart(string& output, xml_node node, xml_node tie) {
	NODE_VERIFY(tie, )

	string id = node.attribute("xml:id").value();
	if (!id.empty()) {
		auto found = m_stoplinks.find(id);
		if (found != m_stoplinks.end()) {
			for (auto item : (*found).second) {
				if (strcmp(tie.attribute("startid").value(), item.attribute("endid").value()) == 0) {
					// deal with tie middles in parseTieStop().
					return;
				}
			}
		}
	}

	string nodename = node.name();
	if (nodename == "note") {
		output = "[" + output;
	} else {
		cerr << DKHTP << "a tie start attached to a "
		     << nodename << " element" << endl;
		return;
	}
}



//////////////////////////////
//
// Tool_mei2hum::parseTrill --
//

void Tool_mei2hum::parseTrill(string& output, xml_node node, xml_node trill) {
	NODE_VERIFY(trill, )

	auto loc = output.find(";");
	if (loc != string::npos) {
		output.insert(loc, "T");
		return;
	}

	loc = output.find(")");
	if (loc != string::npos) {
		output.insert(loc, "T");
		return;
	}

	output += "T";

	// Deal with endid attribute on trills later.
}



//////////////////////////////
//
// Tool_mei2hum::parseTieStop -- Need to deal with chords later.
//

void Tool_mei2hum::parseTieStop(string& output, xml_node node, xml_node tie) {
	NODE_VERIFY(tie, )

	string id = node.attribute("xml:id").value();
	if (!id.empty()) {
		auto found = m_startlinks.find(id);
		if (found != m_startlinks.end()) {
			for (auto item : (*found).second) {
				if (strcmp(tie.attribute("endid").value(), item.attribute("startid").value()) == 0) {
					output += "_";
					return;
				}
			}
		}
	}

	string nodename = node.name();
	if (nodename == "note") {
		output += "]";
	} else {
		cerr << DKHTP << "a tie end attached to a "
		     << nodename << " element" << endl;
		return;
	}
}



//////////////////////////////
//
// Tool_mei2hum::parseFermata -- deal with a fermata attached to something.
///     output is a Humdrum token string (maybe have it as a HumdrumToken object).
//

void Tool_mei2hum::parseFermata(string& output, xml_node node, xml_node fermata) {
	NODE_VERIFY(fermata, )

	string nodename = node.name();
	if (nodename == "note") {
		output += ';';
	} else if (nodename == "chord") {
		output += ';';
	} else if (nodename == "rest") {
		output += ';';
	} else {
		cerr << DKHTP << "a fermata attached to a "
		     << nodename << " element" << endl;
		return;
	}

}



//////////////////////////////
//
// Tool_mei2hum::getHumdrumRecip --
//

string Tool_mei2hum::getHumdrumRecip(HumNum duration, int dotcount) {
	string output;

	if (dotcount > 0) {
		// remove dots from duration
		int top = (1 << (dotcount+1)) - 1;
		int bot = 1 << dotcount;
		HumNum dotfactor(bot, top);
		duration *= dotfactor;
	}

	if (duration.getNumerator() == 1) {
		output = to_string(duration.getDenominator());
	} else if ((duration.getNumerator() == 2) && (duration.getDenominator() == 1)) {
		// breve symbol:
		output = "0";
	} else if ((duration.getNumerator() == 4) && (duration.getDenominator() == 1)) {
		// long symbol:
		output = "00";
	} else if ((duration.getNumerator() == 8) && (duration.getDenominator() == 1)) {
		// maxima symbol:
		output = "000";
	} else {
		output = to_string(duration.getDenominator());
		output += "%";
		output += to_string(duration.getNumerator());
	}

	for (int i=0; i<dotcount; i++) {
		output += '.';
	}

	return output;
}



//////////////////////////////
//
// Tool_mei2hum::getChildAccidVis -- Return accid@accid from any element
//   in the list, if it is not editorial or cautionary.
//

string Tool_mei2hum::getChildAccidVis(vector<xml_node>& children) {
	for (int i=0; i<(int)children.size(); i++) {
		string nodename = children[i].name();
		if (nodename != "accid") {
			continue;
		}
		string func = children[i].attribute("func").value();
		if (func == "caution") {
			// cautionary accidental handled elsewhere
			return "";
		} else if (func == "edit") {
			// editorial accidental handled elsewhere
			return "";
		}
		string accid = children[i].attribute("accid").value();
		return accid;
	}
	return "";
}



//////////////////////////////
//
// Tool_mei2hum::getChildAccidGes -- Return the accid@accid.ges value
//    of any element in the input list, but not if the accidental is
//    part of an cautionary or editorial accidental.
//

string Tool_mei2hum::getChildAccidGes(vector<xml_node>& children) {
	for (int i=0; i<(int)children.size(); i++) {
		string nodename = children[i].name();
		if (nodename != "accid") {
			continue;
		}
		string func = children[i].attribute("func").value();
		if (func == "caution") {
			// cautionary accidental handled elsewhere
			return "";
		} else if (func == "edit") {
			// editorial accidental handled elsewhere
			return "";
		}
		string accidges = children[i].attribute("accid.ges").value();
		return accidges;
	}
	return "";
}



//////////////////////////////
//
// Tool_mei2hum::getHumdrumPitch --
//

string Tool_mei2hum::getHumdrumPitch(xml_node note, vector<xml_node>& children) {
	string pname = note.attribute("pname").value();
	string accidvis = note.attribute("accid").value();
	string accidges = note.attribute("accid.ges").value();

	string accidvischild = getChildAccidVis(children);
	string accidgeschild = getChildAccidGes(children);

	int octnum = 4;
	string oct = note.attribute("oct").value();
	if (oct == "") {
		cerr << "Empty octave" << endl;
	} else if (isdigit(oct[0])) {
		octnum = stoi(oct);
	} else {
		cerr << "Unknown octave value: " << oct << endl;
	}

	if (pname == "") {
		cerr << "Empty pname" << endl;
		return "x";
	}

	string output;
	if (octnum < 4) {
		char val = toupper(pname[0]);
		int count = 4 - octnum;
		for (int i=0; i<count; i++) {
			output += val;
		}
	} else {
		char val = pname[0];
		int count = octnum - 3;
		for (int i=0; i<count; i++) {
			output += val;
		}
	}

	if (accidges != "") {
		string acc = accidToKern(accidges);
		if (acc != "n") {
			output += acc;
			// accidental is not visible
			output += "y";
		}
	} else if (accidvis != "") {
		string acc = accidToKern(accidvis);
		output += acc;
	} else if (accidvischild != "") {
		string acc = accidToKern(accidvischild);
		output += acc;
	} else if (accidgeschild != "") {
		string acc = accidToKern(accidgeschild);
		if (acc != "n") {
			output += acc;
			// accidental is not visible
			output += "y";
		}
	}

	// Transpose to C score if part is transposing:
	if (m_currentStaff) {
		if (m_scoreDef.staves[m_currentStaff-1].base40 != 0) {
			int base40 = Convert::kernToBase40(output);
			base40 += m_scoreDef.staves[m_currentStaff-1].base40;
			output = Convert::base40ToKern(base40);
		}
	}

	return output;
}



//////////////////////////////
//
// Tool_mei2hum::getDuration -- Get duration from note or chord.  If chord does not
//    have @dur then use @dur of first note in children elements.
//

HumNum Tool_mei2hum::getDuration(xml_node element) {
	xml_attribute dur_attr = element.attribute("dur");
	string name = element.name();
	if ((!dur_attr) && (name == "note")) {
		// real notes must have durations, but this one
		// does not, so assign zero duration
		return 0;
	}
	if ((!dur_attr) && (name == "chord")) {
		// if there is no dur attribute on a chord, then look for it
		// on the first note subelement of the chord.
		auto newelement = element.select_node(".//note").node();
		if (newelement) {
			element = newelement;
			dur_attr = element.attribute("dur");
			name = element.name();
		} else {
			return 0;
		}
	}

	string dur = dur_attr.value();
	if (dur == "") {
		return 0;
	}

	HumNum output;
	if (dur == "breve") {
		output = 2;
	} else if (dur == "long") {
		output = 4;
	} else if (dur == "maxima") {
		output = 8;
	} else if (isdigit(dur[0])) {
		output = 1;
		output /= stoi(dur);
	} else {
		cerr << "Unknown " << element.name() << "@dur: " << dur << endl;
		return 0;
	}

	if (output == 0) {
		cerr << "Error: zero duration for note" << endl;
	}

	int dotcount;
	string dots = element.attribute("dots").value();
	if (dots == "") {
		dotcount = 0;
	} else if (isdigit(dots[0])) {
		dotcount = stoi(dots);
	} else {
		cerr << "Unknown " << element.name() << "@dotcount: " << dur << endl;
		return 0;
	}

	if (dotcount > 0) {
		int top = (1 << (dotcount+1)) - 1;
		int bot = 1 << dotcount;
		HumNum dotfactor(top, bot);
		output *= dotfactor;
	}

	// add a correction for the tuplet factor which is currently active.
	if (m_tupletfactor != 1) {
		output *= m_tupletfactor;
	}

	return output;
}



//////////////////////////////
//
// Tool_mei2hum::getDuration_mensural -- Get duration from note or chord.  If chord does not
//    have @dur then use @dur of first note in children elements.
//
// @dur: https://music-encoding.org/guidelines/v4/data-types/data.duration.mensural.html
//          X = maxima
//          L = longa
//          S = brevis
//          s = semibrevis
//          M = minima
//          m = semiminima
//          U = fusa
//          u = semifusa
// @dur.quality:
//          i = imperfecta  :: remove augmentation dot
//          p = perfecta    :: add augmentation dot
//          altera = altera :: duration is double the rhythmic value of notes
//

HumNum Tool_mei2hum::getDuration_mensural(xml_node element, int& dotcount) {
	dotcount = 0;

	xml_attribute dur_qual = element.attribute("dur.quality");
	xml_attribute dur_attr = element.attribute("dur");
	string name = element.name();

	if ((!dur_attr) && (name == "note")) {
		// real notes must have durations, but this one
		// does not, so assign zero duration
		return 0;
	}
	if ((!dur_attr) && (name == "chord")) {
		// if there is no dur attribute on a chord, then look for it
		// on the first note subelement of the chord.
		auto newelement = element.select_node(".//note").node();
		if (newelement) {
			element = newelement;
			dur_attr = element.attribute("dur");
			name = element.name();
			dur_qual = element.attribute("dur.quality");
		} else {
			return 0;
		}
	}

	string dur = dur_attr.value();
	if (dur == "") {
		return 0;
	}
	string durquality = dur_qual.value();

	char rhythm = '\0';
	if (dur == "maxima") {
		rhythm = 'X';
	} else if (dur == "longa") {
		rhythm = 'L';
	} else if (dur == "brevis") {
		rhythm = 'S';
	} else if (dur == "semibrevis") {
		rhythm = 's';
	} else if (dur == "minima") {
		rhythm = 'M';
	} else if (dur == "semiminima") {
		rhythm = 'm';
	} else if (dur == "fusa") {
		rhythm = 'U';
	} else if (dur == "semifusa") {
		rhythm = 'u';
	} else {
		cerr << "Error: unknown rhythm" << element.name() << "@dur: " << dur << endl;
		return 0;
	}

	mei_staffDef& ss = m_scoreDef.staves.at(m_currentStaff - 1);
	int maximodus = ss.maximodus == 3 ? 3 : 2;
	int modus     = ss.modus     == 3 ? 3 : 2;
	int tempus    = ss.tempus    == 3 ? 3 : 2;
	int prolatio  = ss.prolatio  == 3 ? 3 : 2;

	bool altera     = false;
	bool perfecta   = false;
	bool imperfecta = false;

	if (durquality == "imperfecta") {
		imperfecta = true;
	} else if (durquality == "perfecta") {
		perfecta = true;
	} else if (durquality == "altera") {
		altera = true;
	}

	HumNum output = Convert::mensToDuration(rhythm, altera, perfecta, imperfecta, maximodus, modus, tempus, prolatio);
	return output;
}




//////////////////////////////
//
// Tool_mei2hum::parseVerse --
//

void Tool_mei2hum::parseVerse(xml_node verse, GridStaff* staff) {
	NODE_VERIFY(verse, )
	MAKE_CHILD_LIST(children, verse);

	string n = verse.attribute("n").value();
	int nnum = 1;
	if (n.empty()) {
		cerr << "Warning: no layer number on layer element" << endl;
	} else {
		nnum = stoi(n);
	}
	if (nnum < 1) {
		cerr << "Warning: invalid layer number: " << nnum << endl;
		cerr << "Setting it to 1." << endl;
		nnum = 1;
	}

	string versetext;
	int sylcount = 0;

	for (int i=0; i<(int)children.size(); i++) {
		string nodename = children[i].name();
		if (nodename == "syl") {
			if (sylcount > 0) {
				versetext += " ";
			}
			sylcount++;
			versetext += parseSyl(children[i]);
		} else {
			cerr << DKHTP << verse.name() << "/" << nodename << CURRLOC << endl;
		}
	}

	if (versetext == "") {
		// nothing to store
		return;
	}

	staff->setVerse(nnum-1, versetext);
	reportVerseNumber(nnum, m_currentStaff-1);

	return;
}



//////////////////////////////
//
// Tool_mei2hum::parseBareSyl -- Only one syl allows as a bar child of note element.
//     This function is used to process syl elements that are not wrapped in a verse element.
//

void Tool_mei2hum::parseBareSyl(xml_node syl, GridStaff* staff) {
	NODE_VERIFY(syl, )

	int nnum = 1;
	xml_attribute n_attr = syl.attribute("n");
	if (n_attr) {
		nnum = n_attr.as_int();
	}

	if (nnum < 1) {
		cerr << "Warning: invalid layer number: " << nnum << endl;
		cerr << "Setting it to 1." << endl;
		nnum = 1;
	}

	string versetext = parseSyl(syl);

	if (versetext == "") {
		// nothing to store
		return;
	}

	staff->setVerse(nnum-1, versetext);
	reportVerseNumber(nnum, m_currentStaff-1);

	return;
}



//////////////////////////////
//
// Tool_mei2hum::reportVerseNumber --
//

void Tool_mei2hum::reportVerseNumber(int pmax, int staffindex) {
	if (staffindex < 0) {
		return;
	}
	if (staffindex >= (int)m_maxverse.size()) {
		return;
	}
	if (m_maxverse.at(staffindex) < pmax) {
		m_maxverse[staffindex] = pmax;
	}
}



//////////////////////////////
//
// Tool_mei2hum::parseSyl --
//

string Tool_mei2hum::parseSyl(xml_node syl) {
	NODE_VERIFY(syl, "")
	MAKE_CHILD_LIST(children, syl);

	string text = syl.child_value();
	for (int i=0; i<(int)text.size(); i++) {
		if (text[i] == '_') {
			text[i] = ' ';
		}
	}

	string wordpos = syl.attribute("wordpos").value();
	if (wordpos == "i") {
		text = text + "-";
	} else if (wordpos == "m") {
		text = "-" + text + "-";
	} else if (wordpos == "t") {
		text = "-" + text;
	}

	return text;
}



//////////////////////////////
//
// Tool_mei2hum::parseClef --
//
//

void Tool_mei2hum::parseClef(xml_node clef, HumNum starttime) {
	NODE_VERIFY(clef, )

	string shape = clef.attribute("shape").value();
	string line = clef.attribute("line").value();
	string clefdis = clef.attribute("clef.dis").value();
	string clefdisplace = clef.attribute("clef.dis.place").value();

	string tok = makeHumdrumClef(shape, line, clefdis, clefdisplace);

	m_outdata.back()->addClefToken(tok, starttime QUARTER_CONVERT,
			m_currentStaff-1, 0, 0, m_staffcount);

}



//////////////////////////////
//
// Tool_mei2hum::makeHumdrumClef --
//
// Example:
//     <clef shape="G" line="2" clef.dis="8" clef.dis.place="below" />
//

string Tool_mei2hum::makeHumdrumClef(const string& shape,
		const string& line, const string& clefdis, const string& clefdisplace) {
	string output = "*clef" + shape;
	if (!clefdis.empty()) {
		int number = stoi(clefdis);
		int count = 0;
		if (number == 8) {
			count = 1;
		} else if (number == 15) {
			count = 2;
		}
		if (clefdisplace != "above") {
			count = -count;
		}
		switch (count) {
			case 1: output += "^"; break;
			case 2: output += "^^"; break;
			case -1: output += "v"; break;
			case -2: output += "vv"; break;
		}
	}
	output += line;
	return output;
}



//////////////////////////////
//
// Tool_mei2hum::parseChord --
//

HumNum Tool_mei2hum::parseChord(xml_node chord, HumNum starttime, int gracenumber) {
	NODE_VERIFY(chord, starttime)
	MAKE_CHILD_LIST(children, chord);

	processPreliminaryLinkedNodes(chord);

	HumNum duration = getDuration(chord);

	string tok;
	int counter = 0;
	for (int i=0; i<(int)children.size(); i++) {
		string nodename = children[i].name();
		if (nodename == "note") {
			counter++;
			if (counter > 1) {
				tok += " ";
			}
			parseNote(children[i], chord, tok, starttime, gracenumber);
		} else if (nodename == "artic") {
			// This is handled within parseNote();
		} else {
			cerr << DKHTP << chord.name() << "/" << nodename << CURRLOC << endl;
		}
	}

	m_fermata = false;
	processLinkedNodes(tok, chord);
	if (!m_fermata) {
		processFermataAttribute(tok, chord);
	}

	m_outdata.back()->addDataToken(tok, starttime QUARTER_CONVERT, m_currentStaff-1,
		0, m_currentLayer-1, m_staffcount);

	return starttime + duration;
}



//////////////////////////////
//
// Tool_mei2hum::getChildrenVector -- Return a list of all children elements
//   of a given element.  Pugixml does not allow random access, but storing
//   them in a vector allows that possibility.
//

void Tool_mei2hum::getChildrenVector(vector<xml_node>& children,
		xml_node parent) {
	children.clear();
	for (xml_node child : parent.children()) {
		children.push_back(child);
	}
}



//////////////////////////////
//
// Tool_mei2hum::initialize -- Setup for the tool, mostly parsing command-line
//   (input) options.
//

void Tool_mei2hum::initialize(void) {
	m_recipQ   =  getBoolean("recip");
	m_stemsQ   =  getBoolean("stems");
	m_xmlidQ   =  getBoolean("xmlids");
	m_xmlidQ   = 1;  // for testing
	m_appLabel =  getString("app-label");
	m_placeQ   = !getBoolean("no-place");
}



//////////////////////////////
//
// Tool_mei2hum::buildIdLinkMap -- Build table of startid and endid links between elements.
//
// Reference: https://pugixml.org/docs/samples/traverse_walker.cpp
//

void Tool_mei2hum::buildIdLinkMap(xml_document& doc) {
	class linkmap_walker : public pugi::xml_tree_walker {
		public:
			virtual bool for_each(pugi::xml_node& node) {
				xml_attribute startid = node.attribute("startid");
				xml_attribute endid = node.attribute("endid");
				if (startid) {

					string value = startid.value();
					if (!value.empty()) {
						if (value[0] == '#') {
							value = value.substr(1, string::npos);
						}
					}
					if (!value.empty()) {
						(*startlinks)[value].push_back(node);
					}

				}
				if (endid) {

					string value = endid.value();
					if (!value.empty()) {
						if (value[0] == '#') {
							value = value.substr(1, string::npos);
						}
					}
					if (!value.empty()) {
						(*stoplinks)[value].push_back(node);
					}

				}
				return true; // continue traversal
			}

			map<string, vector<xml_node>>* startlinks = NULL;
			map<string, vector<xml_node>>* stoplinks = NULL;
	};

	m_startlinks.clear();
	m_stoplinks.clear();
	linkmap_walker walker;
	walker.startlinks = &m_startlinks;
	walker.stoplinks = &m_stoplinks;
	doc.traverse(walker);
}



//////////////////////////////
//
// Tool_mei2hum::parseDir -- Meter cannot change in middle of measure.
//     Need to implement @startid version.
//
// Example:
//    <dir xml:id="dir-L408F3" place="below" staff="1" tstamp="2.0">con espressione</dir>
//
// or with normal font specified:
//    <dir xml:id="dir-L7F1" staff="1" tstamp="2.000000">
//       <rend xml:id="rend-0000001696821523" fontstyle="normal">test</rend>
//    </dir>
//
// bold font:
//   <dir xml:id="dir-L25F3" place="above" staff="1" tstamp="3.000000">
//      <rend xml:id="rend-0000001714819172" fontstyle="normal" fontweight="bold">comment</rend>
//   </dir>
//

void Tool_mei2hum::parseDir(xml_node dir, HumNum starttime) {
	NODE_VERIFY(dir, )
	MAKE_CHILD_LIST(children, dir);

	string font = "i";  // italic by default in verovio

	string placement = ""; // a = above, b = below

	string place = dir.attribute("place").value();
	if (place == "above") {
		placement = "a:";
	}
	// Below is the default in Humdrum layout commands.

	string text;

	if (!children.empty()) { // also includes the above text node, but only looking at <rend>.
		int count = 0;
		for (int i=0; i<(int)children.size(); i++) {
			string nodename = children[i].name();
			if (nodename == "rend") {
				if (count) {
					text += " ";
				}
				count++;
				text += children[i].child_value();
				if (strcmp(children[i].attribute("fontstyle").value(), "normal") == 0) {
					font = "";  // normal is default in Humdrum layout
				}
				if (strcmp(children[i].attribute("fontweight").value(), "bold") == 0) {
					font += "B";  // normal is default in Humdrum layout
				}
			} else if (nodename == "") {
				// text node
				if (count) {
					text += " ";
				}
				count++;
				text += children[i].value();
			} else {
				cerr << DKHTP << dir.name() << "/" << nodename << CURRLOC << endl;
			}
		}
	}

	if (text.empty()) {
		return;
	}

	string message = "!LO:TX:";
	message += placement;
	if (!font.empty()) {
		message += font + ":";
	}
	message += "t=" + cleanDirText(text);

	string ts = dir.attribute("tstamp").value();
	if (ts.empty()) {
		cerr << "Error: no timestamp on dir element and can't currently processes with @startid." << endl;
		return;
	}

	xml_attribute atstaffnum = dir.attribute("staff");
	if (!atstaffnum) {
		cerr << "Error: staff number required on dir element in measure "
		     << m_currentMeasure  << " (ignoring text: " << cleanWhiteSpace(text) << ")" << endl;
		return;
	}
	int staffnum = dir.attribute("staff").as_int();
	if (staffnum <= 0) {
		cerr << "Error: staff number on dir element in measure should be positive.\n";
		cerr << "Instead the staff number is: " << m_currentMeasure  << " (ignoring text: " <<  cleanWhiteSpace(text) << ")" << endl;
		return;
	}

	double meterunit = m_currentMeterUnit[staffnum - 1];
	double tsd = (stof(ts)-1) * 4.0 / meterunit;

	GridMeasure* gm = m_outdata.back();
	double tsm = gm->getTimestamp().getFloat();
	bool foundslice = false;
	GridSlice* gs;
	for (auto gsit = gm->begin(); gsit != gm->end(); gsit++) {
		gs = *gsit;
		if (!gs->isDataSlice()) {
			continue;
		}
		double gsts = gs->getTimestamp().getFloat();
		double difference = (gsts-tsm) - tsd;
		if (!(fabs(difference) < 0.0001)) {
			continue;
		}
		// GridVoice* voice = gs->at(staffnum-1)->at(0)->at(0);
		// HTp token = voice->getToken();
		// if (token != NULL) {
		// 	token->setValue("LO", "TX", "t", text);
		// } else {
		// 	cerr << "Strange null-token error while inserting dir element." << endl;
		// }
		foundslice = true;

		// Found data line which should prefixed with a layout line
		// should be done with HumHash post-processing, but do it manually for now.

		auto previousit = gsit;
		previousit--;
		if (previousit == gm->end()) {
			previousit = gsit;
		}
		auto previous = *previousit;
		if (previous->isLayoutSlice()) {
			GridVoice* voice = previous->at(staffnum-1)->at(0)->at(0);
			HTp tok = voice->getToken();
			if (tok == NULL) {
				HTp newtok = new HumdrumToken(message);
				voice->setToken(newtok);
				tok = voice->getToken();
				break;
			} else if (tok->isNull()) {
				tok->setText(message);
				break;
			}
		}

		// Insert a layout slice in front of current data slice.
		GridSlice* ngs = new GridSlice(gm, gs->getTimestamp(), SliceType::Layouts, m_maxStaffInFile);
		int parti = staffnum - 1;
		int staffi = 0;
		int voicei = 0;
		ngs->addToken(message, parti, staffi, voicei);
		gm->insert(gsit, ngs);

		break;
	}
	if (!foundslice) {
		cerr << "Warning: dir elements not occuring at note/rest times are not yet supported" << endl;
	}
}



//////////////////////////////
//
// Tool_mei2hum::cleanWhiteSpace -- Convert newlines to "\n", and trim spaces.
//    Also remove more than one space in a row.
//

string Tool_mei2hum::cleanWhiteSpace(const string& input) {
	string output;
	output.reserve(input.size() + 8);
	bool foundstart = false;
	for (int i=0; i<(int)input.size(); i++) {
		if ((!foundstart) && std::isspace(input[i])) {
			continue;
		}
		foundstart = true;
		if (input[i] == '\t') {
			if ((!output.empty()) && (output.back() != ' ')) {
				output += ' ';
			}
		} else if (input[i] == '\n') {
			if ((!output.empty()) && (output.back() != ' ')) {
				output += ' ';
			}
		} else if (input[i] == ' ') {
			if ((!output.empty()) && (output.back() != ' ')) {
				output += ' ';
			}
		} else {
			output += input[i];
		}
	}
	while ((!output.empty()) && (output.back() == ' ')) {
		output.pop_back();
	}

	return output;
}



//////////////////////////////
//
// Tool_mei2hum::cleanDirText -- convert ":" to "&colon;".
//     Remove tabs and newlines, and trim spaces.  Maybe allow
//     newlines using "\n" and allow font changes in the future.
//     Remove redundant whitespace. Do accents later perhaps or
//     monitor for UTF-8.
//

string Tool_mei2hum::cleanDirText(const string& input) {
	string output;
	output.reserve(input.size() + 8);
	bool foundstart = false;
	for (int i=0; i<(int)input.size(); i++) {
		if ((!foundstart) && std::isspace(input[i])) {
			continue;
		}
		foundstart = true;
		if (input[i] == ':') {
			output += "&colon;";
		} else if (input[i] == '\t') {
			if ((!output.empty()) && (output.back() != ' ')) {
				output += ' ';
			}
		} else if (input[i] == '\n') {
			if ((!output.empty()) && (output.back() != ' ')) {
				output += ' ';
			}
		} else if (input[i] == ' ') {
			if ((!output.empty()) && (output.back() != ' ')) {
				output += ' ';
			}
		} else {
			output += input[i];
		}
	}
	while ((!output.empty()) && (output.back() == ' ')) {
		output.pop_back();
	}

	return output;
}



//////////////////////////////
//
// Tool_mei2hum::cleanVerseText --
//     Remove tabs and newlines, and trim spaces.
//     Do accents later perhaps or monitor for UTF-8.
//

string Tool_mei2hum::cleanVerseText(const string& input) {
	string output;
	output.reserve(input.size() + 8);
	bool foundstart = false;
	for (int i=0; i<(int)input.size(); i++) {
		if ((!foundstart) && std::isspace(input[i])) {
			continue;
		}
		foundstart = true;
		if (input[i] == '\t') {
			output += ' ';
		} else if (input[i] == '\n') {
			output += ' ';
		} else {
			output += input[i];
		}
	}
	while ((!output.empty()) && (output.back() == ' ')) {
		output.pop_back();
	}

	return output;
}



//////////////////////////////
//
// Tool_mei2hum::cleanReferenceRecordText -- convert ":" to "&colon;".
//     Remove tabs and newlines, and trim spaces.  Maybe allow
//     newlines using "\n" and allow font changes in the future.
//     Do accents later perhaps or monitor for UTF-8.
//

string Tool_mei2hum::cleanReferenceRecordText(const string& input) {
	string output;
	output.reserve(input.size() + 8);
	bool foundstart = false;
	char lastchar = '\0';
	for (int i=0; i<(int)input.size(); i++) {
		if ((!foundstart) && std::isspace(input[i])) {
			continue;
		}
		foundstart = true;
		if (input[i] == '\n') {
			if (lastchar != ' ') {
				output += ' ';
			}
			lastchar = ' ';
		} else if (input[i] == '\t') {
			if (lastchar != ' ') {
				output += ' ';
			}
			lastchar = ' ';
		} else {
			output += input[i];
			lastchar = input[i];
		}
	}
	while ((!output.empty()) && (output.back() == ' ')) {
		output.pop_back();
	}

	return output;
}



//////////////////////////////
//
// Tool_mei2hum::parseTempo --
//
// Example:
//   <tempo tstamp="1" place="above" staff="1">
//      1 - Allegro con spirito <rend fontname="VerovioText">&#xE1D5;</rend> = 132
//   </tempo>
//
//
// Ways of indicating tempo:
//
// tempo@midi.bpm == tempo per quarter note (Same as Humdrum *MM value)
//
// tempo@midi.mspb == microseconds per quarter note ( bpm = mspb * 60 / 1000000)
//
// tempo@mm == tempo per beat (bpm = mm / unit(dots))
// tempo@mm.unit == beat unit for tempo@mm
// tempo@mm.dots == dots for tempo@unit
//
// Free-form text:
//
// &#xE1D5; == quarter note
//
// #define SMUFL_QUARTER_NOTE "\ue1d5"

void Tool_mei2hum::parseTempo(xml_node tempo, HumNum starttime) {
	NODE_VERIFY(tempo, )

	bool found = false;
	double value = 0.0;

	xml_attribute bpm = tempo.attribute("bpm");
	if (bpm) {
		value = bpm.as_double();
		if (value > 0.0) {
			found = true;
		}
	}

	if (!found) {
		xml_attribute mspb   = tempo.attribute("mspb");
		value = mspb.as_double() * 60.0 / 1000000.0;
		if (value > 0.0) {
			found = true;
		}
	}

	if (!found) {
		xml_attribute mm     = tempo.attribute("mm");
		xml_attribute mmunit = tempo.attribute("mm.unit");
		xml_attribute mmdots = tempo.attribute("mm.dots");
		value = mm.as_double();
		string recip = mmunit.value();
		int dcount = mmdots.as_int();
		for (int i=0; i<dcount; i++) {
			recip += '.';
		}
		HumNum duration = Convert::recipToDuration(recip);
		value *= duration.getFloat();
		if (value > 0.0) {
			found = true;
		}
	}

	if (!found) {
		// search for free-form tempo marking.  Something like:
		//   <tempo tstamp="1" place="above" staff="1">
		//      1 - Allegro con spirito <rend fontname="VerovioText">&#xE1D5;</rend> = 132
		//   </tempo>
		//
		// UTF-8 version in string "\ue1d5";
		string text;

		MAKE_CHILD_LIST(children, tempo);
		for (int i=0; i<(int)children.size(); i++) {
			if (children[i].type() == pugi::node_pcdata) {
				text += children[i].value();
			} else {
				text += children[i].child_value();
			}
			text += " ";

		}
		HumRegex hre;
		// #define SMUFL_QUARTER_NOTE "\ue1d5"
		// if (hre.search(text, SMUFL_QUARTER_NOTE "\\s*=\\s*(\\d+\\.?\\d*)")) {
		if (hre.search(text, "\\s*=\\s*(\\d+\\.?\\d*)")) {
			// assuming quarter note for now.
			value = hre.getMatchDouble(1);
			found = true;
		}
		// further rhythmic values for tempo should go here.
	}

	// also deal with tempo designiations such as "Allegro"...

	if (!found) {
		// no tempo to set
		return;
	}

	// insert tempo
	GridMeasure* gm = m_outdata.back();
	GridSlice* gs = new GridSlice(gm, starttime, SliceType::Tempos, m_maxStaffInFile);
	stringstream stok;
	stok << "*MM" << value;
	string token = stok.str();

	for (int i=0; i<m_maxStaffInFile; i++) {
		gs->at(i)->at(0)->at(0)->setToken(token);
	}

	// insert after time signature at same timestamp if possible
	bool inserted = false;
	for (auto it = gm->begin(); it != gm->end(); it++) {
		if ((*it)->getTimestamp() > starttime) {
			gm->insert(it, gs);
			inserted = true;
			break;
		} else if ((*it)->isTimeSigSlice()) {
			it++;
			gm->insert(it, gs);
			inserted = true;
			break;
		} else if (((*it)->getTimestamp() == starttime) && ((*it)->isNoteSlice()
				|| (*it)->isGraceSlice())) {
			gm->insert(it, gs);
			inserted = true;
			break;
		}
	}

	if (!inserted) {
		gm->push_back(gs);
	}

}



//////////////////////////////
//
// Tool_mei2hum::parseHarm -- Not yet ready to convert <harm> data.
//    There will be different types of harm (such as figured bass), which
//    will need to be subcategorized into different datatypes, such as
//    *fb for figured bass.  Also free-text can be present in <harm>
//    data, so the current datatype for that is **cdata  (meaning chord-like
//    data that will be mapped back into <harm> which converting back to
//    MEI data.
//
// Example:
//     <harm staff="1" tstamp="1.000000">C major</harm>
//

void Tool_mei2hum::parseHarm(xml_node harm, HumNum starttime) {
	NODE_VERIFY(harm, )
	MAKE_CHILD_LIST(children, harm);

	string text = harm.child_value();

	if (text.empty()) { // looking at <rend> sub-elements
		int count = 0;
		for (int i=0; i<(int)children.size(); i++) {
			string nodename = children[i].name();
			if (nodename == "rend") {
				if (count) {
					text += " ";
				}
				count++;
				text += children[i].child_value();
				//if (strcmp(children[i].attribute("fontstyle").value(), "normal") == 0) {
				//	font = "";  // normal is default in Humdrum layout
				//}
				//if (strcmp(children[i].attribute("fontweight").value(), "bold") == 0) {
				//	font += "B";  // normal is default in Humdrum layout
				//}
			} else if (nodename == "") {
				// text node
				if (count) {
					text += " ";
				}
				count++;
				text += children[i].value();
			} else {
				cerr << DKHTP << harm.name() << "/" << nodename << CURRLOC << endl;
			}
		}
	}

	if (text.empty()) {
		return;
	}

   // cerr << "FOUND HARM DATA " << text << endl;

/*

	string startid = harm.attribute("startid").value();

	int staffnum = harm.attribute("staff").as_int();
	if (staffnum == 0) {
		cerr << "Error: staff number required on harm element" << endl;
		return;
	}
	double meterunit = m_currentMeterUnit[staffnum - 1];

	if (!startid.empty()) {
		// Harmony is (or at least should) be attached directly
		// do a note, so it is handled elsewhere.
		cerr << "Warning DYNAMIC " << text << " is not yet processed." << endl;
		return;
	}

	string ts = harm.attribute("tstamp").value();
	if (ts.empty()) {
		cerr << "Error: no timestamp on harm element" << endl;
		return;
	}
	double tsd = (stof(ts)-1) * 4.0 / meterunit;
	double tolerance = 0.001;
	GridMeasure* gm = m_outdata.back();
	double tsm = gm->getTimestamp().getFloat();
	bool foundslice = false;
	GridSlice *nextgs = NULL;
	for (auto gs : *gm) {
		if (!gs->isDataSlice()) {
			continue;
		}
		double gsts = gs->getTimestamp().getFloat();
		double difference = (gsts-tsm) - tsd;
		if (difference < tolerance) {
			// did not find data line at exact timestamp, so move
			// the harm to the next event. Need to think about adding
			// a new timeslice for the harm when it is not attached to
			// a note.
			nextgs = gs;
			break;
		}
		if (!(fabs(difference) < tolerance)) {
			continue;
		}
		GridPart* part = gs->at(staffnum-1);
		part->setHarmony(text);
		m_outdata.setHarmonyPresent(staffnum-1);
		foundslice = true;
		break;
	}
	if (!foundslice) {
		if (nextgs == NULL) {
			cerr << "Warning: harmony not attched to system events "
					<< "are not yet supported in measure " << m_currentMeasure << endl;
		} else {
			GridPart* part = nextgs->at(staffnum-1);
			part->setHarmony(text);
			m_outdata.setHarmonyPresent(staffnum-1);
			// Give a time offset for displaying the harmmony here.
		}
	}
*/

}



//////////////////////////////
//
// Tool_mei2hum::parseDynam --
//
// Example:
//     <dynam staff="1" tstamp="1.000000">p</dynam>
//

void Tool_mei2hum::parseDynam(xml_node dynam, HumNum starttime) {
	NODE_VERIFY(dynam, )
	MAKE_CHILD_LIST(children, dynam);

	string text = dynam.child_value();

	if (text.empty()) { // looking at <rend> sub-elements
		int count = 0;
		for (int i=0; i<(int)children.size(); i++) {
			string nodename = children[i].name();
			if (nodename == "rend") {
				if (count) {
					text += " ";
				}
				count++;
				text += children[i].child_value();
				//if (strcmp(children[i].attribute("fontstyle").value(), "normal") == 0) {
				//	font = "";  // normal is default in Humdrum layout
				//}
				//if (strcmp(children[i].attribute("fontweight").value(), "bold") == 0) {
				//	font += "B";  // normal is default in Humdrum layout
				//}
			} else if (nodename == "") {
				// text node
				if (count) {
					text += " ";
				}
				count++;
				text += children[i].value();
			} else {
				cerr << DKHTP << dynam.name() << "/" << nodename << CURRLOC << endl;
			}
		}
	}

	if (text.empty()) {
		return;
	}

	string startid = dynam.attribute("startid").value();

	int staffnum = dynam.attribute("staff").as_int();
	if (staffnum == 0) {
		cerr << "Error: staff number required on dynam element" << endl;
		return;
	}
	double meterunit = m_currentMeterUnit[staffnum - 1];

	if (!startid.empty()) {
		// Dynamic is (or at least should) be attached directly
		// do a note, so it is handled elsewhere.
		cerr << "Warning DYNAMIC " << text << " is not yet processed." << endl;
		return;
	}

	string ts = dynam.attribute("tstamp").value();
	if (ts.empty()) {
		cerr << "Error: no timestamp on dynam element" << endl;
		return;
	}
	double tsd = (stof(ts)-1) * 4.0 / meterunit;
	double tolerance = 0.001;
	GridMeasure* gm = m_outdata.back();
	double tsm = gm->getTimestamp().getFloat();
	bool foundslice = false;
	GridSlice *nextgs = NULL;
	for (auto gs : *gm) {
		if (!gs->isDataSlice()) {
			continue;
		}
		double gsts = gs->getTimestamp().getFloat();
		double difference = (gsts-tsm) - tsd;
		if (difference < tolerance) {
			// did not find data line at exact timestamp, so move
			// the dynamic to the next event. Maybe think about adding
			// a new timeslice for the dynamic.
			nextgs = gs;
			break;
		}
		if (!(fabs(difference) < tolerance)) {
			continue;
		}
		GridPart* part = gs->at(staffnum-1);
		part->setDynamics(text);
		m_outdata.setDynamicsPresent(staffnum-1);
		foundslice = true;
		break;
	}
	if (!foundslice) {
		if (nextgs == NULL) {
			cerr << "Warning: dynamics not attched to system events "
					<< "are not yet supported in measure " << m_currentMeasure << endl;
		} else {
			GridPart* part = nextgs->at(staffnum-1);
			part->setDynamics(text);
			m_outdata.setDynamicsPresent(staffnum-1);
			// Give a time offset for displaying the dynamic here.
		}
	}
}





/////////////////////////////////
//
// Tool_gridtest::Tool_melisma -- Set the recognized options for the tool.
//

Tool_melisma::Tool_melisma(void) {
	define("m|min=i:2",        "minimum length to identify as a melisma");
	define("r|replace=b",      "replace lyrics with note counts");
	define("a|average|avg=b",  "calculate note-to-syllable ratio");
	define("w|words=b",        "list words that contain a melisma");
	define("p|part=b",         "also calculate note-to-syllable ratios by part");
}



///////////////////////////////
//
// Tool_melisma::run -- Primary interfaces to the tool.
//

bool Tool_melisma::run(HumdrumFileSet& infiles) {
	bool status = true;
	for (int i=0; i<infiles.getCount(); i++) {
		status &= run(infiles[i]);
	}
	return status;
}


bool Tool_melisma::run(const string& indata, ostream& out) {
	HumdrumFile infile(indata);
	return run(infile, out);
}


bool Tool_melisma::run(HumdrumFile& infile, ostream& out) {
	bool status = run(infile);
	return status;
}


bool Tool_melisma::run(HumdrumFile& infile) {
   initialize(infile);
	processFile(infile);
	return true;
}



//////////////////////////////
//
// Tool_melisma::initialize --
//

void Tool_melisma::initialize(HumdrumFile& infile) {
	// do nothing for now
}



//////////////////////////////
//
// Tool_melisma::processFile --
//

void Tool_melisma::processFile(HumdrumFile& infile) {
	vector<vector<int>> notecount;
	getNoteCounts(infile, notecount);
	vector<WordInfo> wordinfo;
	wordinfo.reserve(1000);
	map<string, int> wordlist;
	initializePartInfo(infile);

	if (getBoolean("replace")) {
		replaceLyrics(infile, notecount);
	} else if (getBoolean("words")) {
		markMelismas(infile, notecount);
		extractWordlist(wordinfo, wordlist, infile, notecount);
		printWordlist(infile, wordinfo, wordlist);
	} else {
		markMelismas(infile, notecount);
	}

}



//////////////////////////////
//
// Tool_melisma::initializePartInfo --
//

void Tool_melisma::initializePartInfo(HumdrumFile& infile) {
	m_names.clear();
	m_abbreviations.clear();
	m_partnums.clear();

	m_names.resize(infile.getTrackCount() + 1);
	m_abbreviations.resize(infile.getTrackCount() + 1);
	m_partnums.resize(infile.getTrackCount() + 1);
	fill(m_partnums.begin(), m_partnums.end(), -1);

	vector<HTp> starts;
	infile.getSpineStartList(starts);
	int ktrack = 0;
	int track = 0;
	int part = 0;
	for (int i=0; i<(int)starts.size(); i++) {
		track = starts[i]->getTrack();
		if (starts[i]->isKern()) {
			ktrack = track;
			part++;
			m_partnums[ktrack] = part;
			HTp current = starts[i];
			while (current) {
				if (current->isData()) {
					break;
				}
				if (current->compare(0, 3, "*I\"") == 0) {
					m_names[ktrack] = current->substr(3);
				} else if (current->compare(0, 3, "*I\'") == 0) {
					m_abbreviations[ktrack] = current->substr(3);
				}
				current = current->getNextToken();
			}
		} else if (ktrack) {
			m_names[track] = m_names[ktrack];
			m_abbreviations[track] = m_abbreviations[ktrack];
			m_partnums[track] = m_partnums[ktrack];
		}
	}

}



//////////////////////////////
//
// printWordlist --
//

void Tool_melisma::printWordlist(HumdrumFile& infile, vector<WordInfo>& wordinfo,
		map<string, int> words) {

	// for (auto& item : words) {
	// 	m_free_text << item.first;
	// 	if (item.second > 1) {
	// 		m_free_text << " (" << item.second << ")";
	// 	}
	// 	m_free_text << endl;
	// }

	vector<int> ncounts;
	vector<int> mcounts;
	getMelismaNoteCounts(ncounts, mcounts, infile);

	// m_free_text << "===========================" << endl;

	std::vector<HTp> kspines = infile.getKernSpineStartList();

	m_free_text << "@@BEGIN:\tMELISMAS\n";

	string filename = infile.getFilename();
	auto pos = filename.rfind("/");
	if (pos != string::npos) {
		filename = filename.substr(pos+1);
	}
	m_free_text << "@FILENAME:\t" << filename << endl;
	m_free_text << "@PARTCOUNT:\t" << kspines.size() << endl;
	m_free_text << "@WORDCOUNT:\t" << wordinfo.size() << endl;
	m_free_text << "@SCOREDURATION:\t" << getScoreDuration(infile) << endl;
	m_free_text << "@NOTES:\t\t" << ncounts[0] << endl;
	m_free_text << "@MELISMANOTES:\t" << mcounts[0] << endl;

	m_free_text << "@MELISMASCORE:\t" << int((double)mcounts[0] / (double)ncounts[0] * 1000.0 + 0.5)/10.0 << "%" << endl;
	for (int i=1; i<(int)m_partnums.size(); i++) {
		if (m_partnums[i] == 0) {
			continue;
		}
		if (m_partnums[i] == m_partnums[i-1]) {
			continue;
		}
		m_free_text << "@PARTSCORE-" << m_partnums[i] << ":\t" << int((double)mcounts[i] / (double)ncounts[i] * 1000.0 + 0.5)/10.0 << "%" << endl;
	}

	for (int i=1; i<(int)m_partnums.size(); i++) {
		if (m_partnums[i] == 0) {
			continue;
		}
		if (m_partnums[i] == m_partnums[i-1]) {
			continue;
		}
		m_free_text << "@PARTNAME-" << m_partnums[i] << ":\t" << m_names[i] << endl;
	}

	for (int i=1; i<(int)m_partnums.size(); i++) {
		if (m_partnums[i] == 0) {
			continue;
		}
		if (m_partnums[i] == m_partnums[i-1]) {
			continue;
		}
		m_free_text << "@PARTABBR-" << m_partnums[i] << ":\t" << m_abbreviations[i] << endl;
	}

	m_free_text << endl;

	for (int i=0; i<(int)wordinfo.size(); i++) {
		m_free_text << "@@BEGIN:\tWORD\n";
		m_free_text << "@PARTNUM:\t" << wordinfo[i].partnum << endl;
		// m_free_text << "@NAME:\t\t" << wordinfo[i].name << endl;
		// m_free_text << "@ABBR:\t\t" << wordinfo[i].abbreviation << endl;
		m_free_text << "@WORD:\t\t" << wordinfo[i].word << endl;
		m_free_text << "@STARTTIME:\t" << wordinfo[i].starttime.getFloat() << endl;
		m_free_text << "@ENDTIME:\t" << wordinfo[i].endtime.getFloat() << endl;
		m_free_text << "@STARTBAR:\t" << wordinfo[i].bar << endl;

		m_free_text << "@SYLLABLES:\t";
		for (int j=0; j<(int)wordinfo[i].syllables.size(); j++) {
			m_free_text << wordinfo[i].syllables[j];
			if (j < (int)wordinfo[i].syllables.size() - 1) {
				m_free_text << " ";
			}
		}
		m_free_text << endl;

		m_free_text << "@NOTECOUNTS:\t";
		for (int j=0; j<(int)wordinfo[i].notecounts.size(); j++) {
			m_free_text << wordinfo[i].notecounts[j];
			if (j < (int)wordinfo[i].notecounts.size() - 1) {
				m_free_text << " ";
			}
		}
		m_free_text << endl;

		m_free_text << "@BARLINES:\t";
		for (int j=0; j<(int)wordinfo[i].bars.size(); j++) {
			m_free_text << wordinfo[i].bars[j];
			if (j < (int)wordinfo[i].bars.size() - 1) {
				m_free_text << " ";
			}
		}
		m_free_text << endl;

		m_free_text << "@STARTTIMES:\t";
		for (int j=0; j<(int)wordinfo[i].starttimes.size(); j++) {
			m_free_text << wordinfo[i].starttimes[j].getFloat();
			if (j < (int)wordinfo[i].starttimes.size() - 1) {
				m_free_text << " ";
			}
		}
		m_free_text << endl;

		m_free_text << "@ENDTIMES:\t";
		for (int j=0; j<(int)wordinfo[i].endtimes.size(); j++) {
			m_free_text << wordinfo[i].endtimes[j].getFloat();
			if (j < (int)wordinfo[i].endtimes.size() - 1) {
				m_free_text << " ";
			}
		}
		m_free_text << endl;

		m_free_text << "@@END:\tWORD\n";
		m_free_text << endl;
	}

	m_free_text << "@@END:\tMELISMAS\n";
	m_free_text << endl;
}



//////////////////////////////
//
// Tool_melisma::getScoreDuration --
//

double Tool_melisma::getScoreDuration(HumdrumFile& infile) {
	double output = 0.0;
	for (int i=infile.getLineCount() - 1; i>=0; i--) {
		if (!infile[i].isData()) {
			continue;
		}
		output = (infile[i].getDurationFromStart() + infile[i].getDuration()).getFloat();
		break;
	}
	return output;
}



//////////////////////////////
//
// Tool_melisma::getMelismaNoteCounts --
//

void Tool_melisma::getMelismaNoteCounts(vector<int>& ncounts, vector<int>& mcounts, HumdrumFile& infile) {
	ncounts.resize(infile.getTrackCount() + 1);
	mcounts.resize(infile.getTrackCount() + 1);
	fill(ncounts.begin(), ncounts.end(), 0);
	fill(mcounts.begin(), mcounts.end(), 0);
	vector<HTp> starts = infile.getKernSpineStartList();
	for (int i=0; i<(int)starts.size(); i++) {
		HTp current = starts[i];
		int track = current->getTrack();
		while (current) {
			if (!current->isData()) {
				current = current->getNextToken();
				continue;
			}
			if (current->isNull()) {
				current = current->getNextToken();
				continue;
			}
			if (current->isRest()) {
				current = current->getNextToken();
				continue;
			}
			if (!current->isNoteAttack()) {
				current = current->getNextToken();
				continue;
			}
			ncounts[track]++;
			if (current->find("@") != string::npos) {
				mcounts[track]++;
			}
			current = current->getNextToken();
		}
	}

	for (int i=1; i<(int)mcounts.size(); i++) {
		mcounts[0] += mcounts[i];
		ncounts[0] += ncounts[i];
	}
}



//////////////////////////////
//
// Tool_melisma::extractWordlist --
//

void Tool_melisma::extractWordlist(vector<WordInfo>& wordinfo, map<string, int>& wordlist,
		HumdrumFile& infile, vector<vector<int>>& notecount) {
	int mincount = getInteger("min");
	if (mincount < 2) {
		mincount = 2;
	}
	string word;
	vector<HumNum> lastTimes;
	lastTimes.resize(infile.getMaxTrack() + 1);
	std::fill(lastTimes.begin(), lastTimes.end(), -1);
	WordInfo winfo;
	for (int i=0; i<(int)notecount.size(); i++) {
		for (int j=0; j<(int)notecount[i].size(); j++) {
			if (notecount[i][j] < mincount) {
				continue;
			}
			HTp token = infile.token(i, j);
			word = extractWord(winfo, token, notecount);
			int track = token->getTrack();
			if (winfo.starttime == lastTimes.at(track)) {
				// Exclude duplicate entries of words (multiple melismas in same word).
				continue;
			}
			lastTimes.at(track) = winfo.starttime;
			wordlist[word]++;
			winfo.name = m_names[track];
			winfo.abbreviation = m_abbreviations[track];
			winfo.partnum = m_partnums[track];
			wordinfo.push_back(winfo);
		}
	}
}



//////////////////////////////
//
// Tool_melisma::extractWord --
//

string Tool_melisma::extractWord(WordInfo& winfo, HTp token, vector<vector<int>>& counts) {
	winfo.clear();
	string output = *token;
	string syllable;
	HTp current = token;
	while (current) {
		if (!current->isData()) {
			current = current->getPreviousToken();
			continue;
		}
		if (current->isNull()) {
			current = current->getPreviousToken();
			continue;
		}
		syllable = *current;
		auto pos = syllable.rfind(" ");
		if (pos != string::npos) {
			syllable = syllable.substr(pos + 1);
		}
		if (syllable.size() > 0) {
			if (syllable.at(0) == '-') {
				current = current->getPreviousToken();
				continue;
			} else {
				// found start of word
				break;
			}
		} else {
			// some strange problem
			break;
		}
	}
	if (!current) {
		// strange problem (no start of word)
		return "";
	}
	if (syllable.size() == 0) {
		return "";
	}

	winfo.starttime = current->getDurationFromStart();
	int line = current->getLineIndex();
	int field = current->getFieldIndex();
	winfo.endtime = m_endtimes[line][field];
	winfo.bar = m_measures[line];

	transform(syllable.begin(), syllable.end(), syllable.begin(), ::tolower);
	if (syllable.back() == '-') {
		syllable.resize(syllable.size() - 1);
		winfo.syllables.push_back(syllable);
		winfo.starttimes.push_back(current->getDurationFromStart());
		winfo.endtimes.push_back(m_endtimes[line][field]);
		winfo.notecounts.push_back(counts[line][field]);
		winfo.bars.push_back(m_measures[line]);
	} else {
		// single-syllable word
		winfo.endtime = getEndtime(current);
		transform(syllable.begin(), syllable.end(), syllable.begin(), ::tolower);
		winfo.word = syllable;
		winfo.syllables.push_back(syllable);
		winfo.starttimes.push_back(current->getDurationFromStart());
		winfo.endtimes.push_back(m_endtimes[line][field]);
		winfo.notecounts.push_back(counts[line][field]);
		winfo.bars.push_back(m_measures[line]);
		return syllable;
	}
	output = syllable;
	HumRegex hre;

	current = current->getNextToken();
	while (current) {
		if (!current->isData()) {
			current = current->getNextToken();
			continue;
		}
		if (current->isNull()) {
			current = current->getNextToken();
			continue;
		}
		syllable = *current;

		auto pos = syllable.find(" ");
		if (pos != string::npos) {
			syllable = syllable.substr(0, pos);
		}

		// if there is an elision of words and the second word is more
		// than one syllable, then end the word at the apostrophe.
		pos = syllable.find("'");
		if (pos != string::npos) {
			if (syllable.back() == '-') {
				syllable = syllable.substr(0, pos+1);
			}
		}

		if (syllable.size() == 0) {
			// strange problem
			return "";
		}
		if (syllable.at(0) != '-') {
			// word was not terminated properly?
			cerr << "Syllable error at syllable : " << syllable;
			cerr << ", line: " << current->getLineNumber();
			cerr << ", field: " << current->getFieldNumber();
			cerr << endl;
		} else {
			syllable = syllable.substr(1);
		}
		transform(syllable.begin(), syllable.end(), syllable.begin(), ::tolower);
		winfo.endtime = getEndtime(current);
		hre.replaceDestructive(syllable, "", "[<>.:?!;,\"]", "g");
		winfo.syllables.push_back(syllable);
		winfo.starttimes.push_back(current->getDurationFromStart());
		int cline = current->getLineIndex();
		int cfield = current->getFieldIndex();
		winfo.endtimes.push_back(m_endtimes[cline][cfield]);
		winfo.notecounts.push_back(counts[cline][cfield]);
		winfo.bars.push_back(m_measures[cline]);
		output += syllable;
		if (output.back() == '-') {
			output.resize(output.size() - 1);
			current = current->getNextToken();
			winfo.syllables.back().resize((int)winfo.syllables.back().size() - 1);
			continue;
		} else {
			// last syllable in word
			break;
		}
	}

	winfo.word = output;
	return output;
}



//////////////////////////////
//
// Tool_melisma::getEndtime --
//

HumNum Tool_melisma::getEndtime(HTp text) {
	int line = text->getLineIndex();
	int field = text->getFieldIndex();
	return m_endtimes[line][field];
}



/////////////////////////////
//
// Tool_melisma::markMelismas --
//

void Tool_melisma::markMelismas(HumdrumFile& infile, vector<vector<int>>& counts) {
	int mincount = getInteger("min");
	if (mincount < 2) {
		mincount = 2;
	}
	for (int i=0; i<(int)counts.size(); i++) {
		for (int j=0; j<(int)counts[i].size(); j++) {
			if (counts[i][j] >= mincount) {
				HTp token = infile.token(i, j);
				markMelismaNotes(token, counts[i][j]);
			}
		}
	}
	infile.appendLine("!!!RDF**kern: @ = marked note (melisma)");
	infile.createLinesFromTokens();
}



//////////////////////////////
//
// Tool_melisma::markMelismaNotes --
//

void Tool_melisma::markMelismaNotes(HTp text, int count) {
	int counter = 0;

	HTp current = text->getPreviousFieldToken();
	while (current) {
		if (current->isKern()) {
			break;
		}
		current = current->getPreviousFieldToken();
	}
	if (!current) {
		return;
	}
	while (current) {
		if (!current->isData()) {
			current = current->getNextToken();
			continue;
		}
		if (current->isNull()) {
			current = current->getNextToken();
			continue;
		}
		if (current->isRest()) {
			current = current->getNextToken();
			continue;
		}
		if (current->isNoteAttack()) {
			counter++;
		}
		string text = *current;
		text += "@";
		current->setText(text);
		if (counter >= count) {
			break;
		}
		current = current->getNextToken();
	}
}



//////////////////////////////
//
// Tool_melisma::replaceLyrics --
//

void Tool_melisma::replaceLyrics(HumdrumFile& infile, vector<vector<int>>& counts) {
	for (int i=0; i<(int)counts.size(); i++) {
		for (int j=0; j<(int)counts[i].size(); j++) {
			if (counts[i][j] == -1) {
				continue;
			}
			string text = to_string(counts[i][j]);
			HTp token = infile.token(i, j);
			token->setText(text);
		}
	}
	infile.createLinesFromTokens();
}



//////////////////////////////
//
// Tool_melisma::getNoteCounts --
//

void Tool_melisma::getNoteCounts(HumdrumFile& infile, vector<vector<int>>& counts) {
	infile.initializeArray(counts, -1);
	initBarlines(infile);
	HumNum negativeOne = -1;
	infile.initializeArray(m_endtimes, negativeOne);
	vector<HTp> lyrics;
	infile.getSpineStartList(lyrics, "**text");
	for (int i=0; i<(int)lyrics.size(); i++) {
		getNoteCountsForLyric(counts, lyrics[i]);
	}
}



//////////////////////////////
//
// Tool_melisma::initBarlines --
//

void Tool_melisma::initBarlines(HumdrumFile& infile) {
	m_measures.resize(infile.getLineCount());
	fill(m_measures.begin(), m_measures.end(), 0);
	HumRegex hre;
	for (int i=1; i<infile.getLineCount(); i++) {
		if (!infile[i].isBarline()) {
			m_measures[i] = m_measures[i-1];
			continue;
		}
		HTp token = infile.token(i, 0);
		if (hre.search(token, "(\\d+)")) {
			m_measures[i] = hre.getMatchInt(1);
		}
	}
}




//////////////////////////////
//
// Tool_melisma::getNoteCountsForLyric --
//

void Tool_melisma::getNoteCountsForLyric(vector<vector<int>>& counts, HTp lyricStart) {
	HTp current = lyricStart;
	while (current) {
		if (!current->isData()) {
			current = current->getNextToken();
			continue;
		}
		if (current->isNull()) {
			current = current->getNextToken();
			continue;
		}
		int line = current->getLineIndex();
		int field = current->getFieldIndex();
		counts[line][field] = getCountForSyllable(current);
		current = current->getNextToken();
	}
}



//////////////////////////////
//
// Tool_melisma::getCountForSyllable --
//

int Tool_melisma::getCountForSyllable(HTp token) {
	if (token->back() == '&') {
		return 1;
	}
	HTp nexttok = token->getNextToken();
	int eline   = token->getLineIndex();
	int efield  = token->getFieldIndex();
	m_endtimes[eline][efield] = token->getDurationFromStart() + token->getDuration();
	while (nexttok) {
		if (!nexttok->isData()) {
			nexttok = nexttok->getNextToken();
			continue;
		}
		if (nexttok->isNull()) {
			nexttok = nexttok->getNextToken();
			continue;
		}
		// found non-null data token
		break;
	}

	HumdrumFile& infile = *token->getOwner()->getOwner();
	int endline = infile.getLineCount() - 1;
	if (nexttok) {
		endline = nexttok->getLineIndex();
	}
	int output = 0;
	HTp current = token->getPreviousFieldToken();
	while (current) {
		if (current->isKern()) {
			break;
		}
		current = current->getPreviousFieldToken();
	}
	if (!current) {
		return 0;
	}
	while (current) {
		if (!current->isData()) {
			current = current->getNextToken();
			continue;
		}
		if (current->isNull()) {
			current = current->getNextToken();
			continue;
		}
		if (current->isRest()) {
			current = current->getNextToken();
			continue;
		}
		if (!current->isNoteAttack()) {
			// ignore tied notes
			m_endtimes[eline][efield] = current->getDurationFromStart() + current->getDuration();
			current = current->getNextToken();
			continue;
		}
		int line = current->getLineIndex();
		if (line < endline) {
			m_endtimes[eline][efield] = current->getDurationFromStart() + current->getDuration();
			output++;
		} else {
			break;
		}
		current = current->getNextToken();
	}

	return output;
}





/////////////////////////////////
//
// Tool_mens2kern::Tool_mens2kern -- Set the recognized options for the tool.
//

Tool_mens2kern::Tool_mens2kern(void) {
	define("debug=b",    "print debugging statements");
}



/////////////////////////////////
//
// Tool_mens2kern::run -- Do the main work of the tool.
//

bool Tool_mens2kern::run(HumdrumFileSet& infiles) {
	bool status = true;
	for (int i=0; i<infiles.getCount(); i++) {
		status &= run(infiles[i]);
	}
	return status;
}


bool Tool_mens2kern::run(const string& indata, ostream& out) {
	HumdrumFile infile(indata);
	bool status = run(infile);
	if (hasAnyText()) {
		getAllText(out);
	} else {
		out << infile;
	}
	return status;
}


bool Tool_mens2kern::run(HumdrumFile& infile, ostream& out) {
	bool status = run(infile);
	if (hasAnyText()) {
		getAllText(out);
	} else {
		out << infile;
	}
	return status;
}


bool Tool_mens2kern::run(HumdrumFile& infile) {
	initialize();
	processFile(infile);
	return true;
}



//////////////////////////////
//
// Tool_mens2kern::initialize --  Initializations that only have to be done once
//    for all HumdrumFile segments.
//

void Tool_mens2kern::initialize(void) {
	m_debugQ = getBoolean("debug");
}



//////////////////////////////
//
// Tool_mens2kern::processFile --
//

void Tool_mens2kern::processFile(HumdrumFile& infile) {
	vector<HTp> melody;
	int scount = infile.getStrandCount();
	for (int i=0; i<scount; i++) {
		HTp sstart = infile.getStrandBegin(i);
		if (!sstart->isDataType("**mens")) {
			continue;
		}
		HTp sstop = infile.getStrandEnd(i);
		HTp current = sstart;
		while (current && (current != sstop)) {
			if (current->isNull()) {
				// ignore null data tokens
				current = current->getNextToken();
				continue;
			}
			melody.push_back(current);
			current = current->getNextToken();
		}
		processMelody(melody);
		melody.clear();
	}

	infile.createLinesFromTokens();
}



//////////////////////////////
//
// Tool_mens2kern::processMelody --
//

void Tool_mens2kern::processMelody(vector<HTp>& melody) {
	int maximodus = 0;
	int modus = 0;
	int tempus = 0;
	int prolatio = 0;
	int semibrevis_def = 0;
	int brevis_def     = 0;
	int longa_def      = 0;
	int maxima_def     = 0;
	string regexopts;
	HumRegex hre;
	string rhythm;
	bool imperfecta;
	bool perfecta;
	bool altera;

	for (int i=0; i<(int)melody.size(); i++) {
		if (*melody[i] == "**mens") {
			// convert spine to **kern data:
			melody[i]->setText("**kern");
		}

		if (melody[i]->isMensuration()) {
			getMensuralInfo(melody[i], maximodus, modus, tempus, prolatio);

			// Default value of notes from maxima to semibrevis in minims:
			semibrevis_def = prolatio;
			brevis_def     = tempus    * semibrevis_def;
			longa_def      = modus     * brevis_def;
			maxima_def     = maximodus * longa_def;
			if (m_debugQ) {
				cerr << "LEVELS X_def = "    << maxima_def
					  << " | L_def = " << longa_def
					  << " | S_def = " << brevis_def
					  << " | s_def = " << semibrevis_def << endl;
			}
		}

		if (!melody[i]->isData()) {
			continue;
		}
		string text = melody[i]->getText();
		imperfecta = hre.search(text, "i") ? true : false;
		perfecta = hre.search(text, "p") ? true : false;
		altera = hre.search(text, "\\+") ? true : false;
		if (hre.search(text, "([XLSsMmUu])")) {
			rhythm = hre.getMatch(1);
		} else {
			cerr << "Error: token " << melody[i] << " has no rhythm" << endl;
			cerr << "   ON LINE: "  << melody[i]->getLineNumber()    << endl;
			continue;
		}

		string kernRhythm = mens2kernRhythm(rhythm, altera, perfecta, imperfecta, maxima_def, longa_def, brevis_def, semibrevis_def);

		hre.replaceDestructive(text, kernRhythm, rhythm);
		// Remove any dot of division/augmentation
		hre.replaceDestructive(text, "", ":");
		// remove perfection/imperfection/alteration markers
		hre.replaceDestructive(text, "", "[pi\\+]");
		if (text.empty()) {
			text = ".";
		}
		melody[i]->setText(text);
	}
}


//////////////////////////////
//
// Tool_mens2kern::getMensuralInfo --
//

void Tool_mens2kern::getMensuralInfo(HTp token, int& maximodus, int& modus,
		int& tempus, int& prolatio) {
	HumRegex hre;
	if (!hre.search(token, "^\\*met\\(.*?\\)_(\\d+)")) {
		// need to interpret symbols without underscores.
		if (token->getText() == "*met(C)") {
			maximodus = 2;
			modus = 2;
			tempus = 2;
			prolatio = 2;
		} else if (token->getText() == "*met(O)") {
			maximodus = 2;
			modus = 2;
			tempus = 3;
			prolatio = 2;
		} else if (token->getText() == "*met(C.)") {
			maximodus = 2;
			modus = 2;
			tempus = 2;
			prolatio = 3;
		} else if (token->getText() == "*met(O.)") {
			maximodus = 2;
			modus = 2;
			tempus = 3;
			prolatio = 3;
		} else if (token->getText() == "*met(C|)") {
			maximodus = 2;
			modus = 2;
			tempus = 2;
			prolatio = 2;
		} else if (token->getText() == "*met(O|)") {
			maximodus = 2;
			modus = 2;
			tempus = 3;
			prolatio = 2;
		} else if (token->getText() == "*met(C.|)") {
			maximodus = 2;
			modus = 2;
			tempus = 2;
			prolatio = 3;
		} else if (token->getText() == "*met(O.|)") {
			maximodus = 2;
			modus = 2;
			tempus = 3;
			prolatio = 3;
		} else if (token->getText() == "*met(C2)") {
			maximodus = 2;
			modus = 2;
			tempus = 2;
			prolatio = 2;
		} else if (token->getText() == "*met(C3)") {
			maximodus = 2;
			modus = 2;
			tempus = 3;
			prolatio = 2;
		} else if (token->getText() == "*met(O2)") {
			maximodus = 2;
			modus = 3;
			tempus = 2;
			prolatio = 2;
		} else if (token->getText() == "*met(O3)") {
			maximodus = 3;
			modus = 3;
			tempus = 3;
			prolatio = 2;
		} else if (token->getText() == "*met(C3/2)") {
			maximodus = 2;
			modus = 2;
			tempus = 2;
			prolatio = 3;
		} else if (token->getText() == "*met(C|3/2)") {
			maximodus = 2;
			modus = 2;
			tempus = 3;
			prolatio = 2;
		}
	} else {
		string levels = hre.getMatch(1);
		if (levels.size() >= 1) {
			maximodus = levels[0] - '0';
		}
		if (levels.size() >= 2) {
			modus = levels[1] - '0';
		}
		if (levels.size() >= 3) {
			tempus = levels[2] - '0';
		}
		if (levels.size() >= 4) {
			prolatio = levels[3] - '0';
		}
	}

	if (m_debugQ) {
		cerr << "MENSURAL INFO: maximodus = "   << maximodus
			  << " | modus = "    << modus
			  << " | tempus = "   << tempus
			  << " | prolatio = " << prolatio << endl;
	}
}



//////////////////////////////
//
// Tool_mens2kern::mens2kernRhythm --
//

string Tool_mens2kern::mens2kernRhythm(const string& rhythm, bool altera, bool perfecta, bool imperfecta, int maxima_def, int longa_def, int brevis_def, int semibrevis_def) {
	double val_note;
	double minima_def = 1;
	double semiminima_def = 0.5;
	double fusa_def = 0.25;
	double semifusa_def = 0.125;

	if (rhythm == "X") {
		if (perfecta) { val_note = 3 * longa_def; }
		else if (imperfecta) { val_note = 2 * longa_def; }
		else { val_note = maxima_def; }
	}
	else if (rhythm == "L") {
		if (perfecta) { val_note = 3 * brevis_def; }
		else if (imperfecta) { val_note = 2 * brevis_def; }
		else if (altera) { val_note = 2 * longa_def; }
		else { val_note = longa_def; }
	}
	else if (rhythm == "S") {
		if (perfecta) { val_note = 3 * semibrevis_def; }
		else if (imperfecta) { val_note = 2 * semibrevis_def; }
		else if (altera) { val_note = 2 * brevis_def; }
		else { val_note = brevis_def; }
	}
	else if (rhythm == "s") {
		if (perfecta) { val_note = 3 * minima_def; }
		else if (imperfecta) { val_note = 2 * minima_def; }
		else if (altera) { val_note = 2 * semibrevis_def; }
		else { val_note = semibrevis_def; }
	}
	else if (rhythm == "M") {
		if (perfecta) { val_note = 1.5 * minima_def; }
		else if (altera) { val_note = 2 * minima_def; }
		else { val_note = minima_def; }
	}
	else if (rhythm == "m") {
		if (perfecta) { val_note = 1.5 * semiminima_def; }
		else { val_note = semiminima_def; }
	}
	else if (rhythm == "U") {
		if (perfecta) { val_note = 1.5 * fusa_def; }
		else { val_note = fusa_def; }
	}
	else if (rhythm == "u") {
		if (perfecta) { val_note = 1.5 * semifusa_def; }
		else { val_note = semifusa_def; }
	}
	else { cerr << "UNKNOWN RHYTHM: " << rhythm << endl; return ""; }

	switch ((int)(val_note * 10000)) {
		case 1250:     return "16";   break;   // sixteenth note
		case 1875:     return "16.";  break;   // dotted sixteenth note
		case 2500:     return "8";    break;   // eighth note
		case 3750:     return "8.";   break;   // dotted eighth note
		case 5000:     return "4";    break;   // quarter note
		case 7500:     return "4.";   break;   // dotted quarter note
		case 10000:    return "2";    break;   // half note
		case 15000:    return "2.";   break;   // dotted half note
		case 20000:    return "1";    break;   // whole note
		case 30000:    return "1.";   break;   // dotted whole note
		case 40000:    return "0";    break;   // breve note
		case 60000:    return "0.";   break;   // dotted breve note
		case 90000:    return "2%9";  break;   // or ["0.", "1."];
		case 80000:    return "00";   break;   // long note
		case 120000:   return "00.";  break;   // dotted long note
		case 180000:   return "1%9";  break;   // or ["00.", "0."];
		case 270000:   return "2%27"; break;   // or ["0.", "1.", "0.", "1.", "0.", "1."];
		case 160000:   return "000";  break;   // maxima note
		case 240000:   return "000."; break;   // dotted maxima note
		case 360000:   return "1%18"; break;   // or ["000.", "00."];
		case 540000:   return "1%27"; break;   // or ["00.", "0.", "00.", "0.", "00.", "0."];
		case 810000:   return "2%81"; break;   // or ["00.", "0.", "00.", "0.", "00.", "0.", "0.", "1.", "0.", "1.", "0.", "1."];
		default:
			cerr << "Error: unknown val_note: " << val_note << endl;
	}

	return "";
}




/////////////////////////////////
//
// Tool_meter::Tool_meter -- Set the recognized options for the tool.
//

Tool_meter::Tool_meter(void) {
	define("c|comma=b",                       "display decimal points as commas");
	define("d|denominator=b",                 "display denominator spine");
	define("e|eighth=b",                      "metric positions in eighth notes rather than beats");
	define("f|float=b",                       "floating-point beat values instead of rational numbers");
	define("h|half=b",                        "metric positions in half notes rather than beats");
	define("j|join=b",                        "join time signature information and metric positions into a single token");
	define("n|numerator=b",                   "display numerator spine");
	define("q|quarter=b",                     "metric positions in quarter notes rather than beats");
	define("r|rest=b",                        "add meteric positions of rests");
	define("s|sixteenth=b",                   "metric positions in sixteenth notes rather than beats");
	define("t|time-signature|tsig|m|meter=b", "display active time signature for each note");
	define("w|whole=b",                       "metric positions in whole notes rather than beats");
	define("z|zero=b",                        "start of measure is beat 0 rather than beat 1");

	define("B|no-beat=b",                     "Do not display metric positions (beats)");
	define("D|digits=i:0",                    "number of digits after decimal point");
	define("L|no-label=b",                    "do not add labels to analysis spines");
}



/////////////////////////////////
//
// Tool_meter::run -- Do the main work of the tool.
//

bool Tool_meter::run(HumdrumFileSet& infiles) {
	bool status = true;
	for (int i=0; i<infiles.getCount(); i++) {
		status &= run(infiles[i]);
	}
	return status;
}


bool Tool_meter::run(const string& indata, ostream& out) {
	HumdrumFile infile(indata);
	bool status = run(infile);
	if (hasAnyText()) {
		getAllText(out);
	} else {
		out << infile;
	}
	return status;
}


bool Tool_meter::run(HumdrumFile& infile, ostream& out) {
	bool status = run(infile);
	if (hasAnyText()) {
		getAllText(out);
	} else {
		out << infile;
	}
	return status;
}


bool Tool_meter::run(HumdrumFile& infile) {
	initialize();
	processFile(infile);
	return true;
}



//////////////////////////////
//
// Tool_meter::initialize --
//

void Tool_meter::initialize(void) {

	m_commaQ       = getBoolean("comma");
	m_denominatorQ = getBoolean("denominator");
	m_digits       = getInteger("digits");
	m_floatQ       = getBoolean("float");
	m_halfQ        = getBoolean("half");
	m_joinQ        = getBoolean("join");
	m_nobeatQ      = getBoolean("no-beat");
	m_nolabelQ     = getBoolean("no-label");
	m_numeratorQ   = getBoolean("numerator");
	m_quarterQ     = getBoolean("quarter");
	m_halfQ        = getBoolean("half");
	m_eighthQ      = getBoolean("eighth");
	m_sixteenthQ   = getBoolean("sixteenth");
	m_restQ        = getBoolean("rest");
	m_tsigQ        = getBoolean("meter");
	m_wholeQ       = getBoolean("whole");
	m_zeroQ        = getBoolean("zero");

	if (m_digits < 0) {
		m_digits = 0;
	}
	if (m_digits > 15) {
		m_digits = 15;
	}

	if (m_joinQ && !(m_tsigQ || m_numeratorQ || m_denominatorQ)) {
		m_tsigQ = true;
	}
	if (m_joinQ) {
		m_nobeatQ = false;
	}
	if (m_joinQ && m_numeratorQ && m_denominatorQ) {
		m_tsigQ = true;
	}

	if (m_tsigQ) {
		m_numeratorQ = true;
		m_denominatorQ = true;
	}

	// Only one fix-width metric position allowed, prioritize
	// largest given duration:
	if (m_wholeQ) {
		m_halfQ      = false;
		m_quarterQ   = false;
		m_eighthQ    = false;
		m_sixteenthQ = false;
	} else if (m_halfQ) {
		m_wholeQ     = false;
		m_quarterQ   = false;
		m_eighthQ    = false;
		m_sixteenthQ = false;
	} else if (m_quarterQ) {
		m_wholeQ     = false;
		m_halfQ      = false;
		m_eighthQ    = false;
		m_sixteenthQ = false;
	} else if (m_eighthQ) {
		m_wholeQ     = false;
		m_halfQ      = false;
		m_quarterQ   = false;
		m_sixteenthQ = false;
	} else if (m_sixteenthQ) {
		m_wholeQ     = false;
		m_halfQ      = false;
		m_quarterQ   = false;
		m_eighthQ    = false;
	}

}



//////////////////////////////
//
// Tool_meter::processFile --
//

void Tool_meter::processFile(HumdrumFile& infile) {
	analyzePickupMeasures(infile);
	getMeterData(infile);
	printMeterData(infile);
}


//////////////////////////////
//
// Tool_meter::analyzePickupMeasures --
//

void Tool_meter::analyzePickupMeasures(HumdrumFile& infile) {
	vector<HTp> sstarts;
	infile.getKernSpineStartList(sstarts);
	for (int i=0; i<(int)sstarts.size(); i++) {
		analyzePickupMeasures(sstarts[i]);
	}
}


void Tool_meter::analyzePickupMeasures(HTp sstart) {
	// First dimension are visible barlines.
	// Second dimension are time signature(s) within the barlines.
	vector<vector<HTp>> barandtime;
	barandtime.reserve(1000);
	barandtime.resize(1);
	barandtime[0].push_back(sstart);
	HTp current = sstart->getNextToken();
	while (current) {
		if (current->isTimeSignature()) {
			barandtime.back().push_back(current);
		} else if (current->isBarline()) {
			if (current->find("-") != std::string::npos) {
				current = current->getNextToken();
				continue;
			}
			barandtime.resize(barandtime.size() + 1);
			barandtime.back().push_back(current);
		} else if (*current == "*-") {
			barandtime.resize(barandtime.size() + 1);
			barandtime.back().push_back(current);
			break;
		}
		current = current->getNextToken();
	}

	// Extract the actual duration of measures:
	vector<HumNum> bardur(barandtime.size(), 0);
	for (int i=0; i<(int)barandtime.size() - 1; i++) {
		HumNum starttime = barandtime[i][0]->getDurationFromStart();
		HumNum endtime = barandtime.at(i+1)[0]->getDurationFromStart();
		HumNum duration = endtime - starttime;
		bardur.at(i) = duration;
	}

	// Extract the expected duration of measures:
	vector<HumNum> tsigdur(barandtime.size(), 0);
	int firstmeasure = -1;
	HumNum active = 0;
	for (int i=0; i<(int)barandtime.size() - 1; i++) {
		if (firstmeasure < 0) {
			if (bardur.at(i) > 0) {
				firstmeasure = i;
			}
		}
		if (barandtime[i].size() < 2) {
			tsigdur.at(i) = active;
			continue;
		}
		active = getTimeSigDuration(barandtime.at(i).at(1));
		tsigdur.at(i) = active;
	}

	vector<bool> pickup(barandtime.size(), false);
	for (int i=0; i<(int)barandtime.size() - 1; i++) {
		if (tsigdur.at(i) == bardur.at(i)) {
			// actual and expected are the same
			continue;
		}
		if (tsigdur.at(i) == tsigdur.at(i+1)) {
			if (bardur.at(i) + bardur.at(i+1) == tsigdur.at(i)) {
				pickup.at(i+1) = true;
				i++;
				continue;
			}
		}
	}

	// check for first-measure pickup
	if (firstmeasure >= 0) {
		if (bardur.at(firstmeasure) < tsigdur.at(firstmeasure)) {
			pickup.at(firstmeasure) = true;
		}
	}

	if (m_debugQ) {
		cerr << "============================" << endl;
		for (int i=0; i<(int)barandtime.size(); i++) {
			cerr << pickup.at(i);
			cerr << "\t";
			cerr << bardur.at(i);
			cerr << "\t";
			cerr << tsigdur.at(i);
			cerr << "\t";
			for (int j=0; j<(int)barandtime[i].size(); j++) {
				cerr << barandtime.at(i).at(j) << "\t";
			}
			cerr << endl;
		}
		cerr << endl;
	}

	// Markup pickup measure notes/rests
	for (int i=0; i<(int)pickup.size() - 1; i++) {
		if (!pickup[i]) {
			continue;
		}
		markPickupContent(barandtime.at(i).at(0), barandtime.at(i+1).at(0));
	}

	// Pickup/incomplete measures covering three or more barlines are not considered
	// (these could be used with dashed barlines or similar).

}



//////////////////////////////
//
// Tool_meter::markPickupContent --
//

void Tool_meter::markPickupContent(HTp stok, HTp etok) {
	int endline = etok->getLineIndex();
	HTp current = stok;
	while (current) {
		int line = current->getLineIndex();
		if (line > endline) {
			break;
		}
		if (current->isData()) {
			HTp field = current;
			int track = field->getTrack();
			while (field) {
				int ttrack = field->getTrack();
				if (ttrack != track) {
					break;
				}
				if (field->isNull()) {
					field = field->getNextFieldToken();
					continue;
				}
				field->setValue("auto", "pickup", 1);
				HumNum nbt = etok->getDurationFromStart() - field->getDurationFromStart();
				stringstream ntime;
				ntime.str("");
				ntime << nbt.getNumerator() << "/" << nbt.getDenominator();
				field->setValue("auto", "nextBarTime", ntime.str());
				field = field->getNextFieldToken();
			}
		}
		if (current == etok) {
			break;
		}
		current = current->getNextToken();
	}
}



//////////////////////////////
//
// Tool_meter::getTimeSigDuration --
//

HumNum Tool_meter::getTimeSigDuration(HTp tsig) {
	HumNum output = 0;
	HumRegex hre;
	if (hre.search(tsig, "^\\*M(\\d+)/(\\d+%?\\d*)")) {
		int top = hre.getMatchInt(1);
		string bot = hre.getMatch(2);
		HumNum botdur = Convert::recipToDuration(bot);
		output = botdur * top;
	}
	return output;
}



//////////////////////////////
//
// Tool_meter::printMeterData --
//

void Tool_meter::printMeterData(HumdrumFile& infile) {
	bool foundLabel = false;
	bool foundData  = false;

	for (int i=0; i<infile.getLineCount(); i++) {
		if (!infile[i].hasSpines()) {
			m_humdrum_text << infile[i] << endl;
			continue;
		}

		if ((!foundData) && (!foundLabel) && (!m_nolabelQ) && infile[i].isData()) {
			printLabelLine(infile[i]);
			foundData = true;
		}

		bool hasLabel = false;
		if ((!m_nolabelQ) && (!foundLabel) && (!foundData)) {
			if (searchForLabels(infile[i])) {
				hasLabel = true;
				foundLabel = true;
			}
		}
		printHumdrumLine(infile[i], hasLabel);
	}
}



//////////////////////////////
//
// printLabelLine --
//

void Tool_meter::printLabelLine(HumdrumLine& line) {
	bool forceInterpretation = true;
	bool printLabels = true;
	for (int i=0; i<line.getFieldCount(); i++) {
		HTp token = line.token(i);
		if (token->isKern()) {
			i = printKernAndAnalysisSpine(line, i, printLabels, forceInterpretation);
		} else {
			m_humdrum_text << "*";
		}
		if (i < line.getFieldCount() - 1) {
			m_humdrum_text << "\t";
		}
	}
	m_humdrum_text << "\n";
}



//////////////////////////////
//
// Tool_meter::printHumdrumLine --
//

void Tool_meter::printHumdrumLine(HumdrumLine& line, bool printLabels) {

	for (int i=0; i<line.getFieldCount(); i++) {
		HTp token = line.token(i);
		if (token->isKern()) {
			i = printKernAndAnalysisSpine(line, i, printLabels);
		} else {
			m_humdrum_text << token;
		}
		if (i < line.getFieldCount() - 1) {
			m_humdrum_text << "\t";
		}
	}
	m_humdrum_text << "\n";
}



//////////////////////////////
//
// Tool_meter::searchForLabels --
//

bool Tool_meter::searchForLabels(HumdrumLine& line) {
	if (!line.isInterpretation()) {
		return false;
	}
	HumRegex hre;
	for (int i=0; i<line.getFieldCount(); i++) {
		HTp token = line.token(i);
		if (hre.search(token, "^\\*v[ibB]*:")) {
			return true;
		}
	}
	return false;
}



//////////////////////////////
//
// Tool_meter::getHumNum --
//

HumNum Tool_meter::getHumNum(HTp token, const string& parameter) {
	HumRegex hre;
	HumNum output;
	string value = token->getValue("auto", parameter);
	if (hre.search(value, "(\\d+)/(\\d+)")) {
		output = hre.getMatchInt(1);
		output /=  hre.getMatchInt(2);
	} else if (hre.search(value, "(\\d+)")) {
		output = hre.getMatchInt(1);
	}
	return output;
}



//////////////////////////////
//
// Tool_meter::getHumNumString --
//

string Tool_meter::getHumNumString(HumNum input) {
	stringstream tem;
	input.printTwoPart(tem);
	return tem.str();
}



//////////////////////////////
//
// Tool_meter::printKernAndAnalysisSpine --
//

int Tool_meter::printKernAndAnalysisSpine(HumdrumLine& line, int index, bool printLabels, bool forceInterpretation) {
	HTp starttok = line.token(index);
	int track = starttok->getTrack();
	int counter = 0;

	string analysis = ".";
	string numerator = ".";
	string denominator = ".";
	string meter = ".";
	bool hasNote = false;
	bool hasRest = false;

	for (int i=index; i<line.getFieldCount(); i++) {
		HTp token = line.token(i);
		int ttrack = token->getTrack();
		if (ttrack != track) {
			break;
		}
		if (counter > 0) {
			m_humdrum_text << "\t";
		}
		counter++;
		if (forceInterpretation) {
			m_humdrum_text << "*";
		} else {
			m_humdrum_text << token;
		}

		if (line.isData() && !forceInterpretation) {
			if (token->isNull()) {
				// analysis = ".";
			} else if (token->isRest() && !m_restQ) {
				// analysis = ".";
			} else if ((!token->isNoteAttack()) && !(m_restQ && token->isRest())) {
				// analysis = ".";
			} else if ((analysis == ".") && (token->getValueBool("auto", "hasData"))) {
				string data = token->getValue("auto", "zeroBeat");
				if (m_restQ) {
					if (token->isRest()) {
						hasRest = true;
					} else {
						hasNote = true;
					}
				}
				HumNum value;
				HumNum nvalue;
				HumNum dvalue;
				if (!data.empty()) {
					value = getHumNum(token, "zeroBeat");
					if (m_numeratorQ) {
						nvalue = getHumNum(token, "numerator");
						numerator = getHumNumString(nvalue);
					}
					if (m_denominatorQ) {
						dvalue = getHumNum(token, "denominator");
						denominator = getHumNumString(dvalue);
					}
					if (m_tsigQ) {
						meter = numerator;
						meter += "/";
						meter += denominator;
					}
				}
				if (!m_zeroQ) {
					value += 1;
				}
				if (m_floatQ) {
					stringstream tem;
					if (m_digits) {
						tem << std::setprecision(m_digits + 1) << value.getFloat();
					} else {
						tem << value.getFloat();
					}
					analysis = tem.str();
					if (m_commaQ) {
						HumRegex hre;
						hre.replaceDestructive(analysis, ",", "\\.");
					}
				} else {
					analysis = getHumNumString(value);
				}
			}
		} else if (line.isInterpretation() || forceInterpretation) {
			if (token->compare(0, 2, "**") == 0) {
				analysis = "**cdata-beat";
				if (m_tsigQ) {
					meter = "**cdata-tsig";
				}
				if (m_numeratorQ) {
					numerator = "**cdata-num";
				}
				if (m_denominatorQ) {
					denominator = "**cdata-den";
				}
			} else if (*token == "*-") {
				analysis = "*-";
				numerator = "*-";
				denominator = "*-";
				meter = "*-";
			} else if (token->isTimeSignature()) {
				analysis = *token;
			} else {
				analysis = "*";
				numerator = "*";
				denominator = "*";
				meter = "*";
				if (printLabels) {
					if (m_quarterQ) {
						analysis = "*vi:4ths:";
					} else if (m_eighthQ) {
						analysis = "*vi:8ths:";
					} else if (m_halfQ) {
						analysis = "*vi:half:";
					} else if (m_wholeQ) {
						analysis = "*vi:whole:";
					} else if (m_sixteenthQ) {
						analysis = "*vi:16ths:";
					} else {
						analysis = "*vi:beat:";
					}
					numerator = "*vi:top:";
					denominator = "*vi:bot:";
					meter = "*vi:tsig:";
					if (m_joinQ) {
						numerator = "";
						denominator = "";
						meter = "";
					}
				}
			}
		} else if (line.isBarline()) {
			analysis = *token;
			numerator = *token;
			denominator = *token;
			meter = *token;
		} else if (line.isCommentLocal()) {
			analysis = "!";
			numerator = "!";
			denominator = "!";
			meter = "!";
		} else {
			cerr << "STRANGE LINE: " << line << endl;
		}
	}

	if (m_joinQ) {
		if (line.isData() && !forceInterpretation) {
			if (m_tsigQ) {
					m_humdrum_text << "\t" << meter;
			} else {
				if (m_numeratorQ) {
					m_humdrum_text << "\t" << numerator;
				}
				if (m_denominatorQ) {
					m_humdrum_text << "\t" << denominator;
				}
			}
		}
		if (!m_nobeatQ) {
			if (line.isData() && !forceInterpretation) {
				m_humdrum_text << ":";
			} else {
				m_humdrum_text << "\t";
			}
			m_humdrum_text << analysis;
			if (line.isData() && hasRest && !hasNote) {
				m_humdrum_text << "r";
			}
		}
	} else {
		if (!m_nobeatQ) {
			m_humdrum_text << "\t" << analysis;
			if (line.isData() && hasRest && !hasNote) {
				m_humdrum_text << "r";
			}
		}
		if (m_tsigQ) {
				m_humdrum_text << "\t" << meter;
		} else {
			if (m_numeratorQ) {
				m_humdrum_text << "\t" << numerator;
			}
			if (m_denominatorQ) {
				m_humdrum_text << "\t" << denominator;
			}
		}

	}

	return index + counter - 1;
}



//////////////////////////////
//
// Tool_meter::getMeterData --
//

void Tool_meter::getMeterData(HumdrumFile& infile) {

	int maxtrack = infile.getMaxTrack();
	vector<HumNum> curNum(maxtrack + 1, 0);
	vector<HumNum> curDen(maxtrack + 1, 0);
	vector<HumNum> curBeat(maxtrack + 1, 0);
	vector<HumNum> curBarTime(maxtrack + 1, 0);

	for (int i=0; i<infile.getLineCount(); i++) {
		processLine(infile[i], curNum, curDen, curBeat, curBarTime);
	}
}



//////////////////////////////
//
// Tool_meter::processLine --
//

void Tool_meter::processLine(HumdrumLine& line, vector<HumNum>& curNum,
		vector<HumNum>& curDen, vector<HumNum>& curBeat,
		vector<HumNum>& curBarTime) {

	int fieldCount = line.getFieldCount();

	if (!line.hasSpines()) {
		return;
	}

	HumRegex hre;
	if (line.isBarline()) {
		for (int i=0; i<fieldCount; i++) {
			HTp token = line.token(i);
			if (!token->isKern()) {
				continue;
			}
			if (hre.search(token, "-")) {
				// invisible barline: ignore
				continue;
			}
			int track = token->getTrack();
			HumNum curTime = token->getDurationFromStart();
			curBarTime.at(track) = curTime;
		}
		return;
	}

	if (line.isInterpretation()) {
		// check for time signatures
		for (int i=0; i<fieldCount; i++) {
			HTp token = line.token(i);
			if (!token->isKern()) {
				continue;
			}
			if (hre.search(token, "^\\*M(\\d+)/(\\d+)")) {
				int top = hre.getMatchInt(1);
				int bot = hre.getMatchInt(2);
				int track = token->getTrack();
				curNum.at(track) = top;
				curDen.at(track) = bot;
				curBeat.at(track) = 0;
			} else if (hre.search(token, "^\\*beat:\\s*([\\d.%]+)\\s*$")) {
				int track = token->getTrack();
				string recip = hre.getMatch(1);
				curBeat.at(track) = Convert::recipToDuration(recip);
			}
		}
		return;
	}

	if (line.isData()) {
		// check for time signatures
		for (int i=0; i<fieldCount; i++) {
			HTp token = line.token(i);
			if (!token->isKern()) {
				continue;
			}
			if (token->isNull()) {
				continue;
			}
			if ((!m_restQ) && token->isRest()) {
				continue;
			}
			if (!token->isNoteAttack() && !(m_restQ && token->isRest())) {
				continue;
			}
			int pickup = token->getValueInt("auto", "pickup");
			int track = token->getTrack();
			stringstream value;
			value.str("");
			value << curNum.at(track);
			token->setValue("auto", "numerator", value.str());
			value.str("");
			value << curDen.at(track);
			token->setValue("auto", "denominator", value.str());
			HumNum curTime = token->getDurationFromStart();
			HumNum q;
			if (pickup) {
				HumNum meterDur = curNum.at(track);
				meterDur /= curDen.at(track);
				meterDur *= 4;
				HumNum nbt = getHumNum(token, "nextBarTime");
				q = meterDur - nbt;
			} else {
				q = curTime - curBarTime.at(track);
			}
			value.str("");
			value << q;
			token->setValue("auto", "q", value.str());
			bool compound = false;
			int multiple = curNum.at(track).getNumerator() / 3;
			int remainder = curNum.at(track).getNumerator() % 3;
			int bottom = curDen.at(track).getNumerator();
			if ((curBeat.at(track) == 0) && (bottom >= 8) && (multiple > 1) && (remainder == 0)) {
				compound = true;
			}

			HumNum qq = q;
			if (m_quarterQ) {
				// do nothing (prior calculations are done in quarter notes)
			} else if (m_halfQ) {
				qq /= 2;
			} else if (m_wholeQ) {
				qq /= 4;
			} else if (m_eighthQ) {
				qq *= 2;
			} else if (m_sixteenthQ) {
				qq *= 4;
			} else {
				// convert quarter note metric positions into beat positions
				if (compound) {
					qq *= curDen.at(track);
					qq /= 4;
					qq /= 3;
				} else if (curBeat.at(track) > 0) {
					qq /= curBeat.at(track);
				} else {
					qq *= curDen.at(track);
					qq /= 4;
				}
			}

			value.str("");
			value << qq;
			token->setValue("auto", "zeroBeat", value.str());
			token->setValue("auto", "hasData", 1);

		}
		return;
	}
}




/////////////////////////////////
//
// Tool_gridtest::Tool_metlev -- Set the recognized options for the tool.
//

Tool_metlev::Tool_metlev(void) {
	define("a|append=b",         "append data analysis to input file");
	define("p|prepend=b",        "prepend data analysis to input file");
	define("c|composite=b",      "generate composite rhythm");
	define("i|integer=b",        "quantize metric levels to int values");
	define("x|attacks-only=b",   "only mark lines with note attacks");
	define("G|no-grace-notes=b", "do not mark grace note lines");
	define("k|kern-spine=i:1",   "analyze only given kern spine");
	define("e|exinterp=s:blev",  "exclusive interpretation type for output");
}



///////////////////////////////
//
// Tool_metlev::run -- Primary interfaces to the tool.
//

bool Tool_metlev::run(HumdrumFileSet& infiles) {
	bool status = true;
	for (int i=0; i<infiles.getCount(); i++) {
		status &= run(infiles[i]);
	}
	return status;
}


bool Tool_metlev::run(const string& indata, ostream& out) {
	HumdrumFile infile(indata);
	return run(infile, out);
}


bool Tool_metlev::run(HumdrumFile& infile, ostream& out) {
	bool status = run(infile);
	out << infile;
	return status;
}


bool Tool_metlev::run(HumdrumFile& infile) {
	int lineCount = infile.getLineCount();
	if (lineCount == 0) {
		m_error_text << "No input data";
		return false;
	}

	string exinterp = getString("exinterp");
	if (exinterp.empty()) {
		exinterp = "**blev";
	} else if (exinterp[0] != '*') {
		exinterp.insert(0, "*");
	}
	if (exinterp[1] != '*') {
		exinterp.insert(0, "*");
	}

	m_kernspines = infile.getKernSpineStartList();

	vector<double> beatlev(lineCount, NAN);
	int track = 0;
	if (m_kernspines.size() > 0) {
		track = m_kernspines[0]->getTrack();
	} else {
		m_error_text << "No **kern spines in input file" << endl;
		return false;
	}
	infile.getMetricLevels(beatlev, track, NAN);

	for (int i=0; i<lineCount; i++) {
		if (!infile[i].isData()) {
				continue;
		}
		if (getBoolean("no-grace-notes") && (infile[i].getDuration() == 0)) {
			beatlev[i] = NAN;
			continue;
		}
		if (getBoolean("attacks-only")) {
			if (!infile[i].getKernNoteAttacks()) {
				beatlev[i] = NAN;
				continue;
			}
		}
		if (beatlev[i] - (int)beatlev[i] != 0.0) {
			if (getBoolean("integer")) {
					beatlev[i] = floor(beatlev[i]);
			} else {
				beatlev[i] = Convert::significantDigits(beatlev[i], 2);
			}
		}
	}

	if (getBoolean("kern-spine")) {
		int kspine = getInteger("kern-spine") - 1;
		if ((kspine >= 0) && (kspine < (int)m_kernspines.size())) {
			vector<vector<double> > results;
			fillVoiceResults(results, infile, beatlev);
			if (kspine == (int)m_kernspines.size() - 1) {
				infile.appendDataSpine(results.back(), "nan", exinterp);
			} else {
				int track = m_kernspines[kspine+1]->getTrack();
				infile.insertDataSpineBefore(track, results[kspine],
						"nan", exinterp);
			}
			infile.createLinesFromTokens();
			return true;
		}
	} else if (getBoolean("append")) {
		infile.appendDataSpine(beatlev, "nan", exinterp);
		infile.createLinesFromTokens();
		return true;
	} else if (getBoolean("prepend")) {
		infile.prependDataSpine(beatlev, "nan", exinterp);
		infile.createLinesFromTokens();
		return true;
	} else if (getBoolean("composite")) {
		infile.prependDataSpine(beatlev, "nan", exinterp);
		infile.printFieldIndex(0, m_humdrum_text);
		infile.clear();
		infile.readString(m_humdrum_text.str());
	} else {
		vector<vector<double> > results;
		fillVoiceResults(results, infile, beatlev);
		infile.appendDataSpine(results.back(), "nan", exinterp);
		for (int i = (int)results.size()-1; i>0; i--) {
			int track = m_kernspines[i]->getTrack();
			infile.insertDataSpineBefore(track, results[i-1], "nan", exinterp);
		}
		infile.createLinesFromTokens();
		return true;
	}

	return false;
}



//////////////////////////////
//
// Tool_metlev::fillVoiceResults -- Split the metric level analysis into values
//     for each voice.
//

void Tool_metlev::fillVoiceResults(vector<vector<double> >& results,
		HumdrumFile& infile, vector<double>& beatlev) {

	results.resize(m_kernspines.size());
	for (int i=0; i<(int)results.size(); i++) {
		results[i].resize(beatlev.size());
		fill(results[i].begin(), results[i].end(), NAN);
	}
	int track;
	vector<int> rtracks(infile.getTrackCount() + 1, -1);
	for (int i=0; i<(int)m_kernspines.size(); i++) {
		int track = m_kernspines[i]->getTrack();
		rtracks[track] = i;
	}

	bool attacksQ = getBoolean("attacks-only");
	vector<int> nonnullcount(m_kernspines.size(), 0);
	vector<int> attackcount(m_kernspines.size(), 0);
	HTp token;
	int voice;
	int i, j;
	for (i=0; i<infile.getLineCount(); i++) {
		if (!infile[i].isData()) {
			continue;
		}
		for (j=0; j<infile[i].getFieldCount(); j++) {
			token = infile.token(i, j);
			if (!token->isKern()) {
				continue;
			}
			if (token->isNull()) {
				continue;
			}
			track = token->getTrack();
			voice = rtracks[track];
			nonnullcount[voice]++;
			if (token->isNoteAttack()) {
				attackcount[voice]++;
			}
		}
		for (int v=0; v<(int)m_kernspines.size(); v++) {
			if (attacksQ) {
				if (attackcount[v]) {
					results[v][i] = beatlev[i];
					attackcount[v] = 0;
				}
			} else {
				if (nonnullcount[v]) {
					results[v][i] = beatlev[i];
				}
				nonnullcount[v] = 0;
			}
		}
	}
}




/////////////////////////////////
//
// Tool_modori::Tool_modori -- Set the recognized options for the tool.
//

Tool_modori::Tool_modori(void) {
	define("m|modern=b",                                                 "prepare score for modern style");
	define("o|original=b",                                               "prepare score for original style");
	define("d|info=b",                                                   "display key/clef/mensuration information");
	define("I|no-instrument-name|no-instrument-names=b",                 "do not change part labels");
	define("A|no-instrument-abbreviation|no-instrument-abbreviations=b", "do not change part label abbreviations");
	define("C|no-clef|no-clefs=b",                                       "do not change clefs");
	define("K|no-key|no-keys=b",                                         "do not change key signatures");
	define("L|no-lyrics=b",                                              "do not change **text exclusive interpretations");
	define("M|no-mensuration|no-mensurations=b",                         "do not change mensurations");
	define("R|no-references=b",                                          "do not change reference records keys");
	define("T|no-text=b",                                                "do not change !LO:(TX|DY) layout parameters");
}



/////////////////////////////////
//
// Tool_modori::run -- Do the main work of the tool.
//

bool Tool_modori::run(HumdrumFileSet& infiles) {
	bool status = true;
	for (int i=0; i<infiles.getCount(); i++) {
		status &= run(infiles[i]);
	}
	return status;
}


bool Tool_modori::run(const string& indata, ostream& out) {
	HumdrumFile infile(indata);
	bool status = run(infile);
	if (hasAnyText()) {
		getAllText(out);
	} else {
		out << infile;
	}
	return status;
}


bool Tool_modori::run(HumdrumFile& infile, ostream& out) {
	bool status = run(infile);
	if (hasAnyText()) {
		getAllText(out);
	} else {
		out << infile;
	}
	return status;
}


bool Tool_modori::run(HumdrumFile& infile) {
	initialize();
	processFile(infile);
	return true;
}



//////////////////////////////
//
// Tool_modori::initialize --  Initializations that only have to be done once
//    for all HumdrumFile segments.
//

void Tool_modori::initialize(void) {
	m_infoQ = getBoolean("info");
	m_modernQ = getBoolean("modern");
	m_originalQ = getBoolean("original");
	if (m_modernQ && m_originalQ) {
		// if both options are used, ignore -m:
		m_modernQ = false;
	}
	m_nokeyQ         = getBoolean("no-key");
	m_noclefQ        = getBoolean("no-clef");
	m_nolotextQ      = getBoolean("no-text");
	m_nolyricsQ      = getBoolean("no-lyrics");
	m_norefsQ        = getBoolean("no-references");
	m_nomensurationQ = getBoolean("no-mensuration");
	m_nolabelsQ      = getBoolean("no-instrument-names");
	m_nolabelAbbrsQ  = getBoolean("no-instrument-abbreviations");
}



//////////////////////////////
//
// Tool_modori::processFile --
//

void Tool_modori::processFile(HumdrumFile& infile) {
	m_keys.clear();
	m_labels.clear();
	m_labelAbbrs.clear();
	m_clefs.clear();
	m_mensurations.clear();
	m_references.clear();
	m_lyrics.clear();
	m_lotext.clear();

	int maxtrack = infile.getMaxTrack();
	m_keys.resize(maxtrack+1);
	m_labels.resize(maxtrack+1);
	m_labelAbbrs.resize(maxtrack+1);
	m_clefs.resize(maxtrack+1);
	m_mensurations.resize(maxtrack+1);
	m_references.reserve(1000);
	m_lyrics.reserve(1000);
	m_lotext.reserve(1000);

	HumRegex hre;
	int exinterpLine = -1;

	for (int i=0; i<infile.getLineCount(); i++) {
		if (infile[i].isCommentLocal() || infile[i].isCommentGlobal()) {
			for (int j=0; j<infile[i].getFieldCount(); j++) {
				HTp token = infile.token(i, j);
				if (*token == "!") {
					continue;
				}
				if (hre.search(token, "^!!?LO:(TX|DY).*:mod=")) {
					m_lotext.push_back(token);
				} else if (hre.search(token, "^!!?LO:(TX|DY).*:ori=")) {
					m_lotext.push_back(token);
				}
				if (hre.search(token, "^!LO:MO:.*")) {
					m_lomo.push_back(token);
				}
			}
		}
		if (!infile[i].isInterpretation()) {
			continue;
		}
		HumNum timeval = infile[i].getDurationFromStart();
		for (int j=0; j<infile[i].getFieldCount(); j++) {
			HTp token = infile.token(i, j);
			if (token->isExclusiveInterpretation()) {
				exinterpLine = i;
				continue;
			}
			if (!token->isKern()) {
				continue;
			}
			if (*token == "*") {
				continue;
			}
			int track = token->getTrack();
			if (token->isKeySignature()) {
				m_keys[track][timeval].push_back(token);
			} else if (token->isOriginalKeySignature()) {
				m_keys[track][timeval].push_back(token);
			} else if (token->isModernKeySignature()) {
				m_keys[track][timeval].push_back(token);

			} else if (token->isInstrumentName()) {
				m_labels[track][timeval].push_back(token);
			} else if (token->isOriginalInstrumentName()) {
				m_labels[track][timeval].push_back(token);
			} else if (token->isModernInstrumentName()) {
				m_labels[track][timeval].push_back(token);

			} else if (token->isInstrumentAbbreviation()) {
				m_labelAbbrs[track][timeval].push_back(token);
			} else if (token->isOriginalInstrumentAbbreviation()) {
				m_labelAbbrs[track][timeval].push_back(token);
			} else if (token->isModernInstrumentAbbreviation()) {
				m_labelAbbrs[track][timeval].push_back(token);

			} else if (token->isClef()) {
				m_clefs[track][timeval].push_back(token);
			} else if (token->isOriginalClef()) {
				m_clefs[track][timeval].push_back(token);
			} else if (token->isModernClef()) {
				m_clefs[track][timeval].push_back(token);

			} else if (token->isMensuration()) {
				m_mensurations[track][timeval].push_back(token);
			} else if (token->isOriginalMensuration()) {
				m_mensurations[track][timeval].push_back(token);
			} else if (token->isModernMensuration()) {
				m_mensurations[track][timeval].push_back(token);
			}
		}
	}

	if (exinterpLine >= 0) {
		processExclusiveInterpretationLine(infile, exinterpLine);
	}

	storeModOriReferenceRecords(infile);

	if (m_infoQ) {
		if (m_modernQ || m_originalQ) {
			m_humdrum_text << infile;
		}
		printInfo();
	}

	if (!(m_modernQ || m_originalQ)) {
		// nothing to do
		return;
	}

	switchModernOriginal(infile);
	printModoriOutput(infile);
}


//////////////////////////////
//
// Tool_modori::processExclusiveInterpretationLine --
//

void Tool_modori::processExclusiveInterpretationLine(HumdrumFile& infile, int line) {
	vector<HTp> staffish;
	vector<HTp> staff;
	vector<vector<HTp>> nonstaff;
	bool init = false;
	bool changed = false;

	if (!infile[line].isExclusive()) {
		return;
	}

	for (int i=0; i<infile[line].getFieldCount(); i++) {
		HTp token = infile.token(line, i);
		if (!token->isExclusiveInterpretation()) {
			continue;
		}
		if (token->isStaff()) {
			staff.push_back(token);
			nonstaff.resize(nonstaff.size() + 1);
			init = 1;
		} else {
			if (init) {
				nonstaff.back().push_back(token);
			}
		}
		if (token->isStaff()) {
			staffish.push_back(token);
		} else if (*token == "**mod-kern") {
			staffish.push_back(token);
		} else if (*token == "**mod-mens") {
			staffish.push_back(token);
		} else if (*token == "**ori-kern") {
			staffish.push_back(token);
		} else if (*token == "**ori-mens") {
			staffish.push_back(token);
		}
	}

	for (int i=0; i<(int)staff.size(); i++) {
		changed |= processStaffCompanionSpines(nonstaff[i]);
	}

	changed |= processStaffSpines(staffish);

	if (changed) {
		infile[line].createLineFromTokens();
	}
}



//////////////////////////////
//
// Tool_modori::processStaffSpines --
//

bool Tool_modori::processStaffSpines(vector<HTp>& tokens) {

	HumRegex hre;
	bool changed = false;
	for (int i=0; i<(int)tokens.size(); i++) {
		if (hre.search(tokens[i], "^\\*\\*(ori|mod)-(.*)")) {
			string newexinterp = "**" + hre.getMatch(2) + "-" + hre.getMatch(1);
			tokens[i]->setText(newexinterp);
			changed = true;
		} else if (hre.search(tokens[i], "^\\*\\*(.*?)-(ori|mod)$")) {
			string newexinterp = "**" + hre.getMatch(2) + "-" + hre.getMatch(1);
			tokens[i]->setText(newexinterp);
			changed = true;
		}
	}

	return changed;
}



//////////////////////////////
//
// Tool_modori::processStaffCompanionSpines --
//

bool Tool_modori::processStaffCompanionSpines(vector<HTp> tokens) {

	vector<HTp> mods;
	vector<HTp> oris;
	vector<HTp> other;

	for (int i=0; i<(int)tokens.size(); i++) {
		if (tokens[i]->find("**mod-") != string::npos) {
			mods.push_back(tokens[i]);
		} else if (tokens[i]->find("**ori-") != string::npos) {
			oris.push_back(tokens[i]);
		} else {
			other.push_back(tokens[i]);
		}
	}

	bool gchanged = false;

	if (mods.empty() && oris.empty()) {
		// Nothing to do.
		return false;
	}

	// mods and oris should not be mixed, so if there are no
	// other spines, then also give up:
	if (other.empty()) {
		return false;
	}


	if (m_modernQ) {
		bool changed = false;
		// Swap (**mod-XXX and **XXX) to (**XXX and **ori-XXX)

		for (int i=0; i<(int)other.size(); i++) {
			if (other[i] == NULL) {
				continue;
			}
			string target = "**mod-" + other[i]->substr(2);
			for (int j=0; j<(int)mods.size(); j++) {
				if (mods[j] == NULL) {
					continue;
				}
				if (*mods[j] != target) {
					continue;
				}
				mods[j]->setText(*other[i]);
				mods[j] = NULL;
				changed = true;
				gchanged = true;
			}
			if (changed) {
				string replacement = "**ori-" + other[i]->substr(2);
				other[i]->setText(replacement);
				other[i] = NULL;
			}
		}

	} else if (m_originalQ) {
		bool changed = false;
		// Swap (**ori-XXX and **XXX) to (**XXX and **mod-XXX)

		for (int i=0; i<(int)other.size(); i++) {
			if (other[i] == NULL) {
				continue;
			}
			string target = "**ori-" + other[i]->substr(2);
			for (int j=0; j<(int)oris.size(); j++) {
				if (oris[j] == NULL) {
					continue;
				}
				if (*oris[j] != target) {
					continue;
				}
				oris[j]->setText(*other[i]);
				oris[j] = NULL;
				changed = true;
				gchanged = true;
			}
			if (changed) {
				string replacement = "**mod-" + other[i]->substr(2);
				other[i]->setText(replacement);
				other[i] = NULL;
			}
		}
	}

	return gchanged;
}



//////////////////////////////
//
// Tool_modori::storeModOriReferenceRecors --
//

void Tool_modori::storeModOriReferenceRecords(HumdrumFile& infile) {
	m_references.clear();

	vector<HLp> refs = infile.getGlobalReferenceRecords();
	vector<string> keys(refs.size());
	for (int i=0; i<(int)refs.size(); i++) {
		string key = refs.at(i)->getReferenceKey();
		keys.at(i) = key;
	}

	vector<int> modernIndex;
	vector<int> originalIndex;

	HumRegex hre;
	for (int i=0; i<(int)keys.size(); i++) {
		if (m_modernQ || m_infoQ) {
			if (hre.search(keys[i], "-mod$")) {
				modernIndex.push_back(i);
			}
		} else if (m_originalQ || m_infoQ) {
			if (hre.search(keys[i], "-ori$")) {
				originalIndex.push_back(i);
			}
		}
	}

	if (m_modernQ || m_infoQ) {
		// Store *-mod reference records if there is a pairing:
		int pairing = -1;
		for (int i=0; i<(int)modernIndex.size(); i++) {
			int index = modernIndex[i];
			pairing = getPairedReference(index, keys);
			if (pairing >= 0) {
				m_references.push_back(make_pair(refs[index]->token(0), refs[pairing]->token(0)));
			}
		}
	}

	if (m_originalQ || m_infoQ) {
		// Store *-ori reference records if there is a pairing:
		int pairing = -1;
		string target;
		for (int i=0; i<(int)originalIndex.size(); i++) {
			int index = originalIndex[i];
			pairing = getPairedReference(index, keys);
			if (pairing >= 0) {
				target = keys[index];
				m_references.push_back(make_pair(refs[index]->token(0), refs[pairing]->token(0)));
			}
		}
	}
}



//////////////////////////////
//
// Tool_modori::getPairedReference --
//

int Tool_modori::getPairedReference(int index, vector<string>& keys) {
	string key = keys.at(index);
	string tkey = key;
	if (tkey.size() > 4) {
		tkey.resize(tkey.size() - 4);
	} else {
		return -1;
	}

	for (int i=0; i<(int)keys.size(); i++) {
		int ii = index + i;
		if (ii < (int)keys.size()) {
			if (tkey == keys.at(ii)) {
				return ii;
			}
		}
		ii = index - i;
		if (ii >= 0) {
			if (tkey == keys.at(ii)) {
				return ii;
			}
		}
	}
	return -1;
}



//////////////////////////////
//
// Tool_modori::switchModernOriginal --
//

void Tool_modori::switchModernOriginal(HumdrumFile& infile) {

	set<int> changed;

	if (!m_nokeyQ) {
		for (int t=1; t<(int)m_keys.size(); ++t) {
			for (auto it = m_keys.at(t).begin(); it != m_keys.at(t).end(); ++it) {
				if (it->second.size() != 2) {
					continue;
				}
				bool status = swapKeyStyle(it->second.at(0), it->second.at(1));
				if (status) {
					int line = it->second.at(0)->getLineIndex();
					changed.insert(line);
					line = it->second.at(1)->getLineIndex();
					changed.insert(line);
				}
			}
		}
	}

	if (!m_nolabelsQ) {
		for (int t=1; t<(int)m_labels.size(); ++t) {
			for (auto it = m_labels.at(t).begin(); it != m_labels.at(t).end(); ++it) {
				if (it->second.size() != 2) {
					continue;
				}
				bool status = swapInstrumentNameStyle(it->second.at(0), it->second.at(1));
				if (status) {
					int line = it->second.at(0)->getLineIndex();
					changed.insert(line);
					line = it->second.at(1)->getLineIndex();
					changed.insert(line);
				}
			}
		}
	}

	if (!m_nolabelAbbrsQ) {
		for (int t=1; t<(int)m_labelAbbrs.size(); ++t) {
			for (auto it = m_labelAbbrs.at(t).begin(); it != m_labelAbbrs.at(t).end(); ++it) {
				if (it->second.size() != 2) {
					continue;
				}
				bool status = swapInstrumentAbbreviationStyle(it->second.at(0), it->second.at(1));
				if (status) {
					int line = it->second.at(0)->getLineIndex();
					changed.insert(line);
					line = it->second.at(1)->getLineIndex();
					changed.insert(line);
				}
			}
		}
	}

	if (!m_nolyricsQ) {
		bool adjust = false;
		int line = -1;
		for (int i=0; i<(int)m_lyrics.size(); i++) {
			HTp token = m_lyrics[i];
			line = token->getLineIndex();
			if (m_modernQ) {
				if (*token == "**text") {
					adjust = true;
					token->setText("**ori-text");
				} else if (*token == "**mod-text") {
					adjust = true;
					token->setText("**text");
				}
			} else {
				if (*token == "**text") {
					adjust = true;
					token->setText("**mod-text");
				} else if (*token == "**ori-text") {
					adjust = true;
					token->setText("**text");
				}
			}
		}
		if (adjust && (line >= 0)) {
			infile[line].createLineFromTokens();
		}
	}

	if (!m_nolotextQ) {
		HumRegex hre;
		for (int i=0; i<(int)m_lotext.size(); i++) {
			HTp token = m_lotext[i];
			int line = token->getLineIndex();
			if (hre.search(token, "^!!?LO:(TX|DY).*:mod=")) {
				string text = *token;
				hre.replaceDestructive(text, ":ori=", ":t=");
				hre.replaceDestructive(text, ":t=", ":mod=");
				token->setText(text);
				changed.insert(line);
			} else if (hre.search(token, "^!!?LO:(TX|DY).*:ori=")) {
				string text = *token;
				hre.replaceDestructive(text, ":mod=", ":t=");
				hre.replaceDestructive(text, ":t=", ":ori=");
				token->setText(text);
				changed.insert(line);
			}
		}
	}

	if (!m_norefsQ) {
		HumRegex hre;
		for (int i=0; i<(int)m_references.size(); i++) {
			HTp first = m_references[i].first;
			HTp second = m_references[i].second;

			if (m_modernQ) {
				if (hre.search(first, "^!!![^:]*?-mod:")) {
					string text = *first;
					hre.replaceDestructive(text, ":", "-...:");
					first->setText(text);
					infile[first->getLineIndex()].createLineFromTokens();

					text = *second;
					hre.replaceDestructive(text, "-ori:", ":");
					second->setText(text);
					infile[second->getLineIndex()].createLineFromTokens();
				}
			} else if (m_originalQ) {
				if (hre.search(first, "^!!![^:]*?-ori:")) {
					string text = *first;
					hre.replaceDestructive(text, ":", "-...:");
					first->setText(text);
					infile[first->getLineIndex()].createLineFromTokens();

					text = *second;
					hre.replaceDestructive(text, "-mod:", ":");
					second->setText(text);
					infile[second->getLineIndex()].createLineFromTokens();
				}
			}

		}
	}

	// Mensurations are only used for "original" display.  It is possible
	// to use a modern metric signature (common time or cut time) but these
	// are not currently allowed.  Only one *met at a given time position
	// is allowed.

	if (!m_nomensurationQ) {
		for (int t=1; t<(int)m_mensurations.size(); ++t) {
			for (auto it = m_mensurations.at(t).begin(); it != m_mensurations.at(t).end(); ++it) {
				if (it->second.size() == 1) {
					// swap omet to met, or met to omet:
					bool status = flipMensurationStyle(it->second.at(0));
					if (status) {
						int line = it->second.at(0)->getLineIndex();
						changed.insert(line);
					}
				} else if (it->second.size() == 2) {
					// swap omet/met or mmet/met:
					bool status = swapMensurationStyle(it->second.at(0), it->second.at(1));
					if (status) {
						int line = it->second.at(0)->getLineIndex();
						changed.insert(line);
						line = it->second.at(1)->getLineIndex();
						changed.insert(line);
					}
				}
			}
		}
	}

	if (!m_noclefQ) {
		for (int t=1; t<(int)m_clefs.size(); ++t) {
			for (auto it = m_clefs.at(t).begin(); it != m_clefs.at(t).end(); ++it) {
				if (it->second.size() != 2) {
					continue;
				}
				bool status = swapClefStyle(it->second.at(0), it->second.at(1));
				if (status) {
					int line = it->second.at(0)->getLineIndex();
					changed.insert(line);
					line = it->second.at(1)->getLineIndex();
					changed.insert(line);
				}
			}
		}
	}

	for (auto it = changed.begin(); it != changed.end(); ++it) {
		int line = *it;
		infile[line].createLineFromTokens();
	}

	updateLoMo(infile);
}


//////////////////////////////
//
// Tool_modori::printModoriOutput --
//

void Tool_modori::printModoriOutput(HumdrumFile& infile) {
	string state;
	if (m_modernQ) {

		// convert to modern
		for (int i=0; i<infile.getLineCount(); i++) {
			if (infile[i].isCommentGlobal()) {
				HTp token = infile.token(i, 0);
				if (*token == "!!LO:MO:mod") {
				   state = "mod";
					m_humdrum_text << token << endl;
					continue;
				} else if (*token == "!!LO:MO:ori") {
				   state = "ori";
					m_humdrum_text << token << endl;
					continue;
				} else if (*token == "!!LO:MO:end") {
					state = "";
					m_humdrum_text << token << endl;
					continue;
				}
			}
			if (state == "mod") {
				// Remove global comment prefix "!! ".  Complain if not there.
				if (infile[i].compare(0, 3, "!! ") != 0) {
					cerr << "Error: line does not start with \"!! \":\t" << infile[i] << endl;
				} else {
					m_humdrum_text << infile[i].substr(3) << endl;
				}
			} else if (state == "ori") {
				// Add global comment prefix "!! ".
				m_humdrum_text << "!! " << infile[i] << endl;
			} else {
				m_humdrum_text << infile[i] << endl;
			}
		}

	} else if (m_originalQ) {

		// convert to original
		for (int i=0; i<infile.getLineCount(); i++) {
			if (infile[i].isCommentGlobal()) {
				HTp token = infile.token(i, 0);
				if (*token == "!!LO:MO:mod") {
				   state = "mod";
					m_humdrum_text << token << endl;
					continue;
				} else if (*token == "!!LO:MO:ori") {
				   state = "ori";
					m_humdrum_text << token << endl;
					continue;
				} else if (*token == "!!LO:MO:end") {
					state = "";
					m_humdrum_text << token << endl;
					continue;
				}
			}
			if (state == "ori") {
				// Remove global comment prefix "!! ".  Complain if not there.
				if (infile[i].compare(0, 3, "!! ") != 0) {
					cerr << "Error: line does not start with \"!! \":\t" << infile[i] << endl;
				} else {
					m_humdrum_text << infile[i].substr(3) << endl;
				}
			} else if (state == "mod") {
				// Add global comment prefix "!! ".
				m_humdrum_text << "!! " << infile[i] << endl;
			} else {
				m_humdrum_text << infile[i] << endl;
			}
		}

	}
}



//////////////////////////////
//
// Tool_modori::updateLoMo --
//

void Tool_modori::updateLoMo(HumdrumFile& infile) {
	for (int i=0; i<(int)m_lomo.size(); i++) {
		processLoMo(m_lomo[i]);
	}
}



//////////////////////////////
//
// Tool_modori::processLoMo --
//

void Tool_modori::processLoMo(HTp lomo) {
	HumRegex hre;

	if (m_modernQ) {
		string text = lomo->getText();
		string modtext;
		string oritext;
		string base;
		string rest;
		if (hre.search(text, "(.*):mod=([^:]*)(.*)")) {
			base = hre.getMatch(1);
			modtext = hre.getMatch(2);
			rest = hre.getMatch(3);
			hre.replaceDestructive(modtext, ":", "&colon;", "g");
			HTp current = lomo->getNextToken();
			// null parameter allows next following null token
			// to be swapped out
			bool nullQ = hre.search(text, ":null:");
			if (!nullQ) {
				while (current) {
					if (current->isNull()) {
						current = current->getNextToken();
						continue;
					}
					break;
				}
			}
			if (current) {
				string oritext = current->getText();
				hre.replaceDestructive(oritext, "&colon;", ":", "g");
				current->setText(modtext);
				string newtext = base;
				newtext += ":ori=";
				newtext += oritext;
				newtext += rest;
				lomo->setText(newtext);
				lomo->getLine()->createLineFromTokens();
				current->getLine()->createLineFromTokens();
			}
		}

	} else if (m_originalQ) {
		string text = lomo->getText();
		string modtext;
		string oritext;
		string base;
		string rest;
		if (hre.search(text, "(.*):ori=([^:]*)(.*)")) {
			base = hre.getMatch(1);
			oritext = hre.getMatch(2);
			rest = hre.getMatch(3);
			hre.replaceDestructive(oritext, ":", "&colon;", "g");
			HTp current = lomo->getNextToken();
			// null parameter allows next following null token
			// to be swapped out
			bool nullQ = hre.search(text, ":null:");
			if (nullQ) {
				while (current) {
					if (current->isNull()) {
						current = current->getNextToken();
						continue;
					}
					break;
				}
			}
			if (current) {
				string modtext = current->getText();
				hre.replaceDestructive(modtext, "&colon;", ":", "g");
				current->setText(oritext);
				string newtext = base;
				newtext += ":mod=";
				newtext += modtext;
				newtext += rest;
				lomo->setText(newtext);
				lomo->getLine()->createLineFromTokens();
				current->getLine()->createLineFromTokens();
			}
		}
	}
}



//////////////////////////////
//
// Tool_modori::flipMensurationStyle -- Returns true if swapped.
//

bool Tool_modori::flipMensurationStyle(HTp token) {
	bool output = false;
	HumRegex hre;
	string text;
	if (token->isMensuration()) {
		// switch to invisible mensuration
		text = "*omet";
		text += token->substr(4);
		token->setText(text);
		output = true;
	} else if (token->isOriginalMensuration()) {
		// switch to visible mensuration
		text = "*met";
		text += token->substr(5);
		token->setText(text);
		output = true;
	}

	return output;
}



//////////////////////////////
//
// Tool_modori::swapKeyStyle -- Returns true if swapped.
//

bool Tool_modori::swapKeyStyle(HTp one, HTp two) {
	bool mtype1 = false;
	bool mtype2 = false;
	bool otype1 = false;
	bool otype2 = false;
	bool ktype1 = false;
	bool ktype2 = false;
	bool output = false;

	if (one->isKeySignature()) {
		ktype1 = true;
	} else if (one->isModernKeySignature()) {
		mtype1 = true;
	} else if (one->isOriginalKeySignature()) {
		otype1 = true;
	}

	if (two->isKeySignature()) {
		ktype2 = true;
	} else if (two->isModernKeySignature()) {
		mtype2 = true;
	} else if (two->isOriginalKeySignature()) {
		otype2 = true;
	}

	if (m_modernQ) {
		// Show the modern key signature.  If one key is *mk and the
		// other is *k then change *mk to *k and *k to *ok respectively.
		if (ktype1 && mtype2) {
			convertKeySignatureToOriginal(one);
			convertKeySignatureToRegular(two);
			output = true;
		} else if (mtype1 && ktype2) {
			convertKeySignatureToRegular(one);
			convertKeySignatureToOriginal(two);
			output = true;
		}
	} else if (m_originalQ) {
		// Show the original key.  If one key is *ok and the
		// other is *k then change *ok to *k and *k to *mk respectively.
		if (ktype1 && otype2) {
			convertKeySignatureToModern(one);
			convertKeySignatureToRegular(two);
			output = true;
		} else if (otype1 && ktype2) {
			convertKeySignatureToRegular(one);
			convertKeySignatureToModern(two);
			output = true;
		}
	}
	return output;
}



//////////////////////////////
//
// Tool_modori::swapInstrumentNameStyle -- Returns true if swapped.
//

bool Tool_modori::swapInstrumentNameStyle(HTp one, HTp two) {
	bool mtype1 = false;
	bool mtype2 = false;
	bool otype1 = false;
	bool otype2 = false;
	bool ktype1 = false;
	bool ktype2 = false;
	bool output = false;

	if (one->isInstrumentName()) {
		ktype1 = true;
	} else if (one->isModernInstrumentName()) {
		mtype1 = true;
	} else if (one->isOriginalInstrumentName()) {
		otype1 = true;
	}

	if (two->isInstrumentName()) {
		ktype2 = true;
	} else if (two->isModernInstrumentName()) {
		mtype2 = true;
	} else if (two->isOriginalInstrumentName()) {
		otype2 = true;
	}

	if (m_modernQ) {
		// Show the modern instrument name.  If one name is *mI" and the
		// other is *I" then change *mI" to *I" and *I" to *oI" respectively.
		if (ktype1 && mtype2) {
			convertInstrumentNameToOriginal(one);
			convertInstrumentNameToRegular(two);
			output = true;
		} else if (mtype1 && ktype2) {
			convertInstrumentNameToRegular(one);
			convertInstrumentNameToOriginal(two);
			output = true;
		}
	} else if (m_originalQ) {
		// Show the original key.  If one key is *ok and the
		// other is *k then change *ok to *k and *k to *mk respectively.
		if (ktype1 && otype2) {
			convertInstrumentNameToModern(one);
			convertInstrumentNameToRegular(two);
			output = true;
		} else if (otype1 && ktype2) {
			convertInstrumentNameToRegular(one);
			convertInstrumentNameToModern(two);
			output = true;
		}
	}
	return output;
}



//////////////////////////////
//
// Tool_modori::swapInstrumentAbbreviationStyle -- Returns true if swapped.
//

bool Tool_modori::swapInstrumentAbbreviationStyle(HTp one, HTp two) {
	bool mtype1 = false;
	bool mtype2 = false;
	bool otype1 = false;
	bool otype2 = false;
	bool ktype1 = false;
	bool ktype2 = false;
	bool output = false;

	if (one->isInstrumentAbbreviation()) {
		ktype1 = true;
	} else if (one->isModernInstrumentAbbreviation()) {
		mtype1 = true;
	} else if (one->isOriginalInstrumentAbbreviation()) {
		otype1 = true;
	}

	if (two->isInstrumentAbbreviation()) {
		ktype2 = true;
	} else if (two->isModernInstrumentAbbreviation()) {
		mtype2 = true;
	} else if (two->isOriginalInstrumentAbbreviation()) {
		otype2 = true;
	}

	if (m_modernQ) {
		// Show the modern instrument name.  If one name is *mI" and the
		// other is *I" then change *mI" to *I" and *I" to *oI" respectively.
		if (ktype1 && mtype2) {
			convertInstrumentAbbreviationToOriginal(one);
			convertInstrumentAbbreviationToRegular(two);
			output = true;
		} else if (mtype1 && ktype2) {
			convertInstrumentAbbreviationToRegular(one);
			convertInstrumentAbbreviationToOriginal(two);
			output = true;
		}
	} else if (m_originalQ) {
		// Show the original key.  If one key is *ok and the
		// other is *k then change *ok to *k and *k to *mk respectively.
		if (ktype1 && otype2) {
			convertInstrumentAbbreviationToModern(one);
			convertInstrumentAbbreviationToRegular(two);
			output = true;
		} else if (otype1 && ktype2) {
			convertInstrumentAbbreviationToRegular(one);
			convertInstrumentAbbreviationToModern(two);
			output = true;
		}
	}
	return output;
}



//////////////////////////////
//
// Tool_modori::swapMensurationStyle -- Returns true if swapped.
//

bool Tool_modori::swapMensurationStyle(HTp one, HTp two) {
	bool mtype1 = false;
	bool mtype2 = false;
	bool otype1 = false;
	bool otype2 = false;
	bool ktype1 = false;
	bool ktype2 = false;
	bool output = false;

	if (one->isMensuration()) {
		ktype1 = true;
	} else if (one->isModernMensuration()) {
		mtype1 = true;
	} else if (one->isOriginalMensuration()) {
		otype1 = true;
	}

	if (two->isMensuration()) {
		ktype2 = true;
	} else if (two->isModernMensuration()) {
		mtype2 = true;
	} else if (two->isOriginalMensuration()) {
		otype2 = true;
	}

	if (m_modernQ) {
		if (ktype1 && mtype2) {
			convertMensurationToOriginal(one);
			convertMensurationToRegular(two);
			output = true;
		} else if (mtype1 && ktype2) {
			convertMensurationToRegular(one);
			convertMensurationToOriginal(two);
			output = true;
		}
	} else if (m_originalQ) {
		if (ktype1 && otype2) {
			convertMensurationToModern(one);
			convertMensurationToRegular(two);
			output = true;
		} else if (otype1 && ktype2) {
			convertMensurationToRegular(one);
			convertMensurationToModern(two);
			output = true;
		}
	}
	return output;
}



//////////////////////////////
//
// Tool_modori::swapClefStyle -- Returns true if swapped.
//

bool Tool_modori::swapClefStyle(HTp one, HTp two) {
	bool mtype1 = false;
	bool mtype2 = false;
	bool otype1 = false;
	bool otype2 = false;
	bool ktype1 = false;
	bool ktype2 = false;
	bool output = false;

	if (one->isClef()) {
		ktype1 = true;
	} else if (one->isModernClef()) {
		mtype1 = true;
	} else if (one->isOriginalClef()) {
		otype1 = true;
	}

	if (two->isClef()) {
		ktype2 = true;
	} else if (two->isModernClef()) {
		mtype2 = true;
	} else if (two->isOriginalClef()) {
		otype2 = true;
	}

	if (m_modernQ) {
		// Show the modern key signature.  If one key is *mk and the
		// other is *k then change *mk to *k and *k to *ok respectively.
		if (ktype1 && mtype2) {
			convertClefToOriginal(one);
			convertClefToRegular(two);
			output = true;
		} else if (mtype1 && ktype2) {
			convertClefToRegular(one);
			convertClefToOriginal(two);
			output = true;
		}
	} else if (m_originalQ) {
		// Show the original key.  If one key is *ok and the
		// other is *k then change *ok to *k and *k to *mk respectively.
		if (ktype1 && otype2) {
			convertClefToModern(one);
			convertClefToRegular(two);
			output = true;
		} else if (otype1 && ktype2) {
			convertClefToRegular(one);
			convertClefToModern(two);
			output = true;
		}
	}
	return output;
}



//////////////////////////////
//
// Tool_modori::convertKeySignatureToModern --
//

void Tool_modori::convertKeySignatureToModern(HTp token) {
	HumRegex hre;
	if (hre.search(token, "^\\*[mo]?k(.*)")) {
		string text = "*mk";
		text += hre.getMatch(1);
		token->setText(text);
	}
}



//////////////////////////////
//
// Tool_modori::convertInstrumentNameToModern --
//

void Tool_modori::convertInstrumentNameToModern(HTp token) {
	HumRegex hre;
	if (hre.search(token, "^\\*[mo]?I\"(.*)")) {
		string text = "*mI\"";
		text += hre.getMatch(1);
		token->setText(text);
	}
}



//////////////////////////////
//
// Tool_modori::convertInstrumentAbbreviationToModern --
//

void Tool_modori::convertInstrumentAbbreviationToModern(HTp token) {
	HumRegex hre;
	if (hre.search(token, "^\\*[mo]?I'(.*)")) {
		string text = "*mI'";
		text += hre.getMatch(1);
		token->setText(text);
	}
}



//////////////////////////////
//
// Tool_modori::convertKeySignatureToOriginal --
//

void Tool_modori::convertKeySignatureToOriginal(HTp token) {
	HumRegex hre;
	if (hre.search(token, "^\\*[mo]?k(.*)")) {
		string text = "*ok";
		text += hre.getMatch(1);
		token->setText(text);
	}
}



//////////////////////////////
//
// Tool_modori::convertInstrumentNameToOriginal --
//

void Tool_modori::convertInstrumentNameToOriginal(HTp token) {
	HumRegex hre;
	if (hre.search(token, "^\\*[mo]?I\"(.*)")) {
		string text = "*oI\"";
		text += hre.getMatch(1);
		token->setText(text);
	}
}



//////////////////////////////
//
// Tool_modori::convertInstrumentAbbreviationToOriginal --
//

void Tool_modori::convertInstrumentAbbreviationToOriginal(HTp token) {
	HumRegex hre;
	if (hre.search(token, "^\\*[mo]?I'(.*)")) {
		string text = "*oI'";
		text += hre.getMatch(1);
		token->setText(text);
	}
}



//////////////////////////////
//
// Tool_modori::convertKeySignatureToRegular --
//

void Tool_modori::convertKeySignatureToRegular(HTp token) {
	HumRegex hre;
	if (hre.search(token, "^\\*[mo]?k(.*)")) {
		string text = "*k";
		text += hre.getMatch(1);
		token->setText(text);
	}
}



//////////////////////////////
//
// Tool_modori::convertInstrumentNameToRegular --
//

void Tool_modori::convertInstrumentNameToRegular(HTp token) {
	HumRegex hre;
	if (hre.search(token, "^\\*[mo]?I\"(.*)")) {
		string text = "*I\"";
		text += hre.getMatch(1);
		token->setText(text);
	}
}



//////////////////////////////
//
// Tool_modori::convertInstrumentAbbreviationToRegular --
//

void Tool_modori::convertInstrumentAbbreviationToRegular(HTp token) {
	HumRegex hre;
	if (hre.search(token, "^\\*[mo]?I'(.*)")) {
		string text = "*I'";
		text += hre.getMatch(1);
		token->setText(text);
	}
}



//////////////////////////////
//
// Tool_modori::convertClefToModern --
//

void Tool_modori::convertClefToModern(HTp token) {
	HumRegex hre;
	if (hre.search(token, "^\\*[mo]?clef(.*)")) {
		string text = "*mclef";
		text += hre.getMatch(1);
		token->setText(text);
	}
}



//////////////////////////////
//
// Tool_modori::convertClefToOriginal --
//

void Tool_modori::convertClefToOriginal(HTp token) {
	HumRegex hre;
	if (hre.search(token, "^\\*[mo]?clef(.*)")) {
		string text = "*oclef";
		text += hre.getMatch(1);
		token->setText(text);
	}
}



//////////////////////////////
//
// Tool_modori::convertClefToRegular --
//

void Tool_modori::convertClefToRegular(HTp token) {
	HumRegex hre;
	if (hre.search(token, "^\\*[mo]?clef(.*)")) {
		string text = "*clef";
		text += hre.getMatch(1);
		token->setText(text);
	}
}



//////////////////////////////
//
// Tool_modori::convertMensurationToModern --
//

void Tool_modori::convertMensurationToModern(HTp token) {
	HumRegex hre;
	if (hre.search(token, "^\\*[mo]?met\\((.*)")) {
		string text = "*mmet(";
		text += hre.getMatch(1);
		token->setText(text);
	}
}



//////////////////////////////
//
// Tool_modori::convertMensurationToOriginal --
//

void Tool_modori::convertMensurationToOriginal(HTp token) {
	HumRegex hre;
	if (hre.search(token, "^\\*[mo]?met\\((.*)")) {
		string text = "*omet(";
		text += hre.getMatch(1);
		token->setText(text);
	}
}



//////////////////////////////
//
// Tool_modori::convertMensurationToRegular --
//

void Tool_modori::convertMensurationToRegular(HTp token) {
	HumRegex hre;
	if (hre.search(token, "^\\*[mo]?met\\((.*)")) {
		string text = "*met(";
		text += hre.getMatch(1);
		token->setText(text);
	}
}



////////////////////
//
// Tool_modori::printInfo --
//

void Tool_modori::printInfo(void) {
	m_humdrum_text << "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" << endl;
	m_humdrum_text << "!! KEYS:" << endl;

	for (int t=1; t<(int)m_keys.size(); ++t) {
		for (auto it = m_keys.at(t).begin(); it != m_keys.at(t).end(); ++it) {
			m_humdrum_text << "!!\t" << it->first;
			for (int j=0; j<(int)it->second.size(); ++j) {
				m_humdrum_text << '\t' << it->second.at(j);
		}
			m_humdrum_text << endl;
		}
	}

	m_humdrum_text << "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" << endl;
	m_humdrum_text << "!! CLEFS:" << endl;

	for (int t=1; t<(int)m_keys.size(); ++t) {
		for (auto it = m_clefs.at(t).begin(); it != m_clefs.at(t).end(); ++it) {
			m_humdrum_text << "!!\t" << it->first;
			for (int j=0; j<(int)it->second.size(); ++j) {
				m_humdrum_text << '\t' << it->second.at(j);
			}
			m_humdrum_text << endl;
		}
	}

	m_humdrum_text << "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" << endl;
	m_humdrum_text << "!! MENSURATIONS:" << endl;

	for (int t=1; t<(int)m_mensurations.size(); ++t) {
		for (auto it = m_mensurations.at(t).begin(); it != m_mensurations.at(t).end(); ++it) {
			m_humdrum_text << "!!\t" << it->first;
			for (int j=0; j<(int)it->second.size(); j++) {
				m_humdrum_text << '\t' << it->second.at(j);
			}
			m_humdrum_text << endl;
		}
	}

	m_humdrum_text << "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" << endl;
	m_humdrum_text << "!! LYRICS:" << endl;

	for (int i=0; i<(int)m_lyrics.size(); i++) {
		HTp token = m_lyrics[i];
		m_humdrum_text << "!!\t";
		m_humdrum_text << token;
		m_humdrum_text << endl;
	}

	m_humdrum_text << "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" << endl;
	m_humdrum_text << "!! TEXT:" << endl;

	for (int i=0; i<(int)m_lotext.size(); i++) {
		m_humdrum_text << "!!\t" << m_lotext[i] << endl;
	}

	m_humdrum_text << "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" << endl;
	m_humdrum_text << "!! REFERENCES:" << endl;

	for (int i=0; i<(int)m_references.size(); i++) {
		m_humdrum_text << "!!\t" << m_references[i].first << endl;
		m_humdrum_text << "!!\t" << m_references[i].second << endl;
		m_humdrum_text << "!!\n";
	}

	m_humdrum_text << "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" << endl;
}




//////////////////////////////
//
// SonorityDatabase::buildDatabase --
//

void SonorityDatabase::buildDatabase(HLp line) {
	clear();
	if (line == NULL) {
		return;
	}
	m_line = line;
	bool nullQ = false;
	if (!line->isData()) {
		return;
	}
	int lowesti = 0;
	int lowest12 = 1000;

	for (int i=0; i<line->getFieldCount(); i++) {
		HTp token = m_line->token(i);
		if (!token->isKern()) {
			continue;
		}
		if (token->isRest()) {
			// ignoring rests, at least for now
			continue;
		}
		if (token->isNull()) {
			nullQ = true;
			token = token->resolveNull();
		}
		if (token->isNull()) {
			continue;
		}
		int scount = token->getSubtokenCount();
		for (int j=0; j<scount; j++) {
			expandList();
			m_notes.back().setToken(token, nullQ, j);
			if (m_notes.back().getBase12() < lowest12) {
				lowesti = (int)m_notes.size() - 1;
				lowest12 = m_notes.back().getBase12();
			}
		}
	}
	if (!m_notes.empty()) {
		m_lowest = m_notes[lowesti];
	}
}



//////////////////////////////
//
// SonorityDatabase::addNote --
//

void SonorityDatabase::addNote(const std::string& text) {
	expandList();
	m_notes.back().setString(text);
	// not dealing with lowest note
}



//////////////////////////////
//
// MSearchQueryToken::parseHarmonicQuery --
//

void MSearchQueryToken::parseHarmonicQuery(void) {
	if (!hpieces.empty()) {
		// do not reparse
		return;
	}
	for (int i=0; i<(int)harmonic.size(); i++) {
		char ch = tolower(harmonic[i]);
		if (ch >= 'a' && ch <= 'g') {
			hpieces.resize(hpieces.size() + 1);
			hpieces.back() += harmonic[i];
		} else if (ch == '-') {
			hpieces.back() += ch;
		} else if (ch == 'n') {
			hpieces.back() += ch;
		} else if (ch == '#') {
			hpieces.back() += ch;
		}
	}

	hquery.resize(hpieces.size());
	for (int i=0; i<(int)hpieces.size(); i++) {
		hquery[i].setString(hpieces[i]);
	}
}



/////////////////////////////////
//
// Tool_msearch::Tool_msearch -- Set the recognized options for the tool.
//

Tool_msearch::Tool_msearch(void) {
	define("debug=b",                     "diatonic search");
	define("q|query=s:4c4d4e4f4g",        "combined rhythm/pitch query string");
	define("p|pitch=s:cdefg",             "pitch query string");
	define("i|interval=s:2222",           "interval query string");
	define("r|d|rhythm|duration=s:44444", "rhythm query string");
	define("t|text=s:",                   "lyrical text query string");
	define("O|no-overlap=b",              "do not allow matches to overlap");
	define("x|cross=b",                   "search across parts");
	define("c|color=s",                   "highlight color");
	define("m|mark|marker=s:@",           "marking character");
	define("M|no-mark|no-marker=b",       "do not mark matches");
	define("Q|quiet=b",                   "quiet mode: do not summarize matches");
}



/////////////////////////////////
//
// Tool_msearch::run -- Do the main work of the tool.
//

bool Tool_msearch::run(HumdrumFileSet& infiles) {
	bool status = true;
	for (int i=0; i<infiles.getCount(); i++) {
		status &= run(infiles[i]);
	}
	return status;
}


bool Tool_msearch::run(const string& indata, ostream& out) {
	HumdrumFile infile(indata);
	bool status = run(infile);
	if (hasAnyText()) {
		getAllText(out);
	} else {
		out << infile;
	}
	return status;
}


bool Tool_msearch::run(HumdrumFile& infile, ostream& out) {
	bool status = run(infile);
	if (hasAnyText()) {
		getAllText(out);
	} else {
		out << infile;
	}
	return status;
}


bool Tool_msearch::run(HumdrumFile& infile) {
	m_sonorities.resize(infile.getLineCount());
	m_sonoritiesChecked.resize(infile.getLineCount());
	fill(m_sonoritiesChecked.begin(), m_sonoritiesChecked.end(), false);
	m_debugQ = getBoolean("debug");
	m_quietQ = getBoolean("quiet");
	m_nooverlapQ = getBoolean("no-overlap");
	NoteGrid grid(infile);
	if (m_debugQ) {
		grid.printGridInfo(cerr);
		// return 1;
	}
	initialize();

	if (getBoolean("text")) {
		m_text = getString("text");
	}

	if (m_text.empty()) {
		vector<MSearchQueryToken> query;
		fillMusicQuery(query);
		if (!query.empty()) {
			doMusicSearch(infile, grid, query);
		}
	} else {
		vector<MSearchTextQuery> query;
		fillTextQuery(query, getString("text"));
		doTextSearch(infile, grid, query);
	}

	infile.createLinesFromTokens();
	m_humdrum_text << infile;

	return 1;
}



//////////////////////////////
//
// Tool_msearch::initialize --
//

void Tool_msearch::initialize(void) {
	m_marker = getString("marker");
	// only allowing a single character for now:
	m_markQ = !getBoolean("no-marker");
	if (!m_markQ) {
		m_marker.clear();
	} else if (!m_marker.empty()) {
		m_marker = m_marker[0];
	}
}



//////////////////////////////
//
// Tool_msearch::fillWords --
//

void Tool_msearch::fillWords(HumdrumFile& infile, vector<TextInfo*>& words) {
	vector<HTp> textspines;
	infile.getSpineStartList(textspines, "**silbe");
	if (textspines.empty()) {
		infile.getSpineStartList(textspines, "**text");
	}
	for (int i=0; i<(int)textspines.size(); i++) {
		fillWordsForTrack(words, textspines[i]);
	}
}



//////////////////////////////
//
// Tool_msearch::fillWordsForTrack --
//

void Tool_msearch::fillWordsForTrack(vector<TextInfo*>& words,
		HTp starttoken) {
	HTp tok = starttoken->getNextToken();
	while (tok != NULL) {
		if (tok->empty()) {
			tok = tok->getNextToken();
			continue;
		}
		if (tok->isNull()) {
			tok = tok->getNextToken();
			continue;
		}
		if (!tok->isData()) {
			tok = tok->getNextToken();
			continue;
		}
		if (tok->at(0) == '-') {
			// append a syllable to the end of previous word
			if (!words.empty()) {
				words.back()->fullword += tok->substr(1, string::npos);
				if (words.back()->fullword.back() == '-') {
					words.back()->fullword.pop_back();
				}
			}
			tok = tok->getNextToken();
			continue;
		} else {
			// start a new word
			TextInfo* temp = new TextInfo();
			temp->nexttoken = NULL;
			if (!words.empty()) {
				words.back()->nexttoken = tok;
			}
			temp->fullword = *tok;
			if (!temp->fullword.empty()) {
				if (temp->fullword.back() == '-') {
					temp->fullword.pop_back();
				}
			}
			temp->starttoken = tok;
			words.push_back(temp);
			tok = tok->getNextToken();
			continue;
		}
	}
}



//////////////////////////////
//
// Tool_msearch::doTextSearch -- do a basic text search of all parts.
//

void Tool_msearch::doTextSearch(HumdrumFile& infile, NoteGrid& grid,
		vector<MSearchTextQuery>& query) {

	vector<TextInfo*> words;
	words.reserve(10000);
	fillWords(infile, words);
	int tcount = 0;

	HumRegex hre;
	for (int i=0; i<(int)query.size(); i++) {
		for (int j=0; j<(int)words.size(); j++) {
			if (hre.search(words.at(j)->fullword, query.at(i).word, "i")) {
				tcount++;
				markTextMatch(infile, *words[j]);
			}
		}
	}

	string textinterp = "**text";
	vector<HTp> interps;
	infile.getSpineStartList(interps);
	//int textcount = 0;
	int silbecount = 0;
	for (int i=0; i<(int)interps.size(); i++) {
		//if (interps[i]->getText() == "**text") {
		//	textcount++;
		//}
		if (interps[i]->getText() == "**silbe") {
			silbecount++;
		}
	}
	if (silbecount > 0) {
		// giving priority to **silbe content
		textinterp = "**silbe";
	}

	if (tcount && m_markQ) {
		string content = "!!!RDF";
		content += textinterp;
		content += ": ";
		content += m_marker;
		content += " = marked text";
		if (getBoolean("color")) {
			content += ", color=\"" + getString("color") + "\"";
		}
		infile.appendLine(content);
		infile.createLinesFromTokens();
	}

	for (int i=0; i<(int)words.size(); i++) {
		delete words[i];
		words[i] = NULL;
	}

	if (!m_quietQ) {
		addTextSearchSummary(infile, tcount, m_marker);
	}
}



//////////////////////////////
//
// Tool_msearch::printQuery --
//

void Tool_msearch::printQuery(vector<MSearchQueryToken>& query) {
	for (int i=0; i<(int)query.size(); i++) {
		cout << query[i];
	}
}



//////////////////////////////
//
// Tool_msearch::doMusicSearch -- do a basic melodic search of all parts.
//

void Tool_msearch::doMusicSearch(HumdrumFile& infile, NoteGrid& grid,
		vector<MSearchQueryToken>& query) {

	m_matches.clear();

	if (m_debugQ) {
		printQuery(query);
	}

	vector<vector<NoteCell*>> attacks;
	attacks.resize(grid.getVoiceCount());
	for (int i=0; i<grid.getVoiceCount(); i++) {
		grid.getNoteAndRestAttacks(attacks[i], i);
	}

	vector<NoteCell*> match;
	int mcount = 0;
	for (int i=0; i<(int)attacks.size(); i++) {
		for (int j=0; j<(int)attacks[i].size(); j++) {
			m_tomark.clear();
			bool status = checkForMusicMatch(attacks[i], j, query, match);
			if (!status) {
				m_tomark.clear();
			}
			if (status && !match.empty()) {
				mcount++;
				markMatch(infile, match);
				storeMatch(match);
				// cerr << "FOUND MATCH AT " << i << ", " << j << endl;
				// markNotes(attacks[i], j, (int)query.size());
			}
		}
	}

	if (mcount && m_markQ) {
		string content = "!!!RDF**kern: " + m_marker + " = marked note";
		if (getBoolean("color")) {
			content += ", color=\"" + getString("color") + "\"";
		}
		infile.appendLine(content);
		infile.createLinesFromTokens();
	}
	if (!m_quietQ) {
		addMusicSearchSummary(infile, mcount, m_marker);
	}
}



//////////////////////////////
//
// Tool_msearch::addMusicSearchSummary --
//

void Tool_msearch::addMusicSearchSummary(HumdrumFile& infile, int mcount, const string& marker) {

	m_barnums = infile.getMeasureNumbers();

	infile.appendLine("!!@@BEGIN: MUSIC_SEARCH_RESULT");
	string line;

	line = "!!@QUERY:\t";

	if (getBoolean("query")) {
		line += " -q ";
		string qstring = getString("query");
		makeLowerCase(qstring);
		if ((qstring.find(' ') != string::npos) || (qstring.find('(') != string::npos)) {
			line += '"';
			line += qstring;
			line += '"';
		} else {
			line += qstring;
		}
	}

	if (getBoolean("pitch")) {
		line += " -p ";
		string pstring = getString("pitch");
		makeLowerCase(pstring);
		if ((pstring.find(' ') != string::npos) || (pstring.find('(') != string::npos)) {
			line += '"';
			line += pstring;
			line += '"';
		} else {
			line += pstring;
		}
	}

	if (getBoolean("rhythm")) {
		line += " -r ";
		string rstring = getString("rhythm");
		makeLowerCase(rstring);
		if ((rstring.find(' ') != string::npos) || (rstring.find('(') != string::npos)) {
			line += '"';
			line += rstring;
			line += '"';
		} else {
			line += rstring;
		}
	}

	if (getBoolean("interval")) {
		line += " -i ";
		string istring = getString("interval");
		makeLowerCase(istring);
		if ((istring.find(' ') != string::npos) || (istring.find('(') != string::npos)) {
			line += '"';
			line += istring;
			line += '"';
		} else {
			line += istring;
		}
	}

	infile.appendLine(line);

	line = "!!@MATCHES:\t";
	line += to_string(mcount);
	infile.appendLine(line);

	if (m_markQ) {
		line = "!!@MARKER:\t";
		line += marker;
		infile.appendLine(line);
	}

	// Print music match location here.
	for (int i=0; i<(int)m_matches.size(); i++) {
		addMatch(infile, m_matches[i]);
	}

	infile.appendLine("!!@@END: MUSIC_SEARCH_RESULT");
}



//////////////////////////////
//
// Tool_msearch::addMatch --
//
// Todo:
//		* add duration of match
//

void Tool_msearch::addMatch(HumdrumFile& infile, vector<NoteCell*>& match) {
	if (match.empty()) {
		return;
	}
	if (match.back() == NULL) {
		// strange problem
		return;
	}
	int startIndex   = match.at(0)->getLineIndex();
	int endIndex     = match.back()->getLineIndex();
	int startMeasure = m_barnums.at(startIndex);
	int endMeasure   = m_barnums.at(endIndex);

	infile.appendLine("!!@@BEGIN:\tMATCH");

	string measure = "!!@MEASURE: ";

	measure += to_string(startMeasure);
	if (startMeasure != endMeasure) {
		measure += " ";
		measure += to_string(endMeasure);
	}
	infile.appendLine(measure);

	infile.appendLine("!!@@END:\tMATCH");
}



//////////////////////////////
//
// Tool_msearch::makeLowerCase --
//

void Tool_msearch::makeLowerCase(string& inout) {
	for (int i=0; i<(int)inout.size(); i++) {
		inout[i] = tolower(inout[i]);
	}
}



//////////////////////////////
//
// Tool_msearch::addTextSearchSummary --
//

void Tool_msearch::addTextSearchSummary(HumdrumFile& infile, int mcount, const string& marker) {
	infile.appendLine("!!@@BEGIN: TEXT_SEARCH_RESULT");
	string line;

	line = "!!@QUERY:\t";

	if (getBoolean("text")) {
		line += " -t ";
		string tstring = getString("text");
		if (tstring.find(' ') != string::npos) {
			line += '"';
			line += tstring;
			line += '"';
		} else {
			line += tstring;
		}
	}

	infile.appendLine(line);

	line = "!!@MATCHES:\t";
	line += to_string(mcount);
	infile.appendLine(line);

	if (m_markQ) {
		line = "!!@MARKER:\t";
		line += marker;
		infile.appendLine(line);
	}

	// Print match location here.
	infile.appendLine("!!@@END: TEXT_SEARCH_RESULT");
}



//////////////////////////////
//
// Tool_msearch::markNote --
//

void Tool_msearch::markNote(HTp token, int index) {
	if (index < 0) {
		return;
	}
	if (!token->isChord()) {
		if (token->find(m_marker) == string::npos) {
			string text = *token;
			text += m_marker;
			token->setText(text);
		}
		return;
	}
	vector<std::string> subtoks = token->getSubtokens();
	if (index >= (int)subtoks.size()) {
		return;
	}
	if (subtoks[index].find(m_marker) == string::npos) {
		subtoks[index] += m_marker;
		string output = subtoks[0];
		for (int i=1; i<(int)subtoks.size(); i++) {
			output += " ";
			output += subtoks[i];
		}
		token->setText(output);
	}
}



//////////////////////////////
//
// Tool_msearch::markMatch -- assumes monophonic music.
//

void Tool_msearch::markMatch(HumdrumFile& infile, vector<NoteCell*>& match) {
	for (int i=0; i<(int)m_tomark.size(); i++) {
		markNote(m_tomark[i].first, m_tomark[i].second);
	}
	if (match.empty()) {
		return;
	}
	HTp mstart = match[0]->getToken();
	HTp mend = NULL;
	if (match.back() != NULL) {
		mend = match.back()->getToken();
	} else {
		// there is an extra NULL token at the end of the music to allow
		// marking tied notes.
	}
	HTp tok = mstart;
	string text;
	while (tok && (tok != mend)) {
		if (!tok->isData()) {
			tok = tok->getNextToken();
			continue;
		}
		if (tok->isNull()) {
			tok = tok->getNextToken();
			continue;
		}
		if (tok->empty()) {
			// skip marking null tokens
			tok = tok->getNextToken();
			continue;
		}
		markNote(tok, 0);
		tok = tok->getNextToken();
		if (tok && !tok->isKern()) {
			cerr << "STRANGE LINKING WITH TEXT SPINE" << endl;
			break;
		}
	}
}



//////////////////////////////
//
// Tool_msearch::markTextMatch -- assumes monophonic voices.
//

void Tool_msearch::markTextMatch(HumdrumFile& infile, TextInfo& word) {
	HTp mstart = word.starttoken;
	HTp mnext = word.nexttoken;
	// while (mstart && !mstart->isKern()) {
	// 	mstart = mstart->getPreviousFieldToken();
	// }
	// HTp mend = word.nexttoken;
	// while (mend && !mend->isKern()) {
	// 	mend = mend->getPreviousFieldToken();
	// }

	if (mstart) {
		if (!mstart->isData()) {
			return;
		} else if (mstart->isNull()) {
			return;
		}
	}

	//if (mend) {
	//	if (!mend->isData()) {
	//		mend = NULL;
	//	} else if (mend->isNull()) {
	//		mend = NULL;
	//	}
	//}

	HTp tok = mstart;
	string text;
	while (tok && (tok != mnext)) {
		if (!tok->isData()) {
			tok = tok->getNextToken();
			continue;
		}
		if (tok->isNull()) {
			tok = tok->getNextToken();
			continue;
		}
		text = tok->getText();
		if ((!text.empty()) && (text.back() == '-')) {
			text.pop_back();
			text += m_marker;
			text += '-';
		} else {
			text += m_marker;
		}
		tok->setText(text);
		tok = tok->getNextToken();
	}
}



//////////////////////////////
//
// Tool_msearch::checkForMusicMatch -- See if the given position
//    in the music matches the query.
//

bool Tool_msearch::checkForMusicMatch(vector<NoteCell*>& notes, int index,
		vector<MSearchQueryToken>& query, vector<NoteCell*>& match) {

	match.clear();
	int maxi = (int)notes.size() - index;
	if ((int)query.size() > maxi) {
		// Search would extend off of the end of the music, so cannot be a match.
		match.clear();
		return false;
	}

	int c = 0;

	for (int i=0; i<(int)query.size(); i++) {
		int currindex = index + i - c;
		int lastindex = index + i -c - 1;
		int nextindex = index + i -c + 1;
		if (nextindex >= (int)notes.size()) {
			nextindex = -1;
		}

		if (currindex < 0) {
			cerr << "STRANGE NEGATIVE INDEX " << currindex << endl;
			break;
		}

		// If the query item can be anything, it automatically matches:
		if (query[i].anything) {
			match.push_back(notes[currindex]);
			continue;
		}

		//////////////////////////////
		//
		// RHYTHM
		//

		if (!query[i].anyrhythm) {
			if (notes[currindex]->getDuration() != query[i].duration) {
				match.clear();
				return false;
			}
		}

		//////////////////////////////
		//
		// INTERVALS
		//

		if (query[i].dinterval > -1000) {
			// match to a specific diatonic interval to the next note

			double currpitch;
			double nextpitch;

			currpitch = notes[currindex]->getAbsDiatonicPitch();

			if (nextindex >= 0) {
				nextpitch = notes[nextindex]->getAbsDiatonicPitch();
			} else {
				nextpitch = -123456789.0;
			}

			// maybe be careful of rests getting into this calculation:
			int interval = (int)(nextpitch - currpitch);

			if (interval != query[i].dinterval) {
				match.clear();
				return false;
			}
		} else if (query[i].cinterval > -1000) {
			// match to a specific chromatic interval to the next note

			double currpitch;
			double nextpitch;

			currpitch = notes[currindex]->getAbsBase40Pitch();

			if (nextindex >= 0) {
				nextpitch = notes[nextindex]->getAbsBase40Pitch();
			} else {
				nextpitch = -123456789.0;
			}

			// maybe be careful of rests getting into this calculation:
			int interval = (int)(nextpitch - currpitch);

			if (interval != query[i].cinterval) {
				match.clear();
				return false;
			}

		} else if (!query[i].anyinterval) {

			double currpitch;
			double nextpitch;
			double lastpitch;

			currpitch = notes[currindex]->getAbsDiatonicPitchClass();

			if (nextindex >= 0) {
				nextpitch = notes[nextindex]->getAbsDiatonicPitchClass();
			} else {
				nextpitch = -123456789.0;
			}

			if (lastindex >= 0) {
				lastpitch = notes[nextindex]->getAbsDiatonicPitchClass();
			} else {
				lastpitch = -987654321.0;
			}

			if (query[i].anypitch) {
				// search forward interval
				if (nextindex < 0) {
					// Match can not go off the edge of the music.
					match.clear();
					return false;
				} else {
					// check here if either note is a rest
					if (notes[currindex]->isRest() || notes[nextindex]->isRest()) {
						match.clear();
						return false;
					}

					if (query[i].direction > 0) {
						if (nextpitch - currpitch <= 0.0) {
							match.clear();
							return false;
						}
					} if (query[i].direction < 0) {
						if (nextpitch - currpitch >= 0.0) {
							match.clear();
							return false;
						}
					} else if (query[i].direction == 0.0) {
						if (nextpitch - currpitch != 0) {
							match.clear();
							return false;
						}
					}
				}
			} else {
				// search backward interval
				if (lastindex < 0) {
					// Match can not go off the edge of the music.
					match.clear();
					return false;
				} else {
					// check here if either note is a rest.
					if (notes[currindex]->isRest() || notes[nextindex]->isRest()) {
						match.clear();
						return false;
					}

					if (query[i].direction > 0) {
						if (lastpitch - currpitch <= 0.0) {
							match.clear();
							return false;
						}
					} if (query[i].direction < 0) {
						if (lastpitch - currpitch >= 0.0) {
							match.clear();
							return false;
						}
					} else if (query[i].direction == 0.0) {
						if (lastpitch - currpitch != 0) {
							match.clear();
							return false;
						}
					}
				}
			}
		}

		//////////////////////////////
		//
		// PITCH
		//

		if (!query[i].anypitch) {
			double qpitch = query[i].pc;
			double npitch = 0;
			if (notes[currindex]->isRest()) {
				if (Convert::isNaN(qpitch)) {
					// both notes are rests, so they match
					match.push_back(notes[currindex]);
					continue;
				} else {
					// query is not a rest but test note is
					match.clear();
					return false;
				}
			} else if (Convert::isNaN(qpitch)) {
				// query is a rest but test note is not
				match.clear();
				return false;
			}

			if (query[i].base == 40) {
				npitch = notes[currindex]->getAbsBase40PitchClass();
			} else if (query[i].base == 12) {
				npitch = ((int)notes[currindex]->getAbsMidiPitch()) % 12;
			} else if (query[i].base == 7) {
				npitch = ((int)notes[currindex]->getAbsDiatonicPitch()) % 7;
			} else {
				npitch = notes[currindex]->getAbsBase40PitchClass();
			}

			if (qpitch != npitch) {
				match.clear();
				return false;
			}
		}

		if (!query[i].harmonic.empty()) {
			query[i].parseHarmonicQuery();
			bool status = doHarmonicPitchSearch(query[i], notes[currindex]->getToken());
			if (!status) {
				return false;
			}
		}

		// All requirements for the note were matched, so store note
		// and continue to next note if needed.
		match.push_back(notes[currindex]);
	}

	// Add extra token for marking tied notes at end of match
	if (index + (int)query.size() < (int)notes.size()) {
		match.push_back(notes[index + (int)query.size() - c]);
	} else {
		match.push_back(NULL);
	}

	return true;
}



//////////////////////////////
//
// Tool_msearch::doHarmonicPitchSearch --
//

bool Tool_msearch::doHarmonicPitchSearch(MSearchQueryToken& query, HTp token) {
	if (query.harmonic.empty()) {
		return true;
	}

	int lindex = token->getLineIndex();
	if (m_verticalOnlyQ && m_sonoritiesChecked[lindex]) {
		// Only count once if searching only for vertical sonoroties
		// Later make this more efficient perhaps by not searching every
		// note for vertical-only searches, but rather search
		// the sonorities in one pass (but maybe this will not actually
		// be more efficient).
		return false;
	}
	m_sonoritiesChecked[lindex] = true;
	SonorityDatabase& sonorities = m_sonorities[lindex];
	if (sonorities.isEmpty()) {
		sonorities.buildDatabase(token->getLine());
	}

	bool exactQ = false;
	bool onlyQ = false;

	if (query.harmonic.find("==") != string::npos) {
		exactQ = true;
	} else if (query.harmonic.find("=") != string::npos) {
		onlyQ = true;
	}

	vector<int> diatonicCountsQuery(7, 0);
	vector<int> diatonicCountsMatch(7, 0);
	vector<int> diatonicCountsData(7, 0);
	vector<int> chromaticCountsQuery(40, 0);
	vector<int> chromaticCountsMatch(40, 0);
	vector<int> chromaticCountsData(40, 0);

	for (int i=0; i<sonorities.getNoteCount(); i++) {
		diatonicCountsData.at(sonorities[i].getBase7Pc())++;
		chromaticCountsData.at(sonorities[i].getBase40Pc())++;
	}

	int sum = 0;
	for(int i=0; i<(int)query.hquery.size(); i++) {
		if (query.hquery[i].hasAccidental()) {
			diatonicCountsQuery.at(query.hquery[i].getBase7Pc())++;
			if (query.hquery[i].hasUpperCase()) {
				if (query.hquery[i].getBase7Pc() != sonorities.getLowest().getBase7Pc()) {
					return false;
				}
			}

			// Don't check for same pitch-class twice:
			if (chromaticCountsMatch.at(query.hquery[i].getBase40Pc())) {
				continue;
			}
		} else {
			diatonicCountsQuery.at(query.hquery[i].getBase7Pc())++;
			if (query.hquery[i].hasUpperCase()) {
				if (query.hquery[i].getBase7Pc() != sonorities.getLowest().getBase7Pc()) {
					return false;
				}
			}

			// Don't check for same pitch-class twice:
			if (diatonicCountsMatch.at(query.hquery[i].getBase7Pc())) {
				continue;
			}
		}

		int status = checkHarmonicPitchMatch(query.hquery[i], sonorities, false);

		if (!status) {
			return false;
		}

		if (query.hquery[i].hasAccidental()) {
			chromaticCountsMatch.at(query.hquery[i].getBase40Pc()) += status;
		} else {
			diatonicCountsMatch.at(query.hquery[i].getBase7Pc()) += status;
		}
		sum += status;

	}

	if ((!exactQ) && (!onlyQ)) {
		return true;
	}


	if (exactQ && (sum != sonorities.getNoteCount())) {
		return false;
	}

	if (exactQ) {
		for (int i=0; i<(int)diatonicCountsMatch.size(); i++) {
			if (diatonicCountsMatch[i] != diatonicCountsQuery[i]) {
				return false;
			}
		}
		for (int i=0; i<(int)chromaticCountsMatch.size(); i++) {
			if (chromaticCountsMatch[i] != chromaticCountsQuery[i]) {
				return false;
			}
		}
	} else if (onlyQ) {
		SonorityDatabase son2;
		for (int i=0; i<(int)query.hpieces.size(); i++) {
			son2.addNote(query.hpieces[i]);
		}

		for (int k=0; k<sonorities.getNoteCount(); k++) {
			int status2 = checkHarmonicPitchMatch(sonorities[k], son2, true);
			if (!status2) {
				return false;
			}
		}
	}

	return true;
}



//////////////////////////////
//
// Tool_msearch::checkHarmonicPitchMatch -- Returns the number of matched notes.
//

int Tool_msearch::checkHarmonicPitchMatch(SonorityNoteData& query,
		SonorityDatabase& sonorities, bool suppressQ) {
	bool isChromatic = query.hasAccidental();
	bool isLowest = query.hasUpperCase();

	if (isLowest) {
		if (isChromatic) {
			int cpc = query.getBase40Pc();
			if (cpc != sonorities.getLowest().getBase40Pc()) {
				return 0;
			}
		} else {
			int dpc = query.getBase7Pc();
			if (dpc != sonorities.getLowest().getBase7Pc()) {
				return 0;
			}
		}
	}

	pair<HTp, int> tomark;

	// this algorithm highlights all vertical sonorities of given pitch class.
	int output = 0;
	if (isChromatic) {
		int cpitch = query.getBase40Pc();
		int cpc = cpitch % 40;
		for (int i=0; i<sonorities.getCount(); i++) {
			if (cpc == sonorities[i].getBase40Pc()) {
				if (!suppressQ) {
					tomark.first = sonorities[i].getToken();
					tomark.second = sonorities[i].getIndex();
					m_tomark.push_back(tomark);
				}
				output += 1;
			}
		}
	} else {
		int dpitch = query.getBase7Pc();
		int dpc = dpitch % 7;
		for (int i=0; i<sonorities.getCount(); i++) {
			if (dpc == sonorities[i].getBase7Pc()) {
				if (!suppressQ) {
					tomark.first = sonorities[i].getToken();
					tomark.second = sonorities[i].getIndex();
					m_tomark.push_back(tomark);
				}
				output += 1;
			}
		}
	}

	return output;
}



//////////////////////////////
//
// Tool_msearch::fillTextQuery --
//

void Tool_msearch::fillTextQuery(vector<MSearchTextQuery>& query,
		const string& input) {
	query.clear();
	bool inquote = false;

	query.resize(1);

	for (int i=0; i<(int)input.size(); i++) {
		if (input[i] == '"') {
			inquote = !inquote;
			query.resize(query.size() + 1);
			continue;
		}
		if (isspace(input[i])) {
			query.resize(query.size() + 1);
		}
		query.back().word.push_back(input[i]);
		if (inquote) {
			query.back().link = true;
		}
	}
}



//////////////////////////////
//
// Tool_msearch::fillMusicQuery --
//

void Tool_msearch::fillMusicQuery(vector<MSearchQueryToken>& query) {
	query.clear();

	string qinput;
	string pinput;
	string iinput;
	string rinput;

	if (getBoolean("query")) {
		qinput = getString("query");
	}

	if (getBoolean("pitch")) {
		pinput = getString("pitch");
		m_verticalOnlyQ = checkVerticalOnly(pinput);
	}

	if (getBoolean("interval")) {
		iinput = getString("interval");
	}

	if (getBoolean("rhythm")) {
		rinput = getString("rhythm");
	}

	if (!rinput.empty()) {
		fillMusicQueryRhythm(query, rinput);
	}

	if (!qinput.empty()) {
		fillMusicQueryInterleaved(query, qinput);
	}

	if (!pinput.empty()) {
		fillMusicQueryPitch(query, pinput);
	}

	if (!iinput.empty()) {
		fillMusicQueryInterval(query, iinput);
	}

	if (query.size() == 1) {
		if (query[0].anything) {
			query.clear();
		}
	}

}



//////////////////////////////
//
// Tool_msearch::fillMusicQueryPitch --
//

void Tool_msearch::fillMusicQueryPitch(vector<MSearchQueryToken>& query,
		const string& input) {
	fillMusicQueryInterleaved(query, input);
}



//////////////////////////////
//
// Tool_msearch::fillMusicQueryRhythm --
//

void Tool_msearch::fillMusicQueryRhythm(vector<MSearchQueryToken>& query,
		const string& input) {
	string output;
	output.reserve(input.size() * 4);

	for (int i=0; i<(int)input.size(); i++) {
		output += input[i];
		output += ' ';
	}

	// remove spaces to allow rhythms:
	// 64 => 64
   // 32 => 32
	// 16 => 16
	for (int i=0; i<(int)output.size(); i++) {
		if ((i > 1) && (output[i] == '6') && (output[i-1] == ' ') && (output[i-2] == '1')) {
			output.erase(i-1, 1);
			i--;
		}
		if ((i > 1) && (output[i] == '2') && (output[i-1] == ' ') && (output[i-2] == '3')) {
			output.erase(i-1, 1);
			i--;
		}
		if ((i > 1) && (output[i] == '4') && (output[i-1] == ' ') && (output[i-2] == '6')) {
			output.erase(i-1, 1);
			i--;
		}
      if ((i > 0) && (output[i] == '.')) {
			output.erase(i-1, 1);
			i--;
		}
	}

	fillMusicQueryInterleaved(query, output, true);

}



//////////////////////////////
//
// Tool_msearch::convertPitchesToIntervals --
//

string Tool_msearch::convertPitchesToIntervals(const string& input) {
	if (input.empty()) {
		return "";
	}
	for (int i=0; i<(int)input.size(); i++) {
		if (isdigit(input[i])) {
			return input;
		}
		if (tolower(input[i] == 'r')) {
			// not allowing rests for now
			return input;
		}
	}
	vector<string> pitches;

	for (int i=0; i<(int)input.size(); i++) {
		char ch = tolower(input[i]);
		if (ch >= 'a' && ch <= 'g') {
			string val;
			val += ch;
			pitches.push_back(val);
			if (i > 0) {
				if (input[i-1] == '^') {
					pitches.back().insert(0, "^");
				}
				if (input[i-1] == 'v') {
					pitches.back().insert(0, "v");
				}
			}
			continue;
		}
		if (!pitches.empty()) {
			if (ch == 'n') {
				pitches.back() += 'n';
			} else if (ch == '-') {
				pitches.back() += '-';
			} else if (ch == '#') {
				pitches.back() += '#';
			}
		}
	}

	if (pitches.size() <= 1) {
		return "";
	}

	vector<bool> chromatic(pitches.size(), false);
	for (int i=0; i<(int)pitches.size(); i++) {
		for (int j=(int)pitches[i].size()-1; j>0; j--) {
			int ch = pitches[i][j];
			if ((ch == 'n') || (ch == '-') || (ch == '#')) {
				chromatic[i] = true;
				break;
			}
		}
	}

	string output;
	int p1;
	int p2;
	int base40;
	int base7;
	int sign;
	for (int i=0; i<(int)pitches.size() - 1; i++) {
		if (chromatic[i] && chromatic[i+1]) {
			p1 = Convert::kernToBase40(pitches[i]);
			p2 = Convert::kernToBase40(pitches[i+1]);
			base40 = p2 - p1;
			sign = base40 < 0 ? -1 : +1;
			if (sign < 0) {
				base40 = -base40;
			}
			string value = "";
			if (sign < 0) {
				value += "-";
			}
			value += Convert::base40ToIntervalAbbr(base40);
			output += value;
			output += " ";
		} else {
			p1 = Convert::kernToBase7(pitches[i]);
			p2 = Convert::kernToBase7(pitches[i+1]);
			base7 = p2 - p1;
			sign = base7 < 0 ? -1 : +1;
			if (sign < 0) {
				base7 = -base7;
			}
			string value = "";
			if (sign < 0) {
				value += "-";
			}
			value += to_string(base7 + 1);
			output += value;
			output += " ";
		}
	}

	if (output.size() > 0) {
		if (output.back() == ' ') {
			output.resize((int)output.size() - 1);
		}
	}

	return output;
}



//////////////////////////////
//
// Tool_msearch::fillMusicQueryInterval --
//

void Tool_msearch::fillMusicQueryInterval(vector<MSearchQueryToken>& query,
		const string& input) {

	string newinput = convertPitchesToIntervals(input);

	char ch;
	int counter = 0;
	MSearchQueryToken temp;
	MSearchQueryToken *active = &temp;

	if (query.size() > 0) {
		active = &query.at(counter);
	} else {
		// what is this for?
	}

	int sign = 1;
	string alteration;
	for (int i=0; i<(int)newinput.size(); i++) {
		ch = newinput[i];
		if (ch == ' ') {
			// skip over spaces
			continue;
		}
		if ((ch == 'P') ||  (ch == 'p')) {
			alteration = "P";
			continue;
		}
		if ((ch == 'd') ||  (ch == 'D')) {
			if ((!alteration.empty()) && (alteration[0] == 'd')) {
				alteration += "d";
			} else {
				alteration = "d";
			}
			continue;
		}
		if ((ch == 'A') ||  (ch == 'a')) {
			if ((!alteration.empty()) && (alteration[0] == 'A')) {
				alteration += "A";
			} else {
				alteration = "A";
			}
			continue;
		}
		if ((ch == 'M') ||  (ch == 'm')) {
			alteration = ch;
			continue;
		}
		if (ch == '-') {
			sign = -1;
			continue;
		}
		if (ch == '+') {
			sign = +1;
			continue;
		}
		ch = tolower(ch);

		if (!isdigit(ch)) {
			// skip over non-digits (sign of interval
			// will be read retroactively).
			continue;
		}

		// check for intervals.  Intervals will trigger a
		// new element in the query list

		active->anything = false;
		active->anyinterval = false;
		// active->direction = 1;

		if (alteration.empty()) {
			// store a diatonic interval
			active->dinterval = (ch - '0') - 1; // zero-indexed interval
			active->dinterval *= sign;
		} else {
			active->cinterval = makeBase40Interval((ch - '0') - 1, alteration);
			active->cinterval *= sign;
		}
		sign = 1;
		alteration.clear();

		if (active == &temp) {
			query.push_back(temp);
			temp.clear();
		}
		counter++;
		if ((int)query.size() > counter) {
			active = &query.at(counter);
		} else {
			active = &temp;
		}
	}

	// The last element in the interval search is set to
	// any pitch, because the interval was already checked
	// to the next note, and this value is needed to highlight
	// the next note of the interval.
	active->anything = true;
	active->anyinterval = true;
	if (active == &temp) {
		query.push_back(temp);
		temp.clear();
	}

}



//////////////////////////////
//
// Tool_msearch::makeBase40Interval --
//

int Tool_msearch::makeBase40Interval(int diatonic, const string& alteration) {
	int sign = 1;
	if (diatonic < 0) {
		sign = -1;
		diatonic = -diatonic;
	}
	bool perfectQ = false;
	int base40 = 0;
	switch (diatonic) {
		case 0:  // unison
			base40 = 0;
			perfectQ = true;
			break;
		case 1:  // second
			base40 = 6;
			perfectQ = false;
			break;
		case 2:  // third
			base40 = 12;
			perfectQ = false;
			break;
		case 3:  // fourth
			base40 = 17;
			perfectQ = true;
			break;
		case 4:  // fifth
			base40 = 23;
			perfectQ = true;
			break;
		case 5:  // sixth
			base40 = 29;
			perfectQ = false;
			break;
		case 6:  // seventh
			base40 = 35;
			perfectQ = false;
			break;
		case 7:  // octave
			base40 = 40;
			perfectQ = true;
			break;
		case 8:  // ninth
			base40 = 46;
			perfectQ = false;
			break;
		case 9:  // tenth
			base40 = 52;
			perfectQ = false;
			break;
		default:
			cerr << "cannot handle this interval yet.  Setting to unison" << endl;
			base40 = 0;
			perfectQ = 1;
	}

	if (perfectQ) {
		if (alteration == "P") {
			// do nothing since the interval is already perfect
		} else if ((!alteration.empty()) && (alteration[0] == 'd')) {
			if (alteration.size() <= 2) {
				base40 -= (int)alteration.size();
			} else {
				cerr << "TOO MUCH DIMINISHED, IGNORING" << endl;
			}
		} else if ((!alteration.empty()) && (alteration[0] == 'A')) {
			if (alteration.size() <= 2) {
				base40 += (int)alteration.size();
			} else {
				cerr << "TOO MUCH AUGMENTED, IGNORING" << endl;
			}
		}
	} else {
		if (alteration == "M") {
			// do nothing since the interval is already major
		} else if (alteration == "m") {
			base40--;
		} else if ((!alteration.empty()) && (alteration[0] == 'd')) {
			if (alteration.size() <= 2) {
				base40 -= (int)alteration.size() + 1;
			} else {
				cerr << "TOO MUCH DIMINISHED, IGNORING" << endl;
			}
		} else if ((!alteration.empty()) && (alteration[0] == 'A')) {
			if (alteration.size() <= 2) {
				base40 += (int)alteration.size();
			} else {
				cerr << "TOO MUCH AUGMENTED, IGNORING" << endl;
			}
		}
	}
	base40 *= sign;
	return base40;
}



//////////////////////////////
//
// Tool_msearch::fillMusicQueryInterleaved --
//

void Tool_msearch::fillMusicQueryInterleaved(vector<MSearchQueryToken>& query,
		const string& input, bool rhythmQ) {

	string newinput = input;
	char ch;
	int counter = 0;
	MSearchQueryToken temp;
	MSearchQueryToken *active = &temp;
	string paren;

	if (query.size() > 0) {
		active = &query.at(counter);
	} else {
		// what is this for?
	}

	for (int i=0; i<(int)newinput.size(); i++) {
		paren.clear();
		ch = tolower(newinput[i]);
		if (ch == '(') {
			paren += ch;
			newinput[i] = ' ';
			// A harmonic search initiated
			int j = i;
			bool keepQ = true;
			bool diatonicQ = false;
			for (j=i+1; j<(int)newinput.size(); j++) {
				char ch2 = tolower(newinput[j]);
				if (ch2 == ')') {
					paren += ch2;
					newinput[j] = ' ';
					break;
				}
				if (ch2 >= 'a' && ch2 <= 'g') {
					if (diatonicQ) {
						keepQ = false;
					} else {
						diatonicQ = true;
					}
				}
				if (keepQ) {
					paren += newinput[j];
					continue;
				} else {
					paren += newinput[j];
					newinput[j] = ' ';
				}
			}
			if (!paren.empty()) {
				active->harmonic = paren;
				paren.clear();
			}
			continue;
		}

		if (ch == '=') {
			continue;
		}
		if (ch == ' ') {
			// skip over multiple spaces
			if (i > 0) {
            if (newinput[i-1] == ' ') {
					continue;
				}
			}
		}

		if (ch == '^') {
			active->anything = false;
			active->anyinterval = false;
			active->direction = -1;
			continue;
		}
		if (ch == 'v') {
			active->anything = false;
			active->anyinterval = false;
			active->direction = 1;
			continue;
		}

		// process rhythm.  This must go first then intervals then pitches
		if (isdigit(ch) || (ch == '.')) {
			active->anything = false;
			active->anyrhythm = false;
			active->rhythm += ch;
			if (i < (int)newinput.size() - 1) {
				if (newinput[i+1] == ' ') {
					if (active == &temp) {
						query.push_back(temp);
						temp.clear();
					}
					counter++;
					if ((int)query.size() > counter) {
						active = &query.at(counter);
					} else {
						active = &temp;
					}
					continue;
				}
			} else {
				// this is the last charcter in the input string
				if (active == &temp) {
						query.push_back(temp);
						temp.clear();
				}
				counter++;
				if ((int)query.size() > counter) {
					active = &query.at(counter);
				} else {
					active = &temp;
				}
			}
		}

		// check for intervals.  Intervals will trigger a
		// new element in the query list
		// A new type ^ or v will not increment the query list
		// (and they will expect a pitch after them).
		if (ch == '/') {
			active->anything = false;
			active->anyinterval = false;
			active->direction = 1;
			if (active == &temp) {
				query.push_back(temp);
				temp.clear();
			}
			counter++;
			if ((int)query.size() > counter) {
				active = &query.at(counter);
			} else {
				active = &temp;
			}
			continue;
		} else if (ch == '\\') {
			active->anything = false;
			active->anyinterval = false;
			active->direction = -1;
			if (active == &temp) {
				query.push_back(temp);
				temp.clear();
			}
			counter++;
			if ((int)query.size() > counter) {
				active = &query.at(counter);
			} else {
				active = &temp;
			}
			continue;
		} else if (ch == '=') {
			active->anything = false;
			active->anyinterval = false;
			active->direction = 0;
			if (active == &temp) {
				query.push_back(temp);
				temp.clear();
			}
			counter++;
			if ((int)query.size() > counter) {
				active = &query.at(counter);
			} else {
				active = &temp;
			}
			continue;
		}

		// check for actual pitches
		if ((ch >= 'a' && ch <= 'g')) {
			active->anything = false;
			active->anypitch = false;
			active->base = 7;
			active->pc = (ch - 'a' + 5) % 7;
			if (active == &temp) {
				query.push_back(temp);
				temp.clear();
			}
			counter++;
			if ((int)query.size() > counter) {
				active = &query.at(counter);
			} else {
				active = &temp;
			}
			continue;
		} else if (ch == 'r') {
			active->anything = false;
			active->anypitch = false;
			active->base = 7;
			active->pc = GRIDREST;
			if (active == &temp) {
				query.push_back(temp);
				temp.clear();
			}
			counter++;
			if ((int)query.size() > counter) {
				active = &query.at(counter);
			} else {
				active = &temp;
			}
			continue;
		}

		// accidentals:
		if ((!query.empty()) && (ch == 'n') && (!Convert::isNaN(query.back().pc))) {
			query.back().base = 40;
			query.back().pc = Convert::base7ToBase40((int)query.back().pc + 70) % 40;
		} else if ((!query.empty()) && (ch == '#') && (!Convert::isNaN(query.back().pc))) {
			query.back().base = 40;
			query.back().pc = (Convert::base7ToBase40((int)query.back().pc + 70) + 1) % 40;
		} else if ((!query.empty()) && (ch == '-') && (!Convert::isNaN(query.back().pc))) {
			query.back().base = 40;
			query.back().pc = (Convert::base7ToBase40((int)query.back().pc + 70) - 1) % 40;
		}
		// deal with double sharps and double flats here
	}

	// Convert rhythms to durations
	for (int i=0; i<(int)query.size(); i++) {
		if (query[i].anyrhythm) {
			continue;
		}
		if (query[i].rhythm.empty()) {
			continue;
		}
		query[i].duration = Convert::recipToDuration(query[i].rhythm);
	}

	// what is this for (end condition)?
	//if ((!query.empty()) && (query[0].base <= 0)) {
	//	temp.clear();
	//	temp.anything = true;
	//	query.insert(query.begin(), temp);
	//}
}



//////////////////////////////
//
// checkVerticalOnly --
//

bool Tool_msearch::checkVerticalOnly(const string& input) {
	if (input.empty()) {
		return false;
	}
	if (input.size() < 2) {
		return false;
	}
	if (input[0] != '(') {
		return false;
	}
	if (input.back() != ')') {
		return false;
	}
	for (int i=1; i<(int)input.size()-1; i++) {
		// Maybe allow internal () if there is nothing outside of them.
		if (input[i] == '(') {
			return false;
		}
		if (input[i] == ')') {
			return false;
		}
	}
	return true;
}



//////////////////////////////
//
// Tool_msearch::storeMatch -- Store a search result for later printing
//    in the input file footer.
//

void Tool_msearch::storeMatch(vector<NoteCell*>& match) {
	m_matches.resize(m_matches.size() + 1);
	m_matches.back().resize(match.size());
	for (int i=0; i<(int)match.size(); i++) {
		m_matches.back().at(i) = match.at(i);
	}
}



//////////////////////////////
//
// operator<< -- print MSearchQueryToken item.
//

ostream& operator<<(ostream& out, MSearchQueryToken& item) {
	out << "ITEM: "           << endl;
	out << "\tANYTHING:\t"    << item.anything    << endl;
	out << "\tANYPITCH:\t"    << item.anypitch    << endl;
	out << "\tANYINTERVAL:\t" << item.anyinterval << endl;
	out << "\tANYRHYTHM:\t"   << item.anyrhythm   << endl;
	out << "\tPC:\t\t"        << item.pc          << endl;
	out << "\tBASE:\t\t"      << item.base        << endl;
	out << "\tDIRECTION:\t"   << item.direction   << endl;
	out << "\tDINTERVAL:\t"   << item.dinterval   << endl;
	out << "\tCINTERVAL:\t"   << item.cinterval   << endl;
	out << "\tRHYTHM:\t\t"    << item.rhythm      << endl;
	out << "\tDURATION:\t"    << item.duration    << endl;
	if (!item.harmonic.empty()) {
		out << "\tHARMONIC:\t" << item.harmonic    << endl;
	}
	return out;
}



//////////////////////////////
//
// Tool_musedata2hum::Tool_musedata2hum --
//

Tool_musedata2hum::Tool_musedata2hum(void) {
	// Options& options = m_options;
	// options.define("k|kern=b","display corresponding **kern data");

	define("g|group=s:score", "the data group to process");
	define("r|recip=b",       "output **recip spine");
	define("s|stems=b",       "include stems in output");
	define("omv|no-omv=b",    "exclude extracted OMV record in output data");
}



//////////////////////////////
//
// initialize --
//

void Tool_musedata2hum::initialize(void) {
	m_stemsQ = getBoolean("stems");
	m_recipQ = getBoolean("recip");
	m_group  = getString("group");
	m_noOmvQ = getBoolean("no-omv");
}



//////////////////////////////
//
// Tool_musedata2hum::setOptions --
//

void Tool_musedata2hum::setOptions(int argc, char** argv) {
	m_options.process(argc, argv);
}


void Tool_musedata2hum::setOptions(const vector<string>& argvlist) {
    m_options.process(argvlist);
}



//////////////////////////////
//
// Tool_musedata2hum::getOptionDefinitions -- Used to avoid
//     duplicating the definitions in the test main() function.
//

Options Tool_musedata2hum::getOptionDefinitions(void) {
	return m_options;
}



//////////////////////////////
//
// Tool_musedata2hum::convert -- Convert a MusicXML file into
//     Humdrum content.
//

bool Tool_musedata2hum::convertFile(ostream& out, const string& filename) {
	MuseDataSet mds;
	int result = mds.readFile(filename);
	if (!result) {
		cerr << "\nMuseData file [" << filename << "] has syntax errors\n";
		cerr << "Error description:\t" << mds.getError() << "\n";
		exit(1);
	}
	return convert(out, mds);
}


bool Tool_musedata2hum::convert(ostream& out, istream& input) {
	MuseDataSet mds;
	mds.read(input);
	return convert(out, mds);
}


bool Tool_musedata2hum::convertString(ostream& out, const string& input) {
	MuseDataSet mds;
	int result = mds.readString(input);
	if (!result) {
		cout << "\nXML content has syntax errors\n";
		cout << "Error description:\t" << mds.getError() << "\n";
		exit(1);
	}
	return convert(out, mds);
}


bool Tool_musedata2hum::convert(ostream& out, MuseDataSet& mds) {
	int partcount = mds.getFileCount();
	if (partcount == 0) {
		cerr << "Error: No parts found in data:" << endl;
		cerr << mds << endl;
		return false;
	}
	initialize();

	m_tempo = mds.getMidiTempo();

	vector<int> groupMemberIndex = mds.getGroupIndexList(m_group);
	if (groupMemberIndex.empty()) {
		cerr << "Error: no files in the " << m_group << " membership." << endl;
		return false;
	}

	HumGrid outdata;
	bool status = true;
	for (int i=0; i<(int)groupMemberIndex.size(); i++) {
		status &= convertPart(outdata, mds, groupMemberIndex[i], i, (int)groupMemberIndex.size());
	}

	HumdrumFile outfile;
	outdata.transferTokens(outfile);
	outfile.generateLinesFromTokens();
	stringstream sss;
	sss << outfile;
	outfile.readString(sss.str());

	if (needsAboveBelowKernRdf()) {
		outfile.appendLine("!!!RDF**kern: > = above");
		outfile.appendLine("!!!RDF**kern: < = above");
	}

	outfile.createLinesFromTokens();

	Tool_trillspell trillspell;
	trillspell.run(outfile);

	// Convert comments in header of first part:
	int ii = groupMemberIndex[0];
	bool ending = false;
	HumRegex hre;
	for (int i=0; i< mds[ii].getLineCount(); i++) {
		if (mds[ii][i].isAnyNote()) {
			break;
		}
		if (mds[ii].getLine(i).compare(0, 2, "@@") == 0) {
			string output = mds[ii].getLine(i);
			if (output == "@@@") {
				ending = true;
				continue;
			}
			for (int j=0; j<(int)output.size(); j++) {
				if (output[j] == '@') {
					output[j] = '!';
				} else {
					break;
				}
			}
			if (hre.search(output, "!!!\\s*([^!:]+)\\s*:")) {
				string key = hre.getMatch(1);
				m_usedReferences[key] = true;
			}
			if (ending) {
           m_postReferences.push_back(output);
			} else {
				out << output << endl;
			}
		}
	}

	if (!m_usedReferences["COM"]) {
		string composer = mds[ii].getComposer();
		if (!composer.empty()) {
				out << "!!!COM: " << composer << endl;
		}
	}

	if (!m_usedReferences["CDT"]) {
		string cdate = mds[ii].getComposerDate();
		if (!cdate.empty()) {
			out << "!!!CDT: " << cdate << endl;
		}
	}

	if (!m_usedReferences["OTL"]) {
		string worktitle = mds[ii].getWorkTitle();
		if (!worktitle.empty()) {
			out << "!!!OTL: " << worktitle << endl;
		}
	}

	if (!m_noOmvQ) {
		if (!m_usedReferences["OMV"]) {
			string movementtitle = mds[ii].getMovementTitle();
			if (!movementtitle.empty()) {
				out << "!!!OMV: " << movementtitle << endl;
			}
		}
	}

	if (!m_usedReferences["OPS"]) {
		string opus = mds[ii].getOpus();
		if (!opus.empty()) {
			out << "!!!OPS: " << opus << endl;
		}
	}

	if (!m_usedReferences["ONM"]) {
		string number = mds[ii].getNumber();
		if (!number.empty()) {
			out << "!!!ONM: " << number << endl;
		}
	}

	if (!m_usedReferences["OMD"]) {
		if (!m_omd.empty()) {
			out << "!!!OMD: " << m_omd << endl;
		}
	}

	bool foundDataQ = false;
	for (int i=0; i<outfile.getLineCount(); i++) {
		if (outfile[i].isData()) {
			foundDataQ = true;
		}
		if (outfile[i].isBarline() && !foundDataQ) {
			HTp token = outfile.token(i, 0);
			if (*token == "=") {
				HTp nextBar = NULL;
				for (int j=i+1; j<outfile.getLineCount(); j++) {
					if (outfile[j].isBarline()) {
						nextBar = outfile.token(j, 0);
						break;
					}
				}
				if (nextBar) {
					HumRegex hre;
					if (hre.search(nextBar, "\\b1\\b")) {
						continue;
					} else if (hre.search(nextBar, "\\b2\\b")) {
						for (int j=0; j<outfile[i].getFieldCount(); j++) {
							out << "=1";
							if (j < outfile[i].getFieldCount() - 1) {
								out << "\t";
							}
						}
						out << endl;
						continue;
					}
					// also deal with repeat barlines at the start of the music.
				}
			}
		}
		printLine(out, outfile[i]);
	}

	if (!m_usedReferences["SMS"]) {
		string source = mds[ii].getSource();
		if (!source.empty()) {
			out << "!!!SMS: " << source << endl;
		}
	}

	if (!m_usedReferences["ENC"]) {
		string encoder = mds[ii].getEncoderName();
		if (!encoder.empty()) {
			out << "!!!ENC: " << encoder << endl;
		}
	}

	if (!m_usedReferences["END"]) {
		string edate = mds[ii].getEncoderDate();
		if (!edate.empty()) {
			out << "!!!END: " << edate << endl;
		}
	}

	for (int i=0; i<(int)m_postReferences.size(); i++) {
		out << m_postReferences[i] << endl;
	}
	m_postReferences.clear();

	stringstream ss;
	auto nowtime = std::chrono::system_clock::now();
	time_t currenttime = std::chrono::system_clock::to_time_t(nowtime);
	ss << std::ctime(&currenttime);
	out << "!!!ONB: Converted from MuseData with musedata2hum on " << ss.str();

	string copyright = mds[ii].getCopyright();
	if (!copyright.empty()) {
		out << "!!!YEM: " << copyright << endl;
	}

	// Convert comments in footer of last part:
	int lastone = groupMemberIndex.back();
	vector<string> outputs;
	for (int i=mds[lastone].getLineCount() - 1; i>=0; i--) {
		if (mds[lastone][i].isAnyNote()) {
			break;
		}
		if (mds[lastone].getLine(i).compare(0, 2, "@@") == 0) {
			string output = mds[lastone].getLine(i);
			for (int j=0; j<(int)output.size(); j++) {
				if (output[j] == '@') {
					output[j] = '!';
				} else {
					break;
				}
			}
			outputs.push_back(output);
		}
	}

	for (int i=(int)outputs.size() - 1; i>=0; i--) {
		out << outputs[i] << endl;
	}

	return status;
}



//////////////////////////////
//
// Tool_musedata2hum::printLine -- Print line of Humdrum file
//     contents.  If there is any layout parameter in the line tokens,
//     then print an extra line with these.  Currently only checking for
//     a single parameter.
//

void Tool_musedata2hum::printLine(ostream& out, HumdrumLine& line) {
	vector<string> lo(line.getFieldCount());
	int count = 0;
	for (int i=0; i<line.getFieldCount(); i++) {
		HTp token = line.token(i);
		string value = token->getValue("auto", "LO");
		if (!value.empty()) {
			lo.at(i) = value;
			count++;
		}
	}
	if (count > 0) {
		for (int i=0; i<(int)lo.size(); i++) {
			if (lo[i].empty()) {
				out << "!";
			} else {
				out << lo[i];
			}
			if (i < (int)lo.size() - 1) {
				out << "\t";
			}
		}
		out << endl;
	}
	out << line << endl;
}



//////////////////////////////
//
// Tool_musedata2hum::convertPart --
//

bool Tool_musedata2hum::convertPart(HumGrid& outdata, MuseDataSet& mds, int index, int partindex, int maxstaff) {
	MuseData& part = mds[index];
	m_lastfigure = NULL;
	m_lastnote = NULL;
	m_lastbarnum = -1;
	m_part = partindex;
	// maybe maxpart?
	m_maxstaff = maxstaff;

	bool status = true;
	int i = 0;
	while (i < part.getLineCount()) {
		m_measureLineIndex = i;
		i = convertMeasure(outdata, part, partindex, i);
	}

	storePartName(outdata, part, partindex);

	return status;
}



///////////////////////////////
//
// Tool_musedata2hum::storePartName --
//

void Tool_musedata2hum::storePartName(HumGrid& outdata, MuseData& part, int index) {
	string name = part.getPartName();
	if (!name.empty()) {
		outdata.setPartName(index, name);
	}
}



//////////////////////////////
//
// Tool_musedata2hum::convertMeasure --
//

int Tool_musedata2hum::convertMeasure(HumGrid& outdata, MuseData& part, int partindex, int startindex) {
	if (part.getLineCount() == 0) {
		return 1;
	}
	HumNum starttime = part[startindex].getAbsBeat();
	HumNum filedur = part.getFileDuration();
	HumNum diff = filedur - starttime;
	if (diff == 0) {
		// last barline in score, so ignore
		return startindex + 1;;
	}

	GridMeasure* gm = getMeasure(outdata, starttime);
	int i = startindex;
	for (i=startindex; i<part.getLineCount(); i++) {
		if ((i != startindex) && part[i].isBarline()) {
			break;
		}
		convertLine(gm, part[i]);
	}
	HumNum endtime = starttime;
	if (i >= part.getLineCount()) {
		endtime = part[i-1].getAbsBeat();
	} else {
		endtime = part[i].getAbsBeat();
	}

	// set duration of measures (so it will be printed in conversion to Humdrum):
	gm->setDuration(endtime - starttime);
	gm->setTimestamp(starttime);
	gm->setTimeSigDur(m_timesigdur);

	if ((i < part.getLineCount()) && part[i].isBarline()) {
		if (partindex == 0) {
			// For now setting the barline style from the
			// lowest staff.  This is mostly because
			// MEI/verovio can handle only one style
			// on a system barline.  But also because
			// GridMeasure objects only has a setting
			// for a single barline style.
			setMeasureStyle(outdata.back(), part[i]);
			setMeasureNumber(outdata.back(), part[i]);
			// gm->setBarStyle(MeasureStyle::Plain);
		}
	}

	return i;
}



//////////////////////////////
//
// Tool_musedata2hum::setMeasureNumber --
//

void Tool_musedata2hum::setMeasureNumber(GridMeasure* gm, MuseRecord& mr) {
	int pos = -1;
	string line = mr.getLine();
	bool space = false;
	for (int i=0; i<(int)line.size(); i++) {
		if (isspace(line[i])) {
			space = true;
			continue;
		}
		if (!space) {
			continue;
		}
		if (isdigit(line[i])) {
			pos = i;
			break;
		}
	}
	if (pos < 0) {
		gm->setMeasureNumber(-1);
		return;
	}
	int num = stoi(line.substr(pos));
	if (m_lastbarnum >= 0) {
		int temp = num;
		num = m_lastbarnum;
		m_lastbarnum = temp;
	}
	gm->setMeasureNumber(num);
}



//////////////////////////////
//
// Tool_musedata2hum::setMeasureStyle --
//

void Tool_musedata2hum::setMeasureStyle(GridMeasure* gm, MuseRecord& mr) {
	string line = mr.getLine();
	string barstyle = mr.getMeasureFlags();
	if (line.compare(0, 7, "mheavy2") == 0) {
		if (barstyle.find(":|") != string::npos) {
			gm->setStyle(MeasureStyle::RepeatBackward);
		} else {
			gm->setStyle(MeasureStyle::Final);
		}
	} else if (line.compare(0, 7, "mheavy3") == 0) {
		if (barstyle.find("|:") != string::npos) {
			gm->setStyle(MeasureStyle::RepeatForward);
		}
	} else if (line.compare(0, 7, "mheavy4") == 0) {
		if (barstyle.find(":|:") != string::npos) {
			gm->setStyle(MeasureStyle::RepeatBoth);
		} else if (barstyle.find("|: :|") != string::npos) {
			// Vivaldi op. 1, no. 1, mvmt. 1, m. 10: mheavy4          |: :|
			gm->setStyle(MeasureStyle::RepeatBoth);
		}
	} else if (line.compare(0, 7, "mdouble") == 0) {
		gm->setStyle(MeasureStyle::Double);
	}
}


//////////////////////////////
//
// Tool_musedata2hum::convertLine --
//

void Tool_musedata2hum::convertLine(GridMeasure* gm, MuseRecord& mr) {
	int part         = m_part;
	int staff        = 0;
	int maxstaff     = m_maxstaff;
	int layer        = mr.getLayer();
	if (layer > 0) {
		// convert to an index:
		layer = layer - 1;
	}

	if (mr.isAnyNoteOrRest()) {
		m_figureOffset = 0;
	}

	if (mr.isDirection()) {
		return;
	}

	HumNum timestamp = mr.getAbsBeat();
	// cerr << "CONVERTING LINE " << timestamp << "\t" << mr << endl;
	string tok;
	GridSlice* slice = NULL;

	if (mr.isBarline()) {
		// barline handled elsewhere
		// tok = mr.getKernMeasure();
	} else if (mr.isAttributes()) {
		map<string, string> attributes;
		mr.getAttributeMap(attributes);

		string mtempo = cleanString(attributes["D"]);
		if (!mtempo.empty()) {
			if (timestamp != 0) {
				string value = "!!!OMD: " + mtempo;
				gm->addGlobalComment(value, timestamp);
			} else {
				setInitialOmd(mtempo);
			}
		}

		if (!attributes["Q"].empty()) {
			m_quarterDivisions = std::stoi(attributes["Q"]);
		}

		string mclef = attributes["C"];
		if (!mclef.empty()) {
			string kclef = Convert::museClefToKernClef(mclef);
			if (!kclef.empty()) {
				gm->addClefToken(kclef, timestamp, part, staff, layer, maxstaff);
			}
		}

		string mkeysig = attributes["K"];
		if (!mkeysig.empty()) {
			string kkeysig = Convert::museKeySigToKernKeySig(mkeysig);
			gm->addKeySigToken(kkeysig, timestamp, part, staff, layer, maxstaff);
		}

		string mtimesig = attributes["T"];
		if (!mtimesig.empty()) {
			string ktimesig = Convert::museTimeSigToKernTimeSig(mtimesig);
			slice = gm->addTimeSigToken(ktimesig, timestamp, part, staff, layer, maxstaff);
			setTimeSigDurInfo(ktimesig);
			string kmeter = Convert::museMeterSigToKernMeterSig(mtimesig);
			if (!kmeter.empty()) {
				slice = gm->addMeterSigToken(kmeter, timestamp, part, staff, layer, maxstaff);
			}
			if (m_tempo > 0.00) {
				int value = (int)(m_tempo + 0.5);
				string tempotok = "*MM" + to_string(value);
				slice = gm->addTempoToken(tempotok, timestamp, part, staff, layer, maxstaff);
			}
		}
	} else if (mr.isRegularNote()) {
		tok = mr.getKernNoteStyle(1, 1);
		string other = mr.getKernNoteOtherNotations();
		if (!needsAboveBelowKernRdf()) {
			if (other.find("<") != string::npos) {
				addAboveBelowKernRdf();
			} else if (other.find(">") != string::npos) {
				addAboveBelowKernRdf();
			}
		}
		if (!other.empty()) {
			tok += other;
		}
		slice = gm->addDataToken(tok, timestamp, part, staff, layer, maxstaff);
		if (slice) {
			mr.setVoice(slice->at(part)->at(staff)->at(layer));
			string gr = mr.getLayoutVis();
			if (gr.size() > 0) {
				// Visual and performance durations are not equal:
				HTp token = slice->at(part)->at(staff)->at(layer)->getToken();
				string text = "!LO:N:vis=" + gr;
				token->setValue("auto", "LO", text);
			}
		}
		m_lastnote = slice->at(part)->at(staff)->at(layer)->getToken();
		addNoteDynamics(slice, part, mr);
		addDirectionDynamics(slice, part, mr);
		addLyrics(slice, part, staff, mr);
	} else if (mr.isFiguredHarmony()) {
		addFiguredHarmony(mr, gm, timestamp, part, maxstaff);
	} else if (mr.isChordNote()) {
		tok = mr.getKernNoteStyle(1, 1);
		if (m_lastnote) {
			string text = m_lastnote->getText();
			text += " ";
			text += tok;
			m_lastnote->setText(text);
		} else {
			cerr << "Warning: found chord note with no regular note to attach to" << endl;
		}
	} else if (mr.isCueNote()) {
		cerr << "PROCESS CUE NOTE HERE: " << mr << endl;
	} else if (mr.isGraceNote()) {
		cerr << "PROCESS GRACE NOTE HERE: " << mr << endl;
	} else if (mr.isChordGraceNote()) {
		cerr << "PROCESS GRACE CHORD NOTE HERE: " << mr << endl;
	} else if (mr.isAnyRest()) {
		tok  = mr.getKernRestStyle();
		slice = gm->addDataToken(tok, timestamp, part, staff, layer, maxstaff);
		if (slice) {
			mr.setVoice(slice->at(part)->at(staff)->at(layer));
			string gr = mr.getLayoutVis();
			if (gr.size() > 0) {
				cerr << "GRAPHIC VERSION OF NOTEB " << gr << endl;
			}
		}
	} else if (mr.isDirection()) {
		if (mr.isTextDirection()) {
			addTextDirection(gm, part, staff, mr, timestamp);
		}
	}
}


//////////////////////////////
//
// Tool_musedata2hum::addDirectionDynamics -- search for a dynamic
//     marking before the current line and after any previous note
//     or similar line.   These lines are store in "musical directions"
//     which start the line with a "*" character.
//
// Example for "p" dyamic, with print suggesting.
//             1         2
//    12345678901234567890123456789
//    *               G       p
//    P    C17:Y57
//

void Tool_musedata2hum::addDirectionDynamics(GridSlice* slice, int part, MuseRecord& mr) {
	MuseRecord* direction = mr.getMusicalDirection();
	if (direction == NULL) {
		return;
	}

	if (direction->isDynamic()) {
		string dynamicText = direction->getDynamicText();
		if (!dynamicText.empty()) {
			slice->at(part)->setDynamics(dynamicText);
			HumGrid* grid = slice->getOwner();
			if (grid) {
				grid->setDynamicsPresent(part);
			}
		}
	}
}



//////////////////////////////
//
// Tool_musedata2hum::addAboveBelowKernRdf -- Save for later that
//      !!!RDF**kern: > = above
//      !!!RDF**kern: < = below
//    in the output Humdrum data file.
//

void Tool_musedata2hum::addAboveBelowKernRdf(void) {
	m_aboveBelowKernRdf = true;
}



//////////////////////////////
//
// Tool_musedata2hum::needsAboveBelowKernRdf -- Function name says it all.
//

bool Tool_musedata2hum::needsAboveBelowKernRdf(void) {
	return m_aboveBelowKernRdf;
}



//////////////////////////////
//
// Tool_musedata2hum::addTextDirection --
//

void Tool_musedata2hum::addTextDirection(GridMeasure* gm, int part, int staff,
		MuseRecord& mr, HumNum timestamp) {

	if (!mr.isTextDirection()) {
		return;
	}
	string text = mr.getTextDirection();
	if (text == "") {
		// no text direction to process
		return;
	}
	HumRegex hre;
	hre.replaceDestructive(text, "&colon;", ":", "g");
	string output = "!LO:TX";
	output += ":b";   // text below (figure out above cases)
	output += ":t=";
	output += text;

	// add staff index later
	gm->addLayoutParameter(NULL, part, output);


}


//////////////////////////////
//
// Tool_musedata2hum::addFiguredHarmony --
//

void Tool_musedata2hum::addFiguredHarmony(MuseRecord& mr, GridMeasure* gm,
		HumNum timestamp, int part, int maxstaff) {
	string fh = mr.getFigureString();
	int figureDuration = mr.getFigureDuration();
	fh = Convert::museFiguredBassToKernFiguredBass(fh);
	if (m_figureOffset > 0) {
		if (m_quarterDivisions > 0) {
			HumNum offset(m_figureOffset, m_quarterDivisions);
			timestamp + offset;
		}
	}
	if (fh.find(":") == string::npos) {
		HTp fhtok = new HumdrumToken(fh);
		m_lastfigure = fhtok;
		gm->addFiguredBass(fhtok, timestamp, part, maxstaff);
		m_figureOffset += figureDuration;
		return;
	}

	if (!m_lastfigure) {
		HTp fhtok = new HumdrumToken(fh);
		m_lastfigure = fhtok;
		gm->addFiguredBass(fhtok, timestamp, part, maxstaff);
		m_figureOffset += figureDuration;
		return;
	}

	// For now assuming only one line extension needs to be transferred.

	// Has a line extension that should be moved to the previous token:
	int position = 0;
	int colpos = -1;
	if (fh[0] == ':') {
		colpos = 0;
	} else {
		for (int i=1; i<(int)fh.size(); i++) {
			if (isspace(fh[i]) && !isspace(fh[i-1])) {
				position++;
			}
			if (fh[i] == ':') {
				colpos = i;
				break;
			}
		}
	}

	string lastfh = m_lastfigure->getText();
	vector<string> pieces;
	int state = 0;
	for (int i=0; i<(int)lastfh.size(); i++) {
		if (state) {
			if (isspace(lastfh[i])) {
				state = 0;
			} else {
				pieces.back() += lastfh[i];
			}
		} else {
			if (isspace(lastfh[i])) {
				// do nothing
			} else {
				pieces.resize(pieces.size()+1);
				pieces.back() += lastfh[i];
				state = 1;
			}
		}
	}

	if (pieces.empty() || (position >= (int)pieces.size())) {
		HTp fhtok = new HumdrumToken(fh);
		m_lastfigure = fhtok;
		gm->addFiguredBass(fhtok, timestamp, part, maxstaff);
		m_figureOffset += figureDuration;
		return;
	}

	pieces[position] += ':';
	string oldtok;
	for (int i=0; i<(int)pieces.size(); i++) {
		oldtok += pieces[i];
		if (i<(int)pieces.size() - 1) {
			oldtok += ' ';
		}
	}

	m_lastfigure->setText(oldtok);

	fh.erase(colpos, 1);
	HTp newtok = new HumdrumToken(fh);
	m_lastfigure = newtok;
	gm->addFiguredBass(newtok, timestamp, part, maxstaff);
	m_figureOffset += figureDuration;
}



//////////////////////////////
//
// Tool_musedata2hum::addLyrics --
//

void Tool_musedata2hum::addLyrics(GridSlice* slice, int part, int staff, MuseRecord& mr) {
	int versecount = mr.getVerseCount();
	if (versecount == 0) {
		return;
	}
	for (int i=0; i<versecount; i++) {
		string verse = mr.getVerseUtf8(i);
		slice->at(part)->at(staff)->setVerse(i, verse);
	}
	slice->reportVerseCount(part, staff, versecount);
}



//////////////////////////////
//
// Tool_musedata2hum::addNoteDynamics -- only one contiguous dynamic allowed
//

void Tool_musedata2hum::addNoteDynamics(GridSlice* slice, int part,
		MuseRecord& mr) {
	string notations = mr.getAdditionalNotationsField();
	vector<string> dynamics(1);
	vector<int> column(1, -1);
	int state = 0;
	for (int i=0; i<(int)notations.size(); i++) {
		if (state) {
			switch (notations[i]) {
				case 'p':
				case 'm':
				case 'f':
					dynamics.back() += notations[i];
					break;
				default:
					state = 0;
					dynamics.resize(dynamics.size() + 1);
			}
		} else {
			switch (notations[i]) {
				case 'p':
				case 'm':
				case 'f':
					state = 1;
					dynamics.back() = notations[i];
					column.back() = i;
					break;
			}
		}
	}

	bool setdynamics = false;
	vector<string> ps;
	HumRegex hre;
	for (int i=0; i<(int)dynamics.size(); i++) {
		if (dynamics[i].empty()) {
			continue;
		}
		mr.getPrintSuggestions(ps, column[i]+32);
		if (ps.size() > 0) {
			cerr << "\tPRINT SUGGESTION: " << ps[0] << endl;
			// only checking the first entry (first parameter):
			if (hre.search(ps[0], "Y(-?\\d+)")) {
				int y = hre.getMatchInt(1);
				cerr << "Y = " << y << endl;
			}
		}

		slice->at(part)->setDynamics(dynamics[i]);
		setdynamics = true;
		break;  // only one dynamic allowed (at least for now)
	}

	if (setdynamics) {
		HumGrid* grid = slice->getOwner();
		if (grid) {
			grid->setDynamicsPresent(part);
		}
	}
}



//////////////////////////////
//
// Tool_musedata2hum::setTimeSigDurInfo --
//

void Tool_musedata2hum::setTimeSigDurInfo(const string& ktimesig) {
	HumRegex hre;
	if (hre.search(ktimesig, "(\\d+)/(\\d+)")) {
		int top = hre.getMatchInt(1);
		int bot = hre.getMatchInt(2);
		HumNum value = 1;
		value /= bot;
		value *= top;
		value.invert();
		value *= 4;  // convert from whole notes to quarter notes
		m_timesigdur = value;
	}
}



//////////////////////////////
//
// Tool_musedata2hum::getMeasure --  Could be imporoved by NlogN search.
//

GridMeasure* Tool_musedata2hum::getMeasure(HumGrid& outdata, HumNum starttime) {
	for (int i=0; i<(int)outdata.size(); i++) {
		if (outdata[i]->getTimestamp() == starttime) {
			return outdata[i];
		}
	}
	// Did not find measure in data, so append to end of list.
	// Assuming that unknown measures are at a later timestamp
	// than those in current list, but should fix this later perhaps.
	GridMeasure* gm = new GridMeasure(&outdata);
	outdata.push_back(gm);
	return gm;
}



//////////////////////////////
//
// Tool_musedata2hum::setInitialOmd --
//

void Tool_musedata2hum::setInitialOmd(const string& omd) {
	m_omd = omd;
}



//////////////////////////////
//
// Tool_musedata2hum::cleanString --
//

string Tool_musedata2hum::cleanString(const string& input) {
	return MuseData::cleanString(input);
}




//////////////////////////////
//
// Tool_musicxml2hum::Tool_musicxml2hum --
//

Tool_musicxml2hum::Tool_musicxml2hum(void) {
	// Options& options = m_options;
	// options.define("k|kern=b","display corresponding **kern data");

	define("r|recip=b", "output **recip spine");
	define("s|stems=b", "include stems in output");

	VoiceDebugQ = false;
	DebugQ = false;
}



//////////////////////////////
//
// Tool_musicxml2hum::convert -- Convert a MusicXML file into
//     Humdrum content.
//

bool Tool_musicxml2hum::convertFile(ostream& out, const char* filename) {
	xml_document doc;
	auto result = doc.load_file(filename);
	if (!result) {
		cerr << "\nXML file [" << filename << "] has syntax errors ";
		cerr << "Error description:\t" << result.description() << endl;
		cerr << "Error offset:\t" << result.offset << "\n";
		return false;
	}

	return convert(out, doc);
}


bool Tool_musicxml2hum::convert(ostream& out, istream& input) {
	string s(istreambuf_iterator<char>(input), {});
	return convert(out, s.c_str());
}


bool Tool_musicxml2hum::convert(ostream& out, const char* input) {
	xml_document doc;
	auto result = doc.load_string(input);
	if (!result) {
		cout << "\nXML content has syntax errors";
		cout << " Error description:\t" << result.description() << "\n";
		cout << "Error offset:\t" << result.offset << "\n\n";
		return false;
	}

	return convert(out, doc);
}



bool Tool_musicxml2hum::convert(ostream& out, xml_document& doc) {
	initialize();

	bool status = true; // for keeping track of problems in conversion process.

	setSoftwareInfo(doc);
	vector<string> partids;            // list of part IDs
	map<string, xml_node> partinfo;    // mapping if IDs to score-part elements
	map<string, xml_node> partcontent; // mapping of IDs to part elements

	getPartInfo(partinfo, partids, doc);
	m_used_hairpins.resize(partinfo.size());

	m_current_dynamic.resize(partids.size());
	m_current_brackets.resize(partids.size());
	m_current_figured_bass.resize(partids.size());
	m_stop_char.resize(partids.size(), "[");

	getPartContent(partcontent, partids, doc);
	vector<MxmlPart> partdata;
	partdata.resize(partids.size());
	m_last_ottava_direction.resize(partids.size());

	fillPartData(partdata, partids, partinfo, partcontent);

	// for debugging:
	//printPartInfo(partids, partinfo, partcontent, partdata);

	m_maxstaff = 0;
	// check the voice info
	for (int i=0; i<(int)partdata.size(); i++) {
		partdata[i].prepareVoiceMapping();
		m_maxstaff += partdata[i].getStaffCount();
		// for debugging:
		if (VoiceDebugQ) {
			partdata[i].printStaffVoiceInfo();
		}
	}

	// re-index voices to disallow empty intermediate voices.
	reindexVoices(partdata);

	HumGrid outdata;
	status &= stitchParts(outdata, partids, partinfo, partcontent, partdata);

	if (outdata.size() > 2) {
		if (outdata.at(0)->getDuration() == 0) {
			while (!outdata.at(0)->empty()) {
				outdata.at(1)->push_front(outdata.at(0)->back());
				outdata.at(0)->pop_back();
			}
			outdata.deleteMeasure(0);
		}
	}

	for (int i=0; i<(int)partdata.size(); i++) {
		m_hasOrnamentsQ |= partdata[i].hasOrnaments();
	}

	outdata.removeRedundantClefChanges();
	outdata.removeSibeliusIncipit();
	m_systemDecoration = getSystemDecoration(doc, outdata, partids);

	// tranfer verse counts from parts/staves to HumGrid:
	// should also do part verse counts here (-1 staffindex).
	int versecount;
	for (int p=0; p<(int)partdata.size(); p++) {
		for (int s=0; s<partdata[p].getStaffCount(); s++) {
			versecount = partdata[p].getVerseCount(s);
			outdata.setVerseCount(p, s, versecount);
		}
	}

	// transfer harmony counts from parts to HumGrid:
	for (int p=0; p<(int)partdata.size(); p++) {
		int harmonyCount = partdata[p].getHarmonyCount();
		outdata.setHarmonyCount(p, harmonyCount);
	}

	// transfer dynamics boolean for part to HumGrid
	for (int p = 0; p<(int)partdata.size(); p++) {
		bool dynstate = partdata[p].hasDynamics();
		if (dynstate) {
			outdata.setDynamicsPresent(p);
		}
	}

	// transfer figured bass boolean for part to HumGrid
	for (int p=0; p<(int)partdata.size(); p++) {
		bool fbstate = partdata[p].hasFiguredBass();
		if (fbstate) {
			outdata.setFiguredBassPresent(p);
			// break;
		}
	}

	if (m_recipQ || m_forceRecipQ) {
		outdata.enableRecipSpine();
	}

	outdata.buildSingleList();
	outdata.expandLocalCommentLayers();

	// set the duration of the last slice

	HumdrumFile outfile;
	outdata.transferTokens(outfile);

	addHeaderRecords(outfile, doc);
	addFooterRecords(outfile, doc);

	Tool_ruthfix ruthfix;
	ruthfix.run(outfile);

	addMeasureOneNumber(outfile);

	// Maybe implement barnum tool and apply here based on options.

	Tool_chord chord;
	chord.run(outfile);

	if (m_hasOrnamentsQ) {
		Tool_trillspell trillspell;
		trillspell.run(outfile);
	}

	if (m_hasTremoloQ) {
		Tool_tremolo tremolo;
		tremolo.run(outfile);
	}

	if (m_software == "sibelius") {
		// Needed at least for Sibelius 19.5/Dolet 6.6 for Sibelius
		// where grace note groups are not beamed in the MusicXML export.
		Tool_autobeam gracebeam;
		vector<string> argv;
		argv.push_back("autobeam"); // name of program (placeholder)
		argv.push_back("-g");       // beam adjacent grace notes
		gracebeam.process(argv);
		// Need to force a reparsing of the files contents to
		// analyze strands.  For now just create a temporary
		// Humdrum file to force the analysis of the strands.
		stringstream sstream;
		sstream << outfile;
		HumdrumFile outfile2;
		outfile2.readString(sstream.str());
		gracebeam.run(outfile2);
		outfile = outfile2;
	}

	if (m_hasTransposition) {
		Tool_transpose transpose;
		vector<string> argv;
		argv.push_back("transpose"); // name of program (placeholder)
		argv.push_back("-C");        // transpose to concert pitch
		transpose.process(argv);
		stringstream sstream;
		sstream << outfile;
		HumdrumFile outfile2;
		outfile2.readString(sstream.str());
		transpose.run(outfile2);
		if (transpose.hasHumdrumText()) {
			stringstream ss;
			transpose.getHumdrumText(ss);
			outfile.readString(ss.str());
			printResult(out, outfile);
		}
	} else {
		for (int i=0; i<outfile.getLineCount(); i++) {
			outfile[i].createLineFromTokens();
		}
		printResult(out, outfile);
	}

	// add RDFs
	if (m_slurabove || m_staffabove) {
		out << "!!!RDF**kern: > = above" << endl;
	}
	if (m_slurbelow || m_staffbelow) {
		out << "!!!RDF**kern: < = below" << endl;
	}

	for (int i=0; i<(int)partdata.size(); i++) {
		if (partdata[i].hasEditorialAccidental()) {
			out << "!!!RDF**kern: i = editorial accidental" << endl;
			break;
		}
	}

	// put the above code in here some time:
	prepareRdfs(partdata);
	printRdfs(out);

	return status;
}



//////////////////////////////
//
// Tool_musicxml2hum::addMeasureOneNumber -- For the first measure if it occurs before
//    the first data, change = to =1.  Maybe check next measure for a number and
//    addd one less than that number instead of 1.
//

void Tool_musicxml2hum::addMeasureOneNumber(HumdrumFile& infile) {
	for (int i=0; i<infile.getLineCount(); i++) {
		if (infile[i].isData()) {
			break;
		}
		if (!infile[i].isBarline()) {
			continue;
		}
		HTp token = infile.token(i, 0);
		string value = *token;
		bool hasdigit = false;
		for (int j=0; j<(int)value.size(); j++) {
			if (isdigit(value[j])) {
				hasdigit = true;
				break;
			}
		}
		if (hasdigit) {
			break;
		}
		// there is no digit on barline, so add one.

		string newvalue = "=";
		if (value.size() < 2) {
			newvalue += "1";
		} else if (value[1] != '=') {
			newvalue += "1";
			newvalue += value.substr(1);
		}
		token->setText(newvalue);

		// Add "1" to other spines here:
		for (int j=1; j<infile[i].getFieldCount(); j++) {
			HTp tok = infile.token(i, j);
			tok->setText(newvalue);
		}
		break;
	}
}



//////////////////////////////
//
// Tool_musicxml2hum::printResult -- filter out
//      some item if not necessary:
//
// MuseScore calls everything "Piano" by default, so suppress
// this instrument name if there is only one **kern spine in
// the file.
//

void Tool_musicxml2hum::printResult(ostream& out, HumdrumFile& outfile) {
	vector<HTp> kernspines = outfile.getKernSpineStartList();
	if (kernspines.size() > 1) {
		out << outfile;
	} else {
		for (int i=0; i<outfile.getLineCount(); i++) {
			bool isPianoLabel = false;
			bool isPianoAbbr  = false;
			bool isPartNum    = false;
			bool isStaffNum   = false;
			if (!outfile[i].isInterpretation()) {
				out << outfile[i] << "\n";
				continue;
			}
			for (int j=0; j<outfile[i].getFieldCount(); j++) {
				if (*outfile.token(i, j) == "*I\"Piano") {
					isPianoLabel = true;
				} else if (*outfile.token(i, j) == "*I'Pno.") {
					isPianoAbbr = true;
				} else if (*outfile.token(i, j) == "*staff1") {
					isStaffNum = true;
				} else if (*outfile.token(i, j) == "*part1") {
					isPartNum = true;
				}
			}
			if (isPianoLabel || isPianoAbbr || isStaffNum || isPartNum) {
				continue;
			}
			out << outfile[i] << "\n";
		}
	}
}



//////////////////////////////
//
// Tool_musicxml2hum::printRdfs --
//

void Tool_musicxml2hum::printRdfs(ostream& out) {
	if (!m_caesura_rdf.empty()) {
		out << m_caesura_rdf << "\n";
	}
}



//////////////////////////////
//
// Tool_muisicxml2hum::setSoftwareInfo -- Store which software program generated the
//    MusicXML data to handle locale variants.  There can be more than one
//    <software> entry, so desired information is not necessarily in the first one.
//

void Tool_musicxml2hum::setSoftwareInfo(xml_document& doc) {
	string xpath = "/score-partwise/identification/encoding/software";
	string software = doc.select_node(xpath.c_str()).node().child_value();
	HumRegex hre;
	if (hre.search(software, "sibelius", "i")) {
		m_software = "sibelius";
	}
}



//////////////////////////////
//
// Tool_musicxml2hum::cleanSpaces -- Converts newlines and tabs to spaces, and removes
//     trailing spaces from the string.  Does not remove leading spaces, but this could
//     be added.  Another variation would be to use \n to encode newlines if they need
//     to be preserved, but for now converting them to spaces.
//

string& Tool_musicxml2hum::cleanSpaces(string& input) {
	for (int i=0; i<(int)input.size(); i++) {
		if (std::isspace(input[i])) {
			input[i] = ' ';
		}
	}
	while ((!input.empty()) && std::isspace(input.back())) {
		input.resize(input.size() - 1);
	}
	return input;
}



//////////////////////////////
//
// Tool_musicxml2hum::cleanSpacesAndColons -- Converts newlines and
//     tabs to spaces, and removes leading and trailing spaces from the
//     string.  Another variation would be to use \n to encode newlines
//     if they need to be preserved, but for now converting them to spaces.
//     Colons (:) are also converted to &colon;.

string Tool_musicxml2hum::cleanSpacesAndColons(const string& input) {
	string output;
	bool foundnonspace = false;
	for (int i=0; i<(int)input.size(); i++) {
		if (std::isspace(input[i])) {
			if (!foundnonspace) {
				output += ' ';
			}
		}
		if (input[i] == ':') {
			foundnonspace = true;
			output += "&colon;";
		} else {
			output += input[i];
			foundnonspace = true;
		}
	}
	while ((!output.empty()) && std::isspace(output.back())) {
		output.resize(output.size() - 1);
	}
	return output;
}



//////////////////////////////
//
// Tool_musicxml2hum::addHeaderRecords -- Inserted in reverse order
//      (last record inserted first).
//

void Tool_musicxml2hum::addHeaderRecords(HumdrumFile& outfile, xml_document& doc) {
	string xpath;
	HumRegex hre;

	if (!m_systemDecoration.empty()) {
		// outfile.insertLine(0, "!!!system-decoration: " + m_systemDecoration);
		if (m_systemDecoration != "s1") {
			outfile.appendLine("!!!system-decoration: " + m_systemDecoration);
		}
	}

	xpath = "/score-partwise/credit/credit-words";
   pugi::xpath_node_set credits = doc.select_nodes(xpath.c_str());
	map<string, int> keys;
	vector<string> refs;
	vector<int> positions; // +1 = above, -1 = below;
	for (auto it = credits.begin(); it != credits.end(); it++) {
		string contents = cleanSpaces(it->node().child_value());
		if (contents.empty()) {
			continue;
		}
		if ((contents[0] != '@') && (contents[0] != '!')) {
			continue;
		}

		if (contents.size() >= 3) {
			// If line starts with "@@" then place at end of score.
			if ((contents[0] == '@') && (contents[1] == '@')) {
				positions.push_back(-1);
			} else {
				positions.push_back(1);
			}
		} else {
			positions.push_back(1);
		}

		if (hre.search(contents, "^[@!]+([^\\s]+):")) {
			// reference record
			string key = hre.getMatch(1);
			keys[key] = 1;
			hre.replaceDestructive(contents, "!!!", "^[!@]+");
			refs.push_back(contents);
		} else {
			// global comment
			hre.replaceDestructive(contents, "!!", "^[!@]+");
			refs.push_back(contents);
		}
	}

	// OTL: title //////////////////////////////////////////////////////////

	// Sibelius method
	xpath = "/score-partwise/work/work-title";
	string worktitle = cleanSpaces(doc.select_node(xpath.c_str()).node().child_value());
	string otl_record;
	string omv_record;
	bool worktitleQ = false;
	if ((worktitle != "") && (worktitle != "Title")) {
		otl_record = "!!!OTL: ";
		otl_record += worktitle;
		worktitleQ = true;
	}

	xpath = "/score-partwise/movement-title";
	string mtitle = cleanSpaces(doc.select_node(xpath.c_str()).node().child_value());
	if (mtitle != "") {
		if (worktitleQ) {
			omv_record = "!!!OMV: ";
			omv_record += mtitle;
		} else {
			otl_record = "!!!OTL: ";
			otl_record += mtitle;
		}
	}

	// COM: composer /////////////////////////////////////////////////////////
	// CDT: composer's dates
	xpath = "/score-partwise/identification/creator[@type='composer']";
	string composer = cleanSpaces(doc.select_node(xpath.c_str()).node().child_value());
	string cdt_record;
	if (composer != "") {
		if (hre.search(composer, R"(\((.*?\d.*?)\))")) {
			string dates = hre.getMatch(1);
			// hre.replaceDestructive(composer, "", R"(\()" + dates + R"(\))");
			auto loc = composer.find(dates);
			if (loc != std::string::npos) {
				composer.replace(loc-1, dates.size()+2, "");
			}
			hre.replaceDestructive(composer, "", R"(^\s+)");
			hre.replaceDestructive(composer, "", R"(\s+$)");
			if (hre.search(composer, R"(([^\s]+) +([^\s]+))")) {
				composer = hre.getMatch(2) + ", " + hre.getMatch(1);
			}
			if (dates != "") {
				if (hre.search(dates, R"(\b(\d{4})\?)")) {
					string replacement = "~";
					replacement += hre.getMatch(1);
					hre.replaceDestructive(dates, replacement, R"(\b\d{4}\?)");
					cdt_record = "!!!CDT: ";
					cdt_record += dates;
				}
			}
		}
	}


	for (int i=(int)refs.size()-1; i>=0; i--) {
		if (positions.at(i) > 0) {
			// place at start of file
			outfile.insertLine(0, refs[i]);
		}
	}

	for (int i=0; i<(int)refs.size(); i++) {
		if (positions.at(i) < 0) {
			// place at end of file
			outfile.appendLine(refs[i]);
		}
	}

	if ((!omv_record.empty()) && (!keys["OMV"])) {
		outfile.insertLine(0, omv_record);
	}

	if ((!otl_record.empty()) && (!keys["OTL"])) {
		outfile.insertLine(0, otl_record);
	}

	if ((!cdt_record.empty()) && (!keys["CDT"])) {
		outfile.insertLine(0, cdt_record);
	}

	if ((!composer.empty()) && (!keys["COM"])) {
		// Don't print composer name if it is "Composer".
		if (composer != "Composer") {
			string com_record = "!!!COM: " + composer;
			outfile.insertLine(0, com_record);
		}
	}

}



//////////////////////////////
//
// Tool_musicxml2hum::addFooterRecords --
//

void Tool_musicxml2hum::addFooterRecords(HumdrumFile& outfile, xml_document& doc) {

	// YEM: copyright
	string copy = doc.select_node("/score-partwise/identification/rights").node().child_value();
	bool validcopy = true;
	if (copy == "") {
		validcopy = false;
	}
	if ((copy.length() == 2) && ((unsigned char)copy[0] == 0xc2) && ((unsigned char)copy[1] == 0xa9)) {
		validcopy = false;
	}
	if ((copy.find("opyright") != std::string::npos) && (copy.size() < 15)) {
		validcopy = false;
	}

	if (validcopy) {
		string yem_record = "!!!YEM: ";
		yem_record += cleanSpaces(copy);
		outfile.appendLine(yem_record);
	}

	// RDF:
	if (m_hasEditorial) {
		string rdf_record = "!!!RDF**kern: i = editorial accidental";
		outfile.appendLine(rdf_record);
	}
}



//////////////////////////////
//
// initialize --
//

void Tool_musicxml2hum::initialize(void) {
	m_recipQ = getBoolean("recip");
	m_stemsQ = getBoolean("stems");
	m_hasOrnamentsQ = false;
}



//////////////////////////////
//
// Tool_musicxml2hum::reindexVoices --
//

void Tool_musicxml2hum::reindexVoices(vector<MxmlPart>& partdata) {
	for (int p=0; p<(int)partdata.size(); p++) {
		for (int m=0; m<(int)partdata[p].getMeasureCount(); m++) {
			MxmlMeasure* measure = partdata[p].getMeasure(m);
			if (!measure) {
				continue;
			}
			reindexMeasure(measure);
		}
	}
}



//////////////////////////////
//
// Tool_musicxml2hum::prepareRdfs --
//

void Tool_musicxml2hum::prepareRdfs(vector<MxmlPart>& partdata) {
	string caesura;
	for (int i=0; i<(int)partdata.size(); i++) {
		caesura = partdata[i].getCaesura();
		if (!caesura.empty()) {
		}
	}

	if (!caesura.empty()) {
		m_caesura_rdf = "!!!RDF**kern: " + caesura + " = caesura";
	}

}



//////////////////////////////
//
// Tool_musicxml2hum::reindexMeasure --
//

void Tool_musicxml2hum::reindexMeasure(MxmlMeasure* measure) {
	if (!measure) {
		return;
	}

	vector<vector<int> > staffVoiceCounts;
	vector<MxmlEvent*>& elist = measure->getEventList();

	for (int i=0; i<(int)elist.size(); i++) {
		int staff = elist[i]->getStaffIndex();
		int voice = elist[i]->getVoiceIndex();

		if ((voice >= 0) && (staff >= 0)) {
			if (staff >= (int)staffVoiceCounts.size()) {
				int newsize = staff + 1;
				staffVoiceCounts.resize(newsize);
			}
			if (voice >= (int)staffVoiceCounts[staff].size()) {
				int oldsize = (int)staffVoiceCounts[staff].size();
				int newsize = voice + 1;
				staffVoiceCounts[staff].resize(newsize);
				for (int i=oldsize; i<newsize; i++) {
					staffVoiceCounts[staff][voice] = 0;
				}
			}
			staffVoiceCounts[staff][voice]++;
		}
	}

	bool needreindexing = false;

	for (int i=0; i<(int)staffVoiceCounts.size(); i++) {
		if (staffVoiceCounts[i].size() < 2) {
			continue;
		}
		for (int j=1; j<(int)staffVoiceCounts[i].size(); j++) {
			if (staffVoiceCounts[i][j] == 0) {
				needreindexing = true;
				break;
			}
		}
		if (needreindexing) {
			break;
		}
	}

	if (!needreindexing) {
		return;
	}

	vector<vector<int> > remapping;
	remapping.resize(staffVoiceCounts.size());
	int reindex;
	for (int i=0; i<(int)staffVoiceCounts.size(); i++) {
		remapping[i].resize(staffVoiceCounts[i].size());
		reindex = 0;
		for (int j=0; j<(int)remapping[i].size(); j++) {
			if (remapping[i].size() == 1) {
				remapping[i][j] = 0;
				continue;
			}
			if (staffVoiceCounts[i][j]) {
				remapping[i][j] = reindex++;
			} else {
				remapping[i][j] = -1;  // invalidate voice
			}
		}
	}

	// Go back and remap the voice indexes of elements.
	// Presuming that the staff does not need to be reindex.
	for (int i=0; i<(int)elist.size(); i++) {
		int oldvoice = elist[i]->getVoiceIndex();
		int staff = elist[i]->getStaffIndex();
		if (oldvoice < 0) {
			continue;
		}
		int newvoice = remapping[staff][oldvoice];
		if (newvoice == oldvoice) {
			continue;
		}
		elist[i]->setVoiceIndex(newvoice);
	}

}



//////////////////////////////
//
// Tool_musicxml2hum::setOptions --
//

void Tool_musicxml2hum::setOptions(int argc, char** argv) {
	m_options.process(argc, argv);
}


void Tool_musicxml2hum::setOptions(const vector<string>& argvlist) {
    m_options.process(argvlist);
}



//////////////////////////////
//
// Tool_musicxml2hum::getOptionDefinitions -- Used to avoid
//     duplicating the definitions in the test main() function.
//

Options Tool_musicxml2hum::getOptionDefinitions(void) {
	return m_options;
}


///////////////////////////////////////////////////////////////////////////


//////////////////////////////
//
// Tool_musicxml2hum::fillPartData --
//

bool Tool_musicxml2hum::fillPartData(vector<MxmlPart>& partdata,
		const vector<string>& partids, map<string, xml_node>& partinfo,
		map<string, xml_node>& partcontent) {

	bool output = true;
	for (int i=0; i<(int)partinfo.size(); i++) {
		partdata[i].setPartNumber(i+1);
		output &= fillPartData(partdata[i], partids[i], partinfo[partids[i]],
				partcontent[partids[i]]);
	}
	return output;
}


bool Tool_musicxml2hum::fillPartData(MxmlPart& partdata,
		const string& id, xml_node partdeclaration, xml_node partcontent) {
	if (m_stemsQ) {
		partdata.enableStems();
	}

	partdata.parsePartInfo(partdeclaration);
	// m_last_ottava_direction.at(partdata.getPartIndex()).resize(partdata.getStaffCount());
	// staff count is incorrect at this point? Just assume 32 staves in the part, which should
	// be 28-30 staffs too many.
	m_last_ottava_direction.at(partdata.getPartIndex()).resize(32);

	int count;
	auto measures = partcontent.select_nodes("./measure");
	for (int i=0; i<(int)measures.size(); i++) {
		partdata.addMeasure(measures[i].node());
		count = partdata.getMeasureCount();
		if (count > 1) {
			HumNum dur = partdata.getMeasure(count-1)->getTimeSigDur();
			if (dur == 0) {
				HumNum dur = partdata.getMeasure(count-2)
						->getTimeSigDur();
				if (dur > 0) {
					partdata.getMeasure(count - 1)->setTimeSigDur(dur);
				}
			}
		}

	}
	return true;
}



//////////////////////////////
//
// Tool_musicxml2hum::printPartInfo -- Debug information.
//

void Tool_musicxml2hum::printPartInfo(vector<string>& partids,
		map<string, xml_node>& partinfo, map<string, xml_node>& partcontent,
		vector<MxmlPart>& partdata) {
	cout << "\nPart information in the file:" << endl;
	int maxmeasure = 0;
	for (int i=0; i<(int)partids.size(); i++) {
		cout << "\tPART " << i+1 << " id = " << partids[i] << endl;
		cout << "\tMAXSTAFF " << partdata[i].getStaffCount() << endl;
		cout << "\t\tpart name:\t"
		     << getChildElementText(partinfo[partids[i]], "part-name") << endl;
		cout << "\t\tpart abbr:\t"
		     << getChildElementText(partinfo[partids[i]], "part-abbreviation")
		     << endl;
		auto node = partcontent[partids[i]];
		auto measures = node.select_nodes("./measure");
		cout << "\t\tMeasure count:\t" << measures.size() << endl;
		if (maxmeasure < (int)measures.size()) {
			maxmeasure = (int)measures.size();
		}
		cout << "\t\tTotal duration:\t" << partdata[i].getDuration() << endl;
	}

	MxmlMeasure* measure;
	for (int i=0; i<maxmeasure; i++) {
		cout << "m" << i+1 << "\t";
		for (int j=0; j<(int)partdata.size(); j++) {
			measure = partdata[j].getMeasure(i);
			if (measure) {
				cout << measure->getDuration();
			}
			if (j < (int)partdata.size() - 1) {
				cout << "\t";
			}
		}
		cout << endl;
	}
}



//////////////////////////////
//
// Tool_musicxml2hum::insertPartNames --
//

void Tool_musicxml2hum::insertPartNames(HumGrid& outdata, vector<MxmlPart>& partdata) {

	bool hasname = false;
	bool hasabbr = false;

	for (int i=0; i<(int)partdata.size(); i++) {
		string value;
		value = partdata[i].getPartName();
		if (!value.empty()) {
			hasname = true;
			break;
		}
	}

	for (int i=0; i<(int)partdata.size(); i++) {
		string value;
		value = partdata[i].getPartAbbr();
		if (!value.empty()) {
			hasabbr = true;
			break;
		}
	}

	if (!(hasabbr || hasname)) {
		return;
	}

	GridMeasure* gm;
	if (outdata.empty()) {
		gm = new GridMeasure(&outdata);
		outdata.push_back(gm);
	} else {
		gm = outdata[0];
	}

	int maxstaff;

	if (hasabbr) {
		for (int i=0; i<(int)partdata.size(); i++) {
			string partabbr = partdata[i].getPartAbbr();
			if (partabbr.empty()) {
				continue;
			}
			string abbr = "*I'" + partabbr;
			maxstaff = outdata.getStaffCount(i);
			gm->addLabelAbbrToken(abbr, 0, i, maxstaff-1, 0, (int)partdata.size(), maxstaff);
		}
	}

	if (hasname) {
		for (int i=0; i<(int)partdata.size(); i++) {
			string partname = partdata[i].getPartName();
			if (partname.empty()) {
				continue;
			}
			if (partname.find("MusicXML") != string::npos) {
				// ignore Finale dummy part names
				continue;
			}
			if (partname.find("Part_") != string::npos) {
				// ignore SharpEye dummy part names
				continue;
			}
			if (partname.find("Unnamed") != string::npos) {
				// ignore Sibelius dummy part names
				continue;
			}
			string name = "*I\"" + partname;
			maxstaff = outdata.getStaffCount(i);
			gm->addLabelToken(name, 0, i, maxstaff-1, 0, (int)partdata.size(), maxstaff);
		}
	}

}



//////////////////////////////
//
// Tool_musicxml2hum::stitchParts -- Merge individual parts into a
//     single score sequence.
//

bool Tool_musicxml2hum::stitchParts(HumGrid& outdata,
		vector<string>& partids, map<string, xml_node>& partinfo,
		map<string, xml_node>& partcontent, vector<MxmlPart>& partdata) {
	if (partdata.size() == 0) {
		return false;
	}

	int i;
	int measurecount = partdata[0].getMeasureCount();
	// i used to start at 1 for some strange reason.
	for (i=0; i<(int)partdata.size(); i++) {
		if (measurecount != partdata[i].getMeasureCount()) {
			cerr << "ERROR: cannot handle parts with different measure ";
			cerr << "counts yet. Compare MM" << measurecount << " to MM";
			cerr << partdata[i].getMeasureCount() << endl;
			return false;
		}
	}

	vector<int> partstaves(partdata.size(), 0);
	for (i=0; i<(int)partstaves.size(); i++) {
		partstaves[i] = partdata[i].getStaffCount();
	}

	bool status = true;
	int m;
	for (m=0; m<partdata[0].getMeasureCount(); m++) {
		status &= insertMeasure(outdata, m, partdata, partstaves);
		// a hack for now:
		// insertSingleMeasure(outfile);
		// measures.push_back(&outfile[outfile.getLineCount()-1]);
	}

	moveBreaksToEndOfPreviousMeasure(outdata);

	insertPartNames(outdata, partdata);

	return status;
}



//////////////////////////////
//
// moveBreaksToEndOfPreviousMeasure --
//

void Tool_musicxml2hum::moveBreaksToEndOfPreviousMeasure(HumGrid& outdata) {
	for (int i=1; i<(int)outdata.size(); i++) {
		GridMeasure* gm = outdata[i];
		GridMeasure* gmlast = outdata[i-1];
		if (!gm || !gmlast) {
			continue;
		}
		if (gm->begin() == gm->end()) {
			// empty measure
			return;
		}
		GridSlice *firstit = *(gm->begin());
		HumNum starttime = firstit->getTimestamp();
		for (auto it = gm->begin(); it != gm->end(); it++) {
			HumNum time2 = (*it)->getTimestamp();
			if (time2 > starttime) {
				break;
			}
			if (!(*it)->isGlobalComment()) {
				continue;
			}
			HTp token = (*it)->at(0)->at(0)->at(0)->getToken();
			if (!token) {
				continue;
			}
			if ((*token == "!!linebreak:original") ||
			    (*token == "!!pagebreak:original")) {
				GridSlice *swapper = *it;
				gm->erase(it);
				gmlast->push_back(swapper);
				// there can be only one break, so quit the loop now.
				break;
			}
		}
	}
}



//////////////////////////////
//
// Tool_musicxml2hum::cleanupMeasures --
//     Also add barlines here (keeping track of the
//     duration of each measure).
//

void Tool_musicxml2hum::cleanupMeasures(HumdrumFile& outfile,
		vector<HLp> measures) {

   HumdrumToken* token;
	for (int i=0; i<outfile.getLineCount(); i++) {
		if (!outfile[i].isBarline()) {
			continue;
		}
		if (!outfile[i+1].isInterpretation()) {
			int fieldcount = outfile[i+1].getFieldCount();
			for (int j=1; j<fieldcount; j++) {
				token = new HumdrumToken("=");
				outfile[i].appendToken(token);
			}
		}
	}
}



//////////////////////////////
//
// Tool_musicxml2hum::insertSingleMeasure --
//

void Tool_musicxml2hum::insertSingleMeasure(HumdrumFile& outfile) {
	HLp line = new HumdrumLine;
	HumdrumToken* token;
	token = new HumdrumToken("=");
	line->appendToken(token);
	line->createLineFromTokens();
	outfile.appendLine(line);
}



//////////////////////////////
//
// Tool_musicxml2hum::insertAllToken --
//

void Tool_musicxml2hum::insertAllToken(HumdrumFile& outfile,
		vector<MxmlPart>& partdata, const string& common) {

	HLp line = new HumdrumLine;
	HumdrumToken* token;

	int i, j;
	for (i=0; i<(int)partdata.size(); i++) {
		for (j=0; j<(int)partdata[i].getStaffCount(); j++) {
			token = new HumdrumToken(common);
			line->appendToken(token);
		}
		for (j=0; j<(int)partdata[i].getVerseCount(); j++) {
			token = new HumdrumToken(common);
			line->appendToken(token);
		}
	}
	outfile.appendLine(line);
}



//////////////////////////////
//
// Tool_musicxml2hum::insertMeasure --
//

bool Tool_musicxml2hum::insertMeasure(HumGrid& outdata, int mnum,
		vector<MxmlPart>& partdata, vector<int> partstaves) {

	GridMeasure* gm = outdata.addMeasureToBack();

	MxmlMeasure* xmeasure;
	vector<MxmlMeasure*> measuredata;
	vector<vector<SimultaneousEvents>* > sevents;
	int i;

	for (i=0; i<(int)partdata.size(); i++) {
		xmeasure = partdata[i].getMeasure(mnum);
		measuredata.push_back(xmeasure);
		if (i==0) {
			gm->setDuration(partdata[i].getMeasure(mnum)->getDuration());
			gm->setTimestamp(partdata[i].getMeasure(mnum)->getTimestamp());
			gm->setTimeSigDur(partdata[i].getMeasure(mnum)->getTimeSigDur());
		}
		checkForDummyRests(xmeasure);
		sevents.push_back(xmeasure->getSortedEvents());
		if (i == 0) {
			// only checking measure style of first barline
			gm->setBarStyle(xmeasure->getBarStyle());
		}
	}

	vector<HumNum> curtime(partdata.size());
	vector<HumNum> measuredurs(partdata.size());
	vector<int> curindex(partdata.size(), 0); // assuming data in a measure...
	HumNum nexttime = -1;

	vector<vector<MxmlEvent*>> endingDirections(partdata.size());

	HumNum tsdur;
	for (i=0; i<(int)curtime.size(); i++) {
		tsdur = measuredata[i]->getTimeSigDur();
		if ((tsdur == 0) && (i > 0)) {
			tsdur = measuredata[i-1]->getTimeSigDur();
			measuredata[i]->setTimeSigDur(tsdur);
		}

		// Keep track of hairpin endings that should be attached
		// the the previous note (and doubling the ending marker
		// to indicate that the timestamp of the ending is at the
		// end rather than the start of the note.
		vector<MxmlEvent*>& events = measuredata[i]->getEventList();
		xml_node hairpin = xml_node(NULL);
		for (int j=(int)events.size() - 1; j >= 0; j--) {
			if (events[j]->getElementName() == "note") {
				if (hairpin) {
					events[j]->setHairpinEnding(hairpin);
					hairpin = xml_node(NULL);
				}
				break;
			} else if (events[j]->getElementName() == "direction") {
				stringstream ss;
				ss.str("");
				events[j]->getNode().print(ss);
				if (ss.str().find("wedge") != string::npos) {
					if (ss.str().find("stop") != string::npos) {
						hairpin = events[j]->getNode();
					}
				}
			}
		}

		if (VoiceDebugQ) {
			for (int j=0; j<(int)events.size(); j++) {
				cerr << "!!ELEMENT: ";
				cerr << "\tTIME:  " << events[j]->getStartTime();
				cerr << "\tSTi:   " << events[j]->getStaffIndex();
				cerr << "\tVi:    " << events[j]->getVoiceIndex();
				cerr << "\tTS:    " << events[j]->getStartTime();
				cerr << "\tDUR:   " << events[j]->getDuration();
				cerr << "\tPITCH: " << events[j]->getKernPitch();
				cerr << "\tNAME:  " << events[j]->getElementName();
				cerr << endl;
			}
			cerr << "======================================" << endl;
		}
		if (!(*sevents[i]).empty()) {
			curtime[i] = (*sevents[i])[curindex[i]].starttime;
		} else {
			curtime[i] = tsdur;
		}
		if (nexttime < 0) {
			nexttime = curtime[i];
		} else if (curtime[i] < nexttime) {
			nexttime = curtime[i];
		}
		measuredurs[i] = measuredata[i]->getDuration();
	}

	bool allend = false;
	vector<SimultaneousEvents*> nowevents;
	vector<int> nowparts;
	bool status = true;

	HumNum processtime = nexttime;
	while (!allend) {
		nowevents.resize(0);
		nowparts.resize(0);
		allend = true;
		processtime = nexttime;
		nexttime = -1;
		for (i = (int)partdata.size()-1; i >= 0; i--) {
			if (curindex[i] >= (int)(*sevents[i]).size()) {
				continue;
			}

			if ((*sevents[i])[curindex[i]].starttime == processtime) {
				SimultaneousEvents* thing = &(*sevents[i])[curindex[i]];
				nowevents.push_back(thing);
				nowparts.push_back(i);
				curindex[i]++;
			}

			if (curindex[i] < (int)(*sevents[i]).size()) {
				allend = false;
				if ((nexttime < 0) ||
						((*sevents[i])[curindex[i]].starttime < nexttime)) {
					nexttime = (*sevents[i])[curindex[i]].starttime;
				}
			}
		}
		status &= convertNowEvents(outdata.back(),
				nowevents, nowparts, processtime, partdata, partstaves);

		// Remove all figured bass numbers for this nowtime so that they are not
		// accidentally displayed in the next nowtime, which can currently
		// happen if there are no nonzerodur events in the same part
		for (int i=0; i<(int)m_current_figured_bass.size(); i++) {
			m_current_figured_bass[i].clear();
		}
	}

	if (offsetHarmony.size() > 0) {
		insertOffsetHarmonyIntoMeasure(outdata.back());
	}
	if (m_offsetFiguredBass.size() > 0) {
		insertOffsetFiguredBassIntoMeasure(outdata.back());
	}
	return status;
}



//////////////////////////////
//
// Tool_musicxml2hum::insertOffsetFiguredBassIntoMeasure --
//

void Tool_musicxml2hum::insertOffsetFiguredBassIntoMeasure(GridMeasure* gm) {
	if (m_offsetFiguredBass.empty()) {
		return;
	}

	bool beginQ = true;
	for (auto it = gm->begin(); it != gm->end(); ++it) {
		GridSlice* gs = *it;
		if (!gs->isNoteSlice()) {
			// Only attached harmony to data lines.
			continue;
		}
		HumNum timestamp = gs->getTimestamp();
		for (int i=0; i<(int)m_offsetFiguredBass.size(); i++) {
			if (m_offsetFiguredBass[i].token == NULL) {
				continue;
 			}
			if (m_offsetFiguredBass[i].timestamp == timestamp) {
				// this is the slice to insert the harmony
				gs->at(m_offsetFiguredBass[i].partindex)->setFiguredBass(m_offsetFiguredBass[i].token);
				m_offsetFiguredBass[i].token = NULL;
			} else if (m_offsetFiguredBass[i].timestamp < timestamp) {
				if (beginQ) {
					cerr << "Error: Cannot insert harmony " << m_offsetFiguredBass[i].token
					     << " at timestamp " << m_offsetFiguredBass[i].timestamp
					     << " since first timestamp in measure is " << timestamp << endl;
				} else {
					m_forceRecipQ = true;
					// go back to previous note line and insert
					// new slice to store the harmony token
					auto tempit = it;
					tempit--;
					while (tempit != gm->end()) {
						if ((*tempit)->getTimestamp() == (*it)->getTimestamp()) {
							tempit--;
							continue;
						}
						int partcount = (int)(*tempit)->size();
						tempit++;
						GridSlice* newgs = new GridSlice(gm, m_offsetFiguredBass[i].timestamp,
								SliceType::Notes, partcount);
						newgs->at(m_offsetFiguredBass[i].partindex)->setFiguredBass(m_offsetFiguredBass[i].token);
						gm->insert(tempit, newgs);
						m_offsetFiguredBass[i].token = NULL;
						break;
					}
				}
			}
		}
		beginQ = false;
	}
	// If there are still valid harmonies in the input list, apppend
	// them to the end of the measure.
	for (int i=0; i<(int)m_offsetFiguredBass.size(); i++) {
		if (m_offsetFiguredBass[i].token == NULL) {
			continue;
 		}
		m_forceRecipQ = true;
		int partcount = (int)gm->back()->size();
		GridSlice* newgs = new GridSlice(gm, m_offsetFiguredBass[i].timestamp,
				SliceType::Notes, partcount);
		newgs->at(m_offsetFiguredBass[i].partindex)->setFiguredBass(m_offsetFiguredBass[i].token);
		gm->insert(gm->end(), newgs);
		m_offsetFiguredBass[i].token = NULL;
	}
	m_offsetFiguredBass.clear();
}



//////////////////////////////
//
// Tool_musicxml2hum::insertOffsetHarmonyIntoMeasure --
//

void Tool_musicxml2hum::insertOffsetHarmonyIntoMeasure(GridMeasure* gm) {
	if (offsetHarmony.empty()) {
		return;
	}
	// the offsetHarmony list should probably be time sorted first, and then
	// iterate through the slices once.  But there should not be many offset
	bool beginQ = true;
	for (auto it = gm->begin(); it != gm->end(); ++it) {
		GridSlice* gs = *it;
		if (!gs->isNoteSlice()) {
			// Only attached harmony to data lines.
			continue;
		}
		HumNum timestamp = gs->getTimestamp();
		for (int i=0; i<(int)offsetHarmony.size(); i++) {
			if (offsetHarmony[i].token == NULL) {
				continue;
 			}
			if (offsetHarmony[i].timestamp == timestamp) {
				// this is the slice to insert the harmony
				gs->at(offsetHarmony[i].partindex)->setHarmony(offsetHarmony[i].token);
				offsetHarmony[i].token = NULL;
			} else if (offsetHarmony[i].timestamp < timestamp) {
				if (beginQ) {
					cerr << "Error: Cannot insert harmony " << offsetHarmony[i].token
					     << " at timestamp " << offsetHarmony[i].timestamp
					     << " since first timestamp in measure is " << timestamp << endl;
				} else {
					m_forceRecipQ = true;
					// go back to previous note line and insert
					// new slice to store the harmony token
					auto tempit = it;
					tempit--;
					while (tempit != gm->end()) {
						if ((*tempit)->getTimestamp() == (*it)->getTimestamp()) {
							tempit--;
							continue;
						}
						int partcount = (int)(*tempit)->size();
						tempit++;
						GridSlice* newgs = new GridSlice(gm, offsetHarmony[i].timestamp,
								SliceType::Notes, partcount);
						newgs->at(offsetHarmony[i].partindex)->setHarmony(offsetHarmony[i].token);
						gm->insert(tempit, newgs);
						offsetHarmony[i].token = NULL;
						break;
					}
				}
			}
		}
		beginQ = false;
	}
	// If there are still valid harmonies in the input list, apppend
	// them to the end of the measure.
	for (int i=0; i<(int)offsetHarmony.size(); i++) {
		if (offsetHarmony[i].token == NULL) {
			continue;
 		}
		m_forceRecipQ = true;
		int partcount = (int)gm->back()->size();
		GridSlice* newgs = new GridSlice(gm, offsetHarmony[i].timestamp,
				SliceType::Notes, partcount);
		newgs->at(offsetHarmony[i].partindex)->setHarmony(offsetHarmony[i].token);
		gm->insert(gm->end(), newgs);
		offsetHarmony[i].token = NULL;
	}
	offsetHarmony.clear();
}



//////////////////////////////
//
// Tool_musicxml2hum::checkForDummyRests --
//

void Tool_musicxml2hum::checkForDummyRests(MxmlMeasure* measure) {
	vector<MxmlEvent*>& events = measure->getEventList();

	MxmlPart* owner = measure->getOwner();
	int maxstaff = owner->getStaffCount();
	vector<vector<int> > itemcounts(maxstaff);
	for (int i=0; i<(int)itemcounts.size(); i++) {
		itemcounts[i].resize(1);
		itemcounts[i][0] = 0;
	}

	for (int i=0; i<(int)events.size(); i++) {
		if (!nodeType(events[i]->getNode(), "note")) {
			// only counting notes/(rests) for now.  <forward> may
			// need to be counted.
			continue;
		}
     	int voiceindex = events[i]->getVoiceIndex();
		int staffindex = events[i]->getStaffIndex();

		if (voiceindex < 0) {
			continue;
		}
		if (staffindex < 0) {
			continue;
		}

		if (staffindex >= (int)itemcounts.size()) {
			itemcounts.resize(staffindex+1);
		}

		if (voiceindex >= (int)itemcounts[staffindex].size()) {
			int oldsize = (int)itemcounts[staffindex].size();
			int newsize = voiceindex + 1;
			itemcounts[staffindex].resize(newsize);
			for (int j=oldsize; j<newsize; j++) {
					  itemcounts[staffindex][j] = 0;
			}
		}
		itemcounts[staffindex][voiceindex]++;
  	}

	bool dummy = false;
	for (int i=0; i<(int)itemcounts.size(); i++) {
		for (int j=0; j<(int)itemcounts[i].size(); j++) {
			if (itemcounts[i][j]) {
				continue;
			}
			HumNum mdur = measure->getDuration();
			HumNum starttime = measure->getStartTime();
      	measure->addDummyRest(starttime, mdur, i, j);
			measure->forceLastInvisible();
			dummy = true;
		}
	}

	if (dummy) {
		measure->sortEvents();
	}

}



//////////////////////////////
//
// Tool_musicxml2hum::convertNowEvents --
//

bool Tool_musicxml2hum::convertNowEvents(GridMeasure* outdata,
		vector<SimultaneousEvents*>& nowevents, vector<int>& nowparts,
		HumNum nowtime, vector<MxmlPart>& partdata, vector<int>& partstaves) {

	if (nowevents.size() == 0) {
		// cout << "NOW EVENTS ARE EMPTY" << endl;
		return true;
	}

	//if (0 && VoiceDebugQ) {
	//	for (int j=0; j<(int)nowevents.size(); j++) {
	//		vector<MxmlEvent*> nz = nowevents[j]->nonzerodur;
	//		for (int i=0; i<(int)nz.size(); i++) {
	//			cerr << "NOWEVENT NZ NAME: " << nz[i]->getElementName()
	//			     << "<\t" << nz[i]->getKernPitch() << endl;
	//		}
	//	}
	//}

	appendZeroEvents(outdata, nowevents, nowtime, partdata);

	bool hasNonZeroDurElements = false;
	for (const SimultaneousEvents* event : nowevents) {
		if (event->nonzerodur.size() != 0) {
			hasNonZeroDurElements = true;
			break;
		}
	}
	if (!hasNonZeroDurElements) {
		// no duration events (should be a terminal barline)
		// ignore and deal with in calling function.
		return true;
	}

	appendNonZeroEvents(outdata, nowevents, nowtime, partdata);

	handleFiguredBassWithoutNonZeroEvent(nowevents, nowtime);

	return true;
}



/////////////////////////////
//
// Tool_musicxml2hum::handleFiguredBassWithoutNonZeroEvent --
//

void Tool_musicxml2hum::handleFiguredBassWithoutNonZeroEvent(vector<SimultaneousEvents*>& nowevents, HumNum nowtime) {
	vector<int> nonZeroParts;
	vector<MxmlEvent> floatingFiguredBass;
	for (const SimultaneousEvents* sevent : nowevents) {
		for (MxmlEvent* mxmlEvent : sevent->nonzerodur) {
			nonZeroParts.push_back(mxmlEvent->getPartIndex());
		}
		for (MxmlEvent* mxmlEvent : sevent->zerodur) {
			if ("figured-bass" == mxmlEvent->getElementName()) {
				if (std::find(nonZeroParts.begin(), nonZeroParts.end(), mxmlEvent->getPartIndex()) == nonZeroParts.end()) {
					// cerr << mxmlEvent->getNode() << "\n";
					string fstring = getFiguredBassString(mxmlEvent->getNode());
					HTp ftok = new HumdrumToken(fstring);
					MusicXmlFiguredBassInfo finfo;
					finfo.timestamp = nowtime;
					finfo.partindex = mxmlEvent->getPartIndex();
					finfo.token = ftok;
					m_offsetFiguredBass.push_back(finfo);
					// cerr << "ADD FLOATING FB NUM " << fstring << " " << nowtime << "\n";
				}
			}
		}
	}
}



/////////////////////////////
//
// Tool_musicxml2hum::appendNonZeroEvents --
//

void Tool_musicxml2hum::appendNonZeroEvents(GridMeasure* outdata,
		vector<SimultaneousEvents*>& nowevents, HumNum nowtime,
		vector<MxmlPart>& partdata) {

	GridSlice* slice = new GridSlice(outdata, nowtime,
			SliceType::Notes);
	if (outdata->empty()) {
		outdata->push_back(slice);
	} else {
		HumNum lasttime = outdata->back()->getTimestamp();
		if (nowtime >= lasttime) {
			outdata->push_back(slice);
		} else {
			// travel backwards in the measure until the correct
			// time position is found.
			auto it = outdata->rbegin();
			while (it != outdata->rend()) {
				lasttime = (*it)->getTimestamp();
				if (nowtime >= lasttime) {
					outdata->insert(it.base(), slice);
					break;
				}
				it++;
			}
		}
	}
	slice->initializePartStaves(partdata);

	for (int i=0; i<(int)nowevents.size(); i++) {
		vector<MxmlEvent*>& events = nowevents[i]->nonzerodur;
		for (int j=0; j<(int)events.size(); j++) {
			addEvent(slice, outdata, events[j], nowtime);
		}
	}
}



//////////////////////////////
//
// Tool_musicxml2hum::addEvent -- Add a note or rest.
//

void Tool_musicxml2hum::addEvent(GridSlice* slice, GridMeasure* outdata, MxmlEvent* event,
		HumNum nowtime) {
	int partindex;  // which part the event occurs in
	int staffindex; // which staff the event occurs in (need to fix)
	int voiceindex; // which voice the event occurs in (use for staff)

	partindex  = event->getPartIndex();
	staffindex = event->getStaffIndex();
	voiceindex = event->getVoiceIndex();

	string recip;
	string pitch;
	string prefix;
	string postfix;
	bool invisible = false;
	bool primarynote = true;
	vector<int> slurdirs;

	if (!event->isFloating()) {
		recip     = event->getRecip();
		HumRegex hre;
		if (hre.search(recip, "(\\d+)%(\\d+)(\\.*)")) {
			int first = hre.getMatchInt(1);
			int second = hre.getMatchInt(2);
			string dots = hre.getMatch(3);
			if (dots.empty()) {
				if ((first == 1) && (second == 2)) {
					hre.replaceDestructive(recip, "0", "1%2");
				}
				if ((first == 1) && (second == 4)) {
					hre.replaceDestructive(recip, "00", "1%4");
				}
				if ((first == 1) && (second == 3)) {
					hre.replaceDestructive(recip, "0.", "1%3");
				}
				if ((first == 2) && (second == 3)) {
					hre.replaceDestructive(recip, "1.", "2%3");
				}
			} else {
				if ((first == 1) && (second == 2)) {
					string original = "1%2" + dots;
					string replacement = "0" + dots;
					hre.replaceDestructive(recip, replacement, original);
				}
			}
		}

		pitch     = event->getKernPitch();
		prefix    = event->getPrefixNoteInfo();
		postfix   = event->getPostfixNoteInfo(primarynote, recip);
		if (postfix.find("@") != string::npos) {
			m_hasTremoloQ = true;
		}
		bool grace     = event->isGrace();
		int slurstarts = event->hasSlurStart(slurdirs);
		int slurstops = event->hasSlurStop();

		if (pitch.find('r') != std::string::npos) {
			string restpitch =  event->getRestPitch();
			pitch += restpitch;
		}

		for (int i=0; i<slurstarts; i++) {
			prefix.insert(0, "(");
		// Ignoring slur direction for now.
		// if (slurstart) {
		// 	prefix.insert(0, "(");
		// 	if (slurdir) {
		// 		if (slurdir > 0) {
		// 			prefix.insert(1, ">");
		// 			m_slurabove++;
		// 		} else if (slurdir < 0) {
		// 			prefix.insert(1, "<");
		// 			m_slurbelow++;
		// 		}
		// 	}
		// }
		}
		for (int i=0; i<slurstops; i++) {
			postfix.push_back(')');
		}
		//if (slurstop) {
		//	postfix.push_back(')');
		//}

		invisible = isInvisible(event);
		if (event->isInvisible()) {
			invisible = true;
		}

		if (grace) {
			HumNum modification;
			HumNum dur = event->getEmbeddedDuration(modification, event->getNode()) / 4;
			if (dur.getNumerator() == 1) {
				recip = to_string(dur.getDenominator()) + "q";
			} else {
				recip = "q";
			}
			if (!event->hasGraceSlash()) {
				recip += "q";
			}
		}
	}

	if (event->getCrossStaffOffset() > 0) {
		m_staffbelow = true;
	} else if (event->getCrossStaffOffset() < 0) {
		m_staffabove = true;
	}

	stringstream ss;
	if (event->isFloating()) {
		ss << ".";
		HTp token = new HumdrumToken(ss.str());
		slice->at(partindex)->at(staffindex)->setTokenLayer(voiceindex, token,
			event->getDuration());
	} else {
		ss << prefix << recip << pitch << postfix;
		if (invisible) {
			ss << "yy";
		}

		// check for chord notes.
		HTp token;
		if (event->isChord()) {
			addSecondaryChordNotes(ss, event, recip);
			token = new HumdrumToken(ss.str());
			slice->at(partindex)->at(staffindex)->setTokenLayer(voiceindex, token,
				event->getDuration());
		} else {
			token = new HumdrumToken(ss.str());
			slice->at(partindex)->at(staffindex)->setTokenLayer(voiceindex, token,
				event->getDuration());
		}
	}

	if (DebugQ) {
		cerr << "!!TOKEN: " << ss.str();
		cerr << "\tTS: "    << event->getStartTime();
		cerr << "\tDUR: "   << event->getDuration();
		cerr << "\tSTn: "   << event->getStaffNumber();
		cerr << "\tVn: "    << event->getVoiceNumber();
		cerr << "\tSTi: "   << event->getStaffIndex();
		cerr << "\tVi: "    << event->getVoiceIndex();
		cerr << "\teNAME: " << event->getElementName();
		cerr << endl;
	}

	int vcount = addLyrics(slice->at(partindex)->at(staffindex), event);

	if (vcount > 0) {
		event->reportVerseCountToOwner(staffindex, vcount);
	}

	int hcount = addHarmony(slice->at(partindex), event, nowtime, partindex);
	if (hcount > 0) {
		event->reportHarmonyCountToOwner(hcount);
	}

	int fcount = addFiguredBass(slice->at(partindex), event, nowtime, partindex);
	if (fcount > 0) {
		event->reportFiguredBassToOwner();
	}

	if (m_current_brackets[partindex].size() > 0) {
		for (int i=0; i<(int)m_current_brackets[partindex].size(); i++) {
			event->setBracket(m_current_brackets[partindex].at(i));
		}
		m_current_brackets[partindex].clear();
		addBrackets(slice, outdata, event, nowtime, partindex);
	}

	if (m_current_text.size() > 0) {
		event->setTexts(m_current_text);
		m_current_text.clear();
		addTexts(slice, outdata, event->getPartIndex(), staffindex, voiceindex, event);
	}

	if (m_current_tempo.size() > 0) {
		event->setTempos(m_current_tempo);
		m_current_tempo.clear();
		addTempos(slice, outdata, event->getPartIndex(), staffindex, voiceindex, event);
	}

	if (m_current_dynamic[partindex].size()) {
		// only processing the first dynamic at the current time point for now.
		// Fix later so that multiple dynamics are handleded in the part at the
		// same time.  The LO parameters for multiple dynamics will need to be
		// qualified with "n=#".
		for (int i=0; i<(int)m_current_dynamic[partindex].size(); i++) {
			event->setDynamics(m_current_dynamic[partindex][i]);
			string dparam = getDynamicsParameters(m_current_dynamic[partindex][i]);

			event->reportDynamicToOwner();
			addDynamic(slice->at(partindex), event, partindex);
			if (dparam != "") {
				// deal with multiple layout entries here...
				GridMeasure *gm = slice->getMeasure();
				string fullparam = "!LO:DY" + dparam;
				if (gm) {
					gm->addDynamicsLayoutParameters(slice, partindex, fullparam);
				}
			}
		}
		m_current_dynamic[partindex].clear();
	}

	// see if a hairpin ending needs to be added before end of measure:
	xml_node enode = event->getHairpinEnding();
	if (enode) {
		event->reportDynamicToOwner();  // shouldn't be necessary
		addHairpinEnding(slice->at(partindex), event, partindex);
		// shouldn't need dynamics layout parameter
	}

	if (m_post_note_text.empty()) {
		return;
	}

	// check the text buffer for text which needs to be moved
	// after the current note.
	string index;
	index = to_string(partindex);
	index += ' ';
	index += to_string(staffindex);
	index += ' ';
	index += to_string(voiceindex);

	auto it = m_post_note_text.find(index);
	if (it == m_post_note_text.end()) {
		// There is text waiting, but not for this note
		// (for some strange reason).
		return;
	}
	vector<xml_node>& tnodes = it->second;
	for (int i=0; i<(int)tnodes.size(); i++) {
		addText(slice, outdata, partindex, staffindex, voiceindex, tnodes[i], true);
	}
	m_post_note_text.erase(it);
}



//////////////////////////////
//
// Tool_musicxml2hum::addTexts -- Add all text direction for a note.
//

void Tool_musicxml2hum::addTexts(GridSlice* slice, GridMeasure* measure, int partindex,
		int staffindex, int voiceindex, MxmlEvent* event) {
	vector<pair<int, xml_node>>& nodes = event->getTexts();
	for (auto item : nodes) {
		int newpartindex = item.first;
		int newstaffindex = 0; // Not allowing addressing text by layer (could be changed).
		addText(slice, measure, newpartindex, newstaffindex, voiceindex, item.second, false);
	}
}



//////////////////////////////
//
// Tool_musicxml2hum::addTempos -- Add all text direction for a note.
//

void Tool_musicxml2hum::addTempos(GridSlice* slice, GridMeasure* measure, int partindex,
		int staffindex, int voiceindex, MxmlEvent* event) {
	vector<pair<int, xml_node>>& nodes = event->getTempos();
	for (auto item : nodes) {
		int newpartindex = item.first;
		int newstaffindex = 0; // Not allowing addressing text by layer (could be changed).
		addTempo(slice, measure, newpartindex, newstaffindex, voiceindex, item.second);
	}
}



//////////////////////////////
//
// Tool_musicxml2hum::addBrackets --
//
//
//    <direction placement="above">
//      <direction-type>
//        <bracket default-y="12" line-end="down" line-type="dashed" number="1" type="start"/>
//      </direction-type>
//      <offset>4</offset>
//    </direction>
//
//    <direction placement="above">
//      <direction-type>
//        <bracket line-end="down" number="1" type="stop"/>
//      </direction-type>
//      <offset>5</offset>
//    </direction>
//

void Tool_musicxml2hum::addBrackets(GridSlice* slice, GridMeasure* measure, MxmlEvent* event,
	HumNum nowtime, int partindex) {
	int staffindex = 0;
	int voiceindex = 0;
	string token;
	HumNum timestamp;
	vector<xml_node> brackets = event->getBrackets();
	for (int i=0; i<(int)brackets.size(); i++) {
		xml_node bracket = brackets[i].child("direction-type").child("bracket");
		if (!bracket) {
			continue;
		}
		string linetype = bracket.attribute("line-type").as_string();
		string endtype = bracket.attribute("type").as_string();
		int number = bracket.attribute("number").as_int();
		if (endtype == "stop") {
			linetype = m_bracket_type_buffer[number];
		} else {
			m_bracket_type_buffer[number] = linetype;
		}

		if (linetype == "solid") {
			if (endtype == "start") {
				token = "*lig";
				measure->addInterpretationBefore(slice, partindex, staffindex, voiceindex, token);
			} else if (endtype == "stop") {
				token = "*Xlig";
				timestamp = nowtime + event->getDuration();
				measure->addInterpretationAfter(slice, partindex, staffindex, voiceindex, token, timestamp);
			}
		} else if (linetype == "dashed") {
			if (endtype == "start") {
				token = "*col";
				measure->addInterpretationBefore(slice, partindex, staffindex, voiceindex, token);
			} else if (endtype == "stop") {
				token = "*Xcol";
				timestamp = nowtime + event->getDuration();
				measure->addInterpretationAfter(slice, partindex, staffindex, voiceindex, token, timestamp);
			}
		}
	}
}



//////////////////////////////
//
// Tool_musicxml2hum::addText -- Add a text direction to the grid.
//
//      <direction placement="below">
//        <direction-type>
//          <words font-style="italic">Some Text</words>
//        </direction-type>
//      </direction>
//
// Multi-line example:
//
// <direction placement="above">
//         <direction-type>
//           <words default-y="40.00" relative-x="-9.47" relative-y="2.71">note</words>
//           <words>with newline</words>
//         </direction-type>
//       <staff>2</staff>
//       </direction>
//

void Tool_musicxml2hum::addText(GridSlice* slice, GridMeasure* measure, int partindex,
		int staffindex, int voiceindex, xml_node node, bool force) {
	string placementstring;
	xml_attribute placement = node.attribute("placement");
	if (placement) {
		string value = placement.value();
		if (value == "above") {
			placementstring = ":a";
		} else if (value == "below") {
			placementstring = ":b";
		}
	}

	xml_node child = node.first_child();
	if (!child) {
		return;
	}
	if (!nodeType(child, "direction-type")) {
		return;
	}

	xml_node grandchild = child.first_child();
	if (!grandchild) {
		return;
	}

	xml_node sibling = grandchild;

	bool dyQ = false;
	xml_attribute defaulty;

	string text;
	while (sibling) {
		if (nodeType(sibling, "words")) {
			text += sibling.child_value();
			if (!dyQ) {
				defaulty = sibling.attribute("default-y");
				if (defaulty) {
					dyQ = true;
					double number = std::stod(defaulty.value());
					if (number >= 0.0) {
						placementstring = ":a";
					} else if (number < 0.0) {
						placementstring = ":b";
					}
				}
			}
		}
		sibling = sibling.next_sibling();
	}

	if (text == "") {
		// Don't insert an empty text
		return;
	}

	// Mapping \n (0x0a) to newline (ignoring \r, (0x0d))
	string newtext;
	for (int i=0; i<(int)text.size(); i++) {
		switch (text[i]) {
			case 0x0a:
				newtext += "\\n";
			case 0x0d:
				break;
			default:
				newtext += text[i];
		}
	}
	text = newtext;

	// Remove newlines encodings at end of text.
	HumRegex hre;
	hre.replaceDestructive(text, "", "(\\\\n)+\\s*$");

	/* Problem: these are also possibly signs for figured bass
	if (text == "#") {
		// interpret as an editorial sharp marker
		setEditorialAccidental(+1, slice, partindex, staffindex, voiceindex);
		return;
	} else if (text == "b") {
		// interpret as an editorial flat marker
		setEditorialAccidental(-1, slice, partindex, staffindex, voiceindex);
		return;
	// } else if (text == u8"§") {
	} else if (text == "\xc2\xa7") {
		// interpret as an editorial natural marker
		setEditorialAccidental(0, slice, partindex, staffindex, voiceindex);
		return;
	}
	*/

	//
	// The following code should be merged into the loop to apply
	// font changes within the text.  Internal formatting code for
	// the string would need to be developed if so.  For now, just
	// the first word's style will be processed.
	//

	string stylestring;
	bool italic = false;
	bool bold = false;

	xml_attribute fontstyle = grandchild.attribute("font-style");
	if (fontstyle) {
		string value = fontstyle.value();
		if (value == "italic") {
			italic = true;
		}
	}

	xml_attribute fontweight = grandchild.attribute("font-weight");
	if (fontweight) {
		string value = fontweight.value();
		if (value == "bold") {
			bold = true;
		}
	}

	if (italic && bold) {
		stylestring = ":Bi";
	} else if (italic) {
		stylestring = ":i";
	} else if (bold) {
		stylestring = ":B";
	}

	bool interpQ = false;
	bool specialQ = false;
	bool globalQ = false;
	bool afterQ = false;
	string output;
	if (text == "!") {
		// null local comment
		output = text;
		specialQ = true;
	} else if (text == "*") {
		// null interpretation
		output = text;
		specialQ = true;
		interpQ = true;
	} else if ((text.size() > 1) && (text[0] == '*') && (text[1] != '*')) {
		// regular tandem interpretation, but disallow manipulators:
		if (text == "*^") {
			specialQ = false;
		} else if (text == "*+") {
			specialQ = false;
		} else if (text == "*-") {
			specialQ = false;
		} else if (text == "*v") {
			specialQ = false;
		} else {
			specialQ = true;
			interpQ = true;
			output = text;
		}
	} else if ((text.size() > 2) && (text[0] == '*') && (text[1] == '*')) {
		hre.replaceDestructive(text, "*", "^\\*+");
		output = text;
		specialQ = true;
		afterQ = true;
		interpQ = true;
		if (force == false) {
			// store text for later processing after the next note in the data.
			string index;
			index += to_string(partindex);
			index += ' ';
			index += to_string(staffindex);
			index += ' ';
			index += to_string(voiceindex);
			m_post_note_text[index].push_back(node);
			return;
		}
	} else if ((text.size() > 1) && (text[0] == '!') && (text[1] != '!')) {
		// embedding a local comment
		output = text;
		specialQ = true;
	} else if ((text.size() >= 2) && (text[0] == '!') && (text[1] == '!')) {
		// embedding a global comment (or bibliographic record, etc.).
		output = text;
		globalQ = true;
		specialQ = true;
	} else if (hre.search(text, "\\s*problem\\s*:\\s*(.*)\\s*$")) {
		specialQ = true;
		output = "!LO:TX:t=P:problem:";
		output += hre.getMatch(1);
		hre.replaceDestructive(output, "\\n", "\n", "g");
		hre.replaceDestructive(output, " ", "\t", "g");
	}

	if (!specialQ) {
		text = cleanSpacesAndColons(text);
		if (text.empty()) {
			// no text to display after removing whitespace
			return;
		}

		if (placementstring.empty()) {
			// force above if no placement specified
			placementstring = ":a";
		}

		output = "!LO:TX";
		output += placementstring;
		output += stylestring;
		output += ":t=";
		output += text;
	}

	// The text direction needs to be added before the last line
	// in the measure object.  If there is already an empty layout
	// slice before the current one (with no spine manipulators
	// in between), then insert onto the existing layout slice;
	// otherwise create a new layout slice.

	if (interpQ) {
		if (afterQ) {
			int voicecount = (int)slice->at(partindex)->at(staffindex)->size();
			if (voiceindex >= voicecount) {
				// Adding voices in the new slice.  It might be
				// better to first check for a previous text line
				// at the current timestamp that is empty (because there
				// is text at the same time in another spine).
				GridStaff* gs = slice->at(partindex)->at(staffindex);
				gs->resize(voiceindex+1);
				string null = slice->getNullTokenForSlice();
				for (int m=voicecount; m<voiceindex+1; m++) {
					gs->at(m) = new GridVoice(null, 0);
				}
			}
			HTp token = slice->at(partindex)->at(staffindex)->at(voiceindex)->getToken();
			HumNum tokdur = Convert::recipToDuration(token);
			HumNum timestamp = slice->getTimestamp() + tokdur;
			measure->addInterpretationAfter(slice, partindex, staffindex, voiceindex, output, timestamp);
		} else {
			measure->addInterpretationBefore(slice, partindex, staffindex, voiceindex, output);
		}
	} else if (globalQ) {
		HumNum timestamp = slice->getTimestamp();
		measure->addGlobalComment(text, timestamp);
	} else {
		// adding local comment that is not a layout parameter also goes here:
		measure->addLayoutParameter(slice, partindex, output);
	}
}



//////////////////////////////
//
// Tool_musicxml2hum::addTempo -- Add a tempo direction to the grid.
//
// <direction placement="above">
//    <direction-type>
//       <metronome parentheses="no" default-x="-35.96" relative-y="20.00">
//          <beat-unit>half</beat-unit>
//          <per-minute>80</per-minute>
//       </metronome>
//    </direction-type>
//    <sound tempo="160"/>
// </direction>
//
// Dotted tempo example:
//
// <direction placement="above">
//    <direction-type>
//       <metronome parentheses="no" default-x="-39.10" relative-y="20.00">
//          <beat-unit>quarter</beat-unit>
//          <beat-unit-dot/>
//          <per-minute>80</per-minute>
//       </metronome>
//    </direction-type>
//    <sound tempo="120"/>
// </direction>
//
//

void Tool_musicxml2hum::addTempo(GridSlice* slice, GridMeasure* measure, int partindex,
		int staffindex, int voiceindex, xml_node node) {
	string placementstring;
	xml_attribute placement = node.attribute("placement");
	if (placement) {
		string value = placement.value();
		if (value == "above") {
			placementstring = ":a";
		} else if (value == "below") {
			placementstring = ":b";
		} else {
			// force above if no explicit placement:
			placementstring = ":a";
		}
	}

	xml_node child = node.first_child();
	if (!child) {
		return;
	}
	if (!nodeType(child, "direction-type")) {
		return;
	}

	xml_node sound(NULL);
	xml_node sibling = child;
	while (sibling) {
		if (nodeType(sibling, "sound")) {
			sound = sibling;
			break;
		}
		sibling = sibling.next_sibling();
	}

	// grandchild should be <metronome> (containing textual display)
	// and <sound @tempo> which gives *MM data.
	xml_node metronome(NULL);

	xml_node grandchild = child.first_child();
	if (!grandchild) {
		return;
	}
	sibling = grandchild;

	while (sibling) {
		if (nodeType(sibling, "metronome")) {
			metronome = sibling;
		}
		sibling = sibling.next_sibling();
	}

	// get metronome parameters

	xml_node beatunit(NULL);
	xml_node beatunitdot(NULL);
	xml_node perminute(NULL);

	if (metronome) {
		sibling = metronome.first_child();
		while (sibling) {
			if (nodeType(sibling, "beat-unit")) {
				beatunit = sibling;
			} else if (nodeType(sibling, "beat-unit-dot")) {
				beatunitdot = sibling;
			} else if (nodeType(sibling, "per-minute")) {
				perminute = sibling;
			}
			sibling = sibling.next_sibling();
		}
	}

	string mmvalue;
	if (sound) {
		mmvalue = getAttributeValue(sound, "tempo");
	}

	if (!beatunit) {
		cerr << "Warning: missing beat-unit in tempo setting" << endl;
		return;
	}
	if (!perminute) {
		cerr << "Warning: missing per-minute in tempo setting" << endl;
		return;
	}

	int staff = 0;
	int voice = 0;

	if (sound) {
		string mmtok = "*MM";
		double mmv = stod(mmvalue);
		double mmi = int(mmv + 0.001);
		if (fabs(mmv - mmi) < 0.01) {
			stringstream sstream;
			sstream << mmi;
			mmtok += sstream.str();
		} else {
			mmtok += mmvalue;
		}
		HumNum timestamp = slice->getTimestamp();
		measure->addTempoToken(mmtok, timestamp, partindex, staff, voice, m_maxstaff);
	}

	string butext = beatunit.child_value();
	string pmtext = perminute.child_value();
	string stylestring;

	// create textual tempo marking
	string text;
	text = "[";
	text += butext;
	if (beatunitdot) {
		text += "-dot";
	}
	text += "]";
	text += "=";
	text += pmtext;

	string output = "!LO:TX";
	output += placementstring;
	output += stylestring;
	output += ":t=";
	output += text;

	// The text direction needs to be added before the last line in the measure object.
	// If there is already an empty layout slice before the current one (with no spine manipulators
	// in between), then insert onto the existing layout slice; otherwise create a new layout slice.
	measure->addTempoToken(slice, partindex, output);
}



//////////////////////////////
//
// setEditorialAccidental --
//

void Tool_musicxml2hum::setEditorialAccidental(int accidental, GridSlice* slice,
		int partindex, int staffindex, int voiceindex) {

	HTp tok = slice->at(partindex)->at(staffindex)->at(voiceindex)->getToken();

	if ((accidental < 0) && (tok->find("-") == string::npos))  {
		cerr << "Editorial error for " << tok << ": no flat to mark" << endl;
		return;
	}
	if ((accidental > 0) && (tok->find("#") == string::npos))  {
		cerr << "Editorial error for " << tok << ": no sharp to mark" << endl;
		return;
	}
	if ((accidental == 0) &&
			((tok->find("#") != string::npos) || (tok->find("-") != string::npos)))  {
		cerr << "Editorial error for " << tok << ": requesting a natural accidental" << endl;
		return;
	}

	string newtok = *tok;

	if (accidental == -1) {
		auto loc = newtok.find("-");
		if (loc < newtok.size()) {
			if (newtok[loc+1] == 'X') {
				// replace explicit accidental with editorial accidental
				newtok[loc+1] = 'i';
				tok->setText(newtok);
				m_hasEditorial = 'i';
			} else {
				// append i after -:
				newtok.insert(loc+1, "i");
				tok->setText(newtok);
				m_hasEditorial = 'i';
			}
		}
		return;
	}

	if (accidental == +1) {
		auto loc = newtok.find("#");
		if (loc < newtok.size()) {
			if (newtok[loc+1] == 'X') {
				// replace explicit accidental with editorial accidental
				newtok[loc+1] = 'i';
				tok->setText(newtok);
				m_hasEditorial = 'i';
			} else {
				// append i after -:
				newtok.insert(loc+1, "i");
				tok->setText(newtok);
				m_hasEditorial = 'i';
			}
		}
		return;
	}

	if (accidental == 0) {
		auto loc = newtok.find("n");
		if (loc < newtok.size()) {
			if (newtok[loc+1] == 'X') {
				// replace explicit accidental with editorial accidental
				newtok[loc+1] = 'i';
				tok->setText(newtok);
				m_hasEditorial = 'i';
			} else {
				// append i after -:
				newtok.insert(loc+1, "i");
				tok->setText(newtok);
				m_hasEditorial = 'i';
			}
		} else {
			// no natural sign, so add it after any pitch classes.
			HumRegex hre;
			hre.search(newtok, R"(([a-gA-G]+))");
			string diatonic = hre.getMatch(1);
			string newacc = diatonic + "i";
			hre.replaceDestructive(newtok, newacc, diatonic);
			tok->setText(newtok);
			m_hasEditorial = 'i';
		}
		return;
	}
}



//////////////////////////////
//
// Tool_musicxml2hum::addDynamic -- extract any dynamics for the event
//
// Such as:
//    <direction placement="below">
//      <direction-type>
//        <dynamics>
//          <fff/>
//          </dynamics>
//        </direction-type>
//      <sound dynamics="140.00"/>
//      </direction>
//
// Hairpins:
//      <direction placement="below">
//        <direction-type>
//          <wedge default-y="-75" number="2" spread="15" type="diminuendo"/>
//        </direction-type>
//      </direction>
//
//      <direction>
//        <direction-type>
//          <wedge spread="15" type="stop"/>
//        </direction-type>
//      </direction>
//

void Tool_musicxml2hum::addDynamic(GridPart* part, MxmlEvent* event, int partindex) {
	vector<xml_node> directions = event->getDynamics();
	if (directions.empty()) {
		return;
	}

	HTp tok = NULL;

	for (int i=0; i<(int)directions.size(); i++) {
		xml_node direction = directions[i];
		xml_attribute placement = direction.attribute("placement");
		bool above = false;
		if (placement) {
			string value = placement.value();
			if (value == "above") {
				above = true;
			}
		}
		xml_node child = direction.first_child();
		if (!child) {
			continue;
		}
		if (!nodeType(child, "direction-type")) {
			continue;
		}
		xml_node grandchild = child.first_child();
		if (!grandchild) {
			continue;
		}

		if (!(nodeType(grandchild, "dynamics") || nodeType(grandchild, "wedge"))) {
			continue;
		}

		if (nodeType(grandchild, "dynamics")) {
			xml_node dynamic = grandchild.first_child();
			if (!dynamic) {
				continue;
			}
			string dstring = getDynamicString(dynamic);
			if (!tok) {
				tok = new HumdrumToken(dstring);
			} else {
				string oldtext = tok->getText();
				string newtext = oldtext + " " + dstring;
				tok->setText(newtext);
			}
		} else if ( nodeType(grandchild, "wedge")) {
			xml_node hairpin = grandchild;

			if (isUsedHairpin(hairpin, partindex)) {
				// need to suppress wedge ending if already used in [[ or ]]
				continue;
			}
			if (!hairpin) {
				cerr << "Warning: Expecting a hairpin, but found nothing" << endl;
				continue;
			}
			string hstring = getHairpinString(hairpin, partindex);
			if (!tok) {
				tok = new HumdrumToken(hstring);
			} else {
				string oldtext = tok->getText();
				string newtext = oldtext + " " + hstring;
				tok->setText(newtext);
			}

			// Deal here with adding an index if there is more than one hairpin.
			if ((hstring != "[") && (hstring != "]") && above) {
				tok->setValue("LO", "HP", "a", "true");
			}
		}
	}
	if (tok) {
		part->setDynamics(tok);
	}
}



//////////////////////////////
//
// Tool_musicxml::isUsedHairpin --  Needed to avoid double-insertion
//    of hairpins which were stored before a barline so that they
//    are not also repeated on the first beat of the next barline.
//    This fuction will remove the hairpin from the used array
//    when it is checked.  The used array is only for storing
//    hairpins that end on measures, so in theory there should not
//    be too many, and they will be removed fairly quickly.
//

bool Tool_musicxml2hum::isUsedHairpin(xml_node hairpin, int partindex) {
	for (int i=0; i<(int)m_used_hairpins.at(partindex).size(); i++) {
		if (hairpin == m_used_hairpins.at(partindex).at(i)) {
			// Cannot delete yet: the hairpin endings are being double accessed somewhere.
			//m_used_hairpins[partindex].erase(m_used_hairpins[partindex].begin() + i);
			return true;
		}
	}
	return false;
}



//////////////////////////////
//
// Tool_musicxml2hum::addHairpinEnding -- extract any hairpin ending
//   at the end of a measure.
//
// Hairpins:
//      <direction>
//        <direction-type>
//          <wedge spread="15" type="stop"/>
//        </direction-type>
//      </direction>
//

void Tool_musicxml2hum::addHairpinEnding(GridPart* part, MxmlEvent* event, int partindex) {

	xml_node direction = event->getHairpinEnding();
	if (!direction) {
		return;
	}

	xml_node child = direction.first_child();
	if (!child) {
		return;
	}
	if (!nodeType(child, "direction-type")) {
		return;
	}
	xml_node grandchild = child.first_child();
	if (!grandchild) {
		return;
	}

	if (!nodeType(grandchild, "wedge")) {
		return;
	}

	if (nodeType(grandchild, "wedge")) {
		xml_node hairpin = grandchild;
		if (!hairpin) {
			return;
		}
		string hstring = getHairpinString(hairpin, partindex);
		if (hstring == "[") {
			hstring = "[[";
		} else if (hstring == "]") {
			hstring = "]]";
		}
		m_used_hairpins.at(partindex).push_back(hairpin);
		HTp current = part->getDynamics();
		if (!current) {
			HTp htok = new HumdrumToken(hstring);
			part->setDynamics(htok);
		} else {
			string text = current->getText();
			text += " ";
			text += hstring;
			// Set single-note crescendos
			if (text == "< [[") {
				text = "<[";
			} else if (text == "> ]]") {
				text = ">]";
			} else if (text == "< [") {
				text = "<[";
			} else if (text == "> ]") {
				text = ">]";
			}
			current->setText(text);
		}
	}
}



//////////////////////////////
//
// Tool_musicxml2hum::convertFiguredBassNumber --
//

string Tool_musicxml2hum::convertFiguredBassNumber(const xml_node& figure) {
	string output;
	xml_node fnum = figure.select_node("figure-number").node();
	// assuming one each of prefix/suffix:
	xml_node prefixelement = figure.select_node("prefix").node();
	xml_node suffixelement = figure.select_node("suffix").node();

	string prefix;
	if (prefixelement) {
		prefix = prefixelement.child_value();
	}

	string suffix;
	if (suffixelement) {
		suffix = suffixelement.child_value();
	}

	string number;
	if (fnum) {
		number = fnum.child_value();
	}

	string accidental;
	string slash;

	if (prefix == "flat-flat") {
		accidental = "--";
	} else if (prefix == "flat") {
		accidental = "-";
	} else if (prefix == "double-sharp" || prefix == "sharp-sharp") {
		accidental = "##";
	} else if (prefix == "sharp") {
		accidental = "#";
	} else if (prefix == "natural") {
		accidental = "n";
	} else if (suffix == "flat-flat") {
		accidental = "--r";
	} else if (suffix == "flat") {
		accidental = "-r";
	} else if (suffix == "double-sharp" || suffix == "sharp-sharp") {
		accidental = "##r";
	} else if (suffix == "sharp") {
		accidental = "#r";
	} else if (suffix == "natural") {
		accidental = "nr";
	}

	// If suffix is "cross", "slash" or "backslash",  then an accidental
	// should be given (probably either a natural or a sharp in general, but
	// could be a flat).  At the moment do not assign the accidental, but
	// in the future assign an accidental to the slashed figure, probably
	// with a post-processing tool.
	if (suffix == "cross" || prefix == "cross" || suffix == "vertical" || prefix == "vertical") {
		slash = "|";
		if (accidental.empty()) {
			accidental = "#";
		}
	} else if ((suffix == "backslash" || suffix == "back-slash") || (prefix == "backslash" || prefix == "back-slash")) {
		slash = "\\";
		if (accidental.empty()) {
			accidental = "#";
		}
	} else if ((suffix == "slash") || (prefix == "slash")) {
		slash = "/";
		if (accidental.empty()) {
			accidental = "-";
		}
	}

	string editorial;
	string extension;

	xml_node extendelement = figure.select_node("extend").node();
	if (extendelement) {
		string typestring = extendelement.attribute("type").value();
		if (typestring == "start") {
			extension = "_";
		}
	}

	output += accidental + number + slash + editorial + extension;

	return output;
}



//////////////////////////////
//
// Tool_musicxml2hum::getDynanmicsParameters --  Already presumed to be
//     a dynamic.
//

string Tool_musicxml2hum::getDynamicsParameters(xml_node element) {
	string output;
	if (!nodeType(element, "direction")) {
		return output;
	}

	xml_attribute placement = element.attribute("placement");
	if (!placement) {
		return output;
	}
	string value = placement.value();
	if (value == "above") {
		output = ":a";
	}
	xml_node child = element.first_child();
	if (!child) {
		return output;
	}
	if (!nodeType(child, "direction-type")) {
		return output;
	}
	xml_node grandchild = child.first_child();
	if (!grandchild) {
		return output;
	}
	if (!nodeType(grandchild, "wedge")) {
		return output;
	}

	xml_attribute wtype = grandchild.attribute("type");
	if (!wtype) {
		return output;
	}
	string value2 = wtype.value();
	if (value2 == "stop") {
		// don't apply parameters to ends of hairpins.
		output = "";
	}

	return output;
}



//////////////////////////////
//
// Tool_musicxml2hum::getFiguredBassParameters --  Already presumed to be
//     figured bass.
//

string Tool_musicxml2hum::getFiguredBassParameters(xml_node element) {
	string output;
	if (!nodeType(element, "figured-bass")) {
		return output;
	}
	return output;
}



//////////////////////////////
//
// Tool_musicxml2hum::getHairpinString --
//
// Hairpins:
//      <direction placement="below">
//        <direction-type>
//          <wedge default-y="-75" number="2" spread="15" type="diminuendo"/>
//        </direction-type>
//      </direction>
//
//      <direction>
//        <direction-type>
//          <wedge spread="15" type="stop"/>
//        </direction-type>
//      </direction>
//

string Tool_musicxml2hum::getHairpinString(xml_node element, int partindex) {
	if (nodeType(element, "wedge")) {
		xml_attribute wtype = element.attribute("type");
		if (!wtype) {
			return "???";
		}
		string output;
		string wstring = wtype.value();
		if (wstring == "diminuendo") {
			m_stop_char.at(partindex) = "]";
			output = ">";
		} else if (wstring == "crescendo") {
			m_stop_char.at(partindex) = "[";
			output = "<";
		} else if (wstring == "stop") {
			output = m_stop_char.at(partindex);
		} else {
			output = "???";
		}
		return output;
	}

	return "???";
}



//////////////////////////////
//
// Tool_musicxml2hum::getDynamicString --
//

string Tool_musicxml2hum::getDynamicString(xml_node element) {

	if (nodeType(element, "f")) {
		return "f";
	} else if (nodeType(element, "p")) {
		return "p";
	} else if (nodeType(element, "mf")) {
		return "mf";
	} else if (nodeType(element, "mp")) {
		return "mp";
	} else if (nodeType(element, "ff")) {
		return "ff";
	} else if (nodeType(element, "pp")) {
		return "pp";
	} else if (nodeType(element, "sf")) {
		return "sf";
	} else if (nodeType(element, "sfp")) {
		return "sfp";
	} else if (nodeType(element, "sfpp")) {
		return "sfpp";
	} else if (nodeType(element, "fp")) {
		return "fp";
	} else if (nodeType(element, "rf")) {
		return "rfz";
	} else if (nodeType(element, "rfz")) {
		return "rfz";
	} else if (nodeType(element, "sfz")) {
		return "sfz";
	} else if (nodeType(element, "sffz")) {
		return "sffz";
	} else if (nodeType(element, "fz")) {
		return "fz";
	} else if (nodeType(element, "fff")) {
		return "fff";
	} else if (nodeType(element, "ppp")) {
		return "ppp";
	} else if (nodeType(element, "ffff")) {
		return "ffff";
	} else if (nodeType(element, "pppp")) {
		return "pppp";
	} else {
		return "???";
	}
}


//////////////////////////////
//
// Tool_musicxml2hum::addFiguredBass --
//
// Such as:
//
//      <figured-bass>
//        <figure>
//          <figure-number>0</figure-number>
//        </figure>
//      </figured-bass>
// or:
//      <figured-bass>
//        <figure>
//          <figure-number>5</figure-number>
//          <suffix>backslash</suffix>
//        </figure>
//        <figure>
//          <figure-number>2</figure-number>
//          <suffix>cross</suffix>
//        </figure>
//      </figured-bass>
//
//      <figured-bass parentheses="yes">
//        <figure>
//          <prefix>flat</prefix>
//        </figure>
//      </figured-bass>
//
// Case where there is more than one figure attached to a note:
// (notice <duration> element)
//
//      <figured-bass>
//        <figure>
//          <figure-number>6</figure-number>
//          <extend type="start" />
//        </figure>
//        <duration>2</duration>
//      <figured-bass>
//
//

int Tool_musicxml2hum::addFiguredBass(GridPart* part, MxmlEvent* event, HumNum nowtime, int partindex) {
	if (m_current_figured_bass[partindex].empty()) {
		return 0;
	}

	int dursum = 0;
	for (int i=0; i<(int)m_current_figured_bass[partindex].size(); i++) {
		xml_node fnode = m_current_figured_bass[partindex].at(i);
		if (!fnode) {
			// strange problem
			continue;
		}
		string fstring = getFiguredBassString(fnode);

		HTp ftok = new HumdrumToken(fstring);
		if (i == 0) {
			part->setFiguredBass(ftok);
		} else {
			// store the figured bass for later handling at end of
			// measure processing.
			MusicXmlFiguredBassInfo finfo;
			finfo.timestamp = dursum;
			finfo.timestamp /= (int)event->getQTicks();
			finfo.timestamp += nowtime;
			finfo.partindex = partindex;
			finfo.token = ftok;
			m_offsetFiguredBass.push_back(finfo);
		}
		if (i < (int)m_current_figured_bass[partindex].size() - 1) {
			dursum += getFiguredBassDuration(fnode);
		}
	}
	m_current_figured_bass[partindex].clear();

	return 1;

/* deal with figured bass layout parameters?:
			string fparam = getFiguredBassParameters(fnode);
			if (fparam != "") {
				GridMeasure *gm = slice->getMeasure();
				string fullparam = "!LO:FB" + fparam;
				if (gm) {
					gm->addFiguredBassLayoutParameters(slice, partindex, fullparam);
				}
			}
		}
*/

}



//////////////////////////////
//
// Tool_musicxml2hum::getFiguredBassString -- extract any figured bass string
//   from XML node.
//

string Tool_musicxml2hum::getFiguredBassString(xml_node fnode) {
	string output;

	// Parentheses can only enclose an entire figure stack, not
	// individual numbers or accidentals on numbers in MusicXML,
	// so apply an editorial mark for parentheses.
	string editorial;
	xml_attribute pattr = fnode.attribute("parentheses");
	if (pattr) {
		string pval = pattr.value();
		if (pval == "yes") {
			editorial = "i";
		}
	}
	// There is no bracket for FB in musicxml (3.0).

	auto children = fnode.select_nodes("figure");
	for (int i=0; i<(int)children.size(); i++) {
		output += convertFiguredBassNumber(children[i].node());
		output += editorial;
		if (i < (int)children.size() - 1) {
			output += " ";
		}
	}

	HumRegex hre;
	hre.replaceDestructive(output, "", R"(^\s+|\s+$)");

	if (output.empty()) {
		if (children.size()) {
			cerr << "WARNING: figured bass string is empty but has "
				<< children.size() << " figure elements as children. "
				<< "The output has been replaced with \".\"" << endl;
		}
		output = ".";
	}

	return output;

	// HTp fbtok = new HumdrumToken(fbstring);
	// part->setFiguredBass(fbtok);
}



//////////////////////////////
//
// Tool_musicxml2hum::addHarmony --
//

int Tool_musicxml2hum::addHarmony(GridPart* part, MxmlEvent* event, HumNum nowtime,
		int partindex) {
	xml_node hnode = event->getHNode();
	if (!hnode) {
		return 0;
	}

	// fill in X with the harmony values from the <harmony> node
	string hstring = getHarmonyString(hnode);
	int offset = getHarmonyOffset(hnode);
	HTp htok = new HumdrumToken(hstring);
	if (offset == 0) {
		part->setHarmony(htok);
	} else {
		MusicXmlHarmonyInfo hinfo;
		hinfo.timestamp = offset;
		hinfo.timestamp /= (int)event->getQTicks();
		hinfo.timestamp += nowtime;
		hinfo.partindex = partindex;
		hinfo.token = htok;
		offsetHarmony.push_back(hinfo);
	}

	return 1;
}



//////////////////////////////
//
// Tool_musicxml2hum::getHarmonyOffset --
//   <harmony default-y="40">
//       <root>
//           <root-step>C</root-step>
//       </root>
//       <kind>major-ninth</kind>
//       <bass>
//           <bass-step>E</bass-step>
//       </bass>
//       <offset>-8</offset>
//   </harmony>
//

int Tool_musicxml2hum::getHarmonyOffset(xml_node hnode) {
	if (!hnode) {
		return 0;
	}
	xml_node child = hnode.first_child();
	if (!child) {
		return 0;
	}
	while (child) {
		if (nodeType(child, "offset")) {
			return atoi(child.child_value());
		}
		child = child.next_sibling();
	}

	return 0;
}



//////////////////////////////
//
// Tool_musicxml2hum::getFiguredBaseDuration -- Needed for cases where there is more
//   than one figure attached to a note.  Return value is the integer of the duration
//   element.  If will need to be converted to quarter notes later.
//
//   <figured-bass>
//      <figure>
//         <figure-number>5</figure-number>
//      </figure>
//      <figure>
//         <figure-number>3</figure-number>
//      </figure>
//      <duration>2</duration>    <-- get this field if it exists.
//   </figured-bass>
//

int Tool_musicxml2hum::getFiguredBassDuration(xml_node fnode) {
	if (!fnode) {
		return 0;
	}
	xml_node child = fnode.first_child();
	if (!child) {
		return 0;
	}
	while (child) {
		if (nodeType(child, "duration")) {
			return atoi(child.child_value());
		}
		child = child.next_sibling();
	}

	return 0;
}



//////////////////////////////
//
// Tool_musicxml2hum::getHarmonyString --
//   <harmony default-y="40">
//       <root>
//           <root-step>C</root-step>
//       </root>
//       <kind>major-ninth</kind>
//       <bass>
//           <bass-step>E</bass-step>
//       </bass>
//       <offset>-8</offset>
//   </harmony>
//
// For harmony labels from Musescore:
//
//    <harmony print-frame="no">
//      <root>
//        <root-step text="">C</root-step>
//        </root>
//      <kind text="V43">none</kind>
//      </harmony>
//
// Converts to: "V43" ignoring the root-step and kind contents
// if they are both "C" and "none".
//

string Tool_musicxml2hum::getHarmonyString(xml_node hnode) {
	if (!hnode) {
		return "";
	}
	xml_node child = hnode.first_child();
	if (!child) {
		return "";
	}
	string root;
	string kind;
	string kindtext;
	string bass;
	int rootalter = 0;
	int bassalter = 0;
	xml_node grandchild;
	while (child) {
		if (nodeType(child, "root")) {
			grandchild = child.first_child();
			while (grandchild) {
				if (nodeType(grandchild, "root-step")) {
					root = grandchild.child_value();
				} if (nodeType(grandchild, "root-alter")) {
					rootalter = atoi(grandchild.child_value());
				}
				grandchild = grandchild.next_sibling();
			}
		} else if (nodeType(child, "kind")) {
			kindtext = getAttributeValue(child, "text");
			kind = child.child_value();
			if (kind == "") {
				kind = child.attribute("text").value();
				transform(kind.begin(), kind.end(), kind.begin(), ::tolower);
			}
		} else if (nodeType(child, "bass")) {
			grandchild = child.first_child();
			while (grandchild) {
				if (nodeType(grandchild, "bass-step")) {
					bass = grandchild.child_value();
				} if (nodeType(grandchild, "bass-alter")) {
					bassalter = atoi(grandchild.child_value());
				}
				grandchild = grandchild.next_sibling();
			}
		}
		child = child.next_sibling();
	}
	stringstream ss;

	if ((kind == "none") && (root == "C") && !kindtext.empty()) {
		ss << kindtext;
		string output = cleanSpaces(ss.str());
		return output;
	}

	ss << root;

	if (rootalter > 0) {
		for (int i=0; i<rootalter; i++) {
			ss << "#";
		}
	} else if (rootalter < 0) {
		for (int i=0; i<-rootalter; i++) {
			ss << "-";
		}
	}

	if (root.size() && kind.size()) {
		ss << " ";
	}
	ss << kind;
	if (bass.size()) {
		ss << "/";
	}
	ss << bass;

	if (bassalter > 0) {
		for (int i=0; i<bassalter; i++) {
			ss << "#";
		}
	} else if (bassalter < 0) {
		for (int i=0; i<-bassalter; i++) {
			ss << "-";
		}
	}

	string output = cleanSpaces(ss.str());
	return output;
}



//////////////////////////////
//
// Tool_musicxml2hum::addLyrics --
//

int Tool_musicxml2hum::addLyrics(GridStaff* staff, MxmlEvent* event) {
	xml_node node = event->getNode();
	if (!node) {
		return 0;
	}
	HumRegex hre;
	xml_node child = node.first_child();
	xml_node grandchild;
	// int max;
	int number = 0;
	vector<xml_node> verses;
	string syllabic;
	string text;
	while (child) {
		if (!nodeType(child, "lyric")) {
			child = child.next_sibling();
			continue;
		}
		string value = child.attribute("number").value();
		if (hre.search(value, R"(verse(\d+))")) {
			// Fix for Sibelius which uses number="part8verse5" format.
			number = stoi(hre.getMatch(1));
		} else {
			number = atoi(child.attribute("number").value());
		}
		if (number > 100) {
			cerr << "Error: verse number is too large: number" << endl;
			return 0;
		}
		if (number == (int)verses.size() + 1) {
			verses.push_back(child);
		} else if ((number > 0) && (number < (int)verses.size())) {
			// replace a verse for some reason.
			verses[number-1] = child;
		} else if (number > 0) {
			int oldsize = (int)verses.size();
			int newsize = number;
			verses.resize(newsize);
			for (int i=oldsize; i<newsize; i++) {
				verses[i] = xml_node(NULL);
			}
			verses[number-1] = child;
		}
		child = child.next_sibling();
	}

	string finaltext;
	string fontstyle;
	HTp token;
	for (int i=0; i<(int)verses.size(); i++) {
		if (!verses[i]) {
			// no verse so doing an empty slot.
		} else {
			child = verses[i].first_child();
			finaltext = "";
			while (child) {
				if (nodeType(child, "syllabic")) {
					syllabic = child.child_value();
					child = child.next_sibling();
					continue;
				} else if (nodeType(child, "text")) {
					fontstyle = child.attribute("font-style").value();
					text = cleanSpaces(child.child_value());
					if (fontstyle == "italic") {
						text = "<i>" + text + "</i>";
					}
				} else if (nodeType(child, "elision")) {
					finaltext += " ";
					child = child.next_sibling();
					continue;
				} else {
					// such as <extend>
					child = child.next_sibling();
					continue;
				}
				// escape text which would otherwise be reinterpreated
				// as Humdrum syntax.
				if (!text.empty()) {
					if (text[0] == '!') {
						text.insert(0, 1, '\\');
					} else if (text[0] == '*') {
						text.insert(0, 1, '\\');
					}
				}
				child = child.next_sibling();
				if (syllabic == "middle" ) {
					finaltext += "-";
					finaltext += text;
					finaltext += "-";
				} else if (syllabic == "end") {
					finaltext += "-";
					finaltext += text;
				} else if (syllabic == "begin") {
					finaltext += text;
					finaltext += "-";
				} else {
					finaltext += text;
				}
				syllabic.clear();
			}
		}

		if (finaltext.empty()) {
			continue;
		}
		if (m_software == "sibelius") {
			hre.replaceDestructive(finaltext, " ", "_", "g");
		}

		if (verses[i]) {
			token = new HumdrumToken(finaltext);
			staff->setVerse(i,token);
		} else {
			token = new HumdrumToken(".");
			staff->setVerse(i,token);
		}
	}

	return (int)staff->getVerseCount();
}



//////////////////////////////
//
// cleanSpaces -- remove trailing and leading spaces from text.
//    Also removed doubled spaces, and converts tabs and newlines
//    into spaces.
//

string Tool_musicxml2hum::cleanSpaces(const string& input) {
	int endi = (int)input.size() - 1;
	while (endi >= 0) {
		if (isspace(input[endi])) {
			endi--;
			continue;
		}
		break;
	}
	int starti = 0;
	while (starti <= endi) {
		if (isspace(input[starti])) {
			starti++;
			continue;
		}
		break;

	}
	string output;
   for (int i=starti; i<=endi; i++) {
		if (!isspace(input[i])) {
			output += input[i];
			continue;
		}
		output += " ";
		i++;
		while ((i < endi) && isspace(input[i])) {
			i++;
		}
		i--;
	}
	if ((output.size() == 3) && ((unsigned char)output[0] == 0xee) &&
			((unsigned char)output[1] == 0x95) && ((unsigned char)output[2] == 0x91)) {
		// MuseScore elision character:
		// <text font-family="MScore Text"></text>
		output = " ";
	}

	return output;
}



//////////////////////////////
//
// Tool_musicxml2hum::isInvisible --
//

bool Tool_musicxml2hum::isInvisible(MxmlEvent* event) {
	xml_node node = event->getNode();
	if (!node) {
		return false;
	}
	if (strcmp(node.attribute("print-object").value(), "no") == 0) {
		return true;
	}

	return false;
}



//////////////////////////////
//
// Tool_musicxml2hum::addSecondaryChordNotes --
//

void Tool_musicxml2hum::addSecondaryChordNotes(ostream& output,
		MxmlEvent* head, const string& recip) {
	vector<MxmlEvent*> links = head->getLinkedNotes();
	MxmlEvent* note;
	string pitch;
	string prefix;
	string postfix;
	int slurstarts = 0;
	int slurstops  = 0;
	vector<int> slurdirs;

	bool primarynote = false;
	for (int i=0; i<(int)links.size(); i++) {
		note       = links.at(i);
		pitch      = note->getKernPitch();
		prefix     = note->getPrefixNoteInfo();
		postfix    = note->getPostfixNoteInfo(primarynote, recip);
		slurstarts = note->hasSlurStart(slurdirs);
		slurstops  = note->hasSlurStop();

		// or maybe walk backwards in the following loop?
		for (int i=0; i<slurstarts; i++) {
			prefix.insert(0, "(");
			if (slurdirs[i] > 0) {
				prefix.insert(1, ">");
				m_slurabove++;
			} else if (slurdirs[i] < 0) {
				prefix.insert(1, "<");
				m_slurbelow++;
			}
		}
		for (int i=0; i<slurstops; i++) {
			postfix.push_back(')');
		}
		//if (slurstart) {
		//	prefix.insert(0, "(");
		//	if (slurdir) {
		//		if (slurdir > 0) {
		//			prefix.insert(1, ">");
		//			m_slurabove++;
		//		} else if (slurdir < 0) {
		//			prefix.insert(1, "<");
		//			m_slurbelow++;
		//		}
		//	}
		//}
		//if (slurstop) {
		//	postfix.push_back(')');
		//}

		output << " " << prefix << recip << pitch << postfix;
	}
}



/////////////////////////////
//
// Tool_musicxml2hum::appendZeroEvents --
//

void Tool_musicxml2hum::appendZeroEvents(GridMeasure* outdata,
		vector<SimultaneousEvents*>& nowevents, HumNum nowtime,
		vector<MxmlPart>& partdata) {

	bool hasclef           = false;
	bool haskeysig         = false;
	bool haskeydesignation = false;
	bool hastransposition  = false;
	bool hastimesig        = false;
	bool hasottava         = false;
	bool hasstafflines     = false;

	vector<vector<xml_node>> clefs(partdata.size());
	vector<vector<xml_node>> keysigs(partdata.size());
	vector<vector<xml_node>> transpositions(partdata.size());
	vector<vector<xml_node>> timesigs(partdata.size());
	vector<vector<vector<xml_node>>> ottavas(partdata.size());
	vector<vector<xml_node>> hairpins(partdata.size());
	vector<vector<xml_node>> stafflines(partdata.size());

	vector<vector<vector<vector<MxmlEvent*>>>> gracebefore(partdata.size());
	vector<vector<vector<vector<MxmlEvent*>>>> graceafter(partdata.size());
	bool foundnongrace = false;

	int pindex = 0;
	xml_node child;
	xml_node grandchild;

	for (int i=0; i<(int)nowevents.size(); i++) {
		for (int j=0; j<(int)nowevents[i]->zerodur.size(); j++) {
			xml_node element = nowevents[i]->zerodur[j]->getNode();
			pindex = nowevents[i]->zerodur[j]->getPartIndex();

			if (nodeType(element, "attributes")) {
				child = element.first_child();
				while (child) {
					if (nodeType(child, "clef")) {
						clefs[pindex].push_back(child);
						hasclef = true;
						foundnongrace = true;
					}

					if (nodeType(child, "key")) {
						keysigs[pindex].push_back(child);
						haskeysig = true;
						string xpath = "mode";
						string mode = child.select_node(xpath.c_str()).node().child_value();
						if (mode != "") {
							haskeydesignation = true;
						}
						foundnongrace = true;
					}

					if (nodeType(child, "transpose")) {
						transpositions[pindex].push_back(child);
						hastransposition = true;
						foundnongrace = true;
					}

					if (nodeType(child, "staff-details")) {
						grandchild = child.first_child();
						while (grandchild) {
							if (nodeType(grandchild, "staff-lines")) {
								stafflines[pindex].push_back(grandchild);
								hasstafflines = true;
							}
							grandchild = grandchild.next_sibling();
						}
					}

					if (nodeType(child, "time")) {
						timesigs[pindex].push_back(child);
						hastimesig = true;
						foundnongrace = true;
					}
					child = child.next_sibling();
				}
			} else if (nodeType(element, "direction")) {
				// direction -> direction-type -> words
				// direction -> direction-type -> dynamics
				// direction -> direction-type -> octave-shift
				child = element.first_child();
				if (nodeType(child, "direction-type")) {
					grandchild = child.first_child();
					if (nodeType(grandchild, "words")) {
						m_current_text.emplace_back(std::make_pair(pindex, element));
					} else if (nodeType(grandchild, "metronome")) {
						m_current_tempo.emplace_back(std::make_pair(pindex, element));
					} else if (nodeType(grandchild, "dynamics")) {
						m_current_dynamic[pindex].push_back(element);
					} else if (nodeType(grandchild, "octave-shift")) {
						storeOttava(pindex, grandchild, element, ottavas);
						hasottava = true;
					} else if (nodeType(grandchild, "wedge")) {
						m_current_dynamic[pindex].push_back(element);
					} else if (nodeType(grandchild, "bracket")) {
						m_current_brackets[pindex].push_back(element);
					}
				}
			} else if (nodeType(element, "figured-bass")) {
				m_current_figured_bass[pindex].push_back(element);
			} else if (nodeType(element, "note")) {
				if (foundnongrace) {
					addEventToList(graceafter, nowevents[i]->zerodur[j]);
				} else {
					addEventToList(gracebefore, nowevents[i]->zerodur[j]);
				}
			} else if (nodeType(element, "print")) {
				processPrintElement(outdata, element, nowtime);
			}
		}
	}

	addGraceLines(outdata, gracebefore, partdata, nowtime);

	if (hasstafflines) {
		addStriaLine(outdata, stafflines, partdata, nowtime);
	}

	if (hasclef) {
		addClefLine(outdata, clefs, partdata, nowtime);
	}

	if (hastransposition) {
		addTranspositionLine(outdata, transpositions, partdata, nowtime);
	}

	if (haskeysig) {
		addKeySigLine(outdata, keysigs, partdata, nowtime);
	}

	if (haskeydesignation) {
		addKeyDesignationLine(outdata, keysigs, partdata, nowtime);
	}

	if (hastimesig) {
		addTimeSigLine(outdata, timesigs, partdata, nowtime);
	}

	if (hasottava) {
		addOttavaLine(outdata, ottavas, partdata, nowtime);
	}

	addGraceLines(outdata, graceafter, partdata, nowtime);
}



//////////////////////////////
//
// Tool_musicxml2hum::storeOcttava -- store an ottava mark which has this structure:
//
//  octaveShift:
//     <octave-shift type="down" size="8" number="1" default-y="30.00"/>
//
//  For grand staff or multi-staff parts, the staff number needs to be extracted from an uncle element:
//       <direction placement="below">
//        <direction-type>
//          <octave-shift type="up" size="8" number="1" default-y="-83.10"/>
//        </direction-type>
//        <staff>2</staff>
//      </direction>
//
// ottavas array has three dimensions: (1) is the part, (2) is the staff, and (3) is the list of ottavas.
//

void Tool_musicxml2hum::storeOttava(int pindex, xml_node octaveShift, xml_node direction,
	vector<vector<vector<xml_node>>>& ottavas) {
	int staffindex = 0;
	xml_node staffnode = direction.select_node("staff").node();
	if (staffnode && staffnode.text()) {
		int staffnum = staffnode.text().as_int();
		if (staffnum > 0) {
			staffindex = staffnum - 1;
		}
	}
	// ottavas presumed to be allocated by part, but not by staff.
	if ((int)ottavas[pindex].size() <= staffindex) {
		ottavas[pindex].resize(staffindex+1);
	}
	ottavas[pindex][staffindex].push_back(octaveShift);
}



//////////////////////////////
//
// Tool_musicxml2hum::processPrintElement --
//      <print new-page="yes">
//      <print new-system="yes">
//

void Tool_musicxml2hum::processPrintElement(GridMeasure* outdata, xml_node element,
		HumNum timestamp) {
	bool isPageBreak = false;
	bool isSystemBreak = false;
	string pageparam = element.attribute("new-page").value();
	string systemparam = element.attribute("new-system").value();
	if (pageparam == "yes") {
		isPageBreak = true;
	}
	if (systemparam == "yes") {
		isSystemBreak = true;
	}

	if (!(isPageBreak || isSystemBreak)) {
		return;
	}
	GridSlice* gs = outdata->back();

	HTp token = NULL;
	if (gs && gs->size() > 0) {
		if (gs->at(0)->size() > 0) {
			if (gs->at(0)->at(0)->size() > 0) {
				token = gs->at(0)->at(0)->at(0)->getToken();
			}
		}
	}

	if (isPageBreak) {
		if (!token || *token != "!!pagebreak:original")  {
			outdata->addGlobalComment("!!pagebreak:original", timestamp);
		}
	} else if (isSystemBreak) {
		if (!token || *token != "!!linebreak:original")  {
			outdata->addGlobalComment("!!linebreak:original", timestamp);
		}
	}
}



///////////////////////////////
//
// Tool_musicxml2hum::addEventToList --
//

void Tool_musicxml2hum::addEventToList(vector<vector<vector<vector<MxmlEvent*> > > >& list,
		MxmlEvent* event) {
	int pindex = event->getPartIndex();
	int staffindex = event->getStaffIndex();
	int voiceindex = event->getVoiceIndex();
	if (pindex >= (int)list.size()) {
		list.resize(pindex+1);
	}
	if (staffindex >= (int)list[pindex].size()) {
		list[pindex].resize(staffindex+1);
	}
	if (voiceindex >= (int)list[pindex][staffindex].size()) {
		list[pindex][staffindex].resize(voiceindex+1);
	}
	list[pindex][staffindex][voiceindex].push_back(event);
}



///////////////////////////////
//
// Tool_musicxml2hum::addGraceLines -- Add grace note lines.  The number of
//     lines is equal to the maximum number of successive grace notes in
//     any part.  Grace notes are filled in reverse sequence.
//

void Tool_musicxml2hum::addGraceLines(GridMeasure* outdata,
		vector<vector<vector<vector<MxmlEvent*> > > >& notes,
		vector<MxmlPart>& partdata, HumNum nowtime) {

	int maxcount = 0;

	for (int i=0; i<(int)notes.size(); i++) {
		for (int j=0; j<(int)notes.at(i).size(); j++) {
			for (int k=0; k<(int)notes.at(i).at(j).size(); k++) {
				if (maxcount < (int)notes.at(i).at(j).at(k).size()) {
					maxcount = (int)notes.at(i).at(j).at(k).size();
				}
			}
		}
	}

	if (maxcount == 0) {
		return;
	}

	vector<GridSlice*> slices(maxcount);
	for (int i=0; i<(int)slices.size(); i++) {
		slices[i] = new GridSlice(outdata, nowtime, SliceType::GraceNotes);
		outdata->push_back(slices[i]);
		slices[i]->initializePartStaves(partdata);
	}

	for (int i=0; i<(int)notes.size(); i++) {
		for (int j=0; j<(int)notes[i].size(); j++) {
			for (int k=0; k<(int)notes[i][j].size(); k++) {
				int startm = maxcount - (int)notes[i][j][k].size();
				for (int m=0; m<(int)notes[i][j][k].size(); m++) {
					addEvent(slices.at(startm+m), outdata, notes[i][j][k][m], nowtime);
				}
			}
		}
	}
}



//////////////////////////////
//
// Tool_musicxml2hum::addClefLine --
//

void Tool_musicxml2hum::addClefLine(GridMeasure* outdata,
		vector<vector<xml_node> >& clefs, vector<MxmlPart>& partdata,
		HumNum nowtime) {

	GridSlice* slice = new GridSlice(outdata, nowtime,
		SliceType::Clefs);
	outdata->push_back(slice);
	slice->initializePartStaves(partdata);

	for (int i=0; i<(int)partdata.size(); i++) {
		for (int j=0; j<(int)clefs[i].size(); j++) {
			if (clefs[i][j]) {
				insertPartClefs(clefs[i][j], *slice->at(i));
			}
		}
	}
}



//////////////////////////////
//
// Tool_musicxml2hum::addStriaLine --
//

void Tool_musicxml2hum::addStriaLine(GridMeasure* outdata,
		vector<vector<xml_node> >& stafflines, vector<MxmlPart>& partdata,
		HumNum nowtime) {

	GridSlice* slice = new GridSlice(outdata, nowtime,
		SliceType::Stria);
	outdata->push_back(slice);
	slice->initializePartStaves(partdata);

	for (int i=0; i<(int)partdata.size(); i++) {
		for (int j=0; j<(int)stafflines[i].size(); j++) {
			if (stafflines[i][j]) {
				string lines = stafflines[i][j].child_value();
				int linecount = stoi(lines);
				insertPartStria(linecount, *slice->at(i));
			}
		}
	}
}



//////////////////////////////
//
// Tool_musicxml2hum::addTimeSigLine --
//

void Tool_musicxml2hum::addTimeSigLine(GridMeasure* outdata,
		vector<vector<xml_node> >& timesigs, vector<MxmlPart>& partdata,
		HumNum nowtime) {

	GridSlice* slice = new GridSlice(outdata, nowtime, SliceType::TimeSigs);
	outdata->push_back(slice);
	slice->initializePartStaves(partdata);

	bool status = false;

	for (int i=0; i<(int)partdata.size(); i++) {
		for (int j=0; j<(int)timesigs[i].size(); j++) {
			if (timesigs[i][j]) {
				status |= insertPartTimeSigs(timesigs[i][j], *slice->at(i));
			}
		}
	}

	if (!status) {
		return;
	}

	// Add mensurations related to time signatures

	slice = new GridSlice(outdata, nowtime, SliceType::MeterSigs);
	outdata->push_back(slice);
	slice->initializePartStaves(partdata);

	// now add mensuration symbols associated with time signatures
	for (int i=0; i<(int)partdata.size(); i++) {
		for (int j=0; j<(int)timesigs[i].size(); j++) {
			if (timesigs[i][j]) {
				insertPartMensurations(timesigs[i][j], *slice->at(i));
			}
		}
	}
}



//////////////////////////////
//
// Tool_musicxml2hum::addOttavaLine -- Probably there will be a problem if
//    an ottava line ends and another one starts at the same timestamp.
//    Maybe may OttavaStart and OttavaEnd be separate categories?
//

void Tool_musicxml2hum::addOttavaLine(GridMeasure* outdata,
		vector<vector<vector<xml_node>>>& ottavas, vector<MxmlPart>& partdata,
		HumNum nowtime) {

	GridSlice* slice = new GridSlice(outdata, nowtime,
		SliceType::Ottavas);
	outdata->push_back(slice);
	slice->initializePartStaves(partdata);

	for (int p=0; p<(int)ottavas.size(); p++) { // part loop
		for (int s=0; s<(int)ottavas[p].size(); s++) { // staff loop
			for (int j=0; j<(int)ottavas[p][s].size(); j++) { // ottava loop
				if (ottavas[p][s][j]) {
					// int scount = partdata[p].getStaffCount();
					// int ss = scount - s - 1;
					insertPartOttavas(ottavas[p][s][j], *slice->at(p), p, s, partdata[p].getStaffCount());
				}
			}
		}
	}
}



//////////////////////////////
//
// Tool_musicxml2hum::addKeySigLine -- Only adding one key signature
//   for each part for now.
//

void Tool_musicxml2hum::addKeySigLine(GridMeasure* outdata,
		vector<vector<xml_node> >& keysigs,
		vector<MxmlPart>& partdata, HumNum nowtime) {

	GridSlice* slice = new GridSlice(outdata, nowtime,
		SliceType::KeySigs);
	outdata->push_back(slice);
	slice->initializePartStaves(partdata);

	for (int i=0; i<(int)partdata.size(); i++) {
		for (int j=0; j<(int)keysigs[i].size(); j++) {
			if (keysigs[i][j]) {
				insertPartKeySigs(keysigs[i][j], *slice->at(i));
			}
		}
	}
}



//////////////////////////////
//
// Tool_musicxml2hum::addKeyDesignationLine -- Only adding one key designation line
//   for each part for now.
//

void Tool_musicxml2hum::addKeyDesignationLine(GridMeasure* outdata,
		vector<vector<xml_node> >& keydesigs,
		vector<MxmlPart>& partdata, HumNum nowtime) {

	GridSlice* slice = new GridSlice(outdata, nowtime,
		SliceType::KeyDesignations);
	outdata->push_back(slice);
	slice->initializePartStaves(partdata);

	for (int i=0; i<(int)partdata.size(); i++) {
		for (int j=0; j<(int)keydesigs[i].size(); j++) {
			if (keydesigs[i][j]) {
				insertPartKeyDesignations(keydesigs[i][j], *slice->at(i));
			}
		}
	}
}



//////////////////////////////
//
// Tool_musicxml2hum::addTranspositionLine -- Transposition codes to
//   produce written parts.
//

void Tool_musicxml2hum::addTranspositionLine(GridMeasure* outdata,
		vector<vector<xml_node> >& transpositions,
		vector<MxmlPart>& partdata, HumNum nowtime) {

	GridSlice* slice = new GridSlice(outdata, nowtime,
		SliceType::Transpositions);
	outdata->push_back(slice);
	slice->initializePartStaves(partdata);

	for (int i=0; i<(int)partdata.size(); i++) {
		for (int j=0; j<(int)transpositions[i].size(); j++) {
			if (transpositions[i][j]) {
				insertPartTranspositions(transpositions[i][j], *slice->at(i));
			}
		}
	}
}



//////////////////////////////
//
// Tool_musicxml2hum::insertPartClefs --
//

void Tool_musicxml2hum::insertPartClefs(xml_node clef, GridPart& part) {
	if (!clef) {
		// no clef for some reason.
		return;
	}

	HTp token;
	int staffnum = 0;
	while (clef) {
		clef = convertClefToHumdrum(clef, token, staffnum);
		part[staffnum]->setTokenLayer(0, token, 0);
	}

	// go back and fill in all NULL pointers with null interpretations
	fillEmpties(&part, "*");
}



//////////////////////////////
//
// Tool_musicxml2hum::insertPartStria --
//

void Tool_musicxml2hum::insertPartStria(int lines, GridPart& part) {
	HTp token = new HumdrumToken;
	string value = "*stria" + to_string(lines);
	token->setText(value);
	part[0]->setTokenLayer(0, token, 0);

	// go back and fill in all NULL pointers with null interpretations
	fillEmpties(&part, "*");
}



//////////////////////////////
//
// Tool_musicxml2hum::insertPartOttavas --
//

void Tool_musicxml2hum::insertPartOttavas(xml_node ottava, GridPart& part, int partindex,
		int partstaffindex, int staffcount) {
	if (!ottava) {
		// no ottava for some reason.
		return;
	}

	HTp token = NULL;
	while (ottava) {
		ottava = convertOttavaToHumdrum(ottava, token, partstaffindex, partindex, partstaffindex, staffcount);
		part[partstaffindex]->setTokenLayer(0, token, 0);
	}

	// go back and fill in all NULL pointers with null interpretations
	fillEmpties(&part, "*");
}



//////////////////////////////
//
// Tool_musicxml2hum::fillEmpties --
//

void Tool_musicxml2hum::fillEmpties(GridPart* part, const char* string) {
	int staffcount = (int)part->size();
	GridVoice* gv;
	int vcount;

 	for (int s=0; s<staffcount; s++) {
		GridStaff* staff = part->at(s);
		if (staff == NULL) {
			cerr << "Strange error here" << endl;
			continue;
		}
		vcount = (int)staff->size();
		if (vcount == 0) {
			gv = new GridVoice(string, 0);
			staff->push_back(gv);
		} else {
			for (int v=0; v<vcount; v++) {
				gv = staff->at(v);
				if (gv == NULL) {
					gv = new GridVoice(string, 0);
					staff->at(v) = gv;
				}
			}
		}
	}
}



//////////////////////////////
//
// Tool_musicxml2hum::insertPartKeySigs --
//

void Tool_musicxml2hum::insertPartKeySigs(xml_node keysig, GridPart& part) {
	if (!keysig) {
		return;
	}

	HTp token;
	int staffnum = 0;
	while (keysig) {
		keysig = convertKeySigToHumdrum(keysig, token, staffnum);
		if (staffnum < 0) {
			// key signature applies to all staves in part (most common case)
			for (int s=0; s<(int)part.size(); s++) {
				if (s==0) {
					part[s]->setTokenLayer(0, token, 0);
				} else {
					HTp token2 = new HumdrumToken(*token);
					part[s]->setTokenLayer(0, token2, 0);
				}
			}
		} else {
			part[staffnum]->setTokenLayer(0, token, 0);
		}
	}
}



//////////////////////////////
//
// Tool_musicxml2hum::insertPartKeyDesignations --
//

void Tool_musicxml2hum::insertPartKeyDesignations(xml_node keydesig, GridPart& part) {
	if (!keydesig) {
		return;
	}

	HTp token;
	int staffnum = 0;
	while (keydesig) {
		token = NULL;
		keydesig = convertKeySigToHumdrumKeyDesignation(keydesig, token, staffnum);
		if (token == NULL) {
			return;
		}
		if (staffnum < 0) {
			// key signature applies to all staves in part (most common case)
			for (int s=0; s<(int)part.size(); s++) {
				if (s==0) {
					part[s]->setTokenLayer(0, token, 0);
				} else {
					string value = *token;
					HTp token2 = new HumdrumToken(value);
					part[s]->setTokenLayer(0, token2, 0);
				}
			}
		} else {
			part[staffnum]->setTokenLayer(0, token, 0);
		}
	}
}



//////////////////////////////
//
// Tool_musicxml2hum::insertPartTranspositions --
//

void Tool_musicxml2hum::insertPartTranspositions(xml_node transposition, GridPart& part) {
	if (!transposition) {
		return;
	}

	HTp token;
	int staffnum = 0;
	while (transposition) {
		transposition = convertTranspositionToHumdrum(transposition, token, staffnum);
		if (staffnum < 0) {
			// Transposition applies to all staves in part (most common case)
			for (int s=0; s<(int)part.size(); s++) {
				if (s==0) {
					part[s]->setTokenLayer(0, token, 0);
				} else {
					HTp token2 = new HumdrumToken(*token);
					part[s]->setTokenLayer(0, token2, 0);
				}
			}
		} else {
			part[staffnum]->setTokenLayer(0, token, 0);
		}
	}
}



//////////////////////////////
//
// Tool_musicxml2hum::insertPartTimeSigs -- Only allowing one
//		time signature per part for now.
//

bool Tool_musicxml2hum::insertPartTimeSigs(xml_node timesig, GridPart& part) {
	if (!timesig) {
		// no timesig
		return false;
	}

	bool hasmensuration = false;
	HTp token;
	int staffnum = 0;

	while (timesig) {
		hasmensuration |= checkForMensuration(timesig);
		timesig = convertTimeSigToHumdrum(timesig, token, staffnum);
		if (token && (staffnum < 0)) {
			// time signature applies to all staves in part (most common case)
			for (int s=0; s<(int)part.size(); s++) {
				if (s==0) {
					part[s]->setTokenLayer(0, token, 0);
				} else {
					HTp token2 = new HumdrumToken(*token);
					part[s]->setTokenLayer(0, token2, 0);
				}
			}
		} else if (token) {
			part[staffnum]->setTokenLayer(0, token, 0);
		}
	}

	return hasmensuration;
}



//////////////////////////////
//
// Tool_musicxml2hum::insertPartMensurations --
//

void Tool_musicxml2hum::insertPartMensurations(xml_node timesig,
		GridPart& part) {
	if (!timesig) {
		// no timesig
		return;
	}

	HTp token = NULL;
	int staffnum = 0;

	while (timesig) {
		timesig = convertMensurationToHumdrum(timesig, token, staffnum);
		if (staffnum < 0) {
			// time signature applies to all staves in part (most common case)
			for (int s=0; s<(int)part.size(); s++) {
				if (s==0) {
					part[s]->setTokenLayer(0, token, 0);
				} else {
					HTp token2 = new HumdrumToken(*token);
					part[s]->setTokenLayer(0, token2, 0);
				}
			}
		} else {
			part[staffnum]->setTokenLayer(0, token, 0);
		}
	}

}


//////////////////////////////
//
// Tool_musicxml::checkForMensuration --
//    Examples:
//        <time symbol="common">
//        <time symbol="cut">
//

bool Tool_musicxml2hum::checkForMensuration(xml_node timesig) {
	if (!timesig) {
		return false;
	}

	xml_attribute mens = timesig.attribute("symbol");
	if (mens) {
		return true;
	} else {
		return false;
	}
}


//////////////////////////////
//
//	Tool_musicxml2hum::convertTranspositionToHumdrum --
//
//  <transpose>
//     <diatonic>-1</diatonic>
//     <chromatic>-2</chromatic>
//

xml_node Tool_musicxml2hum::convertTranspositionToHumdrum(xml_node transpose,
		HTp& token, int& staffindex) {

	if (!transpose) {
		return transpose;
	}

	staffindex = -1;
	xml_attribute sn = transpose.attribute("number");
	if (sn) {
		staffindex = atoi(sn.value()) - 1;
	}

	int diatonic = 0;
	int chromatic = 0;

	xml_node child = transpose.first_child();
	while (child) {
		if (nodeType(child, "diatonic")) {
			diatonic = atoi(child.child_value());
		} else if (nodeType(child, "chromatic")) {
			chromatic = atoi(child.child_value());
		}
		child = child.next_sibling();
	}


	// Switching to sounding viewpoint: transposition to get written pitch:
	diatonic = -diatonic;
	chromatic = -chromatic;

	stringstream ss;
	ss << "*Trd" << diatonic << "c" << chromatic;

	token = new HumdrumToken(ss.str());

	int base40 = -Convert::transToBase40(ss.str());
	if (base40 != 0) {
		m_hasTransposition = true;
	}

	transpose = transpose.next_sibling();
	if (!transpose) {
		return transpose;
	}
	if (nodeType(transpose, "transpose")) {
		return transpose;
	} else {
		return xml_node(NULL);
	}
}



//////////////////////////////
//
//	Tool_musicxml2hum::convertKeySigToHumdrumKeyDesignation --
//
//  <key>
//     <fifths>4</fifths>
// and sometimes:
//     <mode>major</mode>
// or
//     <mode>minor</mode>
//

xml_node Tool_musicxml2hum::convertKeySigToHumdrumKeyDesignation(xml_node keysig,
		HTp& token, int& staffindex) {

	if (!keysig) {
		token = new HumdrumToken("*");
		return keysig;
	}

	staffindex = -1;
	xml_attribute sn = keysig.attribute("number");
	if (sn) {
		staffindex = atoi(sn.value()) - 1;
	}

	int fifths = 0;
	int mode = -1;

	xml_node child = keysig.first_child();
	while (child) {
		if (nodeType(child, "fifths")) {
			fifths = atoi(child.child_value());
		}
		if (nodeType(child, "mode")) {
			string value = child.child_value();
			if (value == "major") {
				mode = 0;
			} else if (value == "minor") {
				mode = 1;
			}
		}
		child = child.next_sibling();
	}

	if (mode < 0) {
		token = new HumdrumToken("*");
		return xml_node(NULL);
	}

	stringstream ss;
	ss << "*";

	if (mode == 0) { // major:
		switch (fifths) {
			case +7: ss << "C#"; break;
			case +6: ss << "F#"; break;
			case +5: ss << "B"; break;
			case +4: ss << "E"; break;
			case +3: ss << "A"; break;
			case +2: ss << "D"; break;
			case +1: ss << "G"; break;
			case  0: ss << "C"; break;
			case -1: ss << "F"; break;
			case -2: ss << "B-"; break;
			case -3: ss << "E-"; break;
			case -4: ss << "A-"; break;
			case -5: ss << "D-"; break;
			case -6: ss << "G-"; break;
			case -7: ss << "C-"; break;
			default:
				token = new HumdrumToken("*");
				return xml_node(NULL);
		}
	} else if (mode == 1) { // minor:
		switch (fifths) {
			case +7: ss << "a#"; break;
			case +6: ss << "d#"; break;
			case +5: ss << "g#"; break;
			case +4: ss << "c#"; break;
			case +3: ss << "f#"; break;
			case +2: ss << "b"; break;
			case +1: ss << "e"; break;
			case  0: ss << "a"; break;
			case -1: ss << "d"; break;
			case -2: ss << "g"; break;
			case -3: ss << "c"; break;
			case -4: ss << "f"; break;
			case -5: ss << "b-"; break;
			case -6: ss << "e-"; break;
			case -7: ss << "a-"; break;
			default:
				token = new HumdrumToken("*");
				return xml_node(NULL);
		}
	}
	ss << ":";

	token = new HumdrumToken(ss.str());

	keysig = keysig.next_sibling();
	if (!keysig) {
		return keysig;
	}
	if (nodeType(keysig, "key")) {
		return keysig;
	} else {
		return xml_node(NULL);
	}
}



//////////////////////////////
//
//	Tool_musicxml2hum::convertKeySigToHumdrum --
//
//  <key>
//     <fifths>4</fifths>
// and sometimes:
//     <mode>major</mode>
// or
//     <mode>minor</mode>
//

xml_node Tool_musicxml2hum::convertKeySigToHumdrum(xml_node keysig,
		HTp& token, int& staffindex) {

	if (!keysig) {
		return keysig;
	}

	staffindex = -1;
	xml_attribute sn = keysig.attribute("number");
	if (sn) {
		staffindex = atoi(sn.value()) - 1;
	}

	int fifths = 0;
	//int mode = -1;

	xml_node child = keysig.first_child();
	while (child) {
		if (nodeType(child, "fifths")) {
			fifths = atoi(child.child_value());
		}
		if (nodeType(child, "mode")) {
			string value = child.child_value();
			if (value == "major") {
				// mode = 0;
			} else if (value == "minor") {
				// mode = 1;
			}
		}
		child = child.next_sibling();
	}

	stringstream ss;
	ss << "*k[";
	if (fifths > 0) {
		if (fifths > 0) { ss << "f#"; }
		if (fifths > 1) { ss << "c#"; }
		if (fifths > 2) { ss << "g#"; }
		if (fifths > 3) { ss << "d#"; }
		if (fifths > 4) { ss << "a#"; }
		if (fifths > 5) { ss << "e#"; }
		if (fifths > 6) { ss << "b#"; }
	} else if (fifths < 0) {
		if (fifths < 0)  { ss << "b-"; }
		if (fifths < -1) { ss << "e-"; }
		if (fifths < -2) { ss << "a-"; }
		if (fifths < -3) { ss << "d-"; }
		if (fifths < -4) { ss << "g-"; }
		if (fifths < -5) { ss << "c-"; }
		if (fifths < -6) { ss << "f-"; }
	}
	ss << "]";

	token = new HumdrumToken(ss.str());

	keysig = keysig.next_sibling();
	if (!keysig) {
		return keysig;
	}
	if (nodeType(keysig, "key")) {
		return keysig;
	} else {
		return xml_node(NULL);
	}
}



//////////////////////////////
//
//	Tool_musicxml2hum::convertTimeSigToHumdrum --
//
//  <time symbol="common">
//     <beats>4</beats>
//     <beat-type>4</beat-type>
//
// also:
//  <time symbol="common">
//

xml_node Tool_musicxml2hum::convertTimeSigToHumdrum(xml_node timesig,
		HTp& token, int& staffindex) {

	token = NULL;

	if (!timesig) {
		return xml_node(NULL);
	}

	staffindex = -1;
	xml_attribute sn = timesig.attribute("number");
	if (sn) {
		staffindex = atoi(sn.value()) - 1;
	}

	int beats = -1;
	int beattype = -1;

	xml_node child = timesig.first_child();
	while (child) {
		if (nodeType(child, "beats")) {
			beats = atoi(child.child_value());
		} else if (nodeType(child, "beat-type")) {
			beattype = atoi(child.child_value());
		}
		child = child.next_sibling();
	}

	if ((beats == -1) && (beattype == -1)) {
		// No time signature, such as:
		// <time print-object="no">
		//   <senza-misura/>
		// </time>
		return xml_node(NULL);
	}

	stringstream ss;
	ss << "*M" << beats<< "/" << beattype;
	token = new HumdrumToken(ss.str());

	timesig = timesig.next_sibling();
	if (!timesig) {
		return timesig;
	}
	if (nodeType(timesig, "time")) {
		return timesig;
	} else {
		return xml_node(NULL);
	}
}



//////////////////////////////
//
//	Tool_musicxml2hum::convertMensurationToHumdrum --
//
//  <time symbol="common">
//     <beats>4</beats>
//     <beat-type>4</beat-type>
//
// also:
//  <time symbol="common">
//

xml_node Tool_musicxml2hum::convertMensurationToHumdrum(xml_node timesig,
		HTp& token, int& staffindex) {

	if (!timesig) {
		return timesig;
	}

	staffindex = -1;
	xml_attribute mens = timesig.attribute("symbol");
	if (!mens) {
		token = new HumdrumToken("*");
	} else {
		string text = mens.value();
		if (text == "cut") {
			token = new HumdrumToken("*met(c|)");
		} else if (text == "common") {
			token = new HumdrumToken("*met(c)");
		} else {
			token = new HumdrumToken("*");
		}
	}

	timesig = timesig.next_sibling();
	if (!timesig) {
		return timesig;
	}
	if (nodeType(timesig, "time")) {
		return timesig;
	} else {
		return xml_node(NULL);
	}
}



//////////////////////////////
//
//	Tool_musicxml2hum::convertClefToHumdrum --
//

xml_node Tool_musicxml2hum::convertClefToHumdrum(xml_node clef,
		HTp& token, int& staffindex) {

	if (!clef) {
		// no clef for some reason.
		return clef;
	}

	staffindex = 0;
	xml_attribute sn = clef.attribute("number");
	if (sn) {
		staffindex = atoi(sn.value()) - 1;
	}

	string sign;
	int line = -1000;
	int octadjust = 0;

	xml_node child = clef.first_child();
	while (child) {
		if (nodeType(child, "sign")) {
			sign = child.child_value();
		} else if (nodeType(child, "line")) {
			line = atoi(child.child_value());
		} else if (nodeType(child, "clef-octave-change")) {
			octadjust = atoi(child.child_value());
		}
		child = child.next_sibling();
	}

	// Check for percussion clefs, etc., here.
	if (sign == "percussion") {
		sign = "X";
		// ignore line of percussion clef (assume it is centered on staff).
		line = -1000;
	}
	stringstream ss;
	ss << "*clef" << sign;
	if (octadjust < 0) {
		for (int i=0; i < -octadjust; i++) {
			ss << "v";
		}
	} else if (octadjust > 0) {
		for (int i=0; i<octadjust; i++) {
			ss << "^";
		}
	}
	if (line > 0) {
		ss << line;
	}
	token = new HumdrumToken(ss.str());

	clef = clef.next_sibling();
	if (!clef) {
		return clef;
	}
	if (nodeType(clef, "clef")) {
		return clef;
	} else {
		return xml_node(NULL);
	}
}



//////////////////////////////
//
//	Tool_musicxml2hum::convertOttavaToHumdrum --
//    Example:
//      <direction placement="above">
//        <direction-type>
//          <octave-shift type="down" size="8" number="1"/>
//        </direction-type>
//      </direction>
//      ...
//      <direction placement="above">
//        <direction-type>
//          <octave-shift type="stop" size="8" number="1"/>
//        </direction-type>
//      </direction>
//
//

xml_node Tool_musicxml2hum::convertOttavaToHumdrum(xml_node ottava,
		HTp& token, int& staffindex, int partindex, int partstaffindex, int staffcount) {

	// partstaffindex is useless or incorrect? At least for grand staff parts.
	// The staffindex calculated below is the one to used.

	if (!ottava) {
		// no clef for some reason.
		return ottava;
	}


	// Don't use "number" to set the staff index, now use the input parameter
	// which comes from the direction/staff element that is an uncle of the ottava node.
	//staffindex = 0;
	//xml_attribute sn = ottava.attribute("number");
	//if (sn) {
	//	staffindex = atoi(sn.value()) - 1;
	//}
	//staffindex = staffcount - staffindex - 1;

	int interval = 0;

	interval = ottava.attribute("size").as_int();
	string otype = ottava.attribute("type").as_string();
	string lastotype = m_last_ottava_direction.at(partindex).at(staffindex);

	string ss;
	ss = "*";
	if (otype == "stop") {
		ss += "X";
	} else {
	   m_last_ottava_direction.at(partindex).at(staffindex) = otype;
   }
	if (interval == 15) {
		ss += "15";
		if (otype == "down") {
			ss += "ma";
		} else if (otype == "up") {
			ss += "ba";
		} else if (otype == "stop") {
			if (m_last_ottava_direction.at(partindex).at(staffindex) == "up") {
				ss += "ba";
			} else if (m_last_ottava_direction.at(partindex).at(staffindex) == "down") {
				ss += "ma";
			}
		}
	} else if (interval == 8) {
		ss += "8";
		if (otype == "down") {
			ss += "va";
		} else if (otype == "up") {
			ss += "ba";
		} else if (otype == "stop") {
			if (m_last_ottava_direction.at(partindex).at(staffindex) == "up") {
				ss += "ba";
			} else if (m_last_ottava_direction.at(partindex).at(staffindex) == "down") {
				ss += "va";
			}
		}
	} else {
		ss += "*8";
		if (otype == "down") {
			ss += "va";
		} else if (otype == "up") {
			ss += "ba";
		} else if (otype == "stop") {
			if (m_last_ottava_direction.at(partindex).at(staffindex) == "up") {
				ss += "ba";
			} else if (m_last_ottava_direction.at(partindex).at(staffindex) == "down") {
				ss += "va";
			}
		}
	}
	token = new HumdrumToken(ss);

	ottava = ottava.next_sibling();
	if (!ottava) {
		return ottava;
	}
	if (nodeType(ottava, "octave-shift")) {
		return ottava;
	} else {
		return xml_node(NULL);
	}
}



//////////////////////////////
//
// Tool_musicxml2hum::nodeType -- return true if node type matches
//     string.
//

bool Tool_musicxml2hum::nodeType(xml_node node, const char* testname) {
	if (strcmp(node.name(), testname) == 0) {
		return true;
	} else {
		return false;
	}
}



//////////////////////////////
//
// Tool_musicxml2hum::appendNullTokens --
//

void Tool_musicxml2hum::appendNullTokens(HLp line,
		MxmlPart& part) {
	int i;
	int staffcount = part.getStaffCount();
	int versecount = part.getVerseCount();
	for (i=staffcount-1; i>=0; i--) {
		line->appendToken(".");
	}
	for (i=0; i<versecount; i++) {
		line->appendToken(".");
	}
}



//////////////////////////////
//
// Tool_musicxml2hum::getPartContent -- Extract the part elements in
//     the file indexed by part ID.
//

bool Tool_musicxml2hum::getPartContent(
		map<string, xml_node>& partcontent,
		vector<string>& partids, xml_document& doc) {

	auto parts = doc.select_nodes("/score-partwise/part");
	int count = (int)parts.size();
	if (count != (int)partids.size()) {
		cerr << "Warning: part element count does not match part IDs count: "
		     << parts.size() << " compared to " << partids.size() << endl;
	}

	string partid;
	for (int i=0; i<(int)parts.size(); i++) {
		partid = getAttributeValue(parts[i], "id");
		if (partid.size() == 0) {
			cerr << "Warning: Part " << i << " has no ID" << endl;
		}
		auto status = partcontent.insert(make_pair(partid, parts[i].node()));
		if (status.second == false) {
			cerr << "Error: ID " << partids.back()
			     << " is duplicated and secondary part will be ignored" << endl;
		}
		if (find(partids.begin(), partids.end(), partid) == partids.end()) {
			cerr << "Error: Part ID " << partid
			     << " is not present in part-list element list" << endl;
			continue;
		}
	}

	if (partcontent.size() != partids.size()) {
		cerr << "Error: part-list count does not match part count "
		     << partcontent.size() << " compared to " << partids.size() << endl;
		return false;
	} else {
		return true;
	}
}



//////////////////////////////
//
// Tool_musicxml2hum::getPartInfo -- Extract a list of the part ids,
//    and a reverse mapping to the <score-part> element to which is refers.
//
//	   part-list structure:
//        <part-list>
//          <score-part id="P1"/>
//          <score-part id="P2"/>
//          etc.
//        </part-list>
//

bool Tool_musicxml2hum::getPartInfo(map<string, xml_node>& partinfo,
		vector<string>& partids, xml_document& doc) {
	auto scoreparts = doc.select_nodes("/score-partwise/part-list/score-part");
	partids.reserve(scoreparts.size());
	bool output = true;
	for (auto el : scoreparts) {
		partids.emplace_back(getAttributeValue(el.node(), "id"));
		auto status = partinfo.insert(make_pair(partids.back(), el.node()));
		if (status.second == false) {
			cerr << "Error: ID " << partids.back()
			     << " is duplicated and secondary part will be ignored" << endl;
		}
		output &= status.second;
		partinfo[partids.back()] = el.node();
	}
	return output;
}



//////////////////////////////
//
// Tool_musicxml2hum::getChildElementText -- Return the (first)
//    matching child element's text content.
//

string Tool_musicxml2hum::getChildElementText(xml_node root,
		const char* xpath) {
	return root.select_node(xpath).node().child_value();
}

string Tool_musicxml2hum::getChildElementText(xpath_node root,
		const char* xpath) {
	return root.node().select_node(xpath).node().child_value();
}



//////////////////////////////
//
// Tool_musicxml2hum::getAttributeValue -- For an xml_node, return
//     the value for the given attribute name.
//

string Tool_musicxml2hum::getAttributeValue(xml_node xnode,
		const string& target) {
	for (auto at = xnode.first_attribute(); at; at = at.next_attribute()) {
		if (target == at.name()) {
			return at.value();
		}
	}
	return "";
}


string Tool_musicxml2hum::getAttributeValue(xpath_node xnode,
		const string& target) {
	auto node = xnode.node();
	for (auto at = node.first_attribute(); at; at = at.next_attribute()) {
		if (target == at.name()) {
			return at.value();
		}
	}
	return "";
}



//////////////////////////////
//
// Tool_musicxml2hum::printAttributes -- Print list of all attributes
//     for an xml_node.
//

void Tool_musicxml2hum::printAttributes(xml_node node) {
	int counter = 1;
	for (auto at = node.first_attribute(); at; at = at.next_attribute()) {
		cout << "\tattribute " << counter++
		     << "\tname  = " << at.name()
		     << "\tvalue = " << at.value()
		     << endl;
	}
}



//////////////////////////////
//
// Tool_musicxml2hum::getSystemDecoration --
//
// Example:  [1,2]{(3,4)}
//
//  <part-list>
//    <part-group type="start" number="1">
//      <group-symbol>bracket</group-symbol>
//    </part-group>
//
//    <score-part id="P1">
//      <part-name>S A</part-name>
//      <score-instrument id="P1-I1">
//        <instrument-name>Soprano/Alto</instrument-name>
//      </score-instrument>
//      <midi-device id="P1-I1" port="1"></midi-device>
//      <midi-instrument id="P1-I1">
//        <midi-channel>1</midi-channel>
//        <midi-program>53</midi-program>
//        <volume>78.7402</volume>
//        <pan>0</pan>
//      </midi-instrument>
//    </score-part>
//
//    <score-part id="P2">
//      <part-name>T B</part-name>
//      <score-instrument id="P2-I1">
//        <instrument-name>Tenor/Bass</instrument-name>
//      </score-instrument>
//      <midi-device id="P2-I1" port="1"></midi-device>
//      <midi-instrument id="P2-I1">
//        <midi-channel>2</midi-channel>
//        <midi-program>53</midi-program>
//        <volume>78.7402</volume>
//        <pan>0</pan>
//      </midi-instrument>
//    </score-part>
//
//    <part-group type="stop" number="1"/>
//
//    <score-part id="P3">
//      <part-name>Organ</part-name>
//      <part-abbreviation>Org.</part-abbreviation>
//      <score-instrument id="P3-I1">
//        <instrument-name>Pipe Organ</instrument-name>
//      </score-instrument>
//      <midi-device id="P3-I1" port="1"></midi-device>
//      <midi-instrument id="P3-I1">
//        <midi-channel>3</midi-channel>
//        <midi-program>76</midi-program>
//        <volume>78.7402</volume>
//        <pan>0</pan>
//      </midi-instrument>
//    </score-part>
//
//  </part-list>
//

string Tool_musicxml2hum::getSystemDecoration(xml_document& doc, HumGrid& grid,
	vector<string>& partids) {

	xml_node partlist = doc.select_node("/score-partwise/part-list").node();
	if (!partlist) {
		cerr << "Error: cannot find partlist\n";
		return "";
	}
	vector<xml_node> children;
	getChildrenVector(children, partlist);

	vector<vector<int>> staffnumbers;
	int pcount = grid.getPartCount();
	staffnumbers.resize(pcount);

	int scounter = 1;
	for (int i=0; i<pcount; i++) {
		int staffcount = grid.getStaffCount(i);
		for (int j=0; j<staffcount; j++) {
			staffnumbers[i].push_back(scounter++);
		}
	}

	string output;

	// part-group @type=start @number=1
   //   <group-symbol>bracket</group-symbol>
	// score-part
	// score-part
	// part-group @type=stop @number=1
	// score-part
	int pcounter = 0;
	scounter = 1;
	vector<string> typeendings(100);
	for (int i=0; i<(int)children.size(); i++) {
		string name = children[i].name();
		if (name == "part-group") {
			string grouptype = children[i].attribute("type").value();
			string gsymbol = "";
			int number = children[i].attribute("number").as_int();
			if (grouptype == "start") {
				string g = children[i].select_node("//group-symbol").node().child_value();
				if (g == "bracket") {
					output += "[(";
					typeendings[number] = ")]";
				} else if (g == "brace") {
					output += "{(";
					typeendings[number] = ")}";
				} else {
					cerr << "Unknown part grouping symbol: " << g << endl;
				}
			} else if (grouptype == "stop") {
				output += typeendings[number];
				typeendings[number].clear();
			}
		} else if (name == "score-part") {
			pcounter++;
			int staffcount = grid.getStaffCount(pcounter-1);
			if (staffcount == 1) {
				output += "s" + to_string(scounter++);
			} else if (staffcount > 1) {
				output += "{(";
				for (int k=0; k<staffcount; k++) {
					output += "s" + to_string(scounter++);
				}
				output += ")}";
			}
		}
	}

	string newoutput;
	for (int i=0; i<(int)output.size(); i++) {
		if ((i>0) && (output[i] == 's') && isdigit(output[i-1])) {
			newoutput += ',';
		}
		newoutput += output[i];
	}

	return newoutput;
}



//////////////////////////////
//
// Tool_musicxml2hum::getChildrenVector -- Return a list of all children
//   elements of a given element.  Pugixml does not allow random access,
//   but storing them in a vector allows that possibility.
//

void Tool_musicxml2hum::getChildrenVector(vector<xml_node>& children,
		xml_node parent) {
	children.clear();
	for (xml_node child : parent.children()) {
		children.push_back(child);
	}
}




/////////////////////////////////
//
// Tool_myank::Tool_myank -- Set the recognized options for the tool.
//

Tool_myank::Tool_myank(void) {
	define("v|verbose=b",                        "verbose output of data");
	define("debug=b",                            "debugging information");
	define("inlist=b",                           "show input measure list");
	define("outlist=b",                          "show output measure list");
	define("mark|marks=b",                       "yank measure with marked notes");
	define("T|M|bar-number-text=b",              "print barnum with LO text above system ");
	define("d|double|dm|md|mdsep|mdseparator=b", "put double barline between non-consecutive measure segments");
	define("m|b|measures|bars|measure|bar=s",    "measures to yank");
	define("l|lines|line-range=s",               "line numbers range to yank (e.g. 40-50)");
	define("I|i|instrument=b",                   "Include instrument codes from start of data");
	define("visible|not-invisible=b",            "do not make initial measure invisible");
	define("B|noendbar=b",                       "do not print barline at end of data");
	define("max=b",                              "print maximum measure number");
	define("min=b",                              "print minimum measure number");
	define("section-count=b",                    "count the number of sections, JRP style");
	define("section=i:0",                        "extract given section number (indexed from 1");
	define("author=b",                           "program author");
	define("version=b",                          "program version");
	define("example=b",                          "program examples");
	define("h|help=b",                           "short description");
	define("hide-starting=b",                    "prevent printStarting");
	define("hide-ending=b",                      "prevent printEnding");
}



/////////////////////////////////
//
// Tool_myank::run -- Primary interfaces to the tool.
//

bool Tool_myank::run(HumdrumFileSet& infiles) {
	bool status = true;
	for (int i=0; i<infiles.getCount(); i++) {
		status &= run(infiles[i]);
	}
	return status;
}


bool Tool_myank::run(const string& indata, ostream& out) {
	HumdrumFile infile(indata);
	bool status = run(infile);
	if (hasAnyText()) {
		getAllText(out);
	} else {
		out << infile;
	}
	return status;
}


bool Tool_myank::run(HumdrumFile& infile, ostream& out) {
	bool status = run(infile);
	if (hasAnyText()) {
		getAllText(out);
	} else {
		out << infile;
	}
	return status;
}

//
// In-place processing of file:
//

bool Tool_myank::run(HumdrumFile& infile) {
	// Max track in enscripten is wrong for some reason,
	// so making a copy and forcing reanalysis:
	//perhaps not needed anymore:
	//stringstream ss;
	//ss << infile;
	//infile.read(ss);
	initialize(infile);
	processFile(infile);
	// Re-load the text for each line from their tokens.
	infile.createLinesFromTokens();
	return true;
}


///////////////////////////////////////////////////////////////////////////

ostream& operator<<(ostream& out, MyCoord& value) {
	out << "(" << value.x << "," << value.y << ")";
	return out;
}


ostream& operator<<(ostream& out, MeasureInfo& info) {
	if (info.file == NULL) {
		return out;
	}
	HumdrumFile& infile = *(info.file);
	out << "================================== " << endl;
	out << "NUMBER      = " << info.num   << endl;
	out << "SEGMENT     = " << info.seg   << endl;
	out << "START       = " << info.start << endl;
	out << "STOP        = " << info.stop  << endl;
	out << "STOP_STYLE  = " << info.stopStyle << endl;
	out << "START_STYLE = " << info.startStyle << endl;

	for (int i=1; i<(int)info.sclef.size(); i++) {
		out << "TRACK " << i << ":" << endl;
		if (info.sclef[i].isValid()) {
			out << "   START CLEF    = " << infile.token(info.sclef[i].x, info.sclef[i].y)       << endl;
		}
		if (info.skeysig[i].isValid()) {
			out << "   START KEYSIG  = " << infile.token(info.skeysig[i].x, info.skeysig[i].y)   << endl;
		}
		if (info.skey[i].isValid()) {
			out << "   START KEY     = " << infile.token(info.skey[i].x, info.skey[i].y)         << endl;
		}
		if (info.stimesig[i].isValid()) {
			out << "   START TIMESIG = " << infile.token(info.stimesig[i].x, info.stimesig[i].y) << endl;
		}
		if (info.smet[i].isValid()) {
			out << "   START MET     = " << infile.token(info.smet[i].x, info.smet[i].y)         << endl;
		}
		if (info.stempo[i].isValid()) {
			out << "   START TEMPO   = " << infile.token(info.stempo[i].x, info.stempo[i].y)     << endl;
		}

		if (info.eclef[i].isValid()) {
			out << "   END CLEF    = " << infile.token(info.eclef[i].x, info.eclef[i].y)       << endl;
		}
		if (info.ekeysig[i].isValid()) {
			out << "   END KEYSIG  = " << infile.token(info.ekeysig[i].x, info.ekeysig[i].y)   << endl;
		}
		if (info.ekey[i].isValid()) {
			out << "   END KEY     = " << infile.token(info.ekey[i].x, info.ekey[i].y)         << endl;
		}
		if (info.etimesig[i].isValid()) {
			out << "   END TIMESIG = " << infile.token(info.etimesig[i].x, info.etimesig[i].y) << endl;
		}
		if (info.emet[i].isValid()) {
			out << "   END MET     = " << infile.token(info.emet[i].x, info.emet[i].y)         << endl;
		}
		if (info.etempo[i].isValid()) {
			out << "   END TEMPO   = " << infile.token(info.etempo[i].x, info.etempo[i].y)     << endl;
		}
	}

	return out;
}

///////////////////////////////////////////////////////////////////////////


//////////////////////////////
//
// Tool_myank::initialize -- extract time signature lines for
//    each **kern spine in file.
//

void Tool_myank::initialize(HumdrumFile& infile) {
	// handle basic options:
	if (getBoolean("author")) {
		m_free_text << "Written by Craig Stuart Sapp, "
			  << "craig@ccrma.stanford.edu, December 2010" << endl;
		return;
	} else if (getBoolean("version")) {
		m_free_text << getCommand() << ", version: 26 December 2010" << endl;
		m_free_text << "compiled: " << __DATE__ << endl;
		return;
	} else if (getBoolean("help")) {
		usage(getCommand());
		return;
	} else if (getBoolean("example")) {
		example();
		return;
	}

	m_debugQ        = getBoolean("debug");
	m_inlistQ       = getBoolean("inlist");
	m_outlistQ      = getBoolean("outlist");
	m_verboseQ      = getBoolean("verbose");
	m_maxQ          = getBoolean("max");
	m_minQ          = getBoolean("min");

	m_invisibleQ    = !getBoolean("not-invisible");
	m_instrumentQ   =  getBoolean("instrument");
	m_nolastbarQ    =  getBoolean("noendbar");
	m_markQ         =  getBoolean("mark");
	m_doubleQ       =  getBoolean("mdsep");
	m_barnumtextQ   =  getBoolean("bar-number-text");
	m_sectionCountQ =  getBoolean("section-count");
	m_section       =  getInteger("section");

	m_lineRange     = getString("lines");
	m_hideStarting  = getBoolean("hide-starting");
	m_hideEnding    = getBoolean("hide-ending");


	if (!m_section) {
		if (!(getBoolean("measures") || m_markQ) && !getBoolean("lines")) {
			// if -m option is not given, then --mark option presumed
			m_markQ = 1;
			// cerr << "Error: the -m option is required" << endl;
			// exit(1);
		}
	}

}



////////////////////////
//
// Tool_myank::processFile --
//

void Tool_myank::processFile(HumdrumFile& infile) {
	if (m_sectionCountQ) {
		int sections = getSectionCount(infile);
		m_humdrum_text << sections << endl;
		return;
	}

	getMetStates(m_metstates, infile);
	getMeasureStartStop(m_measureInList, infile);

	string measurestring = getString("measures");

	if (getBoolean("lines")) {
		int startLineNumber = getStartLineNumber();
		int endLineNumber = getEndLineNumber();
		if ((startLineNumber > endLineNumber) || (endLineNumber > infile.getLineCount())) {
			// Disallow when end line number is bigger then line count or when
			// start line number greather than end line number
			return;
		}
		m_barNumbersPerLine = analyzeBarNumbers(infile);
		int startBarNumber = getBarNumberForLineNumber(startLineNumber);
		int endBarNumber = getBarNumberForLineNumber(endLineNumber);
		measurestring = to_string(startBarNumber) + "-" + to_string(endBarNumber);
	}

	measurestring = expandMultipliers(measurestring);
	if (m_markQ) {
		stringstream mstring;
		getMarkString(mstring, infile);
		measurestring = mstring.str();
		if (m_debugQ) {
			m_free_text << "MARK STRING: " << mstring.str() << endl;
		}
	} else if (m_section) {
		string sstring;
		getSectionString(sstring, infile, m_section);
		measurestring = sstring;
	}
	if (m_debugQ) {
		m_free_text << "MARK MEASURES: " << measurestring << endl;
	}

	// expand to multiple measures later.
	expandMeasureOutList(m_measureOutList, m_measureInList, infile,
			measurestring);

	if (m_inlistQ) {
		m_free_text << "INPUT MEASURE MAP: " << endl;
		for (int i=0; i<(int)m_measureInList.size(); i++) {
			m_free_text << m_measureInList[i];
		}
	}
	if (m_outlistQ) {
		m_free_text << "OUTPUT MEASURE MAP: " << endl;
		for (int i=0; i<(int)m_measureOutList.size(); i++) {
			m_free_text << m_measureOutList[i];
		}
	}

	if (m_measureOutList.size() == 0) {
		// disallow processing files with no barlines
		return;
	}

	// move stopStyle to startStyle of next measure group.
	for (int i=(int)m_measureOutList.size()-1; i>0; i--) {
		m_measureOutList[i].startStyle = m_measureOutList[i-1].stopStyle;
		m_measureOutList[i-1].stopStyle = "";
	}

	myank(infile, m_measureOutList);
}



////////////////////////
//
// Tool_myank::analyzeBarNumbers -- Stores the bar number of each line in a vector
//

vector<int> Tool_myank::analyzeBarNumbers(HumdrumFile& infile) {
	vector<int> m_barnum;
	m_barnum.resize(infile.getLineCount());
	int current = 0;
	HumRegex hre;
	for (int i=0; i<infile.getLineCount(); i++) {
		if (!infile[i].isBarline()) {
			m_barnum.at(i) = current;
			continue;
		}
		if (hre.search(infile[i].token(0), "=(\\d+)")) {
			current = hre.getMatchInt(1);
		}
		m_barnum.at(i) = current;
	}
	return m_barnum;
}



////////////////////////
//
// Tool_myank::getBarNumberForLineNumber --
//

int Tool_myank::getBarNumberForLineNumber(int lineNumber) {
	return m_barNumbersPerLine[lineNumber-1];
}



////////////////////////
//
// Tool_myank::getStartLineNumber -- Get start line number from --lines
//

int Tool_myank::getStartLineNumber(void) {
	HumRegex hre;
	if (hre.search(m_lineRange, "^(\\d+)\\-(\\d+)$")) {
		return hre.getMatchInt(1);
	}
	return -1;
}



////////////////////////
//
// Tool_myank::getEndLineNumber -- Get end line number from --lines
//

int Tool_myank::getEndLineNumber(void) {
	HumRegex hre;
	if (hre.search(m_lineRange, "^(\\d+)\\-(\\d+)$")) {
		return hre.getMatchInt(2);
	}
	return -1;
}



//////////////////////////////
//
// Tool_myank::expandMultipliers -- 2*5 => 2,2,2,2,2
//    Limit of 100 times expansion
//

string Tool_myank::expandMultipliers(const string& inputstring) {
	HumRegex hre;
	if (!hre.search(inputstring, "\\*")) {
		return inputstring;
	}
	string outputstring = inputstring;
	while (hre.search(outputstring, "(\\d+)\\*([1-9]+[0-9]*)")) {
		string measurenum = hre.getMatch(1);
		int multiplier = hre.getMatchInt(2);
		if (multiplier > 100) {
			cerr << "Reducing multiplier from " << multiplier << " to 100" << endl;
			multiplier = 100;
		}
		string expansion = measurenum;
		for (int i=1; i<multiplier; i++) {
			expansion += ",";
			expansion += measurenum;
		}
		hre.replaceDestructive(outputstring, expansion, "(\\d+)\\*([1-9]+[0-9]*)");
	}
	return outputstring;
}


//////////////////////////////
//
// Tool_myank::getMetStates --  Store the current *met for every token
// in the score, keeping track of meter without metric symbols.
//

void Tool_myank::getMetStates(vector<vector<MyCoord> >& metstates,
		HumdrumFile& infile) {
	vector<MyCoord> current;
	current.resize(infile.getMaxTrack()+1);
	metstates.resize(infile.getLineCount());
	HumRegex hre;

	int track;
	for (int i=0; i<infile.getLineCount(); i++) {
		if (infile[i].isInterpretation()) {
			for (int j=0; j<infile[i].getFieldCount(); j++) {
				track = infile.token(i, j)->getTrack();
				if (hre.search(infile.token(i, j), R"(^\*met\([^\)]+\))")) {
					current[track].x = i;
					current[track].y = j;
				} else if (hre.search(infile.token(i, j), R"(^\*M\d+\d+)")) {
					current[track] = getLocalMetInfo(infile, i, track);
				}
			}
		}

		// metstates[i].resize(infile[i].getFieldCount());
		// for (j=0; j<infile[i].getFieldCount(); j++) {
		//    track = infile.token(i, j)->getTrack();
		//    metstates[i][j] = current[track];
		// }
		metstates[i].resize(infile.getMaxTrack()+1);
		for (int j=1; j<=infile.getMaxTrack(); j++) {
			metstates[i][j] = current[j];
		}
	}

	if (m_debugQ) {
		for (int i=0; i<infile.getLineCount(); i++) {
			for (int j=1; j<(int)metstates[i].size(); j++) {
				if (metstates[i][j].x < 0) {
					m_humdrum_text << ".";
				} else {
					m_humdrum_text << infile.token(metstates[i][j].x, metstates[i][j].y);
				}
				m_humdrum_text << "\t";
			}
			m_humdrum_text << infile[i] << endl;
		}

	}
}



//////////////////////////////
//
// Tool_myank::getLocalMetInfo -- search in the non-data region indicated by the
// input row for a *met entry in the input track.  Return empty
// value if none found.
//

MyCoord Tool_myank::getLocalMetInfo(HumdrumFile& infile, int row, int track) {
	MyCoord output;
	int startline = -1;
	int stopline = -1;
	int i = row;
	int j;
	int xtrac;
	HumRegex hre;

	while (i>=0) {
		if (infile[i].isData()) {
			startline = i+1;
			break;
		}
		i--;
	}
	if (startline < 0) {
		startline = 0;
	}
	i = row;
	while (i<infile.getLineCount()){
		if (infile[i].isData()) {
			stopline = i-1;
			break;
		}
		i++;
	}
	if (stopline >= infile.getLineCount()) {
		stopline = infile.getLineCount()-1;
	}
	for (i=startline; i<=stopline; i++) {
		if (!infile[i].isInterpretation()) {
			continue;
		}
		for (j=0; j<infile[i].getFieldCount(); j++) {
			xtrac = infile.token(i, j)->getTrack();
			if (track != xtrac) {
				continue;
			}
			if (hre.search(infile.token(i, j), R"(^\*met\([^\)]+\))")) {
				output.x = i;
				output.x = j;
			}
		}
	}
	return output;
}



//////////////////////////////
//
// Tool_myank::getMarkString -- return a list of measures which contain marked
//    notes (primarily from search matches).
// This function scans for reference records in this form:
// !!!RDF**kern: @= matched note
// or
// !!!RDF**kern: i= marked note
// If it finds any lines like that, it will extract the character before
// the equals sign, and scan for it in the **kern data in the file.
// any measure which contains such a mark will be stored in the output
// string.
//

void Tool_myank::getMarkString(ostream& out, HumdrumFile& infile)  {
	string mchar; // list of characters which are marks
	char target;
	HumRegex hre;
	for (int i=0; i<infile.getLineCount(); i++) {
		if (!infile[i].isReference()) {
			continue;
		}
		if (hre.search(infile.token(i, 0),
				R"(!!!RDF\*\*kern\s*:\s*([^=])\s*=\s*match)", "i")) {
			target = hre.getMatch(1)[0];
			mchar.push_back(target);
		} else if (hre.search(infile.token(i, 0),
				R"(!!!RDF\*\*kern\s*:\s*([^=])\s*=\s*mark)", "i")) {
			target = hre.getMatch(1)[0];
			mchar.push_back(target);
		}
	}

	if (m_debugQ) {
		for (int i=0; i<(int)mchar.size(); i++) {
			m_free_text << "\tMARK CHARCTER: " << mchar[i] << endl;
		}
	}

	if (mchar.size() == 0) {
		return;
	}

	// now search for measures which contains any of those character
	// in **kern data:
	int curmeasure = 0;
	int inserted = 0;
	int hasmark = 0;
	string str;
	for (int i=0; i<infile.getLineCount(); i++) {
		if (infile[i].isBarline()) {
			if (hre.search(infile.token(i, 0), "^=.*?(\\d+)", "")) {
				curmeasure = stoi(hre.getMatch(1));
				hasmark = 0;
			}
		}
		if (hasmark) {
			continue;
		}
		if (!infile[i].isData()) {
			continue;
		}
		for (int j=0; j<infile[i].getFieldCount(); j++) {
			if (infile.token(i, j)->isKern()) {
				int k=0;
				str = *infile.token(i, j);
				while (str[k] != '\0') {
					for (int m=0; m<(int)mchar.size(); m++) {
						if (str[k] == mchar[m]) {
							if (inserted) {
								out << ',';
							} else {
								inserted++;
							}
							out << curmeasure;
							hasmark = 1;
							goto outerforloop;
						}
					}
					k++;
				}
			}
		}
outerforloop: ;
	}
}



//////////////////////////////
//
// Tool_myank::myank -- yank the specified measures.
//

void Tool_myank::myank(HumdrumFile& infile, vector<MeasureInfo>& outmeasures) {

	if (outmeasures.size() > 0) {
		printStarting(infile);
	}

	int lastline = -1;
	int lastDataLine = -1;
	int h, i, j;
	int counter;
	int printed = 0;
	int mcount = 0;
	int measurestart = 1;
	int lastbarnum = -1;
	int barnum = -1;
	int datastart = 0;
	int bartextcount = 0;
	bool startLineHandled = false;

	int lastLineIndex = getBoolean("lines") ? getEndLineNumber() - 1 : outmeasures[outmeasures.size() - 1].stop;

	// Find the actual last line of the selected section that is a line with
	// data tokens
	while (infile.getLine(lastLineIndex)->isData() == false) {
		lastLineIndex--;
	}

	// Mapping with with the start token for each spine
	vector<int> lastLineResolvedTokenLineIndex;
	// Mapping with the later needed durations of the note that fits within the
	// selected section
	vector<HumNum> lastLineDurationsFromNoteStart;

	lastLineResolvedTokenLineIndex.resize(infile.getLine(lastLineIndex)->getTokenCount());
	lastLineDurationsFromNoteStart.resize(infile.getLine(lastLineIndex)->getTokenCount());

	for (int a = 0; a < infile.getLine(lastLineIndex)->getTokenCount(); a++) {
		HTp token = infile.token(lastLineIndex, a);
		// Get lineIndex for last data token with an attack
		lastLineResolvedTokenLineIndex[a] = infile.token(lastLineIndex, a)->resolveNull()->getLineIndex();
		// Get needed duration for this token until section end
		lastLineDurationsFromNoteStart[a] = token->getDurationFromNoteStart() + token->getLine()->getDuration();
	}

	int startLineNumber = getStartLineNumber();
	int endLineNumber = getEndLineNumber();

	if (getBoolean("lines")) {
		int firstDataLineIndex = -1;
		for (int b = startLineNumber - 1; b <= endLineNumber - 1; b++) {
			if (infile.getLine(b)->isData()) {
				firstDataLineIndex = b;
				break;
			}
		}
		if (firstDataLineIndex >= 0) {
			if (infile.getLine(firstDataLineIndex)->getDurationFromBarline() == 0) {
				for (int c = startLineNumber - 1; c >=  0; c--) {
					if (infile.getLine(c)->isBarline()) {
						startLineNumber = c + 1;
						break;
					}
				}
			}
		}
	}

	for (h=0; h<(int)outmeasures.size(); h++) {
		barnum = outmeasures[h].num;
		measurestart = 1;
		printed = 0;
		counter = 0;
		if (m_debugQ) {
			m_humdrum_text << "!! =====================================\n";
			m_humdrum_text << "!! processing " << outmeasures[h].num << endl;
		}
		if (h > 0) {
			reconcileSpineBoundary(infile, outmeasures[h-1].stop,
				outmeasures[h].start);
		} else {
			reconcileStartingPosition(infile, outmeasures[0].start);
		}
		int startLine = getBoolean("lines") ? std::max(startLineNumber-1, outmeasures[h].start)
			: outmeasures[h].start;
		int endLine = getBoolean("lines") ? std::min(endLineNumber, outmeasures[h].stop)
			: outmeasures[h].stop;
		for (i=startLine; i<endLine; i++) {
			counter++;
			if ((!printed) && ((mcount == 0) || (counter == 2))) {
				if ((datastart == 0) && outmeasures[h].num == 0) {
					// not ideal setup...
					datastart = 1;
				} else{
					// Fix adjustGlobalInterpretations when line is a global comment
					int nextLineIndexWithSpines = i;
					if (infile.getLine(i)->isCommentGlobal()) {
						for (int d = i; d <= endLineNumber - 1; d++) {
							if (!infile.getLine(d)->isCommentGlobal()) {
								nextLineIndexWithSpines = d;
								break;
							}
						}
					}
					adjustGlobalInterpretations(infile, nextLineIndexWithSpines, outmeasures, h);
					printed = 1;
				}
			}
			if (infile[i].isData() && (mcount == 0)) {
				mcount++;
			}
			if (infile[i].isBarline()) {
				mcount++;
			}
			if ((mcount == 1) && m_invisibleQ && infile[i].isBarline()) {
				printInvisibleMeasure(infile, i);
				measurestart = 0;
				if ((bartextcount++ == 0) && infile[i].isBarline()) {
					int barline = 0;
					sscanf(infile.token(i, 0)->c_str(), "=%d", &barline);
					if (m_barnumtextQ && (barline > 0)) {
						m_humdrum_text << "!!LO:TX:Z=20:X=-90:t=" << barline << endl;
					}
				}
			} else if (m_doubleQ && (lastbarnum > -1) && (abs(barnum - lastbarnum) > 1)) {
				printDoubleBarline(infile, i);
				measurestart = 0;
			} else if (measurestart && infile[i].isBarline()) {
				printMeasureStart(infile, i, outmeasures[h].startStyle);
				measurestart = 0;
			} else {
				printDataLine(infile.getLine(i), startLineHandled, lastLineResolvedTokenLineIndex, lastLineDurationsFromNoteStart);
				if (m_barnumtextQ && (bartextcount++ == 0) && infile[i].isBarline()) {
					int barline = 0;
					sscanf(infile.token(i, 0)->c_str(), "=%d", &barline);
					if (barline > 0) {
						m_humdrum_text << "!!LO:TX:Z=20:X=-25:t=" << barline << endl;
					}
				}
			}
			lastline = i;
			if (infile.getLine(i)->isData()) {
				lastDataLine = i;
			}
		}
		lastbarnum = barnum;
	}

	if (getBoolean("lines") && (lastDataLine >= 0) &&
			(infile.getLine(lastDataLine)->getDurationToBarline() > infile.getLine(lastDataLine)->getDuration())) {
		m_nolastbarQ = true;
	}

	HumRegex hre;
	string token;
	int lasti;
	if (outmeasures.size() > 0) {
		lasti = outmeasures.back().stop;
	} else {
		lasti = -1;
	}
	if ((!m_nolastbarQ) &&  (lasti >= 0) && infile[lasti].isBarline()) {
		for (j=0; j<infile[lasti].getFieldCount(); j++) {
			token = *infile.token(lasti, j);
			hre.replaceDestructive(token, outmeasures.back().stopStyle, "\\d+.*");
			// collapse final barlines
			hre.replaceDestructive(token, "==", "===+");
			if (m_doubleQ) {
				if (hre.search(token, "=(.+)")) {
					// don't add double barline, there is already
					// some style on the barline
				} else {
					// add a double barline
					hre.replaceDestructive(token, "||", "$");
				}
			}
			m_humdrum_text << token;
			if (j < infile[lasti].getFieldCount() - 1) {
				m_humdrum_text << '\t';
			}
		}
		m_humdrum_text << '\n';
	}

	collapseSpines(infile, lasti);

	if (m_debugQ) {
		m_free_text << "PROCESSING ENDING" << endl;
	}

	if (lastline >= 0) {
		printEnding(infile, outmeasures.back().stop, lasti);
	}
}



//////////////////////////////
//
// Tool_myank::collapseSpines -- Shrink all sub-spines to single spine.
//

void Tool_myank::collapseSpines(HumdrumFile& infile, int line) {
	if (line < 0) {
		return;
	}
	vector<int> counts(infile.getMaxTrack() + 1, 0);
	for (int i=0; i<infile[line].getFieldCount(); i++) {
		int track = infile.token(line, i)->getTrack();
		counts.at(track)++;
	}
	for (int i=1; i<(int)counts.size(); i++) {
		if (counts[i] <= 1) {
			continue;
		}
		bool started = false;
		for (int j=1; j<(int)counts.size(); j++) {
			if (j < i) {
				if (started) {
					m_humdrum_text << "\t";
				}
				m_humdrum_text << "*";
				started = true;
				continue;
			} else if (j == i) {
				for (int k=0; k<counts[j]; k++) {
					if (started) {
						m_humdrum_text << "\t";
					}
					m_humdrum_text << "*v";
					started = true;
				}
			} else if (j > i) {
				for (int k=0; k<counts[j]; k++) {
					if (started) {
						m_humdrum_text << "\t";
					}
					m_humdrum_text << "*";
					started = true;
				}
			}
		}
		m_humdrum_text << "\n";
		counts[i] = 1;
	}
}



//////////////////////////////
//
// Tool_myank::adjustGlobalInterpretations --
//

void Tool_myank::adjustGlobalInterpretations(HumdrumFile& infile, int ii,
		vector<MeasureInfo>& outmeasures, int index) {

	if (index <= 0) {
		adjustGlobalInterpretationsStart(infile, ii, outmeasures, index);
		return;
	}

	// the following lines will not work when non-contiguous measures are
	// elided.
	//   if (!infile[ii].isInterpretation()) {
	//      return;
	//   }

	int clefQ    = 0;
	int keysigQ  = 0;
	int keyQ     = 0;
	int timesigQ = 0;
	int metQ     = 0;
	int tempoQ   = 0;

	int x, y;
	int xo, yo;

	int tracks = infile.getMaxTrack();

	// these lines may cause bugs, but they get rid of zeroth measure
	// problem.
// ggg
//   if ((outmeasures.size() > 1) && (outmeasures[index-1].num == 0)) {
//      return;
//   }
//   if ((outmeasures.size() > 0) && (outmeasures[index].num == 0)) {
//      return;
//   }

	for (int i=1; i<=tracks; i++) {
		if (!clefQ && (outmeasures[index].sclef.size() > 0)) {
			x  = outmeasures[index].sclef[i].x;
			y  = outmeasures[index].sclef[i].y;
			xo = outmeasures[index-1].eclef[i].x;
			yo = outmeasures[index-1].eclef[i].y;
			if ((x>=0)&&(y>=0)&&(xo>=0)&&(yo>=0)) {
				if (*infile.token(x, y) != *infile.token(xo, yo)) {
					clefQ = 1;
				}
			}
		}

		if (!keysigQ && (outmeasures[index].skeysig.size() > 0)) {
			x  = outmeasures[index].skeysig[i].x;
			y  = outmeasures[index].skeysig[i].y;
			xo = outmeasures[index-1].ekeysig[i].x;
			yo = outmeasures[index-1].ekeysig[i].y;
			if ((x>=0)&&(y>=0)&&(xo>=0)&&(yo>=0)) {
				if (*infile.token(x, y) != *infile.token(xo, yo)) {
					keysigQ = 1;
				}
			}
		}

		if (!keyQ && (outmeasures[index].skey.size() > 0)) {
			x  = outmeasures[index].skey[i].x;
			y  = outmeasures[index].skey[i].y;
			xo = outmeasures[index-1].ekey[i].x;
			yo = outmeasures[index-1].ekey[i].y;
			if ((x>=0)&&(y>=0)&&(xo>=0)&&(yo>=0)) {
				if (*infile.token(x, y) != *infile.token(xo, yo)) {
					keyQ = 1;
				}
			}
		}

		if (!timesigQ && (outmeasures[index].stimesig.size() > 0)) {
			x  = outmeasures[index].stimesig[i].x;
			y  = outmeasures[index].stimesig[i].y;
			xo = outmeasures[index-1].etimesig[i].x;
			yo = outmeasures[index-1].etimesig[i].y;
			if ((x>=0)&&(y>=0)&&(xo>=0)&&(yo>=0)) {
				if (*infile.token(x, y) != *infile.token(xo, yo)) {
					timesigQ = 1;
				}
			}
		}

		if (!metQ && (outmeasures[index].smet.size() > 0)) {
			x  = outmeasures[index].smet[i].x;
			y  = outmeasures[index].smet[i].y;
			xo = outmeasures[index-1].emet[i].x;
			yo = outmeasures[index-1].emet[i].y;
			if ((x>=0)&&(y>=0)&&(xo>=0)&&(yo>=0)) {
				if (*infile.token(x, y) != *infile.token(xo, yo)) {
					metQ = 1;
				}
			}
		}

		if (!tempoQ && (outmeasures[index].stempo.size() > 0)) {
			x  = outmeasures[index].stempo[i].x;
			y  = outmeasures[index].stempo[i].y;
			xo = outmeasures[index-1].etempo[i].x;
			yo = outmeasures[index-1].etempo[i].y;
			if ((x>=0)&&(y>=0)&&(xo>=0)&&(yo>=0)) {
				if (*infile.token(x, y) != *infile.token(xo, yo)) {
					tempoQ = 1;
				}
			}
		}
	}

	int track;

	if (clefQ) {
		for (int i=0; i<infile[ii].getFieldCount(); i++) {
			track = infile.token(ii, i)->getTrack();
			x  = outmeasures[index].sclef[track].x;
			y  = outmeasures[index].sclef[track].y;
			xo = outmeasures[index-1].eclef[track].x;
			yo = outmeasures[index-1].eclef[track].y;
			if ((x>=0)&&(y>=0)&&(xo>=0)&&(yo>=0)) {
				if (*infile.token(x, y) != *infile.token(xo, yo)) {
					m_humdrum_text << infile.token(x, y);
				} else {
					m_humdrum_text << "*";
				}
			} else {
				m_humdrum_text << "*";
			}
			if (i < infile[ii].getFieldCount()-1) {
				m_humdrum_text << "\t";
			}
		}
		m_humdrum_text << "\n";
	}

	if (keysigQ) {
		for (int i=0; i<infile[ii].getFieldCount(); i++) {
			track = infile.token(ii, i)->getTrack();
			x  = outmeasures[index].skeysig[track].x;
			y  = outmeasures[index].skeysig[track].y;
			xo = outmeasures[index-1].ekeysig[track].x;
			yo = outmeasures[index-1].ekeysig[track].y;
			if ((x>=0)&&(y>=0)&&(xo>=0)&&(yo>=0)) {
				if (*infile.token(x, y) != *infile.token(xo, yo)) {
					m_humdrum_text << infile.token(x, y);
				} else {
					m_humdrum_text << "*";
				}
			} else {
				m_humdrum_text << "*";
			}
			if (i < infile[ii].getFieldCount()-1) {
				m_humdrum_text << "\t";
			}
		}
		m_humdrum_text << "\n";
	}

	if (keyQ) {
		for (int i=0; i<infile[ii].getFieldCount(); i++) {
			track = infile.token(ii, i)->getTrack();
			x  = outmeasures[index].skey[track].x;
			y  = outmeasures[index].skey[track].y;
			xo = outmeasures[index-1].ekey[track].x;
			yo = outmeasures[index-1].ekey[track].y;
			if ((x>=0)&&(y>=0)&&(xo>=0)&&(yo>=0)) {
				if (*infile.token(x, y) != *infile.token(xo, yo)) {
					m_humdrum_text << infile.token(x, y);
				} else {
					m_humdrum_text << "*";
				}
			} else {
				m_humdrum_text << "*";
			}
			if (i < infile[ii].getFieldCount()-1) {
				m_humdrum_text << "\t";
			}
		}
		m_humdrum_text << "\n";
	}

	if (timesigQ) {
		for (int i=0; i<infile[ii].getFieldCount(); i++) {
			track = infile.token(ii, i)->getTrack();
			x  = outmeasures[index].stimesig[track].x;
			y  = outmeasures[index].stimesig[track].y;
			xo = outmeasures[index-1].etimesig[track].x;
			yo = outmeasures[index-1].etimesig[track].y;
			if ((x>=0)&&(y>=0)&&(xo>=0)&&(yo>=0)) {
				if (*infile.token(x, y) != *infile.token(xo, yo)) {
					m_humdrum_text << infile.token(x, y);
				} else {
					m_humdrum_text << "*";
				}
			} else {
				m_humdrum_text << "*";
			}
			if (i < infile[ii].getFieldCount()-1) {
				m_humdrum_text << "\t";
			}
		}
		m_humdrum_text << "\n";
	}

	if (metQ) {
		for (int i=0; i<infile[ii].getFieldCount(); i++) {
			track = infile.token(ii, i)->getTrack();
			x  = outmeasures[index].smet[track].x;
			y  = outmeasures[index].smet[track].y;
			xo = outmeasures[index-1].emet[track].x;
			yo = outmeasures[index-1].emet[track].y;
			if ((x>=0)&&(y>=0)&&(xo>=0)&&(yo>=0)) {
				if (*infile.token(x, y) != *infile.token(xo, yo)) {
					m_humdrum_text << infile.token(x, y);
				} else {
					m_humdrum_text << "*";
				}
			} else {
				m_humdrum_text << "*";
			}
			if (i < infile[ii].getFieldCount()-1) {
				m_humdrum_text << "\t";
			}
		}
		m_humdrum_text << "\n";
	}

	if (tempoQ) {
		for (int i=0; i<infile[ii].getFieldCount(); i++) {
			track = infile.token(ii, i)->getTrack();
			x  = outmeasures[index].stempo[track].x;
			y  = outmeasures[index].stempo[track].y;
			xo = outmeasures[index-1].etempo[track].x;
			yo = outmeasures[index-1].etempo[track].y;
			if ((x>=0)&&(y>=0)&&(xo>=0)&&(yo>=0)) {
				if (*infile.token(x, y) != *infile.token(xo, yo)) {
					m_humdrum_text << infile.token(x, y);
				} else {
					m_humdrum_text << "*";
				}
			} else {
				m_humdrum_text << "*";
			}
			if (i < infile[ii].getFieldCount()-1) {
				m_humdrum_text << "\t";
			}
		}
		m_humdrum_text << "\n";
	}

}



//////////////////////////////
//
// Tool_myank::adjustGlobalInterpretationsStart --
//

void Tool_myank::adjustGlobalInterpretationsStart(HumdrumFile& infile, int ii,
		vector<MeasureInfo>& outmeasures, int index) {
	if (index != 0) {
		cerr << "Error in adjustGlobalInterpetationsStart" << endl;
		exit(1);
	}

	int i;

	int clefQ    = 0;
	int keysigQ  = 0;
	int keyQ     = 0;
	int timesigQ = 0;
	int metQ     = 0;
	int tempoQ   = 0;

	int x, y;

	// ignore the zeroth measure
	// (may not be proper).
// ggg
	if (outmeasures[index].num == 0) {
		return;
	}

	int tracks = infile.getMaxTrack();

	for (i=1; i<=tracks; i++) {

		if (!clefQ) {
			x  = outmeasures[index].sclef[i].x;
			y  = outmeasures[index].sclef[i].y;

			if ((x>=0)&&(y>=0)) {
				clefQ = 1;
			}
		}

		if (!keysigQ) {
			x  = outmeasures[index].skeysig[i].x;
			y  = outmeasures[index].skeysig[i].y;
			if ((x>=0)&&(y>=0)) {
				keysigQ = 1;
			}
		}

		if (!keyQ) {
			x  = outmeasures[index].skey[i].x;
			y  = outmeasures[index].skey[i].y;
			if ((x>=0)&&(y>=0)) {
				keyQ = 1;
			}
		}

		if (!timesigQ) {
			x  = outmeasures[index].stimesig[i].x;
			y  = outmeasures[index].stimesig[i].y;
			if ((x>=0)&&(y>=0)) {
				timesigQ = 1;
			}
		}

		if (!metQ) {
			x  = outmeasures[index].smet[i].x;
			y  = outmeasures[index].smet[i].y;
			if ((x>=0)&&(y>=0)) {
				metQ = 1;
			}
		}

		if (!tempoQ) {
			x  = outmeasures[index].stempo[i].x;
			y  = outmeasures[index].stempo[i].y;
			if ((x>=0)&&(y>=0)) {
				tempoQ = 1;
			}
		}
	}

	int ptrack;

	if (clefQ) {
		for (i=0; i<infile[ii].getFieldCount(); i++) {
			ptrack = infile.token(ii, i)->getTrack();
			x  = outmeasures[index].sclef[ptrack].x;
			y  = outmeasures[index].sclef[ptrack].y;
			if ((x>=0)&&(y>=0)) {
				m_humdrum_text << infile.token(x, y);
			} else {
				m_humdrum_text << "*";
			}
			if (i < infile[ii].getFieldCount()-1) {
				m_humdrum_text << "\t";
			}
		}
		m_humdrum_text << "\n";
	}

	if (keysigQ) {
		for (i=0; i<infile[ii].getFieldCount(); i++) {
			ptrack = infile.token(ii, i)->getTrack();
			x  = outmeasures[index].skeysig[ptrack].x;
			y  = outmeasures[index].skeysig[ptrack].y;
			if ((x>=0)&&(y>=0)) {
				m_humdrum_text << infile.token(x, y);
			} else {
				m_humdrum_text << "*";
			}
			if (i < infile[ii].getFieldCount()-1) {
				m_humdrum_text << "\t";
			}
		}
		m_humdrum_text << "\n";
	}

	if (keyQ) {
		for (i=0; i<infile[ii].getFieldCount(); i++) {
			ptrack = infile.token(ii, i)->getTrack();
			x  = outmeasures[index].skey[ptrack].x;
			y  = outmeasures[index].skey[ptrack].y;
			if ((x>=0)&&(y>=0)) {
				m_humdrum_text << infile.token(x, y);
			} else {
				m_humdrum_text << "*";
			}
			if (i < infile[ii].getFieldCount()-1) {
				m_humdrum_text << "\t";
			}
		}
		m_humdrum_text << "\n";
	}

	if (timesigQ) {
		for (i=0; i<infile[ii].getFieldCount(); i++) {
			ptrack = infile.token(ii, i)->getTrack();
			x  = outmeasures[index].stimesig[ptrack].x;
			y  = outmeasures[index].stimesig[ptrack].y;
			if ((x>=0)&&(y>=0)) {
				m_humdrum_text << infile.token(x, y);
			} else {
				m_humdrum_text << "*";
			}
			if (i < infile[ii].getFieldCount()-1) {
				m_humdrum_text << "\t";
			}
		}
		m_humdrum_text << "\n";
	}
	if (metQ) {
		for (i=0; i<infile[ii].getFieldCount(); i++) {
			ptrack = infile.token(ii, i)->getTrack();
			x  = outmeasures[index].smet[ptrack].x;
			y  = outmeasures[index].smet[ptrack].y;
			if ((x>=0)&&(y>=0)) {
				m_humdrum_text << infile.token(x, y);
			} else {
				m_humdrum_text << "*";
			}
			if (i < infile[ii].getFieldCount()-1) {
				m_humdrum_text << "\t";
			}
		}
		m_humdrum_text << "\n";
	}

	if (tempoQ) {
		for (i=0; i<infile[ii].getFieldCount(); i++) {
			ptrack = infile.token(ii, i)->getTrack();
			x  = outmeasures[index].stempo[ptrack].x;
			y  = outmeasures[index].stempo[ptrack].y;
			if ((x>=0)&&(y>=0)) {
				m_humdrum_text << infile.token(x, y);
			} else {
				m_humdrum_text << "*";
			}
			if (i < infile[ii].getFieldCount()-1) {
				m_humdrum_text << "\t";
			}
		}
		m_humdrum_text << "\n";
	}
}



//////////////////////////////
//
// Tool_myank::printDataLine -- Print line with data tokens of selected section
//

void Tool_myank::printDataLine(HLp line,
		bool& startLineHandled,
		const vector<int>& lastLineResolvedTokenLineIndex,
		const vector<HumNum>& lastLineDurationsFromNoteStart) {
	bool lineChange = false;
	string recipRegex = R"re(([\d%.]+))re";
	// Handle cutting the previeous token of a note that hangs into the selected
	// section
	if (startLineHandled == false) {
		if (line->isData()) {
			vector<HTp> tokens;
			line->getTokens(tokens);
			for (HTp token : tokens) {
				if (token->isKern() && token->isNull()) {
					HTp resolvedToken = token->resolveNull();
					if (resolvedToken->isNull()) {
						continue;
					}
					HumRegex hre;
					string recip = Convert::durationToRecip(token->getDurationToNoteEnd());
					vector<string> subtokens = resolvedToken->getSubtokens();
					string tokenText;
					for (int i=0; i<(int)subtokens.size(); i++) {
						if (hre.search(subtokens[i], recipRegex)) {
							string before = hre.getPrefix();
							string after = hre.getSuffix();
							hre.replaceDestructive(after, "", recipRegex, "g");
							string subtokenText;
							// Replace the old duration with the clipped one
							subtokenText += before + recip + after;
							// Add a tie end if not already in a tie group
							if (!hre.search(subtokens[i], "[_\\]]")) {
									subtokenText += "]";
							}
							tokenText += subtokenText;
							if (i < (int)subtokens.size() - 1) {
								tokenText += " ";
							}
						}
					}
					token->setText(tokenText);
					lineChange = true;
				}
			}
			startLineHandled = true;
		}
	// Handle cutting the last attacked note of the selected section
	} else {
		// Check if line has a note that needs to be handled
		if (find(lastLineResolvedTokenLineIndex.begin(), lastLineResolvedTokenLineIndex.end(), line->getLineIndex()) !=
				lastLineResolvedTokenLineIndex.end()) {
			for (int i = 0; i < line->getTokenCount(); i++) {
				HTp token = line->token(i);
				// Check if token need the be handled and is of type **kern
				if (token->isKern() && (lastLineResolvedTokenLineIndex[i] == line->getLineIndex())) {
					HTp resolvedToken = token->resolveNull();
					if (resolvedToken->isNull()) {
						continue;
					}
					HumNum dur = lastLineDurationsFromNoteStart[i];
					HumRegex hre;
					string recip = Convert::durationToRecip(dur);
					vector<string> subtokens = resolvedToken->getSubtokens();
					for (int i=0; i<(int)subtokens.size(); i++) {
						if (hre.search(subtokens[i], recipRegex)) {
							string before = hre.getPrefix();
							string after = hre.getSuffix();
							hre.replaceDestructive(after, "", recipRegex, "g");
							string subtokenText;
							if (resolvedToken->getDuration() > dur) {
								// Add a tie start if not already in a tie group
								if (!hre.search(subtokens[i], "[_\\[]")) {
										subtokenText += "[";
								}
							}
							// Replace the old duration with the clipped one
							subtokenText += before + recip + after;
							token->replaceSubtoken(i, subtokenText);
							lineChange = true;
						}
					}
				}
			}
		}
	}
	if (lineChange) {
		line->createLineFromTokens();
	}
	m_humdrum_text << line << "\n";
}



//////////////////////////////
//
// Tool_myank::printMeasureStart -- print a starting measure of a segment.
//

void Tool_myank::printMeasureStart(HumdrumFile& infile, int line, const string& style) {
	if (!infile[line].isBarline()) {
		m_humdrum_text << infile[line] << "\n";
		return;
	}

	HumRegex hre;
	int j;
	for (j=0; j<infile[line].getFieldCount(); j++) {
		if (hre.search(infile.token(line, j), "=(\\d*)(.*)", "")) {
			if (style == "==") {
				m_humdrum_text << "==";
				m_humdrum_text << hre.getMatch(1);
			} else {
				m_humdrum_text << "=";
				m_humdrum_text << hre.getMatch(1);
				m_humdrum_text << style;
			}
		} else {
			if (style == "==") {
				m_humdrum_text << "==";
			} else {
				m_humdrum_text << "=" << style;
			}
		}
		if (j < infile[line].getFieldCount()-1) {
			m_humdrum_text << "\t";
		}
	}
	m_humdrum_text << "\n";

	if (m_barnumtextQ) {
		int barline = 0;
		sscanf(infile.token(line, 0)->c_str(), "=%d", &barline);
		if (barline > 0) {
			m_humdrum_text << "!!LO:TX:Z=20:X=-25:t=" << barline << endl;
		}
	}
}


//////////////////////////////
//
// Tool_myank::printDoubleBarline --
//

void Tool_myank::printDoubleBarline(HumdrumFile& infile, int line) {

	if (!infile[line].isBarline()) {
		m_humdrum_text << infile[line] << "\n";
		return;
	}

	HumRegex hre;
	int j;
	for (j=0; j<infile[line].getFieldCount(); j++) {
		if (hre.search(infile.token(line, j), "(=\\d*)(.*)", "")) {
			m_humdrum_text << hre.getMatch(1);
			m_humdrum_text << "||";
		} else {
			m_humdrum_text << "=||";
		}
		if (j < infile[line].getFieldCount()-1) {
			m_humdrum_text << "\t";
		}
	}
	m_humdrum_text << "\n";

	if (m_barnumtextQ) {
		int barline = 0;
		sscanf(infile.token(line, 0)->c_str(), "=%d", &barline);
		if (barline > 0) {
			m_humdrum_text << "!!LO:TX:Z=20:X=-25:t=" << barline << endl;
		}
	}

}



//////////////////////////////
//
// Tool_myank::printInvisibleMeasure --
//

void Tool_myank::printInvisibleMeasure(HumdrumFile& infile, int line) {
	if (!infile[line].isBarline()) {
		m_humdrum_text << infile[line] << "\n";
		return;
	}

	HumRegex hre;
	int j;
	for (j=0; j<infile[line].getFieldCount(); j++) {
		if (infile.token(line, j)->find('-') != string::npos) {
			m_humdrum_text << infile.token(line, j);
			if (j < infile[line].getFieldCount()-1) {
				m_humdrum_text << "\t";
			}
			continue;
		}
		if (hre.search(infile.token(line, j), "(=\\d*)(.*)", "")) {
			m_humdrum_text << hre.getMatch(1);
			// m_humdrum_text << "-";
			m_humdrum_text << hre.getMatch(2);
		} else {
			m_humdrum_text << infile.token(line, j);
		}
		if (j < infile[line].getFieldCount()-1) {
			m_humdrum_text << "\t";
		}
	}
	m_humdrum_text << "\n";
}



//////////////////////////////
//
// Tool_myank::reconcileSpineBoundary -- merge spines correctly between segments.
//    will not be able to handle all permutations of spine manipulators.
//    So don't expect exotic manipulators to work...
//

void Tool_myank::reconcileSpineBoundary(HumdrumFile& infile, int index1, int index2) {

	if (m_debugQ) {
		m_humdrum_text << "RECONCILING LINES " << index1+1 << " and " << index2+1 << endl;
		m_humdrum_text << "FIELD COUNT OF " << index1+1 << " is "
			            << infile[index1].getFieldCount() << endl;
		m_humdrum_text << "FIELD COUNT OF " << index2+1 << " is "
			            << infile[index2].getFieldCount() << endl;
	}

	// check to see if any changes need reconciling; otherwise, exit function
	int i, j;
	if (infile[index1].getFieldCount() == infile[index2].getFieldCount()) {
		int same = 1;
		for (i=0; i<infile[index1].getFieldCount(); i++) {
			if (infile.token(index1,i)->getSpineInfo() != infile.token(index2, i)->getSpineInfo()) {
				same = 0;
			}
		}
		if (same != 0) {
			return;
		}
	}

	// handle splits all at once
	string buff1;
	string buff2;

	vector<int> splits(infile[index1].getFieldCount());
	fill(splits.begin(), splits.end(), 0);

	int hassplit = 0;
	for (i=0; i<infile[index1].getFieldCount(); i++) {
		buff1 = "(";
		buff1 += infile.token(index1, i)->getSpineInfo();
		buff1 += ")";
		buff2 = buff1;
		buff1 += "a";
		buff2 += "b";
		for (j=0; j<infile[index2].getFieldCount()-1; j++) {
			if ((buff1 == infile.token(index2, j)->getSpineInfo()
					&& (buff2 == infile.token(index2,j+1)->getSpineInfo()))) {
				splits[i] = 1;
				hassplit++;
			}
		}
	}

	if (hassplit) {
		for (i=0; i<(int)splits.size(); i++) {
			if (splits[i]) {
				m_humdrum_text << "*^";
			} else {
				m_humdrum_text << '*';
			}
			if (i < (int)splits.size()-1) {
				m_humdrum_text << '\t';
			}
		}
		m_humdrum_text << '\n';
	}

	// make splits cumulative;
	//for (i=1; i<(int)splits.size(); i++) {
	//   splits[i] += splits[i-1];
	//}

	HumRegex hre1;
	HumRegex hre2;
	// handle joins one at a time, only look for binary joins at the moment.
	// assuming that no *x has been used to mix the voices up.
	for (i=0; i<infile[index1].getFieldCount()-1; i++) {
		if (!hre1.search(infile.token(index1, i)->getSpineInfo(), "\\((.*)\\)a")) {
			continue;
		}
		if (!hre2.search(infile.token(index1, i+1)->getSpineInfo(), "\\((.*)\\)b")) {
			continue;
		}
		if (hre1.getMatch(1) != hre2.getMatch(1)) {
			// spines are not split from same source
			continue;
		}

		// found an "a" and "b" portion of a spine split, now search
		// through the target line for a joint of those two sub-spines
		for (j=0; j<infile[index2].getFieldCount(); j++) {
			if (infile.token(index2, j)->getSpineInfo() != hre1.getMatch(1)) {
				continue;
			}
			// found a simple binary spine join: emmit a spine manipulator line
			printJoinLine(splits, i, 2);
		}
	}

	// handle *x switches, not perfect since ordering might need to be
	// handled between manipulators...

}



//////////////////////////////
//
// Tool_myank::printJoinLine -- count is currently ignored, but may in the future
//    allow for more than two spines to join at the same time.
//

void Tool_myank::printJoinLine(vector<int>& splits, int index, int count) {
	int i;
	for (i=0; i<(int)splits.size(); i++) {
		if (i == index) {
			m_humdrum_text << "*v\t*v";
			i+=count-1;
		} else {
			m_humdrum_text << "*";
		}
		if (i<(int)splits.size()-1) {
			m_humdrum_text << "\t";
		}
	}
	m_humdrum_text << "\n";

	// merge splits by one element
	for (i=index+1; i<(int)splits.size()-1; i++) {
		splits[i] = splits[i+1];
	}
	splits.resize(splits.size()-1);
}



//////////////////////////////
//
// Tool_myank::reconcileStartingPosition -- merge spines from start of data and
//    first measure in output.
//

void Tool_myank::reconcileStartingPosition(HumdrumFile& infile, int index2) {
	int i;
	for (i=0; i<infile.getLineCount(); i++) {
		if (infile[i].isInterpretation()) {
			reconcileSpineBoundary(infile, i, index2);
			break;
		}
	}
}



//////////////////////////////
//
// Tool_myank::printStarting -- print header information before start of data.
//

void Tool_myank::printStarting(HumdrumFile& infile) {
	int i, j;
	int exi = -1;
	for (i=0; i<infile.getLineCount(); i++) {
		if (infile[i].isInterpretation()) {
			// the first interpretation is the exclusive one
			m_humdrum_text << infile[i] << "\n";
			exi = i;
			break;
		}
		if (!m_hideStarting) {
			m_humdrum_text << infile[i] << "\n";
		} else {
			if (infile[i].rfind("!!!RDF", 0) == 0 || infile[i].rfind("!!!system-decoration", 0) == 0) {
				m_humdrum_text << infile[i] << "\n";
			}
		}
	}

	// keep *part interpretations
	bool hasPart = false;
	for (i=exi+1; i<infile.getLineCount(); i++) {
		hasPart = false;
		for (j=0; j<infile[i].getFieldCount(); j++) {
			if (infile.token(i, j)->compare(0, 5, "*part") == 0) {
				hasPart = true;
				break;
			}
		}
		if (hasPart) {
			for (j=0; j<infile[i].getFieldCount(); j++) {
				if (infile.token(i, j)->compare(0, 5, "*part") == 0) {
					m_humdrum_text << infile.token(i, j);
				} else {
					m_humdrum_text << "*";
				}
				if (j < infile[i].getFieldCount() - 1) {
					m_humdrum_text << "\t";
				}
			}
			m_humdrum_text << "\n";
		}
	}

	// keep *staff interpretations
	bool hasStaff = false;
	for (i=exi+1; i<infile.getLineCount(); i++) {
		hasStaff = false;
		for (j=0; j<infile[i].getFieldCount(); j++) {
			if (infile.token(i, j)->compare(0, 6, "*staff") == 0) {
				hasStaff = true;
				break;
			}
		}
		if (hasStaff) {
			for (j=0; j<infile[i].getFieldCount(); j++) {
				if (infile.token(i, j)->compare(0, 6, "*staff") == 0) {
					m_humdrum_text << infile.token(i, j);
				} else {
					m_humdrum_text << "*";
				}
				if (j < infile[i].getFieldCount() - 1) {
					m_humdrum_text << "\t";
				}
			}
			m_humdrum_text << "\n";
		}
	}

	int hasI = 0;

	if (m_instrumentQ) {
		// print any tandem interpretations which start with *I found
		// at the start of the data before measures, notes, or any
		// spine manipulator lines
		for (i=exi+1; i<infile.getLineCount(); i++) {
			if (infile[i].isData()) {
				break;
			}
			if (infile[i].isBarline()) {
				break;
			}
			if (!infile[i].isInterpretation()) {
				continue;
			}
			if (infile[i].isManipulator()) {
				break;
			}
			hasI = 0;
			for (j=0; j<infile[i].getFieldCount(); j++) {
				if (infile.token(i, j)->compare(0, 2, "*I") == 0) {
					hasI = 1;
					break;
				}
			}
			if (hasI) {
				for (j=0; j<infile[i].getFieldCount(); j++) {
					if (infile.token(i, j)->compare(0, 2, "*I") == 0) {
						m_humdrum_text << infile.token(i, j);
					} else {
						m_humdrum_text << "*";
					}
					if (j < infile[i].getFieldCount() - 1) {
						m_humdrum_text << "\t";
					}
				}
				m_humdrum_text << "\n";
			}
		}
	}

}



//////////////////////////////
//
// Tool_myank::printEnding -- print the spine terminators and any
//     content after the end of the data.
//

void Tool_myank::printEnding(HumdrumFile& infile, int lastline, int adjlin) {
	if (m_debugQ) {
		m_humdrum_text << "IN printEnding" << endl;
	}
	int ending = -1;
	int marker = -1;
	int i;
	for (i=infile.getLineCount()-1; i>=0; i--) {
		if (infile[i].isInterpretation() && (ending <0)
				&& (*infile.token(i, 0) == "*-")) {
			ending = i;
		}
		if (infile[i].isData()) {
			marker = i+1;
			break;
		}
		if (infile[i].isBarline()) {
			marker = i+1;
			break;
		}
	}

	if (ending >= 0) {
		reconcileSpineBoundary(infile, adjlin, ending);
	}

	int startline  = ending;
	if (marker >= 0) {
		// capture any comment which occur after the last measure
		// line in the data.
		startline = marker;
	}

	// reconcileSpineBoundary(infile, lastline, startline);

	if (startline >= 0) {
		for (i=startline; i<infile.getLineCount(); i++) {
			if (m_hideEnding && (i > ending)) {
				if (infile[i].rfind("!!!RDF", 0) == 0 || infile[i].rfind("!!!system-decoration", 0) == 0) {
					m_humdrum_text << infile[i] << "\n";
				}
			} else {
				m_humdrum_text << infile[i] << "\n";
			}
		}
	}

}



//////////////////////////////
//
// Tool_myank::getMeasureStartStop --  Get a list of the (numbered) measures in the
//    input file, and store the start/stop lines for those measures.
//    All data before the first numbered measure is in measure 0.
//    although, if the first measure is not labeled, then ...
//

void Tool_myank::getMeasureStartStop(vector<MeasureInfo>& measurelist, HumdrumFile& infile) {
	measurelist.reserve(infile.getLineCount());
	measurelist.resize(0);

	MeasureInfo current;
	int i, ii;
	int lastend = -1;
	int dataend = -1;
	int barnum1 = -1;
	int barnum2 = -1;
	HumRegex hre;

	insertZerothMeasure(measurelist, infile);

	for (i=0; i<infile.getLineCount(); i++) {
		if (infile[i].isInterpretation()) {
			if (*infile.token(i, 0) == "*-") {
				dataend = i;
				break;
			}
		}
		if (!infile[i].isBarline()) {
			continue;
		}
		//if (!hre.search(infile.token(i, 0), "^=.*(\\d+)")) {
		//   continue;
		//}
		//barnum1 = stoi(hre.getMatch(1));
		if (!sscanf(infile.token(i, 0)->c_str(), "=%d", &barnum1)) {
			continue;
		}
		current.clear();
		current.start = i;
		current.num   = barnum1;
		for (ii=i+1; ii<infile.getLineCount(); ii++) {
			if (!infile[ii].isBarline()) {
				continue;
			}
			//if (hre.search(infile.token(ii, 0), "^=.*(\\d+)")) {
			//   barnum2 = stoi(hre.getMatch(1));
			//   current.stop = ii;
			//   lastend = ii;
			//   i = ii - 1;
			//   measurelist.push_back(current);
			//   break;
			//}
			if (hre.search(infile.token(ii, 0), "=[^\\d]*(\\d+)")) {
			// if (sscanf(infile.token(ii, 0), "=%d", &barnum2)) {
				barnum2 = stoi(hre.getMatch(1));
				current.stop = ii;
				lastend = ii;
				i = ii - 1;
				current.file = &infile;
				measurelist.push_back(current);
				break;
			} else {
				if (atEndOfFile(infile, ii)) {
					break;
				}
			}
		}
	}

	int lastdata    = -1;   // last line in file with data
	int lastmeasure = -1;   // last line in file with measure

	for (i=infile.getLineCount()-1; i>=0; i--) {
		if ((lastdata < 0) && infile[i].isData()) {
			lastdata = i;
		}
		if ((lastmeasure < 0) && infile[i].isBarline()) {
			lastmeasure = i;
		}
		if ((lastmeasure >= 0) && (lastdata >= 0)) {
			break;
		}
	}

	if (lastmeasure < lastdata) {
		// no final barline, so set to ignore
		lastmeasure = -1;
		lastdata    = -1;
	}

	if ((barnum2 >= 0) && (lastend >= 0) && (dataend >= 0)) {
		current.clear();
		current.num = barnum2;
		current.start = lastend;
		current.stop = dataend;
		if (lastmeasure > lastdata) {
			current.stop = lastmeasure;
		}
		current.file = &infile;
		measurelist.push_back(current);
	}

	// allow "myank -l" when there are no measure numbers
	if (getBoolean("lines") && measurelist.size() == 0) {
		current.clear();
		current.num = 0;
		current.start = 0;
		current.stop = dataend;
		current.file = &infile;
		measurelist.push_back(current);
	}

}



//////////////////////////////
//
// Tool_myank::getSectionCount -- Count the number of sections in a file according to
//     JRP rules: sections are defined by double barlines. There may be some
//     corner cases to consider.
//

int Tool_myank::getSectionCount(HumdrumFile& infile) {
	int i;
	int count = 0;
	int dataQ = 0;
	for (i=0; i<infile.getLineCount(); i++) {
		if (!dataQ && infile[i].isData()) {
			dataQ = 1;
			count++;
			continue;
		}
		if (infile[i].isBarline()) {
			if (infile.token(i, 0)->find("||") != string::npos) {
				dataQ = 0;
			}
		}
	}
	return count;
}



//////////////////////////////
//
// Tool_myank::getSectionString -- return the measure range of a section.
//

void Tool_myank::getSectionString(string& sstring, HumdrumFile& infile, int sec) {
	int i;
	int first = -1;
	int second = -1;
	int barnum = 0;
	int count = 0;
	int dataQ = 0;
	HumRegex hre;
	for (i=0; i<infile.getLineCount(); i++) {
		if (!dataQ && infile[i].isData()) {
			dataQ = 1;
			count++;
			if (count == sec) {
				first = barnum;
			} else if (count == sec+1) {
				second = barnum - 1;
			}
			continue;
		}
		if (infile[i].isBarline()) {
			if (infile.token(i, 0)->find("||") != string::npos) {
				dataQ = 0;
			}
			if (hre.search(infile.token(i, 0), "(\\d+)")) {
				barnum = hre.getMatchInt(1);
			}
		}
	}
	if (second < 0) {
		second = barnum;
	}
	sstring = to_string(first);
	sstring += "-";
	sstring += to_string(second);
}



//////////////////////////////
//
// Tool_myank::atEndOfFile --
//

int Tool_myank::atEndOfFile(HumdrumFile& infile, int line) {
	int i;
	for (i=line+1; i<infile.getLineCount(); i++) {
		if (infile[i].isData()) {
			return 0;
		}
	}

	return 1;
}



//////////////////////////////
//
// Tool_myank::insertZerothMeasure --
//

void Tool_myank::insertZerothMeasure(vector<MeasureInfo>& measurelist,
		HumdrumFile& infile) {

	HumRegex hre;
	int exinterpline = -1;
	int startline = -1;
	int stopline = -1;
	int i;
	for (i=0; i<infile.getLineCount(); i++) {
		if ((exinterpline < 0) && infile[i].isInterpretation()) {
			exinterpline = i;
		}
		if ((startline < 0) && (infile[i].isData())) {
			startline = i;
		}
		if (infile[i].isBarline() && hre.search(infile.token(i, 0), "^=.*\\d+", "")) {
			stopline = i;
			break;
		}
	}

	if (exinterpline < 0) {
		// somethind weird happend, just return
		return;
	}
	if (startline < 0) {
		// no zeroth measure;
		return;
	}
	if (stopline < 0) {
		// strange situation, no measure numbers
		// consider what to do later...
		return;
	}

	MeasureInfo current;
	current.clear();
	current.num = 0;
	current.start = startline;
	// current.start = exinterpline+1;
	current.stop = stopline;
	current.file = &infile;
	measurelist.push_back(current);
}



//////////////////////////////
//
// Tool_myank::expandMeasureOutList -- read the measure list for the sequence of measures
//     to extract.
//

void Tool_myank::expandMeasureOutList(vector<MeasureInfo>& measureout,
		vector<MeasureInfo>& measurein, HumdrumFile& infile,
		const string& optionstring) {

	HumRegex hre;
	// find the largest measure number in the score
	int maxmeasure = -1;
	int minmeasure = -1;
	for (int i=0; i<(int)measurein.size(); i++) {
		if (maxmeasure < measurein[i].num) {
			maxmeasure = measurein[i].num;
		}
		if ((minmeasure == -1) || (minmeasure > measurein[i].num)) {
			minmeasure = measurein[i].num;
		}
	}
	if (maxmeasure <= 0 && !getBoolean("lines")) {
		cerr << "Error: There are no measure numbers present in the data" << endl;
		exit(1);
	}
	if (maxmeasure > 1123123) {
		cerr << "Error: ridiculusly large measure number: " << maxmeasure << endl;
		exit(1);
	}
	if (m_maxQ) {
		if (measurein.size() == 0) {
			m_humdrum_text << 0 << endl;
		} else {
			m_humdrum_text << maxmeasure << endl;
		}
		exit(0);
	} else if (m_minQ) {
		for (int ii=0; ii<infile.getLineCount(); ii++) {
			if (infile[ii].isBarline()) {
				if (hre.search(infile.token(ii, 0), "=\\d", "")) {
					break;
				} else {
					m_humdrum_text << 0 << endl;
					exit(0);
				}
			}
			if (infile[ii].isData()) {
				m_humdrum_text << 0 << endl;
				exit(0);
			}
		}
		if (measurein.size() == 0) {
			m_humdrum_text << 0 << endl;
		} else {
			m_humdrum_text << minmeasure << endl;
		}
		exit(0);
	}

	// create reverse-lookup list
	vector<int> inmap(maxmeasure+1);
	fill(inmap.begin(), inmap.end(), -1);
	for (int i=0; i<(int)measurein.size(); i++) {
		inmap[measurein[i].num] = i;
	}

	fillGlobalDefaults(infile, measurein, inmap);
	string ostring = optionstring;
	removeDollarsFromString(ostring, maxmeasure);

	if (m_debugQ) {
		m_free_text << "Option string expanded: " << ostring << endl;
	}

	hre.replaceDestructive(ostring, "", "\\s+", "g");  // remove any spaces between items.
	hre.replaceDestructive(ostring, "-", "--+", "g");  // remove extra dashes
	int value = 0;
	int start = 0;
	vector<MeasureInfo>& range = measureout;
	range.reserve(10000);
	string searchexp = "^([\\d$-]+[^\\d$-]*)";
	value = hre.search(ostring, searchexp);
	while (value != 0) {
		start += value - 1;
		start += (int)hre.getMatch(1).size();
		processFieldEntry(range, hre.getMatch(1), infile, maxmeasure, measurein, inmap);
		value = hre.search(ostring, start, searchexp);
	}
}



//////////////////////////////
//
// Tool_myank::fillGlobalDefaults -- keep track of the clef, key signature, key, etc.
//

void Tool_myank::fillGlobalDefaults(HumdrumFile& infile, vector<MeasureInfo>& measurein,
		vector<int>& inmap) {
	int i, j;
	HumRegex hre;

	int tracks = infile.getMaxTrack();
	// cerr << "MAX TRACKS " << tracks << " ===============================" << endl;

	vector<MyCoord> currclef(tracks+1);
	vector<MyCoord> currkeysig(tracks+1);
	vector<MyCoord> currkey(tracks+1);
	vector<MyCoord> currtimesig(tracks+1);
	vector<MyCoord> currmet(tracks+1);
	vector<MyCoord> currtempo(tracks+1);

	MyCoord undefMyCoord;
	undefMyCoord.clear();

	fill(currclef.begin(), currclef.end(), undefMyCoord);
	fill(currkeysig.begin(), currkeysig.end(), undefMyCoord);
	fill(currkey.begin(), currkey.end(), undefMyCoord);
	fill(currtimesig.begin(), currtimesig.end(), undefMyCoord);
	fill(currmet.begin(), currmet.end(), undefMyCoord);
	fill(currtempo.begin(), currtempo.end(), undefMyCoord);

	int currmeasure = -1;
	int lastmeasure = -1;
	int datafound   = 0;
	int track;
	int thingy = 0;

	for (i=0; i<infile.getLineCount(); i++) {
		if ((currmeasure == -1) && (thingy == 0) && infile[i].isData()) {
			currmeasure = 0;
		}
		if (infile[i].isBarline()) {
			if (!hre.search(infile.token(i, 0), "(\\d+)", "")) {
				continue;
			}
			thingy = 1;

			// store state of global music values at end of measure
			if (currmeasure >= 0) {
				measurein[inmap[currmeasure]].eclef    = currclef;
				measurein[inmap[currmeasure]].ekeysig  = currkeysig;
				measurein[inmap[currmeasure]].ekey     = currkey;
				measurein[inmap[currmeasure]].etimesig = currtimesig;
				measurein[inmap[currmeasure]].emet     = currmet;
				measurein[inmap[currmeasure]].etempo   = currtempo;
			}

			lastmeasure = currmeasure;
			currmeasure = hre.getMatchInt(1);

			if (currmeasure < (int)inmap.size()) {
				// [20120818] Had to compensate for last measure being single
				// and un-numbered.
				if (inmap[currmeasure] < 0) {
					// [20111008] Had to compensate for "==85" barline
					datafound = 0;
					break;
				}
// cerr << "CURRCLEF: ";
// for (int z=0; z<(int)currclef.size(); z++) {
// cerr << "(" << currclef[z].x << "," << currclef[z].y << ") ";
// }
// cerr << endl;
				measurein[inmap[currmeasure]].sclef    = currclef;
				measurein[inmap[currmeasure]].skeysig  = currkeysig;
				measurein[inmap[currmeasure]].skey     = currkey;
				measurein[inmap[currmeasure]].stimesig = currtimesig;
				// measurein[inmap[currmeasure]].smet     = metstates[i];
				measurein[inmap[currmeasure]].smet     = currmet;
				measurein[inmap[currmeasure]].stempo   = currtempo;
			}

			datafound   = 0;
			continue;
		}
		if (infile[i].isInterpretation()) {
			for (j=0; j<infile[i].getFieldCount(); j++) {
				if (!infile.token(i, j)->isKern()) {
					continue;
				}
				track = infile.token(i, j)->getTrack();

				if ((datafound == 0) && (lastmeasure >= 0)) {
					if (infile.token(i, j)->compare(0, 5, "*clef") == 0) {
						measurein[inmap[currmeasure]].sclef[track].x = -1;
						measurein[inmap[currmeasure]].sclef[track].y = -1;
					} else if (hre.search(infile.token(i, j), "^\\*k\\[.*\\]", "")) {
						measurein[inmap[currmeasure]].skeysig[track].x = -1;
						measurein[inmap[currmeasure]].skeysig[track].y = -1;
					} else if (hre.search(infile.token(i, j), "^\\*[A-G][#-]?:", "i")) {
						measurein[inmap[currmeasure]].skey[track].x = -1;
						measurein[inmap[currmeasure]].skey[track].y = -1;
					} else if (hre.search(infile.token(i, j), R"(^\*M\d+/\d+)")) {
						measurein[inmap[currmeasure]].stimesig[track].x = -1;
						measurein[inmap[currmeasure]].stimesig[track].y = -1;
					} else if (hre.search(infile.token(i, j), R"(^\*met\(.*\))")) {
						measurein[inmap[currmeasure]].smet[track].x = -1;
						measurein[inmap[currmeasure]].smet[track].y = -1;
					} else if (hre.search(infile.token(i, j), "^\\*MM\\d+", "i")) {
						measurein[inmap[currmeasure]].stempo[track].x = -1;
						measurein[inmap[currmeasure]].stempo[track].y = -1;
					}
				}

				if (infile.token(i, j)->compare(0, 5, "*clef") == 0) {
					currclef[track].x = i;
					currclef[track].y = j;
					continue;
				}
				if (hre.search(infile.token(i, j), R"(^\*k\[.*\])")) {
					currkeysig[track].x = i;
					currkeysig[track].y = j;
					continue;
				}
				if (hre.search(infile.token(i, j), "^\\*[A-G][#-]?:", "i")) {
					currkey[track].x = i;
					currkey[track].y = j;
					continue;
				}
				if (hre.search(infile.token(i, j), R"(^\*M\d+/\d+)")) {
					currtimesig[track].x = i;
					currtimesig[track].y = j;
					continue;
				}
				if (hre.search(infile.token(i, j), R"(^\*met\(.*\))")) {
					currmet[track].x = i;
					currmet[track].y = j;
					continue;
				}
				if (hre.search(infile.token(i, j), R"(^\*MM[\d.]+)")) {
					currtempo[track].x = i;
					currtempo[track].y = j;
					continue;
				}

			}
		}
		if (infile[i].isData()) {
			datafound = 1;
		}
	}

	// store state of global music values at end of music
	if ((currmeasure >= 0) && (currmeasure < (int)inmap.size())
			&& (inmap[currmeasure] >= 0)) {
		measurein[inmap[currmeasure]].eclef    = currclef;
		measurein[inmap[currmeasure]].ekeysig  = currkeysig;
		measurein[inmap[currmeasure]].ekey     = currkey;
		measurein[inmap[currmeasure]].etimesig = currtimesig;
		measurein[inmap[currmeasure]].emet     = currmet;
		measurein[inmap[currmeasure]].etempo   = currtempo;
	}

	// go through the measure list and clean up start/end states
	for (i=0; i<(int)measurein.size()-2; i++) {

		if (measurein[i].sclef.size() == 0) {
			measurein[i].sclef.resize(tracks+1);
			fill(measurein[i].sclef.begin(), measurein[i].sclef.end(), undefMyCoord);
		}
		if (measurein[i].eclef.size() == 0) {
			measurein[i].eclef.resize(tracks+1);
			fill(measurein[i].eclef.begin(), measurein[i].eclef.end(), undefMyCoord);
		}
		if (measurein[i+1].sclef.size() == 0) {
			measurein[i+1].sclef.resize(tracks+1);
			fill(measurein[i+1].sclef.begin(), measurein[i+1].sclef.end(), undefMyCoord);
		}
		if (measurein[i+1].eclef.size() == 0) {
			measurein[i+1].eclef.resize(tracks+1);
			fill(measurein[i+1].eclef.begin(), measurein[i+1].eclef.end(), undefMyCoord);
		}
		for (j=1; j<(int)measurein[i].sclef.size(); j++) {
			if (!measurein[i].eclef[j].isValid()) {
				if (measurein[i].sclef[j].isValid()) {
					measurein[i].eclef[j] = measurein[i].sclef[j];
				}
			}
			if (!measurein[i+1].sclef[j].isValid()) {
				if (measurein[i].eclef[j].isValid()) {
					measurein[i+1].sclef[j] = measurein[i].eclef[j];
				}
			}
		}

		if (measurein[i].skeysig.size() == 0) {
			measurein[i].skeysig.resize(tracks+1);
			fill(measurein[i].skeysig.begin(), measurein[i].skeysig.end(), undefMyCoord);
		}
		if (measurein[i].ekeysig.size() == 0) {
			measurein[i].ekeysig.resize(tracks+1);
			fill(measurein[i].ekeysig.begin(), measurein[i].ekeysig.end(), undefMyCoord);
		}
		if (measurein[i+1].skeysig.size() == 0) {
			measurein[i+1].skeysig.resize(tracks+1);
			fill(measurein[i+1].skeysig.begin(), measurein[i+1].skeysig.end(), undefMyCoord);
		}
		if (measurein[i+1].ekeysig.size() == 0) {
			measurein[i+1].ekeysig.resize(tracks+1);
			fill(measurein[i+1].ekeysig.begin(), measurein[i+1].ekeysig.end(), undefMyCoord);
		}
		for (j=1; j<(int)measurein[i].skeysig.size(); j++) {
			if (!measurein[i].ekeysig[j].isValid()) {
				if (measurein[i].skeysig[j].isValid()) {
					measurein[i].ekeysig[j] = measurein[i].skeysig[j];
				}
			}
			if (!measurein[i+1].skeysig[j].isValid()) {
				if (measurein[i].ekeysig[j].isValid()) {
					measurein[i+1].skeysig[j] = measurein[i].ekeysig[j];
				}
			}
		}

		if (measurein[i].skey.size() == 0) {
			measurein[i].skey.resize(tracks+1);
			fill(measurein[i].skey.begin(), measurein[i].skey.end(), undefMyCoord);
		}
		if (measurein[i].ekey.size() == 0) {
			measurein[i].ekey.resize(tracks+1);
			fill(measurein[i].ekey.begin(), measurein[i].ekey.end(), undefMyCoord);
		}
		if (measurein[i+1].skey.size() == 0) {
			measurein[i+1].skey.resize(tracks+1);
			fill(measurein[i+1].skey.begin(), measurein[i+1].skey.end(), undefMyCoord);
		}
		if (measurein[i+1].ekey.size() == 0) {
			measurein[i+1].ekey.resize(tracks+1);
			fill(measurein[i+1].ekey.begin(), measurein[i+1].ekey.end(), undefMyCoord);
		}
		for (j=1; j<(int)measurein[i].skey.size(); j++) {
			if (!measurein[i].ekey[j].isValid()) {
				if (measurein[i].skey[j].isValid()) {
					measurein[i].ekey[j] = measurein[i].skey[j];
				}
			}
			if (!measurein[i+1].skey[j].isValid()) {
				if (measurein[i].ekey[j].isValid()) {
					measurein[i+1].skey[j] = measurein[i].ekey[j];
				}
			}
		}

		if (measurein[i].stimesig.size() == 0) {
			measurein[i].stimesig.resize(tracks+1);
			fill(measurein[i].stimesig.begin(), measurein[i].stimesig.end(), undefMyCoord);
		}
		if (measurein[i].etimesig.size() == 0) {
			measurein[i].etimesig.resize(tracks+1);
			fill(measurein[i].etimesig.begin(), measurein[i].etimesig.end(), undefMyCoord);
		}
		if (measurein[i+1].stimesig.size() == 0) {
			measurein[i+1].stimesig.resize(tracks+1);
			fill(measurein[i+1].stimesig.begin(), measurein[i+1].stimesig.end(), undefMyCoord);
		}
		if (measurein[i+1].etimesig.size() == 0) {
			measurein[i+1].etimesig.resize(tracks+1);
			fill(measurein[i+1].etimesig.begin(), measurein[i+1].etimesig.end(), undefMyCoord);
		}
		for (j=1; j<(int)measurein[i].stimesig.size(); j++) {
			if (!measurein[i].etimesig[j].isValid()) {
				if (measurein[i].stimesig[j].isValid()) {
					measurein[i].etimesig[j] = measurein[i].stimesig[j];
				}
			}
			if (!measurein[i+1].stimesig[j].isValid()) {
				if (measurein[i].etimesig[j].isValid()) {
					measurein[i+1].stimesig[j] = measurein[i].etimesig[j];
				}
			}
		}

		if (measurein[i].smet.size() == 0) {
			measurein[i].smet.resize(tracks+1);
			fill(measurein[i].smet.begin(), measurein[i].smet.end(), undefMyCoord);
		}
		if (measurein[i].emet.size() == 0) {
			measurein[i].emet.resize(tracks+1);
			fill(measurein[i].emet.begin(), measurein[i].emet.end(), undefMyCoord);
		}
		if (measurein[i+1].smet.size() == 0) {
			measurein[i+1].smet.resize(tracks+1);
			fill(measurein[i+1].smet.begin(), measurein[i+1].smet.end(), undefMyCoord);
		}
		if (measurein[i+1].emet.size() == 0) {
			measurein[i+1].emet.resize(tracks+1);
			fill(measurein[i+1].emet.begin(), measurein[i+1].emet.end(), undefMyCoord);
		}
		for (j=1; j<(int)measurein[i].smet.size(); j++) {
			if (!measurein[i].emet[j].isValid()) {
				if (measurein[i].smet[j].isValid()) {
					measurein[i].emet[j] = measurein[i].smet[j];
				}
			}
			if (!measurein[i+1].smet[j].isValid()) {
				if (measurein[i].emet[j].isValid()) {
					measurein[i+1].smet[j] = measurein[i].emet[j];
				}
			}
		}

		if (measurein[i].stempo.size() == 0) {
			measurein[i].stempo.resize(tracks+1);
			fill(measurein[i].stempo.begin(), measurein[i].stempo.end(), undefMyCoord);
		}
		if (measurein[i].etempo.size() == 0) {
			measurein[i].etempo.resize(tracks+1);
			fill(measurein[i].etempo.begin(), measurein[i].etempo.end(), undefMyCoord);
		}
		if (measurein[i+1].stempo.size() == 0) {
			measurein[i+1].stempo.resize(tracks+1);
			fill(measurein[i+1].stempo.begin(), measurein[i+1].stempo.end(), undefMyCoord);
		}
		if (measurein[i+1].etempo.size() == 0) {
			measurein[i+1].etempo.resize(tracks+1);
			fill(measurein[i+1].etempo.begin(), measurein[i+1].etempo.end(), undefMyCoord);
		}
		for (j=1; j<(int)measurein[i].stempo.size(); j++) {
			if (!measurein[i].etempo[j].isValid()) {
				if (measurein[i].stempo[j].isValid()) {
					measurein[i].etempo[j] = measurein[i].stempo[j];
				}
			}
			if (!measurein[i+1].stempo[j].isValid()) {
				if (measurein[i].etempo[j].isValid()) {
					measurein[i+1].stempo[j] = measurein[i].etempo[j];
				}
			}
		}
	}
}



//////////////////////////////
//
// Tool_myank::processFieldEntry --
//   3-6 expands to 3 4 5 6
//   $   expands to maximum spine track
//   $0  expands to maximum spine track
//   $1  expands to maximum spine track minus 1, etc.
//   2-$1 expands to 2 through the maximum minus one.
//   6-3 expands to 6 5 4 3
//   $2-5 expands to the maximum minus 2 down through 5.
//   Ignore negative values and values which exceed the maximum value.
//

void Tool_myank::processFieldEntry(vector<MeasureInfo>& field,
		const string& str, HumdrumFile& infile, int maxmeasure,
		vector<MeasureInfo>& inmeasures, vector<int>& inmap) {

	MeasureInfo current;

	HumRegex hre;
	string buffer = str;

	// remove any comma left at end of input string (or anywhere else)
	hre.replaceDestructive(buffer, "", ",", "g");

	string measureStyling = "";
	if (hre.search(buffer, "([|:!=]+)$")) {
		measureStyling = hre.getMatch(1);
		hre.replaceDestructive(buffer, "", "([|:!=]+)$");
	}

	if (hre.search(buffer, "^(\\d+)[a-z]?-(\\d+)[a-z]?$")) {
		// processing a measure range
		int firstone = hre.getMatchInt(1);
		int lastone  = hre.getMatchInt(2);

		// limit the range to 0 to maxmeasure
		if (firstone > maxmeasure) { firstone = maxmeasure; }
		if (lastone  > maxmeasure) { lastone  = maxmeasure; }
		if (firstone < 0         ) { firstone = 0         ; }
		if (lastone  < 0         ) { lastone  = 0         ; }

		if ((firstone < 1) && (firstone != 0)) {
			cerr << "Error: range token: \"" << str << "\""
				  << " 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: \"" << str << "\""
				  << " contains too small a number at end: " << lastone << endl;
			cerr << "Minimum number allowed is " << 1 << endl;
			exit(1);
		}

		if (firstone > lastone) {
			// reverse the order of the measures
			for (int i=firstone; i>=lastone; i--) {
				if (inmap[i] >= 0) {
					current.clear();
					current.file = &infile;
					current.num = i;
					current.start = inmeasures[inmap[i]].start;
					current.stop = inmeasures[inmap[i]].stop;

					current.sclef    = inmeasures[inmap[i]].sclef;
					current.skeysig  = inmeasures[inmap[i]].skeysig;
					current.skey     = inmeasures[inmap[i]].skey;
					current.stimesig = inmeasures[inmap[i]].stimesig;
					current.smet     = inmeasures[inmap[i]].smet;
					current.stempo   = inmeasures[inmap[i]].stempo;

					current.eclef    = inmeasures[inmap[i]].eclef;
					current.ekeysig  = inmeasures[inmap[i]].ekeysig;
					current.ekey     = inmeasures[inmap[i]].ekey;
					current.etimesig = inmeasures[inmap[i]].etimesig;
					current.emet     = inmeasures[inmap[i]].emet;
					current.etempo   = inmeasures[inmap[i]].etempo;

					field.push_back(current);
				}
			}
		} else {
			// measure range not reversed
			for (int i=firstone; i<=lastone; i++) {
				if (inmap[i] >= 0) {
					current.clear();
					current.file = &infile;
					current.num = i;
					current.start = inmeasures[inmap[i]].start;
					current.stop = inmeasures[inmap[i]].stop;

					current.sclef    = inmeasures[inmap[i]].sclef;
					current.skeysig  = inmeasures[inmap[i]].skeysig;
					current.skey     = inmeasures[inmap[i]].skey;
					current.stimesig = inmeasures[inmap[i]].stimesig;
					current.smet     = inmeasures[inmap[i]].smet;
					current.stempo   = inmeasures[inmap[i]].stempo;

					current.eclef    = inmeasures[inmap[i]].eclef;
					current.ekeysig  = inmeasures[inmap[i]].ekeysig;
					current.ekey     = inmeasures[inmap[i]].ekey;
					current.etimesig = inmeasures[inmap[i]].etimesig;
					current.emet     = inmeasures[inmap[i]].emet;
					current.etempo   = inmeasures[inmap[i]].etempo;

					field.push_back(current);
				}
			}
		}
	} else if (hre.search(buffer, "^(\\d+)([a-z]*)")) {
		// processing a single measure number
		int value = hre.getMatchInt(1);
		// do something with letter later...

		if ((value < 1) && (value != 0)) {
			cerr << "Error: range token: \"" << str << "\""
				  << " contains too small a number at end: " << value << endl;
			cerr << "Minimum number allowed is " << 1 << endl;
			exit(1);
		}
		if (inmap[value] >= 0) {
			current.clear();
			current.file = &infile;
			current.num = value;
			current.start = inmeasures[inmap[value]].start;
			current.stop = inmeasures[inmap[value]].stop;

			current.sclef    = inmeasures[inmap[value]].sclef;
			current.skeysig  = inmeasures[inmap[value]].skeysig;
			current.skey     = inmeasures[inmap[value]].skey;
			current.stimesig = inmeasures[inmap[value]].stimesig;
			current.smet     = inmeasures[inmap[value]].smet;
			current.stempo   = inmeasures[inmap[value]].stempo;

			current.eclef    = inmeasures[inmap[value]].eclef;
			current.ekeysig  = inmeasures[inmap[value]].ekeysig;
			current.ekey     = inmeasures[inmap[value]].ekey;
			current.etimesig = inmeasures[inmap[value]].etimesig;
			current.emet     = inmeasures[inmap[value]].emet;
			current.etempo   = inmeasures[inmap[value]].etempo;

			field.push_back(current);
		}
	}

	field.back().stopStyle = measureStyling;

}



//////////////////////////////
//
// Tool_myank::removeDollarsFromString -- substitute $ sign for maximum track count.
//

void Tool_myank::removeDollarsFromString(string& buffer, int maxx) {
	HumRegex hre;
	HumRegex hre2;
	string tbuf;
	string obuf;
	int outval;
	int value;

	if (m_debugQ) {
		m_free_text << "MEASURE STRING BEFORE DOLLAR REMOVAL: " << buffer << endl;
	}

	while (hre.search(buffer, "(\\$\\d*)", "")) {
		tbuf = hre.getMatch(1);
		if (hre2.search(tbuf, "(\\$\\d+)")) {
		  sscanf(hre2.getMatch(1).c_str(), "$%d", &value);
		  outval = maxx - value;
		} else {
			outval = maxx;
		}

		if (outval < 0) {
			outval = 0;
		}

		tbuf = to_string(outval);
		obuf = "\\";
		obuf += hre.getMatch(1);
		hre.replaceDestructive(buffer, tbuf, obuf);
	}
	if (m_debugQ) {
		m_free_text << "DOLLAR EXPAND: " << buffer << endl;
	}
}






//////////////////////////////
//
// Tool_myank::example -- example function calls to the program.
//

void Tool_myank::example(void) {


}



//////////////////////////////
//
// Tool_myank::usage -- command-line usage description and brief summary
//

void Tool_myank::usage(const string& ommand) {

}




/////////////////////////////////
//
// Tool_nproof::Tool_nproof -- Set the recognized options for the tool.
//

Tool_nproof::Tool_nproof(void) {
	define("B|no-blank|no-blanks=b",                 "do not check for blank lines.\n");
	define("b|only-blank|only-blanks=b",             "only check for blank lines.\n");

	define("I|no-instrument|no-instruments=b",       "do not check instrument interpretations.\n");
	define("i|only-instrument|only-instruments=b",   "only check instrument interpretations.\n");

	define("K|no-key=b",                             "do not check for !!!key: manual initial key designation.\n");
	define("k|only-key=b",                           "only check for !!!key: manual initial key designation.\n");

	define("R|no-reference=b",                       "do not check for reference records.\n");
	define("r|only-reference=b",                     "only check for reference records.\n");

	define("T|no-termination|no-terminations=b",     "do not check spine terminations.\n");
	define("t|only-termination|only-terminations=b", "only check spine terminations.\n");

	define("file|filename=b",                        "print filename with raw count (if available).\n");
	define("raw=b",                                  "only print error count.\n");
}



/////////////////////////////////
//
// Tool_nproof::run -- Do the main work of the tool.
//

bool Tool_nproof::run(HumdrumFileSet& infiles) {
	bool status = true;
	for (int i=0; i<infiles.getCount(); i++) {
		status &= run(infiles[i]);
	}
	return status;
}


bool Tool_nproof::run(const string& indata, ostream& out) {
	HumdrumFile infile(indata);
	bool status = run(infile);
	if (hasAnyText()) {
		getAllText(out);
	} else {
		out << infile;
	}
	return status;
}


bool Tool_nproof::run(HumdrumFile& infile, ostream& out) {
	bool status = run(infile);
	if (hasAnyText()) {
		getAllText(out);
	} else {
		out << infile;
	}
	return status;
}


bool Tool_nproof::run(HumdrumFile& infile) {
	initialize();
	processFile(infile);
	return true;
}



//////////////////////////////
//
// Tool_nproof::initialize --
//

void Tool_nproof::initialize(void) {
	m_noblankQ       = getBoolean("no-blank");
	m_noinstrumentQ  = getBoolean("no-instrument");
	m_nokeyQ         = getBoolean("no-key");
	m_noreferenceQ   = getBoolean("no-reference");
	m_noterminationQ = getBoolean("no-termination");

	bool onlyBlank       = getBoolean("only-blank");
	bool onlyInstrument  = getBoolean("only-instrument");
	bool onlyKey         = getBoolean("only-key");
	bool onlyReference   = getBoolean("only-reference");
	bool onlyTermination = getBoolean("only-termination");

	if (onlyBlank || onlyInstrument || onlyKey || onlyReference || onlyTermination) {
		m_noblankQ       = !onlyBlank;
		m_noinstrumentQ  = !onlyInstrument;
		m_nokeyQ         = !onlyKey;
		m_noreferenceQ   = !onlyReference;
		m_noterminationQ = !onlyTermination;
	}

	m_fileQ          = getBoolean("file");
	m_rawQ           = getBoolean("raw");
}



//////////////////////////////
//
// Tool_nproof::processFile --
//

void Tool_nproof::processFile(HumdrumFile& infile) {
	m_errorCount = 0;
	m_errorList = "";
	m_errorHtml = "";

	if (!m_noblankQ) {
		checkForBlankLines(infile);
	}
	if (!m_nokeyQ) {
		checkKeyInformation(infile);
	}
	if (!m_noinstrumentQ) {
		checkInstrumentInformation(infile);
	}
	if (!m_noreferenceQ) {
		checkReferenceRecords(infile);
	}
	if (!m_noterminationQ) {
		checkSpineTerminations(infile);
	}

	m_humdrum_text << infile;

	if (m_rawQ) {
		// print error count only.
		if (m_fileQ) {
			m_free_text << infile.getFilename() << "\t";
		}
		m_free_text << m_errorCount << endl;
		return;
	}

	if (m_errorCount > 0) {
		m_humdrum_text << m_errorList;
		m_humdrum_text << "!!!TOOL-nproof-error-count: " << m_errorCount << endl;
		m_humdrum_text << "!!@@BEGIN: PREHTML\n";
		m_humdrum_text << "!!@TOOL: nproof\n";
		m_humdrum_text << "!!@CONTENT:\n";
		m_humdrum_text << "!! <h2 style='color:red'> @{TOOL-nproof-error-count} problem";
		if (m_errorCount != 1) {
			m_humdrum_text << "s";
		}
		m_humdrum_text << " detected </h2>\n";
		m_humdrum_text << "!! <ul style='color:darkred'>\n";
		m_humdrum_text << m_errorHtml;
		m_humdrum_text << "!! </ul>\n";
		m_humdrum_text << "!!@@END: PREHTML\n";
	} else {
		m_humdrum_text << "!!@@BEGIN: PREHTML\n";
		m_humdrum_text << "!!@TOOL: nproof\n";
		m_humdrum_text << "!!@CONTENT:\n";
		m_humdrum_text << "!! <h2 style='color:red'> No problems detected </h2>\n";
		m_humdrum_text << "!!@@END: PREHTML\n";
	}
}



//////////////////////////////
//
// Tool_nproof::checkForBlankLines --
//

void Tool_nproof::checkForBlankLines(HumdrumFile& infile) {
	vector<int> blanks;
	// -1: Not checking for a blank line at the very end of the score.
	for (int i=0; i<infile.getLineCount() - 1; i++) {
		if (infile[i].hasSpines()) {
			continue;
		}
		HTp token = infile.token(i, 0);
		if (*token == "") {
			blanks.push_back(i+1);
		}
	}

	if (blanks.empty()) {
		return;
	}

	m_errorCount++;
	m_errorList += "!!!TOOL-nproof-error-" + to_string(m_errorCount) + ": Blank lines on row";
	if (blanks.size() != 1) {
		m_errorList += "s";
	}
	m_errorList += ": ";
	for (int i=0; i<(int)blanks.size(); i++) {
		m_errorList += to_string(blanks[i]);
		if (i < (int)blanks.size() - 1) {
			m_errorList += ", ";
		}
	}
	m_errorList += ".\n";
	m_errorHtml += "!! <li> @{TOOL-nproof-error-" + to_string(m_errorCount) + "} </li>\n";
}



//////////////////////////////
//
// Tool_nproof::checkForValidInstrumentCode --
//

void Tool_nproof::checkForValidInstrumentCode(HTp token,
		vector<pair<string, string>>& instrumentList) {

	if ((token->find("&") == string::npos) && (token->find("|") == string::npos)) {
		string code = token->substr(2);
		for (int i=0; i<(int)instrumentList.size(); i++) {
			if (instrumentList[i].first == code) {
				return;
			}
		}

		m_errorCount++;
		m_errorList += "!!!TOOL-nproof-error-" + to_string(m_errorCount) + ": Unknown instrument code \"" + code + "\" on line " + to_string(token->getLineNumber()) + ", field " + to_string(token->getFieldNumber()) + ". See list of codes at <a target='_blank' href='https://bit.ly/humdrum-instrument-codes'>https://bit.ly/humdrum-instrument-codes</a>.\n";
		m_errorHtml += "!! <li> @{TOOL-nproof-error-" + to_string(m_errorCount) + "} </li>\n";
		return;
	}

	bool found1 = false;
	bool found2 = false;
	string inst1;
	string inst2;
	HumRegex hre;
	if (hre.match(token, "^\\*I(.*)[&|](I.*)")) {
		inst1 = hre.getMatch(1);
		inst2 = hre.getMatch(2);

		for (int i=0; i<(int)instrumentList.size(); i++) {
			if (instrumentList[i].first == inst1) {
				found1 = true;
			}
			if (instrumentList[i].first == inst2) {
				found2 = true;
			}
		}
	}

	if (!found1) {
		m_errorCount++;
		m_errorList += "!!!TOOL-nproof-error-" + to_string(m_errorCount) + ": Unknown instrument code \"" + inst1 + "\" in token " + *token + " on line " + to_string(token->getLineNumber()) + ", field " + to_string(token->getFieldNumber()) + ". See list of codes at <a target='_blank' href='https://bit.ly/humdrum-instrument-codes'>https://bit.ly/humdrum-instrument-codes</a>.\n";
		m_errorHtml += "!! <li> @{TOOL-nproof-error-" + to_string(m_errorCount) + "} </li>\n";
	}

	if (!found2) {
		m_errorCount++;
		m_errorList += "!!!TOOL-nproof-error-" + to_string(m_errorCount) + ": Unknown instrument code \"" + inst2 + "\" in token " + *token + " on line " + to_string(token->getLineNumber()) + ", field " + to_string(token->getFieldNumber()) + ". See list of codes at <a target='_blank' href='https://bit.ly/humdrum-instrument-codes'>https://bit.ly/humdrum-instrument-codes</a>.\n";
		m_errorHtml += "!! <li> @{TOOL-nproof-error-" + to_string(m_errorCount) + "} </li>\n";
	}

}



//////////////////////////////
//
// Tool_nproof::checkInstrumentInformation --
//

void Tool_nproof::checkInstrumentInformation(HumdrumFile& infile) {
	int codeLine = -1;
	int classLine = -1;
	HumRegex hre;

	vector<pair<string, string>> instrumentList = Convert::getInstrumentList();

	for (int i=0; i<infile.getLineCount(); i++) {
		if (infile[i].isData()) {
			break;
		}
		if (!infile[i].isInterpretation()) {
			continue;
		}
		if (infile[i].isManipulator()) {
			continue;
		}
		for (int j=0; j<infile[i].getFieldCount(); j++) {
			HTp token = infile.token(i, j);
			if (!token->isKern()) {
				continue;
			}
			if (token->compare(0, 3, "*IC") == 0) {
				if (classLine < 0) {
					classLine = i;
				}
			} else if (hre.search(token, "^\\*I[a-z]")) {
				if (codeLine < 0) {
					codeLine = i;
				}
			}
		}
	}

	if (codeLine < 0) {
		m_errorCount++;
		m_errorList += "!!!TOOL-nproof-error-" + to_string(m_errorCount) + ": No instrument code line.\n";
		m_errorHtml += "!! <li> @{TOOL-nproof-error-" + to_string(m_errorCount) + "} </li>\n";
	} else {
		for (int i=0; i<infile[codeLine].getFieldCount(); i++) {
			HTp token = infile.token(codeLine, i);
			if (token->isKern()) {
				if (!hre.search(token, "^\\*I[a-z]")) {
					m_errorCount++;
					m_errorList += "!!!TOOL-nproof-error-" + to_string(m_errorCount) + ": expected instrument code on line " + to_string(token->getLineNumber()) + ", field " + to_string(token->getFieldNumber()) + ".\n";
					m_errorHtml += "!! <li> @{TOOL-nproof-error-" + to_string(m_errorCount) + "} </li>\n";
				} else {
					checkForValidInstrumentCode(token, instrumentList);
				}
			} else {
				if (*token != "*") {
					m_errorCount++;
					m_errorList += "!!!TOOL-nproof-error-" + to_string(m_errorCount) + ": Expected null interpretation on instrument code line " + to_string(token->getLineNumber()) + ", field " + to_string(token->getFieldNumber()) + ".\n";
					m_errorHtml += "!! <li> @{TOOL-nproof-error-" + to_string(m_errorCount) + "} </li>\n";
				}
			}
		}
	}

	if (classLine < 0) {
		m_errorCount++;
		m_errorList += "!!!TOOL-nproof-error-" + to_string(m_errorCount) + ": No instrument class line.\n";
		m_errorHtml += "!! <li> @{TOOL-nproof-error-" + to_string(m_errorCount) + "} </li>\n";
	} else {
		for (int i=0; i<infile[classLine].getFieldCount(); i++) {
			HTp token = infile.token(classLine, i);
			if (token->isKern()) {
				if (!hre.search(token, "^\\*IC[a-z]")) {
					m_errorCount++;
					m_errorList += "!!!TOOL-nproof-error-" + to_string(m_errorCount) + ": expected instrument class on line " + to_string(token->getLineNumber()) + ", field " + to_string(token->getFieldNumber()) + ".\n";
					m_errorHtml += "!! <li> @{TOOL-nproof-error-" + to_string(m_errorCount) + "} </li>\n";
				}
			} else {
				if (*token != "*") {
					m_errorCount++;
					m_errorList += "!!!TOOL-nproof-error-" + to_string(m_errorCount) + ": Expected null interpretation on instrument class line " + to_string(token->getLineNumber()) + ", field " + to_string(token->getFieldNumber()) + ".\n";
					m_errorHtml += "!! <li> @{TOOL-nproof-error-" + to_string(m_errorCount) + "} </li>\n";
				}
			}
		}
	}
}



//////////////////////////////
//
// Tool_nproof::checkReferenceRecords --
//

void Tool_nproof::checkReferenceRecords(HumdrumFile& infile) {
	vector<int> foundENC;  // Musescore encoder's name
	vector<int> foundEND;  // Musescore encdoer's date
	vector<int> foundEED;  // VHV editor's name
	vector<int> foundEEV;  // VHV editor's date

	HumRegex hre;
	for (int i=0; i<infile.getLineCount(); i++) {
		if (!infile[i].isReferenceRecord()) {
			continue;
		}
		string key = infile[i].getReferenceKey();

		if (hre.search(key, "^EED\\d*$")) {
			if (key == "EED") {
				foundEED.push_back(i);
			}
			string value = infile[i].getReferenceValue();
			if (hre.search(value, "^\\d\\d\\d\\d")) {
				m_errorCount++;
				m_errorList += "!!!TOOL-nproof-error-" + to_string(m_errorCount) + ": For EED (Electronic EDitor) record on line " + to_string(i+1) + ", found a date rather than a name: " + value + ".\n";
				m_errorHtml += "!! <li> @{TOOL-nproof-error-" + to_string(m_errorCount) + "} </li>\n";
			}
		}
		if (hre.search(key, "^EEV\\d*$")) {
			if (key == "EEV") {
				foundEEV.push_back(i);;
			}
			string value = infile[i].getReferenceValue();
			if (!hre.search(value, "^\\d\\d\\d\\d-\\d\\d-\\d\\d")) {
				m_errorCount++;
				m_errorList += "!!!TOOL-nproof-error-" + to_string(m_errorCount) + ": For EEV (ElEctronic Version) record on line " + to_string(i+1) + ", found a name rather than a date (or invalid date): " + value + ".\n";
				m_errorHtml += "!! <li> @{TOOL-nproof-error-" + to_string(m_errorCount) + "} </li>\n";
			}
		}
		if (hre.search(key, "^ENC\\d*(-modern|-iiif)?$")) {
			string value = infile[i].getReferenceValue();
			if (hre.search(value, "^\\d\\d\\d\\d-\\d\\d-\\d\\d")) {
				m_errorCount++;
				m_errorList += "!!!TOOL-nproof-error-" + to_string(m_errorCount) + ": For ENC (Electronic eNCoder) record on line " + to_string(i+1) + ", found a date rather than a name: " + value + ".\n";
				m_errorHtml += "!! <li> @{TOOL-nproof-error-" + to_string(m_errorCount) + "} </li>\n";
			}
		}
		if (hre.search(key, "^END\\d*(-modern|-iiif)?$")) {
			string value = infile[i].getReferenceValue();
			if (!hre.search(value, "^\\d\\d\\d\\d-\\d\\d-\\d\\d")) {
				m_errorCount++;
				m_errorList += "!!!TOOL-nproof-error-" + to_string(m_errorCount) + ": For END (Electronic eNcoding Date) record on line " + to_string(i+1) + ", found a name rather than a date (or an invalid date): " + value + ".\n";
				m_errorHtml += "!! <li> @{TOOL-nproof-error-" + to_string(m_errorCount) + "} </li>\n";
			}
		}
		if (hre.search(key, "^ENC(\\d*.*)$")) {
				if (key == "ENC") {
					foundENC.push_back(i);
				}
		}
		if (hre.search(key, "^ENC-(\\d+.*)$")) {
				string newvalue = "ENC" + hre.getMatch(1);
				m_errorCount++;
				m_errorList += "!!!TOOL-nproof-error-" + to_string(m_errorCount) + ": " + key + " reference record on line " + to_string(i+1) + " should not include a dash and instead be: " + newvalue + ".\n";
				m_errorHtml += "!! <li> @{TOOL-nproof-error-" + to_string(m_errorCount) + "} </li>\n";
		}
		if (hre.search(key, "END(\\d*.*)")) {
				if (key == "END") {
					foundEND.push_back(i);
				}
		}
		if (hre.search(key, "^END-(\\d+.*)$")) {
				string newvalue = "END" + hre.getMatch(1);
				m_errorCount++;
				m_errorList += "!!!TOOL-nproof-error-" + to_string(m_errorCount) + ": " + key + " reference record on line " + to_string(i+1) + " should not include a dash and instead be: " + newvalue + ".\n";
				m_errorHtml += "!! <li> @{TOOL-nproof-error-" + to_string(m_errorCount) + "} </li>\n";
		}
		if (key == "filter-") {
				m_errorCount++;
				m_errorList += "!!!TOOL-nproof-error-" + to_string(m_errorCount) + ": \"filter-\" reference record on line " + to_string(i+1) + " should probably be \"filter-modern\" instead.\n";
				m_errorHtml += "!! <li> @{TOOL-nproof-error-" + to_string(m_errorCount) + "} </li>\n";
		}
		if (key == "ENC-mod") {
				m_errorCount++;
				m_errorList += "!!!TOOL-nproof-error-" + to_string(m_errorCount) + ": ENC-mod reference record on line " + to_string(i+1) + " should be ENC-modern instead.\n";
				m_errorHtml += "!! <li> @{TOOL-nproof-error-" + to_string(m_errorCount) + "} </li>\n";
		}
		if (key == "END-mod") {
				m_errorCount++;
				m_errorList += "!!!TOOL-nproof-error-" + to_string(m_errorCount) + ": END-mod reference record on line " + to_string(i+1) + " should be END-modern instead.\n";
				m_errorHtml += "!! <li> @{TOOL-nproof-error-" + to_string(m_errorCount) + "} </li>\n";
		}
		if (key == "AIN-mod") {
				m_errorCount++;
				m_errorList += "!!!TOOL-nproof-error-" + to_string(m_errorCount) + ": AIN-mod reference record on line " + to_string(i+1) + " should be AIN-modern instead.\n";
				m_errorHtml += "!! <li> @{TOOL-nproof-error-" + to_string(m_errorCount) + "} </li>\n";
		}
		if (hre.search(key, "^(.*)-ori$")) {
				string piece = hre.getMatch(1);
				m_errorCount++;
				m_errorList += "!!!TOOL-nproof-error-" + to_string(m_errorCount) + ": " + key + " reference record on line " + to_string(i+1) + " should not be used (either use " + piece + "-mod or don't add -ori qualifier to " + piece + ").\n";
				m_errorHtml += "!! <li> @{TOOL-nproof-error-" + to_string(m_errorCount) + "} </li>\n";
		}
	}

	// vector<int> foundENC;  // Musescore encoder's name
	// vector<int> foundEND;  // Musescore encdoer's date
	// vector<int> foundEED;  // VHV editor's name
	// vector<int> foundEEV;  // VHV editor's date

	if (foundENC.empty()) {
		m_errorCount++;
		m_errorList += "!!!TOOL-nproof-error-" + to_string(m_errorCount) + ": Missing ENC (initial encoder's name) reference record.\n";
		m_errorHtml += "!! <li> @{TOOL-nproof-error-" + to_string(m_errorCount) + "} </li>\n";
	}
	if (foundEND.empty()) {
		m_errorCount++;
		m_errorList += "!!!TOOL-nproof-error-" + to_string(m_errorCount) + ": Missing END (initial encoding date) reference record.\n";
		m_errorHtml += "!! <li> @{TOOL-nproof-error-" + to_string(m_errorCount) + "} </li>\n";
	}
	if (foundEED.empty()) {
		m_errorCount++;
		m_errorList += "!!!TOOL-nproof-error-" + to_string(m_errorCount) + ": Missing EED (Humdrum electronic editor's name) reference record.\n";
		m_errorHtml += "!! <li> @{TOOL-nproof-error-" + to_string(m_errorCount) + "} </li>\n";
	}
	if (foundEEV.empty()) {
		m_errorCount++;
		m_errorList += "!!!TOOL-nproof-error-" + to_string(m_errorCount) + ": Missing EEV (Humdrum electronic edition date) reference record.\n";
		m_errorHtml += "!! <li> @{TOOL-nproof-error-" + to_string(m_errorCount) + "} </li>\n";
	}

	if ((foundENC.size() == 1) && (foundEED.size() == 1)) {
		if (foundENC[0] > foundEED[0]) {
			m_errorCount++;
			m_errorList += "!!!TOOL-nproof-error-" + to_string(m_errorCount) + ": ENC reference record on line " + to_string(foundENC[0]+1) + " should come before EED reference record on line " + to_string(foundEED[0]+1) + "\n";
			m_errorHtml += "!! <li> @{TOOL-nproof-error-" + to_string(m_errorCount) + "} </li>\n";
		}
	}

	if ((foundEND.size() == 1) && (foundEEV.size() == 1)) {
		if (foundEND[0] > foundEEV[0]) {
			m_errorCount++;
			m_errorList += "!!!TOOL-nproof-error-" + to_string(m_errorCount) + ": END reference record on line " + to_string(foundEND[0]+1) + " should come before EEV reference record on line " + to_string(foundEEV[0]+1) + "\n";
			m_errorHtml += "!! <li> @{TOOL-nproof-error-" + to_string(m_errorCount) + "} </li>\n";
		}
	}


	if ((foundENC.size() == 2) && (foundEED.size() == 0)) {
		string date1;
		string date2;
		if (foundEND.size() == 2) {
			date1 = infile[foundEND[0]].getReferenceValue();
			date2 = infile[foundEND[1]].getReferenceValue();
			hre.replaceDestructive(date1, "", "-", "g");
			hre.replaceDestructive(date2, "", "-", "g");
			int number1 = 0;
			int number2 = 0;
			if (hre.search(date1, "^(20\\d{6})$")) {
				number1 = hre.getMatchInt(1);
			}
			if (hre.search(date2, "^(20\\d{6})$")) {
				number2 = hre.getMatchInt(1);
			}
			if ((number1 > 0) && (number2 > 0)) {
				if (number1 > number2) {
					m_errorCount++;
					m_errorList += "!!!TOOL-nproof-error-" + to_string(m_errorCount) + ": Second ENC reference record on line " + to_string(foundENC[1]+1) + " should probably be changed to EED reference record (and second END reference record on line " + to_string(foundEND[1]+1) + " changed to EEV).\n";
					m_errorHtml += "!! <li> @{TOOL-nproof-error-" + to_string(m_errorCount) + "} </li>\n";
				}
			}
		} else {
			m_errorCount++;
			m_errorList += "!!!TOOL-nproof-error-" + to_string(m_errorCount) + ": There are two ENC records on lines " + to_string(foundENC[0]+1) + " and " + to_string(foundENC[1]+1) + ". The Humdrum editor's name should be changed to EED, and the editing date should be changed from END to EEV if necessary.\n";
			m_errorHtml += "!! <li> @{TOOL-nproof-error-" + to_string(m_errorCount) + "} </li>\n";
		}
	}

}



//////////////////////////////
//
// Tool_nproof::checkKeyInformation --
//

void Tool_nproof::checkKeyInformation(HumdrumFile& infile) {
	int foundKey = -1;
	for (int i=0; i<infile.getLineCount(); i++) {
		if (infile[i].hasSpines()) {
			continue;
		}
		HTp token = infile.token(i, 0);
		if (token->compare(0, 7, "!!!key:") == 0) {
			foundKey = i;
			break;
		}
	}

	if (foundKey < 0) {
		m_errorCount++;
		m_errorList += "!!!TOOL-nproof-error-" + to_string(m_errorCount) + ": No <tt>!!!key:</tt> reference record.\n";
		m_errorHtml += "!! <li> @{TOOL-nproof-error-" + to_string(m_errorCount) + "} </li>\n";
		return;
	}

	string value = infile[foundKey].getReferenceValue();
	if (value.empty()) {
		m_errorCount++;
		m_errorList += "!!!TOOL-nproof-error-" + to_string(m_errorCount) + ": <tt>!!!key:</tt> reference record on line " + to_string(foundKey+1) + " should not be empty.  If no key, then use \"none\".\n";
		m_errorHtml += "!! <li> @{TOOL-nproof-error-" + to_string(m_errorCount) + "} </li>\n";
		return;
	}

	HumRegex hre;
	if (hre.search(value, "^([a-gA-G][#-n]?):(dor|phr|lyd|mix|aeo|loc|ion)$")) {
		string tonic = hre.getMatch(1);
		string mode  = hre.getMatch(2);
		int major = 0;
		if ((mode == "lyd") || (mode == "mix") || (mode == "ion")) {
			major = 1;
		}
		int uppercase = isupper(tonic[0]);
		if ((major == 1) && (uppercase == 0)) {
			tonic[0] = toupper(tonic[0]);
			string correct = tonic + ":" + mode;
			m_errorCount++;
			m_errorList += "!!!TOOL-nproof-error-" + to_string(m_errorCount) + ": <tt>!!!key:</tt> reference record on line " + to_string(foundKey + 1) + " should be \"" + correct + "\".\n";
			m_errorHtml += "!! <li> @{TOOL-nproof-error-" + to_string(m_errorCount) + "} </li>\n";
		} else if ((major == 0) && (uppercase == 1)) {
			tonic[0] = tolower(tonic[0]);
			string correct = tonic + ":" + mode;
			m_errorCount++;
			m_errorList += "!!!TOOL-nproof-error-" + to_string(m_errorCount) + ": <tt>!!!key:</tt> reference record on line " + to_string(foundKey + 1) + " should be \"" + correct + "\".\n";
			m_errorHtml += "!! <li> @{TOOL-nproof-error-" + to_string(m_errorCount) + "} </li>\n";
		}
	} else if (hre.search(value, "([a-gA-G][#-n]?):(.+)")) {
		string mode = hre.getMatch(2);
		m_errorCount++;
		m_errorList += "!!!TOOL-nproof-error-" + to_string(m_errorCount) + ": Unknown mode in <tt>!!!key:</tt> reference record contents on line " + to_string(foundKey + 1) + ": \"" + mode + "\".\n";
		m_errorHtml += "!! <li> @{TOOL-nproof-error-" + to_string(m_errorCount) + "} </li>\n";
	} else if (!hre.search(value, "([a-gA-G][#-n]?):?")) {
		m_errorCount++;
		m_errorList += "!!!TOOL-nproof-error-" + to_string(m_errorCount) + ": Unknown key designation in <tt>!!!key:</tt> reference record contents on line " + to_string(foundKey + 1) + ": \"" + value + "\".\n";
		m_errorHtml += "!! <li> @{TOOL-nproof-error-" + to_string(m_errorCount) + "} </li>\n";
	}

}



//////////////////////////////
//
// Tool_nproof::checkSpineTerminations --
//

void Tool_nproof::checkSpineTerminations(HumdrumFile& infile) {
	int foundTerminal = 0;
	for (int i=infile.getLineCount() - 1; i>0; i--) {
		if (!infile[i].isInterpretation()) {
			continue;
		}
		HTp token = infile.token(i, 0);
		if (*token == "*-") {
			foundTerminal = i;
			break;
		}
	}

	if (!foundTerminal) {
		m_errorCount++;
		m_errorList += "!!!TOOL-nproof-error-" + to_string(m_errorCount) + ": No spine terminators.\n";
		m_errorHtml += "!! <li> @{TOOL-nproof-error-" + to_string(m_errorCount) + "} </li>\n";
		return;
	}

	bool problem = false;
	for (int i=0; i<infile[foundTerminal].getFieldCount(); i++) {
		HTp token = infile[foundTerminal].token(i);
		string value = token->getSpineInfo();
		if (value.find(" ") != string::npos) {
			problem = true;
			break;
		}
	}

	if (!problem) {
		return;
	}

	m_errorCount++;
	m_errorList += "!!!TOOL-nproof-error-" + to_string(m_errorCount) + ": Incorrect spine merger(s): ";
	for (int i=0; i<infile[foundTerminal].getFieldCount(); i++) {
		HTp token = infile[foundTerminal].token(i);
		m_errorList += "<" + token->getSpineInfo() + ">";
		if (i < infile[foundTerminal].getFieldCount() - 1) {
			m_errorList += " ";
		}
	}
	m_errorList += "\n";
	m_errorHtml += "!! <li> @{TOOL-nproof-error-" + to_string(m_errorCount) + "} </li>\n";
}





/////////////////////////////////
//
// Tool_ordergps::Tool_ordergps -- Set the recognized options for the tool.
//

Tool_ordergps::Tool_ordergps(void) {
	define("e|empty=b",   "list files that have no group/part/staff (used with -p option).");
	define("f|file=b",    "list input files only.");
	define("l|list=b",    "list files that will be changed.");
	define("p|problem=b", "list files that have mixed content for *group, *part, *staff info.");
	define("r|reverse=b", "order *staff, *part, *group");
	define("s|staff=b",   "Add staff line if none present already in score.");
	define("t|top=b",     "Place group/part/staff lines first after exinterp.");
}



/////////////////////////////////
//
// Tool_ordergps::run -- Do the main work of the tool.
//

bool Tool_ordergps::run(HumdrumFileSet& infiles) {
	bool status = true;
	for (int i=0; i<infiles.getCount(); i++) {
		status &= run(infiles[i]);
	}
	return status;
}


bool Tool_ordergps::run(const string& indata, ostream& out) {
	HumdrumFile infile(indata);
	bool status = run(infile);
	if (hasAnyText()) {
		getAllText(out);
	} else {
		out << infile;
	}
	return status;
}


bool Tool_ordergps::run(HumdrumFile& infile, ostream& out) {
	bool status = run(infile);
	if (hasAnyText()) {
		getAllText(out);
	} else {
		out << infile;
	}
	return status;
}


bool Tool_ordergps::run(HumdrumFile& infile) {
	initialize();
	processFile(infile);
	return true;
}



//////////////////////////////
//
// Tool_ordergps::initialize -- Setup to do before processing a file.
//

void Tool_ordergps::initialize(void) {
	m_emptyQ   = getBoolean("empty");
	m_fileQ    = getBoolean("file");
	m_listQ    = getBoolean("list");
	m_problemQ = getBoolean("problem");
	m_reverseQ = getBoolean("reverse");
	m_staffQ   = getBoolean("staff");
	m_topQ     = getBoolean("top");
}



//////////////////////////////
//
// Tool_ordergps::processFile -- Analyze an input file.
//

void Tool_ordergps::processFile(HumdrumFile& infile) {
	vector<int> groupIndex;
	vector<int> partIndex;
	vector<int> staffIndex;
	bool foundProblem = false;

	for (int i=0; i<infile.getLineCount(); i++) {
		if (infile[i].isData()) {
			break;
		}
		if (infile[i].isManipulator()) {
			// Don't deall with spine splits/mergers/exchanges/additions.
			if (!infile[i].isExclusiveInterpretation()) {
				break;
			}
		}
		if (infile[i].isCommentLocal()) {
			// Don't process after local comments.   The file header
			// is too complex perhaps, so do not alter anything
			// after the local comment.  This can be related to modori
			// assignment for groups, for example.
			break;
		}
		if (!infile[i].hasSpines()) {
			continue;
		}
		if (infile[i].isExclusiveInterpretation()) {
			continue;
		}
		if (!infile[i].isInterpretation()) {
			continue;
		}
		int hasGroup = false;
		int hasPart  = false;
		int hasStaff = false;
		int hasOther = false;
		for (int j=0; j<infile[i].getFieldCount(); j++) {
			HTp token = infile.token(i, j);
			if (*token == "*") {
				continue;
			}
			if (token->compare(0, 6, "*group") == 0) {
				hasGroup = true;
			} else if (token->compare(0, 5, "*part") == 0) {
				hasPart = true;
			} else if (token->compare(0, 6, "*staff") == 0) {
				hasStaff = true;
			} else {
				hasOther = true;
			}
		}

		if (hasOther && hasGroup) {
			foundProblem = true;
			if (m_problemQ) {
				cerr << infile.getFilename() << " HAS MIXED GROUP LINE:" << endl;
				cerr << "\t" << infile[i] << endl;
			}
		}

		if (hasOther && hasPart) {
			foundProblem = true;
			if (m_problemQ) {
				cerr << infile.getFilename() << " HAS MIXED PART LINE:" << endl;
				cerr << "\t" << infile[i] << endl;
			}
		}

		if (hasOther && hasStaff) {
			foundProblem = true;
			if (m_problemQ) {
				cerr << infile.getFilename() << " HAS MIXED STAFF LINE:" << endl;
				cerr << "\t" << infile[i] << endl;
			}
		}

		if (hasOther) {
			continue;
		}

		if (hasGroup && hasPart) {
			foundProblem = true;
			if (m_problemQ) {
				cerr << infile.getFilename() << " HAS MIXED GROUP AND PART LINE:" << endl;
				cerr << "\t" << infile[i] << endl;
			}
		}

		if (hasGroup && hasStaff) {
			foundProblem = true;
			if (m_problemQ) {
				cerr << infile.getFilename() << " HAS MIXED GROUP AND STAFF LINE:" << endl;
				cerr << "\t" << infile[i] << endl;
			}
		}

		if (hasPart && hasStaff) {
			foundProblem = true;
			if (m_problemQ) {
				cerr << infile.getFilename() << " HAS MIXED PART AND STAFF LINE:" << endl;
				cerr << "\t" << infile[i] << endl;
			}
		}

		if (hasGroup) {
			groupIndex.push_back(i);
		}

		if (hasPart)  {
			partIndex.push_back(i);
		}

		if (hasStaff) {
			staffIndex.push_back(i);
		}
	}

	if (groupIndex.size() > 1) {
		foundProblem = true;
		if (m_problemQ) {
			cerr << infile.getFilename() << " HAS MORE THAN ONE GROUP LINE:" << endl;
			for (int i=0; i<(int)groupIndex.size(); i++) {
				cerr << "\t" << infile[groupIndex[i]] << endl;
			}
		}
	}

	if (partIndex.size() > 1) {
		foundProblem = true;
		if (m_problemQ) {
			cerr << infile.getFilename() << " HAS MORE THAN ONE PART LINE:" << endl;
			for (int i=0; i<(int)partIndex.size(); i++) {
				cerr << "\t" << infile[partIndex[i]] << endl;
			}
		}
	}

	if (staffIndex.size() > 1) {
		foundProblem = true;
		if (m_problemQ) {
			cerr << infile.getFilename() << " HAS MORE THAN ONE STAFF LINE:" << endl;
			for (int i=0; i<(int)staffIndex.size(); i++) {
				cerr << "\t" << infile[staffIndex[i]] << endl;
			}
		}
	}

	if (m_problemQ) {
		if (m_emptyQ) {
			if (groupIndex.empty() && partIndex.empty() && staffIndex.empty()) {
				cerr << infile.getFilename() << " HAS NO GROUP/PART/STAFF INFO" << endl;
			}
		}
	} else {
		if (foundProblem) {
			// Don try to fix anything, just echo the input:
			m_humdrum_text << infile;
		} else {
			if (m_staffQ && groupIndex.empty() && partIndex.empty() && staffIndex.empty()) {
				printStaffLine(infile);
			} else {
				// Process further here
				// Check the order of the group/part/staff lines.
				int gindex = groupIndex.empty() ? -1 : groupIndex.at(0);
				int pindex = partIndex.empty() ? -1 : partIndex.at(0);
				int sindex = staffIndex.empty() ? -1 : staffIndex.at(0);
				if (m_topQ) {
					printFileTop(infile, gindex, pindex, sindex);
				} else {
					printFile(infile, gindex, pindex, sindex);
				}
			}
		}
	}
}



//////////////////////////////
//
// Tool_ordergps::printFileTop -- Print group/part/staff first after exclusive
//     interpretations.
//

void Tool_ordergps::printFileTop(HumdrumFile& infile, int gindex, int pindex, int sindex) {
	for (int i=0; i<infile.getLineCount(); i++) {
		if (i == gindex) {
			continue;
		} else if (i == pindex) {
			continue;
		} else if (i == sindex) {
			continue;
		} else if (infile[i].isExclusiveInterpretation()) {
			m_humdrum_text << infile[i] << endl;
			if (m_reverseQ) {
				if (sindex >= 0) {
					m_humdrum_text << infile[sindex] << endl;
				}
				if (pindex >= 0) {
					m_humdrum_text << infile[pindex] << endl;
				}
				if (gindex >= 0) {
					m_humdrum_text << infile[gindex] << endl;
				}
			} else {
				if (gindex >= 0) {
					m_humdrum_text << infile[gindex] << endl;
				}
				if (pindex >= 0) {
					m_humdrum_text << infile[pindex] << endl;
				}
				if (sindex >= 0) {
					m_humdrum_text << infile[sindex] << endl;
				}
			}
		} else {
			m_humdrum_text << infile[i] << endl;
		}
	}
}



//////////////////////////////
//
// Tool_ordergps::printFile -- Check to see if the group/part/staff
//    lines need to be adjusted, and the print the file.  Lines
//    will be ordered group/part/staff, placing the lines where
//    the first of group/part/staff is found.
//

void Tool_ordergps::printFile(HumdrumFile& infile, int gindex, int pindex, int sindex) {
	int startIndex = gindex;
	if (pindex >= 0) {
		if (startIndex < 0) {
			startIndex = pindex;
		} else if (pindex < startIndex) {
			startIndex = pindex;
		}
	}
	if (sindex >= 0) {
		if (startIndex < 0) {
			startIndex = sindex;
		} else if (sindex < startIndex) {
			startIndex = sindex;
		}
	}
	if (startIndex < 0) {
		// no group/part/staff lines in file, so just print it:
		m_humdrum_text << infile;
		return;
	}

	for (int i=0; i<infile.getLineCount(); i++) {
		if (i == startIndex) {
			if (m_reverseQ) {
				if (sindex >= 0) {
					m_humdrum_text << infile[sindex] << endl;
				}
				if (pindex >= 0) {
					m_humdrum_text << infile[pindex] << endl;
				}
				if (gindex >= 0) {
					m_humdrum_text << infile[gindex] << endl;
				}
			} else {
				if (gindex >= 0) {
					m_humdrum_text << infile[gindex] << endl;
				}
				if (pindex >= 0) {
					m_humdrum_text << infile[pindex] << endl;
				}
				if (sindex >= 0) {
					m_humdrum_text << infile[sindex] << endl;
				}
			}
		} else if (i == gindex) {
			continue;
		} else if (i == pindex) {
			continue;
		} else if (i == sindex) {
			continue;
		} else {
			m_humdrum_text << infile[i] << endl;
		}
	}
}



//////////////////////////////
//
// Tool_ordergps::printStaffLine --  Add a *staff at the start of the
//     data since none was detected.  Does not label staff-like spines
//     other than **kern (such as **kernyy, **kern-mod, **mens).

void Tool_ordergps::printStaffLine(HumdrumFile& infile) {
	for (int i=0; i<infile.getLineCount(); i++) {
		if (!infile[i].isExclusiveInterpretation()) {
			m_humdrum_text << infile[i] << endl;
			continue;
		}
		m_humdrum_text << infile[i] << endl;
		vector<string> staffLine(infile[i].getFieldCount(), "*");
		int counter = 0;
		for (int j=infile[i].getFieldCount() - 1; j>=0; j--) {
			HTp token = infile.token(i, j);
			if (token->isKern()) {
				counter++;
				string text = "*staff" + to_string(counter);
				staffLine.at(j) = text;
			}
		}
		for (int j=0; j<(int)staffLine.size(); j++) {
			m_humdrum_text << staffLine[j];
			if (j < (int)staffLine.size() - 1) {
				m_humdrum_text << '\t';
			}
		}
		m_humdrum_text << endl;
	}
}




/////////////////////////////////
//
// Tool_pbar::Tool_pbar -- Set the recognized options for the tool.
//

Tool_pbar::Tool_pbar(void) {
	define("i|invisible-barlines=b", "make barlines invisible");
}



//////////////////////////////
//
// Tool_pbar::initialize --  Initializations that only have to be done once
//    for all HumdrumFile segments.
//

void Tool_pbar::initialize(void) {
	m_invisibleQ = getBoolean("invisible-barlines");
}



/////////////////////////////////
//
// Tool_pbar::run -- Do the main work of the tool.
//

bool Tool_pbar::run(HumdrumFileSet& infiles) {
	bool status = true;
	for (int i=0; i<infiles.getCount(); i++) {
		status &= run(infiles[i]);
	}
	return status;
}



bool Tool_pbar::run(const string& indata, ostream& out) {
	HumdrumFile infile(indata);
	bool status = run(infile);
	if (hasAnyText()) {
		getAllText(out);
	} else {
		out << infile;
	}
	return status;
}


bool Tool_pbar::run(HumdrumFile& infile, ostream& out) {
	bool status = run(infile);
	if (hasAnyText()) {
		getAllText(out);
	} else {
		out << infile;
	}
	return status;
}


bool Tool_pbar::run(HumdrumFile& infile) {
	initialize();
	processFile(infile);
	return true;
}



//////////////////////////////
//
// Tool_pbar::processFile --
//

void Tool_pbar::processFile(HumdrumFile& infile) {
	vector<HTp> kstarts = infile.getKernSpineStartList();
	for (int i=0; i<(int)kstarts.size(); i++) {
		processSpine(kstarts[i]);
	}

	for (int i=0; i<infile.getLineCount(); i++) {
		if (!infile[i].hasSpines()) {
			m_humdrum_text << infile[i] << endl;
			continue;
		}
		if (infile[i].isData()) {
			printDataLine(infile, i);
		} else if (infile[i].isCommentLocal()) {
			printLocalCommentLine(infile, i);
		} else if (infile[i].isBarline()) {
			printBarLine(infile, i);
			if (m_invisibleQ) {
				printInvisibleBarlines(infile, i);
			} else {
				m_humdrum_text << infile[i] << endl;
			}
		} else {
			m_humdrum_text << infile[i] << endl;
		}
	}
}



//////////////////////////////
//
// Tool_pbar::printInvisibleBarlines --
//

void Tool_pbar::printInvisibleBarlines(HumdrumFile& infile, int index) {
	HumRegex hre;
	for (int i=0; i<infile[index].getFieldCount(); i++) {
		HTp token = infile.token(index, i);
		if (hre.search(token, "-")) {
			m_humdrum_text << token;
		} else if (hre.search(token, "==")) {
			m_humdrum_text << token;
		} else if (hre.search(token, "\\|\\|")) {
			m_humdrum_text << token;
		} else {
			m_humdrum_text << token << "-";
		}
		if (i < infile[index].getFieldCount() - 1) {
			m_humdrum_text << "\t";
		}
	}
	m_humdrum_text << "\n";
}



///////////////////////////////
//
// Tool_pbar::printDataLine --
//

void Tool_pbar::printDataLine(HumdrumFile& infile, int index) {
	printBarLine(infile, index);
	m_humdrum_text << infile[index] << endl;
}



///////////////////////////////
//
// Tool_pbar::printBarLine -- Add *bar line.
//

void Tool_pbar::printBarLine(HumdrumFile& infile, int index) {
	bool hasBarline = false;
	for (int j=0; j<infile[index].getFieldCount(); j++) {
		HTp token = infile.token(index, j);
		string value = token->getValue("auto", "pbar");
		if (value == "true") {
			hasBarline = true;
			break;
		}
	}

	if (hasBarline) {
		for (int j=0; j<infile[index].getFieldCount(); j++) {
			HTp token = infile.token(index, j);
			string value = token->getValue("auto", "pbar");
			if (value == "true") {
				m_humdrum_text << "*bar";
			} else {
				m_humdrum_text << "*";
			}
			if (j < infile[index].getFieldCount() - 1) {
				m_humdrum_text << "\t";
			}
		}
		m_humdrum_text << "\n";
	}
}



///////////////////////////////
//
// Tool_pbar::printLocalCommentLine --
//

void Tool_pbar::printLocalCommentLine(HumdrumFile& infile, int index) {
	HumRegex hre;
	bool hasKp = false;
	bool hasOther = false;
	for (int i=0; i<infile[index].getFieldCount(); i++) {
		HTp token = infile.token(index, i);
		if (hre.search(token, "kreska pseudotaktowa")) {
			hasKp = true;
		} else if (*token != "!") {
			hasOther = true;
		}
	}

	if (!hasKp) {
		m_humdrum_text << infile[index] << endl;
		return;
	}

	if (hasOther) {
		for (int i=0; i<infile[index].getFieldCount(); i++) {
			HTp token = infile.token(index, i);
			if (hre.search(token, "kreska pseudotaktowa")) {
				m_humdrum_text << "!";
			} else {
				m_humdrum_text << token;
			}
			if (i < infile[index].getFieldCount() - 1) {
				m_humdrum_text << "\t";
			}
		}
		m_humdrum_text << "\n";
	}
}



//////////////////////////////
//
// Tool_pbar::processSpine --
//

void Tool_pbar::processSpine(HTp spineStart) {
	HTp current = spineStart;
	HumRegex hre;
	while (current) {
		if (!current->isLocalComment()) {
			current = current->getNextToken();
			continue;
		}
		if (hre.search(current, "kreska\\s*pseudotaktowa")) {
			addBarLineToFollowingNoteOrRest(current);
		}
		current = current->getNextToken();
	}
}



//////////////////////////////
//
// Tool_pbar::addBarLineToFollowingNoteOrRest --
//

void Tool_pbar::addBarLineToFollowingNoteOrRest(HTp token) {
	HTp current = token->getNextToken();
	int counter = 0;
	while (current) {
		if (!current->isBarline()) {
			if (!current->isData() || current->isNull()) {
				current = current->getNextToken();
				continue;
			}
		}
		counter++;
		if (counter == 2) {
			current->setValue("auto", "pbar", "true");
			break;
		}
		current = current->getNextToken();
	}
}




/////////////////////////////////
//
// Tool_gridtest::Tool_pccount -- Set the recognized options for the tool.
//

Tool_pccount::Tool_pccount(void) {
	define("a|attacks=b",                 "count attacks instead of durations");
	define("d|data|vega-data=b",          "display the vega-lite template.");
	define("f|full=b",                    "full count attacks all single sharps and flats.");
	define("ff|double-full=b",            "full count attacks all double sharps and flats.");
	define("h|html=b",                    "generate vega-lite HTML content");
	define("i|id=s:id",                   "ID for use as variable and in plot title");
	define("K|no-key|no-final=b",         "do not label key tonic or final");
	define("m|maximum=b",                 "normalize by maximum count");
	define("n|normalize=b",               "normalize counts");
	define("p|page=b",                    "generate vega-lite stand-alone HTML page");
	define("r|ratio|aspect-ratio=d:0.67", "width*ratio=height of vega-lite plot");
	define("s|script|vega-script=b",      "generate vega-lite javascript content");
	define("title=s",                     "title for plot");
	define("t|template|vega-template=b",  "display the vega-lite template.");
	define("w|width=i:400",               "width of vega-lite plot");
}



///////////////////////////////
//
// Tool_pccount::run -- Primary interfaces to the tool.
//

bool Tool_pccount::run(HumdrumFileSet& infiles) {
	bool status = true;
	for (int i=0; i<infiles.getCount(); i++) {
		status &= run(infiles[i]);
	}
	return status;
}


bool Tool_pccount::run(const string& indata, ostream& out) {
	HumdrumFile infile(indata);
	return run(infile, out);
}


bool Tool_pccount::run(HumdrumFile& infile, ostream& out) {
	bool status = run(infile);
	return status;
}


bool Tool_pccount::run(HumdrumFile& infile) {
   initialize(infile);
	processFile(infile);
	return true;
}



//////////////////////////////
//
// Tool_pccount::initialize --
//

void Tool_pccount::initialize(HumdrumFile& infile) {
	m_attack     = getBoolean("attacks");
	m_full       = getBoolean("full");
	m_doublefull = getBoolean("double-full");
	m_normalize  = getBoolean("normalize");
	m_maximum    = getBoolean("maximum");
	m_template   = getBoolean("vega-template");
	m_data       = getBoolean("vega-data");
	m_script     = getBoolean("vega-script");
	m_width      = getInteger("width");
	m_ratio      = getDouble("aspect-ratio");
	m_key        = !getBoolean("no-key");
	if (getBoolean("title")) {
		m_title = getString("title");
	}
	m_html       = getBoolean("html");
	m_page       = getBoolean("page");
	if (getBoolean("id")) {
		m_id = getString("id");
	} else {
		string filename = infile.getFilename();
	 	auto pos = filename.rfind("/");
		if (pos != string::npos) {
			filename = filename.substr(pos+1);
		}
		pos = filename.find("-");
		if (pos != string::npos) {
			m_id = filename.substr(0, pos);
		}
	}
	m_parttracks.clear();
	m_names.clear();
	m_abbreviations.clear();
	initializePartInfo(infile);


	// https://encycolorpedia.com/36cd27
	m_vcolor.clear();

	m_vcolor["Canto"]		=	"#e49689";
	m_vcolor["Canto (Canto I)"]	=	"#e49689";
	m_vcolor["Canto I"]		=	"#e49689";
	m_vcolor["Canto Primo"]		=	"#e49689";
	m_vcolor["[Canto 1]"]		=	"#e49689";
	m_vcolor["[Canto]"]		=	"#e49689";
	m_vcolor["[Soprano o Tenore]"]	=	"#e49689";
	m_vcolor["Soprano"]		=	"#e49689";

	m_vcolor["Canto 2."]		=	"#d67365";
	m_vcolor["Canto II"]		=	"#d67365";
	m_vcolor["Canto II [Sesto]"]	=	"#d67365";
	m_vcolor["Canto Sec."]		=	"#d67365";
	m_vcolor["Canto Secondo"]	=	"#d67365";
	m_vcolor["Canto secondo"]	=	"#d67365";
	m_vcolor["[Canto 2]"]		=	"#d67365";

	m_vcolor["Canto III [Settimo]"]	=	"#c54f43";

	m_vcolor["Alto"]		=	"#f4c6a1";
	m_vcolor["Alti"]		=	"#f4c6a1";
	m_vcolor["Alto (Canto III)"]	=	"#f4c6a1";

	m_vcolor["Alto II"]		=	"#edb383";

	m_vcolor["Tenor"]		=	"#ecdf7a";
	m_vcolor["Tenore"]		=	"#ecdf7a";
	m_vcolor["Tenore over Canto"]	=	"#ecdf7a";
	m_vcolor["[Tenore]"]		=	"#ecdf7a";

	m_vcolor["Sesto"]		=	"#c8f0bb";
	m_vcolor["Sesto (Canto II)"]	=	"#c8f0bb";
	m_vcolor["Sesto Canto II"]	=	"#c8f0bb";

	m_vcolor["Quinto"]		=	"#e3f5f8";
	m_vcolor["Qvinto"]		=	"#e3f5f8";

	m_vcolor["Ottava parte [Ottavo]"]	=	"#e0e4f7";

	m_vcolor["Nona parte [Nono]"]	=	"#a39ce5";

	m_vcolor["Basso"]		=	"#d2aef7";
	m_vcolor["Bass"]		=	"#d2aef7";

	m_vcolor["Basso II"]		=	"#c69af5";
	m_vcolor["Basso II [Decimo]"]	=	"#c69af5";

	m_vcolor["Basso Continuo"]	=	"#a071ec";
	m_vcolor["Basso continuo"]	=	"#a071ec";
	m_vcolor["[B. C.]"]		=	"#a071ec";
	m_vcolor["[Basso Continuo]"]	=	"#a071ec";
	m_vcolor["[Basso continuo]"]	=	"#a071ec";
	m_vcolor["B.C."]		=	"#a071ec";
	m_vcolor["B.c."]		=	"#a071ec";
}



//////////////////////////////
//
// Tool_pccount::getFinal -- Extract the last unparenthesed letter from a ref record like this:
//
// !!!final: (A)D
//

string Tool_pccount::getFinal(HumdrumFile& infile) {
	string finalref = infile.getReferenceRecord("final");
	HumRegex hre;
	hre.replaceDestructive(finalref, "", "\\(.*?\\)", "g");
	hre.replaceDestructive(finalref, "", "\\s+", "g");
	if (hre.search(finalref, "^[A-G]$", "i")) {
		return finalref;
	} else {
		return "";
	}
}



//////////////////////////////
//
// Tool_pccount::processFile --
//

void Tool_pccount::processFile(HumdrumFile& infile) {
	countPitches(infile);

	string datavar;
	string target;
	string jsonvar;

	if (m_attack) {
		datavar = "data_" + m_id + "_count";
		target = "id_" + m_id + "_count";
		jsonvar = "vega_" + m_id + "_count";
	} else {
		datavar = "data_" + m_id + "_dur";
		target = "id_" + m_id + "_dur";
		jsonvar = "vega_" + m_id + "_dur";
	}

	if (m_template) {
		printVegaLiteJsonTemplate(datavar, infile);
	} else if (m_data) {
		printVegaLiteJsonData();
	} else if (m_script) {
		printVegaLiteScript(jsonvar, target, datavar, infile);
	} else if (m_html) {
		printVegaLiteHtml(jsonvar, target, datavar, infile);
	} else if (m_page) {
		printVegaLitePage(jsonvar, target, datavar, infile);
	} else {
		printHumdrumTable();
	}
}



//////////////////////////////
//
// Tool_pccount::printVegaLitePage --
//

void Tool_pccount::printVegaLitePage(const string& jsonvar,
		const string& target, const string& datavar, HumdrumFile& infile) {
	stringstream& out = m_free_text;

	out << "<!DOCTYPE html>\n";
	out << "<html>\n";
	out << "  <head>\n";
	out << "    <title>Vega-Lite Bar Chart</title>\n";
	out << "    <meta charset=\"utf-8\" />\n";
	out << "\n";
	out << "    <script src=\"https://cdn.jsdelivr.net/npm/vega@5.4.0\"></script>\n";
	out << "    <script src=\"https://cdn.jsdelivr.net/npm/vega-lite@4.0.0-beta.1\"></script>\n";
	out << "    <script src=\"https://cdn.jsdelivr.net/npm/vega-embed@5\"></script>\n";
	out << "\n";
	out << "    <style media=\"screen\">\n";
	out << "      /* Add space between Vega-Embed links  */\n";
	out << "      .vega-actions a {\n";
	out << "        margin-right: 5px;\n";
	out << "      }\n";
	out << "    </style>\n";
	out << "  </head>\n";
	out << "  <body>\n";
	out << "    <h1>Pitch-class histogram</h1>\n";
	printVegaLiteHtml(jsonvar, target, datavar, infile);
	out << "</body>\n";
	out << "</html>\n";
}



//////////////////////////////
//
// Tool_pccount::printVegaLiteHtml --
//

void Tool_pccount::printVegaLiteHtml(const string& jsonvar,
		const string& target, const string& datavar, HumdrumFile& infile) {
	stringstream& out = m_free_text;

	out << "<div class=\"vega-svg\" id=\"" << target << "\"></div>\n";
	out << "\n";
	out << "<script>\n";
	printVegaLiteScript(jsonvar, target, datavar, infile);
	out << "</script>\n";
}



//////////////////////////////
//
// Tool_pccount::printVegaLiteScript --
//

void Tool_pccount::printVegaLiteScript(const string& jsonvar,
		const string& target, const string& datavar, HumdrumFile& infile) {
	stringstream& out = m_free_text;

	out << "var " << datavar << " =\n";
	printVegaLiteJsonData();
	out << ";\n";
	out << "\n";
	out << "var " << jsonvar << " =\n";
	printVegaLiteJsonTemplate(datavar, infile);
	out << ";\n";
	out << "vegaEmbed('#" << target << "', " << jsonvar << ");\n";
}



//////////////////////////////
//
// Tool_pccount::printVegaLiteJsonData --
//

void Tool_pccount::printVegaLiteJsonData(void) {
	stringstream& out = m_free_text;

	m_maxpc = 0;
	for (int i=0; i<(int)m_counts[0].size(); i++) {
		if (m_counts[0][i] > m_maxpc) {
			m_maxpc = m_counts[0][i];
		}
	}
	out << "[\n";
	int commacounter = 0;
	double percent = 100.0;
	for (int i=1; i<(int)m_counts.size(); i++) {
		for (int j=0; j<(int)m_counts[i].size(); j++) {
			if (m_counts[i][j] == 0.0) {
				continue;
			}
			if (commacounter > 0) {
				out << ",\n\t";
			} else {
				out << "\t";
			}
			commacounter++;
			if (m_attack) {
				out << "{\"count\":" << m_counts[i][j] << ", ";
			} else {
				out << "{\"percent\":" << m_counts[i][j]/m_maxpc*percent << ", ";
			}
			out << "\"pitch class\":\"" << getPitchClassString(j) << "\", ";
			out << "\"voice\":\"" << m_names[i] << "\"";
			out << "}";
		}
	}
	out << "\n]\n";
}



//////////////////////////////
//
// Tool_pccount::setFactorMaximum -- normalize by the maximum pitch-class value.
//

void Tool_pccount::setFactorMaximum(void) {
	m_factor = 0.0;
	for (int i=0; i<(int)m_counts[0].size(); i++) {
		if (m_counts[0][i] > m_factor) {
			m_factor = m_counts[0][i];
		}
	}
}



//////////////////////////////
//
// Tool_pccount::setFactorNormalize -- normalize by the sum of all pitch class values.
//

void Tool_pccount::setFactorNormalize(void) {
	m_factor = 0.0;
	for (int i=0; i<(int)m_counts[0].size(); i++) {
		m_factor += m_counts[0][i];
	}
}



//////////////////////////////
//
// Tool_pccount::printHumdrumTable --
//

void Tool_pccount::printHumdrumTable(void) {

	double factor = 0.0;

	if (m_maximum) {
		setFactorMaximum();
		m_free_text << "!!!MAX: " << m_factor << endl;
	} else if (m_normalize) {
		setFactorNormalize();
		m_free_text << "!!!TOTAL: " << factor << endl;
	}

	// exclusive interpretation
	m_free_text << "**kern";
	m_free_text << "\t**all";
	for (int i=0; i<(int)m_counts.size() - 1; i++) {
		m_free_text << "\t**part";
	}
	m_free_text << endl;

	// part names
	m_free_text << "*";
	for (int i=0; i<(int)m_counts.size(); i++) {
		if (i < (int)m_names.size()) {
			m_free_text << "\t*I\"" << m_names.at(i);
		} else {
			m_free_text << "\t*";
		}
	}
	m_free_text << endl;

	if (!m_abbreviations.empty()) {

		// part abbreviation
		m_free_text << "*";
		for (int i=0; i<(int)m_counts.size(); i++) {
			if (i < (int)m_abbreviations.size()) {
				m_free_text << "\t*I\'" << m_abbreviations.at(i);
			} else {
				m_free_text << "\t*";
			}
		}
		m_free_text << endl;
	}

	for (int i=0; i<(int)m_counts[0].size(); i++) {
		if (m_counts[0][i] == 0) {
			continue;
		}
		if ((i == 5) || (i == 11) || (i == 22) || (i == 28) || (i == 34)) {
			continue;
		}
		string pitch = Convert::base40ToKern(i + 4*40);
		m_free_text << pitch;
		for (int j=0; j<(int)m_counts.size(); j++) {
			if (m_normalize) {
				m_free_text << "\t" << m_counts[j][i] / m_factor;
			} else if (m_maximum) {
				m_free_text << "\t" << m_counts[j][i] / m_factor;
			} else {
				m_free_text << "\t" << m_counts[j][i];
			}
		}
		m_free_text << endl;
	}

	int columns = (int)m_counts.size() + 1;
	for (int i=0; i<columns; i++) {
		m_free_text << "*-";
		if (i < columns - 1) {
			m_free_text << "\t";
		}
	}
	m_free_text << endl;
}



//////////////////////////////
//
// Tool_pccount::countPitches --
//

void Tool_pccount::countPitches(HumdrumFile& infile) {
	if (m_parttracks.size() == 0) {
		return;
	}
	m_counts.clear();
	m_counts.resize(m_parttracks.size());
	for (int i=0; i<(int)m_parttracks.size(); i++) {
		m_counts[i].resize(40);
		fill(m_counts[i].begin(), m_counts[i].end(), 0.0);
	}
	for (int i=0; i<infile.getStrandCount(); i++) {
		HTp sstart = infile.getStrandStart(i);
		HTp send = infile.getStrandEnd(i);
		addCounts(sstart, send);
	}

	// fill in sum for all parts
	for (int i=0; i<(int)m_counts[0].size(); i++) {
		for (int j=1; j<(int)m_counts.size(); j++) {
			m_counts[0][i] += m_counts[j][i];
		}
	}

}


//////////////////////////////
//
// Tool_pccount::addCounts --
//

void Tool_pccount::addCounts(HTp sstart, HTp send) {
	if (!sstart) {
		return;
	}
	if (!sstart->isKern()) {
		return;
	}
	int track = sstart->getTrack();
	int kindex = m_rkern[track];
	HTp current = sstart;
	while (current && (current != send)) {
		if (!current->isData()) {
			current = current->getNextToken();
			continue;
		}
		if (current->isNull() || current->isRest()) {
			current = current->getNextToken();
			continue;
		}
		vector<string> subtokens = current->getSubtokens();
		for (int i=0; i<(int)subtokens.size(); i++) {
			if (m_attack) {
				// ignore sustained parts of notes when counting attacks
				if (subtokens[i].find("_") != string::npos) {
					continue;
				}
				if (subtokens[i].find("]") != string::npos) {
					continue;
				}
			}
			int b40 = Convert::kernToBase40(subtokens[i]);
			if (m_attack) {
				m_counts[kindex][b40%40]++;
			} else {
				double duration = Convert::recipToDuration(subtokens[i]).getFloat();
				m_counts[kindex][b40%40] += duration;
			}
		}
		current = current->getNextToken();
	}
}




//////////////////////////////
//
// Tool_pccount::initializePartInfo --
//

void Tool_pccount::initializePartInfo(HumdrumFile& infile) {
	m_names.clear();
	m_abbreviations.clear();
	m_parttracks.clear();
	m_rkern.clear();

	m_rkern.resize(infile.getTrackCount() + 1);
	fill(m_rkern.begin(), m_rkern.end(), -1);

	m_parttracks.push_back(-1);
	m_names.push_back("all");
	m_abbreviations.push_back("all");

	vector<HTp> starts = infile.getKernSpineStartList();

	int foundpart = false;
	int foundabbr = false;

	int track = 0;
	for (int i=0; i<(int)starts.size(); i++) {
		track = starts[i]->getTrack();
		m_rkern[track] = i+1;
		m_parttracks.push_back(track);
		HTp current = starts[i];
		foundpart = false;
		foundabbr = false;
		if (!current->isKern()) {
			continue;
		}
		while (current) {
			if (current->isData()) {
				break;
			}
			if ((!foundpart) && (current->compare(0, 3, "*I\"") == 0)) {
				m_names.emplace_back(current->substr(3));
				foundpart = true;
			} else if ((!foundabbr) && (current->compare(0, 3, "*I\'") == 0)) {
				m_abbreviations.emplace_back(current->substr(3));
				foundabbr = true;
			}
			current = current->getNextToken();
		}
		//if (!foundpart) {
		//		m_names.emplace_back("");
		//}
		//if (!foundabbr) {
		//		m_names.emplace_back("");
		//}
	}

}


//////////////////////////////
//
// printVegaLiteJsonTemplate --
//

void Tool_pccount::printVegaLiteJsonTemplate(const string& datavariable, HumdrumFile& infile) {
	stringstream& out = m_free_text;

	string idinfo;
	if (m_id.empty() || m_id == "id") {
		// do nothing
	} else {
		idinfo = "for " + m_id;
	}
	out << "{\n";
	out << "	\"$schema\": \"https://vega.github.io/schema/vega-lite/v4.0.0-beta.1.json\",\n";
	out << "	\"data\": {\"values\": " << datavariable << "},\n";
	if (getBoolean("title")) {
		out << "	\"title\": \"" << m_title << "\",\n";
	} else {
		if (m_attack) {
			out << "	\"title\": \"Note-count pitch-class distribution " << idinfo <<" \",\n";
		} else {
			out << "	\"title\": \"Duration-weighted pitch-class distribution " << idinfo <<" \",\n";
		}
	}
	out << "	\"width\": " << m_width << ",\n";
	out << "	\"height\": " << int(m_width * m_ratio) << ",\n";
	out << "	\"encoding\": {\n";
	out << "		\"y\": {\n";
	if (m_attack) {
		out << "			\"field\": \"count\",\n";
		out << "			\"title\": \"Number of note attacks\",\n";
	} else {
		out << "			\"field\": \"percent\",\n";
		out << "			\"title\": \"Percent of maximum pitch class\",\n";
	}
	out << "			\"type\": \"quantitative\",\n";
	if (m_attack) {
		out << "			\"scale\": {\"domain\": [0, " << m_maxpc << "]},\n";
	} else {
		out << "			\"scale\": {\"domain\": [0, 100]},\n";
	}
	out << "			\"aggregate\": \"sum\"\n";
	out << "		},\n";
	out << "		\"x\": {\n";
	out << "			\"field\": \"pitch class\",\n";
	out << "			\"type\": \"nominal\",\n";
	out << "			\"scale\": {\n";
	out << "				\"domain\": [";
		printPitchClassList();
		out << "]\n";
	out << "			},\n";
	out << "			\"axis\": {\n";
	out << "				\"labelAngle\": 0\n";
	out << "			}\n";
	out << "		},\n";
	out << "		\"order\": {\"type\": \"quantitative\"},\n";
	out << "		\"color\": {\n";
	out << "			\"field\": \"voice\",\n";
	out << "			\"type\": \"nominal\",\n";
	if (m_counts.size() == 2) {
		out << "			\"legend\": {\"title\": \"Voice\"},\n";
	} else {
		out << "			\"legend\": {\"title\": \"Voices\"},\n";
	}
	out << "			\"scale\": {\n";
  	out << "				\"domain\": [";
		printVoiceList();
		out << "],\n";
 	out << "				\"range\": [";
		printColorList();
		out << "],\n";
 	out << "				}\n";
	out << "		}\n";
	out << "	},\n";

	out << "	\"layer\": [\n";
	out << "		{\"mark\": \"bar\"}";

	string final = getFinal(infile);
	if (m_key && !final.empty()) {
		out << ",\n";
		out << "		{\n";
		out << "			\"mark\": {\"type\":\"text\", \"align\":\"center\", \"fill\":\"black\", \"baseline\":\"bottom\"},\n";
		if (m_attack) {
			int count = getCount(final);
			out << "			\"data\": {\"values\": [ {\"pitch class\":\"" << final << "\", \"count\":" << count << "}]},\n";
		} else {
			double percent = getPercent(final);
			out << "			\"data\": {\"values\": [ {\"pitch class\":\"" << final << "\", \"percent\":" << percent << "}]},\n";
		}
		out << "			\"encoding\": {\"text\": {\"value\":\"final\"}}\n";
		out << "		}\n";
	}

	out << "	]\n";
	out << "}\n";

}



//////////////////////////////
//
// Tool_pccount::getCount --
//

int Tool_pccount::getCount(const string& pitchclass) {
	int b40 = Convert::kernToBase40(pitchclass);
	int index = b40 % 40;
	int output = (int)m_counts[0][index];
	return output;
}



//////////////////////////////
//
// Tool_pccount::getPercent --
//

double Tool_pccount::getPercent(const string& pitchclass) {
	setFactorMaximum();
	int b40 = Convert::kernToBase40(pitchclass);
	int index = b40 % 40;
	double output = m_counts[0][index] / m_factor * 100.0;
	return output;
}



//////////////////////////////
//
// Tool_pccount::printColorList --
//

void Tool_pccount::printColorList(void) {
	stringstream& out = m_free_text;
	for (int i=(int)m_names.size() - 1; i>0; i--) {
		string color = m_vcolor[m_names[i]];
		out << "\"";
		if (color.empty()) {
			out << "black";
		} else {
			out << color;
		}
		out << "\"";
		if (i > 1) {
			out << ", ";
		}
	}
}



//////////////////////////////
//
// Tool_pccount::printVoiceList --
//

void Tool_pccount::printVoiceList(void) {
	stringstream& out = m_free_text;
	for (int i=(int)m_names.size() - 1; i>0; i--) {
		out << "\"";
		out << m_names[i];
		out << "\"";
		if (i > 1) {
			out << ", ";
		}
	}
}



//////////////////////////////
//
// Tool_pccount::printReverseVoiceList --
//

void Tool_pccount::printReverseVoiceList(void) {
	stringstream& out = m_free_text;
	for (int i=1; i<(int)m_names.size(); i++) {
		out << "\"";
		out << m_names[i];
		out << "\"";
		if (i < (int)m_names.size() - 1) {
			out << ", ";
		}
	}
}



//////////////////////////////
//
// Tool_pccount::printPitchClassList --
//

void Tool_pccount::printPitchClassList(void) {
	stringstream& out = m_free_text;

	if (m_counts[0][0] > 0.0)  { out << "\"C♭♭\", "; }
	if (m_counts[0][1] > 0.0)  { out << "\"C♭\", "; }
	out << "\"C\"";
	if (m_counts[0][3] > 0.0)  { out << ", \"C♯\""; }
	if (m_counts[0][4] > 0.0)  { out << ", \"C♯♯\""; }
	// 5 is empty

	if (m_counts[0][6] > 0.0)  { out << ", \"D♭♭\""; }
	if (m_counts[0][7] > 0.0)  { out << ", \"D♭\""; }
	out << ", \"D\"";
	if (m_counts[0][9] > 0.0)  { out << ", \"D♯\""; }
	if (m_counts[0][10] > 0.0) { out << ", \"D♯♯\""; }
	// 11 is empty

	if (m_counts[0][12] > 0.0) { out << ", \"E♭♭\""; }
	if (m_counts[0][13] > 0.0) { out << ", \"E♭\""; }
	out << ", \"E\"";
	if (m_counts[0][15] > 0.0) { out << ", \"E♯\""; }
	if (m_counts[0][16] > 0.0) { out << ", \"E♯♯\""; }

	if (m_counts[0][17] > 0.0) { out << ", \"F♭♭\""; }
	if (m_counts[0][18] > 0.0) { out << ", \"F♭\""; }
	out << ", \"F\"";
	if (m_counts[0][20] > 0.0) { out << ", \"F♯\""; }
	if (m_counts[0][21] > 0.0) { out << ", \"F♯♯\""; }
	// 22 is empty

	if (m_counts[0][23] > 0.0) { out << ", \"G♭♭\""; }
	if (m_counts[0][24] > 0.0) { out << ", \"G♭\""; }
	out << ", \"G\"";
	if (m_counts[0][26] > 0.0) { out << ", \"G♯\""; }
	if (m_counts[0][27] > 0.0) { out << ", \"G♯♯\""; }
	// 28 is empty

	if (m_counts[0][29] > 0.0) { out << ", \"A♭♭\""; }
	if (m_counts[0][30] > 0.0) { out << ", \"A♭\""; }
	out << ", \"A\"";
	if (m_counts[0][32] > 0.0) { out << ", \"A♯\""; }
	if (m_counts[0][33] > 0.0) { out << ", \"A♯♯\""; }
	// 34 is empty

	if (m_counts[0][35] > 0.0) { out << ", \"B♭♭\""; }
	if (m_counts[0][36] > 0.0) { out << ", \"B♭\""; }
	out << ", \"B\"";
	if (m_counts[0][38] > 0.0) { out << ", \"B♯\""; }
	if (m_counts[0][39] > 0.0) { out << ", \"B♯♯\""; }

}


//////////////////////////////
//
// Tool_pccount::getPitchClassString --
//

string Tool_pccount::getPitchClassString(int b40) {
	switch (b40%40) {
		case 0: return "C♭♭";
		case 1: return "C♭";
		case 2: return "C";
		case 3: return "C♯";
		case 4: return "C♯♯";
		// 5 is empty
		case 6: return "D♭♭";
		case 7: return "D♭";
		case 8: return "D";
		case 9: return "D♯";
		case 10: return "D♯♯";
		// 11 is empty
		case 12: return "E♭♭";
		case 13: return "E♭";
		case 14: return "E";
		case 15: return "E♯";
		case 16: return "E♯♯";
		case 17: return "F♭♭";
		case 18: return "F♭";
		case 19: return "F";
		case 20: return "F♯";
		case 21: return "F♯♯";
		// 22 is empty
		case 23: return "G♭♭";
		case 24: return "G♭";
		case 25: return "G";
		case 26: return "G♯";
		case 27: return "G♯♯";
		// 28 is empty
		case 29: return "A♭♭";
		case 30: return "A♭";
		case 31: return "A";
		case 32: return "A♯";
		case 33: return "A♯♯";
		// 34 is empty
		case 35: return "B♭♭";
		case 36: return "B♭";
		case 37: return "B";
		case 38: return "B♯";
		case 39: return "B♯♯";
	}

	return "?";
}






/////////////////////////////////
//
// Tool_periodicity::Tool_periodicity -- Set the recognized options for the tool.
//

Tool_periodicity::Tool_periodicity(void) {
	define("m|min=b",         "minimum time unit (other than grace notes)");
	define("n|max-rows=i:-1", "maxumum number of rows in svg analysis display");
	define("t|track=i:0",     "track to analyze");
	define("attacks=b",       "extract attack grid)");
	define("raw=b",           "show only raw period data");
	define("s|svg=b",         "output svg image");
	define("p|power=d:2.0",   "scaling power for visual display");
	define("1|one=b",         "composite rhythms are not weighted by attack");
}



/////////////////////////////////
//
// Tool_periodicity::run -- Primary interfaces to the tool.
//

bool Tool_periodicity::run(HumdrumFileSet& infiles) {
	bool status = true;
	for (int i=0; i<infiles.getCount(); i++) {
		status &= run(infiles[i]);
	}
	return status;
}


bool Tool_periodicity::run(const string& indata, ostream& out) {
	HumdrumFile infile(indata);
	bool status;
	status = run(infile);
	if (hasAnyText()) {
		getAllText(out);
	} else {
		out << infile;
	}
	return status;
}


bool Tool_periodicity::run(HumdrumFile& infile, ostream& out) {
	bool status;
	status = run(infile);
	if (hasAnyText()) {
		getAllText(out);
	} else {
		out << infile;
	}
	return status;
}

//
// In-place processing of file:
//

bool Tool_periodicity::run(HumdrumFile& infile) {
	processFile(infile);
	return true;
}



//////////////////////////////
//
// Tool_periodicity::processFile --
//

void Tool_periodicity::processFile(HumdrumFile& infile) {
	HumNum minrhy = infile.tpq() * 4;
	if (getBoolean("min")) {
		m_free_text << minrhy << endl;
		return;
	}

	vector<vector<double>> attackgrids;
	attackgrids.resize(infile.getTrackCount()+1);
	fillAttackGrids(infile, attackgrids, minrhy);
	if (getBoolean("attacks")) {
		printAttackGrid(m_free_text, infile, attackgrids, minrhy);
		return;
	}

	int atrack = getInteger("track");
	vector<vector<double>> analysis;
	doPeriodicityAnalysis(analysis, attackgrids[atrack], minrhy);

	if (getBoolean("raw")) {
		printPeriodicityAnalysis(m_free_text, analysis);
		return;
	}

	printSvgAnalysis(m_free_text, analysis, minrhy);
}



//////////////////////////////
//
// Tool_periodicity::printPeriodicityAnalysis --
//

void Tool_periodicity::printPeriodicityAnalysis(ostream& out, vector<vector<double>>& analysis) {
	for (int i=0; i<(int)analysis.size(); i++) {
		for (int j=0; j<(int)analysis[i].size(); j++) {
			out << analysis[i][j];
			if (j < (int)analysis[i].size() - 1) {
				out << "\t";
			}
		}
		out << "\n";
	}
}



//////////////////////////////
//
// Tool_periodicity::doPeriodicAnalysis --
//

void Tool_periodicity::doPeriodicityAnalysis(vector<vector<double>> &analysis, vector<double>& grid, HumNum minrhy) {
	analysis.resize(minrhy.getNumerator());
	for (int i=0; i<(int)analysis.size(); i++) {
		doAnalysis(analysis, i, grid);
	}
}



//////////////////////////////
//
// Tool_periodicity::doAnalysis --
//

void Tool_periodicity::doAnalysis(vector<vector<double>>& analysis, int level, vector<double>& grid) {
	int period = level + 1;
	analysis[level].resize(period);
	std::fill(analysis[level].begin(), analysis[level].end(), 0.0);
	for (int i=0; i<period; i++) {
		int j = i;
		while (j < (int)grid.size()) {
			analysis[level][i] += grid[j];
			j += period;
		}
	}
}



//////////////////////////////
//
// Tool_periodicity::printAttackGrid --
//

void Tool_periodicity::printAttackGrid(ostream& out, HumdrumFile& infile, vector<vector<double>>& grids, HumNum minrhy) {
	out << "!!!minrhy: " << minrhy << endl;
	out << "**all";
	for (int i=1; i<(int)grids.size(); i++) {
		out << "\t**track";
	}
	out << "\n";
	for (int j=0; j<(int)grids[0].size(); j++) {
		for (int i=0; i<(int)grids.size(); i++) {
			out << grids[i][j];
			if (i < (int)grids.size() - 1) {
				out << "\t";
			}
		}
		out << "\n";
	}
	for (int i=0; i<(int)grids.size(); i++) {
		out << "*-";
		if (i < (int)grids.size() - 1) {
			out << "\t";
		}
	}
	out << "\n";

}



//////////////////////////////
//
// Tool_periodicity::fillAttackGrids --
//

void Tool_periodicity::fillAttackGrids(HumdrumFile& infile, vector<vector<double>>& grids, HumNum minrhy) {
	HumNum elements = minrhy * infile.getScoreDuration() / 4;

	for (int t=0; t<(int)grids.size(); t++) {
		grids[t].resize(elements.getNumerator());
	}

	for (int i=0; i<infile.getLineCount(); i++) {
		if (!infile[i].isData()) {
			continue;
		}
		HumNum position = infile[i].getDurationFromStart() / 4 * minrhy;
		for (int j=0; j<infile[i].getFieldCount(); j++) {
			HTp token = infile.token(i, j);
			if (!token->isKern()) {
				continue;
			}
			if (token->isNull()) {
				continue;
			}
			if (!token->isNoteAttack()) {
				continue;
			}
			int track = token->getTrack();
			grids.at(track).at(position.getNumerator()) += 1;
		}
	}

	bool oneQ = getBoolean("one");
	for (int j=0; j<(int)grids.at(0).size(); j++) {
		grids.at(0).at(j) = 0;
		for (int i=0; i<(int)grids.size(); i++) {
			if (!grids.at(i).at(j)) {
				continue;
			}
			if (oneQ) {
				grids.at(0).at(j) = 1;
			} else {
				grids.at(0).at(j) += grids.at(i).at(j);
			}
		}
	}
}



//////////////////////////////
//
// Tool_periodicity::printSvgAnalysis --
//

void Tool_periodicity::printSvgAnalysis(ostream& out, vector<vector<double>>& analysis, HumNum minrhy) {
	pugi::xml_document image;
	auto declaration = image.prepend_child(pugi::node_declaration);
	declaration.append_attribute("version") = "1.0";
	declaration.append_attribute("encoding") = "UTF-8";
	declaration.append_attribute("standalone") = "no";

	auto svgnode = image.append_child("svg");
	svgnode.append_attribute("version") = "1.1";
	svgnode.append_attribute("xmlns") = "http://www.w3.org/2000/svg";
	svgnode.append_attribute("xmlns:xlink") = "http://www.w3.org/1999/xlink";
	svgnode.append_attribute("overflow") = "visible";
	svgnode.append_attribute("viewBox") = "0 0 1000 1000";
	svgnode.append_attribute("width") = "1000px";
	svgnode.append_attribute("height") = "1000px";

	auto style = svgnode.append_child("style");
	style.text().set(".label { font: 14px sans-serif; alignment-baseline: middle; text-anchor: left; }");

	auto grid = svgnode.append_child("g");
	grid.append_attribute("id") = "grid";

	auto labels = svgnode.append_child("g");

	double hue = 0.0;
	double saturation = 100;
	double lightness = 75;

	pugi::xml_node crect;
	double width;
	double height;

	stringstream ss;
	stringstream ssl;
	//stringstream css;
	double x;
	double y;

	double imagewidth = 1000.0;
	double imageheight = 1000.0;

	int maxrow = getInteger("max-rows");
	if (maxrow <= 0) {
		maxrow = (int)analysis.back().size();
	}


	// double sdur = (double)analysis.back().size();
	double sdur = (double)maxrow;

	double maxscore = 0.0;
	for (int i=0; i<maxrow; i++) {
		for (int j=0; j<(int)analysis[i].size(); j++) {
			if (maxscore < analysis[i][j]) {
				maxscore = analysis[i][j];
			}
		}
	}

	double power = getDouble("power");
	for (int i=0; i<maxrow; i++) {
		for (int j=0; j<(int)analysis[i].size(); j++) {
			width = 1 / sdur * imagewidth;
			height = 1 / sdur * imageheight;

			x = j/sdur * imageheight;
			y = i/sdur * imagewidth;

			double value = analysis[i][j]/maxscore;
			value = pow(value, 1.0/power);

			getColorMapping(value, hue, saturation, lightness);
			ss << "hsl(" << hue << "," << saturation << "%," << lightness << "%)";
			crect = grid.append_child("rect");
			crect.append_attribute("x") = to_string(x).c_str();
			crect.append_attribute("y") = to_string(y).c_str();
			crect.append_attribute("width") = to_string(width*0.99).c_str();
			crect.append_attribute("height") = to_string(height*0.99).c_str();
			crect.append_attribute("fill") = ss.str().c_str();
			//css << "Xm" << getMeasure1(i) << " Ym" << getMeasure2(j);
			//css << " X" << getQon1(i)     << " Y" << getQon2(j);
			//css << " X" << getQoff1(i)    << " Y" << getQoff2(j);
			//crect.append_attribute("class") = css.str().c_str();
			ss.str("");
			//css.str("");
		}

		pugi::xml_node label = labels.append_child("text");
		label.append_attribute("class") = "label";

		HumNum rval = (i+1);
		rval /= minrhy;
		rval *= 4;

		std::string rhythm = Convert::durationToRecip(rval);
		rhythm += " (" + to_string(i+1) + ")";
		label.text().set(rhythm.c_str());
		x = (i+1+0.5)/sdur * imageheight;
		y = (i+0.5)/sdur * imagewidth;
		label.append_attribute("x") = to_string(x).c_str();
		label.append_attribute("y") = to_string(y).c_str();
	}

	image.save(out);
}



//////////////////////////////
//
// Tool_periodicity::getColorMapping --
//

void Tool_periodicity::getColorMapping(double input, double& hue,
		double& saturation, double& lightness) {
	double maxhue = 0.75 * 360.0;
	hue = input;
	if (hue < 0.0) {
		hue = 0.0;
	}
	hue = hue * hue;
	if (hue != 1.0) {
		hue *= 0.95;
	}

	hue = (1.0 - hue) * 360.0;
	if (hue == 0.0) {
		// avoid -0.0;
		hue = 0.0;
	}

	if (hue > maxhue) {
		hue = maxhue;
	}
	if (hue < 0.0) {
		hue = maxhue;
	}

	saturation = 100.0;
	lightness = 50.0;

	if (hue > 60) {
		lightness = lightness - (hue-60) / (maxhue-60) * lightness / 1.5;
	}
}




/////////////////////////////////
//
// Tool_gridtest::Tool_phrase -- Set the recognized options for the tool.
//

Tool_phrase::Tool_phrase(void) {
	define("A|no-average=b", "do not do average phrase-length analysis");
	define("R|remove2=b",    "remove phrase boundaries in data and do not do analysis");
	define("m|mark=b",       "mark phrase boundaries based on rests");
	define("r|remove=b",     "remove phrase boundaries in data");
	define("c|color=s",      "display color of analysis data");
}



///////////////////////////////
//
// Tool_phrase::run -- Primary interfaces to the tool.
//

bool Tool_phrase::run(HumdrumFileSet& infiles) {
	bool status = true;
	for (int i=0; i<infiles.getCount(); i++) {
		status &= run(infiles[i]);
	}
	return status;
}


bool Tool_phrase::run(const string& indata, ostream& out) {
	HumdrumFile infile(indata);
	return run(infile, out);
}


bool Tool_phrase::run(HumdrumFile& infile, ostream& out) {
	bool status = run(infile);
	return status;
}


bool Tool_phrase::run(HumdrumFile& infile) {
	initialize(infile);
	for (int i=0; i<(int)m_starts.size(); i++) {
		if (m_removeQ) {
			removePhraseMarks(m_starts[i]);
		}
		if (m_remove2Q) {
			continue;
		}
		if (hasPhraseMarks(m_starts[i])) {
			analyzeSpineByPhrase(i);
		} else {
			analyzeSpineByRests(i);
		}
	}
	if (!m_remove2Q) {
		prepareAnalysis(infile);
	}
	infile.createLinesFromTokens();
	return true;
}



//////////////////////////////
//
// Tool_phrase::prepareAnalysis --
//

void Tool_phrase::prepareAnalysis(HumdrumFile& infile) {
	string exinterp = "**cdata";
	infile.appendDataSpine(m_results.back(), "", exinterp);
	for (int i = (int)m_results.size()-1; i>0; i--) {
		int track = m_starts[i]->getTrack();
		infile.insertDataSpineBefore(track, m_results[i-1], "", exinterp);
	}
	if (m_averageQ) {
		addAverageLines(infile);
	}
	if (!m_color.empty()) {
		int insertline = -1;
		for (int i=0; i<infile.getLineCount(); i++) {
			if (infile[i].isData() || infile[i].isBarline()) {
				insertline = i;
				break;
			}
		}
		if (insertline > 0) {
			stringstream ss;
			int fsize = infile[insertline].getFieldCount();
			for (int j=0; j<fsize; j++) {
				ss << "*";
				HTp token = infile.token(insertline, j);
				string dt = token->getDataType();
				if (dt.empty() || (dt == "**cdata")) {
					ss << "color:" << m_color;
				}
				if (j < fsize  - 1) {
					ss << "\t";
				}
			}
			string output = ss.str();
			infile.insertLine(insertline, output);
		}
	}
}



///////////////////////////////
//
// Tool_pharse::addAverageLines --
//

void Tool_phrase::addAverageLines(HumdrumFile& infile) {
	vector<string> averages;
	averages.resize(m_starts.size()+1);
	int tcount = 0;
	HumNum tsum = 0;
	double average;
	stringstream ss;
	for (int i=0; i<(int)m_starts.size(); i++) {
		if (m_pcount[i] > 0) {
			average = m_psum[i].getFloat() / m_pcount[i];
		} else {
			average = 0.0;
		}
		ss.str("");
		ss.clear();
		ss << "!!average-phrase-length-k" << i+1 << ":\t" << average;
		averages[i+1] = ss.str();
		tcount += m_pcount[i];
		tsum += m_psum[i];
	}
	average = tsum.getFloat() / tcount;
	ss.str("");
	ss.clear();
	ss << "!!average-phrase-length:\t" << average;
	averages[0] = ss.str();

	for (int i=0; i<(int)averages.size(); i++) {
		infile.appendLine(averages[i]);
	}
}



///////////////////////////////
//
// Tool_phrase::initialize --
//

void Tool_phrase::initialize(HumdrumFile& infile) {
	m_starts = infile.getKernSpineStartList();
	m_results.resize(m_starts.size());
	int lines = infile.getLineCount();
	for (int i=0; i<(int)m_results.size(); i++) {
		m_results[i].resize(lines);
	}
	m_pcount.resize(m_starts.size());
	m_psum.resize(m_starts.size());
	std::fill(m_pcount.begin(), m_pcount.end(), 0);
	std::fill(m_psum.begin(), m_psum.end(), 0);
	m_markQ = getBoolean("mark");
	m_removeQ = getBoolean("remove");
	m_averageQ = !getBoolean("no-average");
	m_remove2Q = getBoolean("remove2");
	if (getBoolean("color")) {
		m_color = getString("color");
	}
}



///////////////////////////////
//
// Tool_phrase::analyzeSpineByRests --
//

void Tool_phrase::analyzeSpineByRests(int index) {
	HTp start    = m_starts[index];
	HTp current  = start;
	HTp lastnote = NULL;   // last note to be processed
	HTp pstart   = NULL;   // phrase start;
	HumNum dur;
	stringstream ss;
	while (current) {
		if (current->isBarline()) {
			if (current->find("||") != std::string::npos) {
				if (pstart) {
					dur = current->getDurationFromStart()
							- pstart->getDurationFromStart();
					ss.str("");
					ss.clear();
					ss << dur.getFloat();
					m_psum[index] += dur;
					m_pcount[index]++;
					m_results[index][pstart->getLineIndex()] = ss.str();
					pstart = NULL;
					if (m_markQ && lastnote) {
						lastnote->setText(lastnote->getText() + "}");
					}
				}
			}
		}
		if (!current->isData()) {
			current = current->getNextToken();
			continue;
		}
		if (current->isNull()) {
			current = current->getNextToken();
			continue;
		}
		if (pstart && current->isRest()) {
			if (lastnote) {
				dur = current->getDurationFromStart()
						- pstart->getDurationFromStart();
				ss.str("");
				ss.clear();
				ss << dur.getFloat();
				m_psum[index] += dur;
				m_pcount[index]++;
				m_results[index][pstart->getLineIndex()] = ss.str();
				if (m_markQ) {
					lastnote->setText(lastnote->getText() + "}");
				}
			}
			pstart = NULL;
			lastnote = NULL;
			current = current->getNextToken();
			continue;
		}
		if (current->isRest()) {
			current = current->getNextToken();
			continue;
		}
		if (current->isNote()) {
			lastnote = current;
		}
		if (pstart && current->isNote() && (current->find(";") != std::string::npos)) {
			// fermata at end of phrase.
			dur = current->getDurationFromStart() + current->getDuration()
					- pstart->getDurationFromStart();
			ss.str("");
			ss.clear();
			ss << dur.getFloat();
			m_psum[index] += dur;
			m_pcount[index]++;
			m_results[index][pstart->getLineIndex()] = ss.str();
			if (m_markQ) {
				current->setText(current->getText() + "}");
			}
			current = current->getNextToken();
			pstart = NULL;
			continue;
		}
		if (current->isNote() && pstart == NULL) {
			pstart = current;
			if (m_markQ) {
				current->setText("{" + current->getText());
			}
		}
		current = current->getNextToken();
	}
	if (pstart) {
		dur = start->getOwner()->getOwner()->getScoreDuration()
				- pstart->getDurationFromStart();
		ss.str("");
		ss.clear();
		ss << dur.getFloat();
		m_psum[index] += dur;
		m_pcount[index]++;
		m_results[index][pstart->getLineIndex()] = ss.str();
		if (m_markQ && lastnote) {
			lastnote->setText(lastnote->getText() + "}");
		}
	}
}



///////////////////////////////
//
// Tool_phrase::analyzeSpineByPhrase --
//

void Tool_phrase::analyzeSpineByPhrase(int index) {
	HTp start    = m_starts[index];
	HTp current  = start;
	HTp pstart   = NULL;   // phrase start;
	HumNum dur;
	stringstream ss;
	while (current) {
		if (!current->isData()) {
			current = current->getNextToken();
			continue;
		}
		if (current->isNull()) {
			current = current->getNextToken();
			continue;
		}
		if (current->find("{") != std::string::npos) {
			pstart = current;
			current = current->getNextToken();
			continue;
		}
		if (current->find("}") != std::string::npos) {
			if (pstart) {
				dur = current->getDurationFromStart() + current->getDuration()
						- pstart->getDurationFromStart();
				ss.str("");
				ss.clear();
				ss << dur.getFloat();
				m_psum[index] += dur;
				m_pcount[index]++;
				m_results[index][pstart->getLineIndex()] = ss.str();
			}
			current = current->getNextToken();
			continue;
		}
		current = current->getNextToken();
	}
}



//////////////////////////////
//
// Tool_phrase::removePhraseMarks -- Remvoe { and } characters from **kern data.
//

void Tool_phrase::removePhraseMarks(HTp start) {
	HTp current = start;
	HumRegex hre;
	while (current) {
		if (!current->isData()) {
			current = current->getNextToken();
			continue;
		}
		if (current->isNull()) {
			current = current->getNextToken();
			continue;
		}
		if (current->find("{") != std::string::npos) {
			string data = *current;
			hre.replaceDestructive(data, "", "\\{", "g");
			current->setText(data);
		}
		if (current->find("}") != std::string::npos) {
			string data = *current;
			hre.replaceDestructive(data, "", "\\}", "g");
			current->setText(data);
		}
		current = current->getNextToken();
	}
}



//////////////////////////////
//
// Tool_phrase::hasPhraseMarks -- True if **kern data spine (primary layer), has
//   "{" (or "}", but this is not checked) characters (phrase markers).
//

bool Tool_phrase::hasPhraseMarks(HTp start) {
	HTp current = start;
	while (current) {
		if (!current->isData()) {
			current = current->getNextToken();
			continue;
		}
		if (current->find("{") != std::string::npos) {
			return true;
		}
		current = current->getNextToken();
	}
	return false;
}





/////////////////////////////////
//
// Tool_pline::Tool_pline -- Set the recognized options for the tool.
//

Tool_pline::Tool_pline(void) {
	define("c|color=b",   "color poetic lines (currently only by notes)");
	define("o|overlap=b", "do overlap analysis/markup");
}



/////////////////////////////////
//
// Tool_pline::run -- Do the main work of the tool.
//

bool Tool_pline::run(HumdrumFileSet& infiles) {
	bool status = true;
	for (int i=0; i<infiles.getCount(); i++) {
		status &= run(infiles[i]);
	}
	return status;
}


bool Tool_pline::run(const string& indata, ostream& out) {
	HumdrumFile infile(indata);
	bool status = run(infile);
	if (hasAnyText()) {
		getAllText(out);
	} else {
		out << infile;
	}
	return status;
}


bool Tool_pline::run(HumdrumFile& infile, ostream& out) {
	bool status = run(infile);
	if (hasAnyText()) {
		getAllText(out);
	} else {
		out << infile;
	}
	return status;
}


bool Tool_pline::run(HumdrumFile& infile) {
	initialize();
	processFile(infile);
	return true;
}


//////////////////////////////
//
// Tool_pline::initialize --
//

void Tool_pline::initialize(void) {
	m_colors.resize(14);

	m_colors[0] = "red";        // red
	m_colors[1] = "darkorange"; // orange
	m_colors[2] = "gold";       // yellow
	m_colors[3] = "limegreen";  // green
	m_colors[4] = "skyblue";    // light blue
	m_colors[5] = "mediumblue"; // dark blue
	m_colors[6] = "purple";     // purple

	m_colors[7]  = "darkred";        // red
	m_colors[8]  = "lightsalmon";    // orange
	m_colors[9]  = "darkgoldenrod";  // yellow
	m_colors[10] = "olivedrab";      // green
	m_colors[11] = "darkturquoise";  // light blue
	m_colors[12] = "darkblue";       // dark blue
	m_colors[13] = "indigo";         // purple

	// lighter colors for staff highlighting:
	//m_colors[0] = "#ffaaaa";  // red
	//m_colors[1] = "#ffbb00";  // orange
	//m_colors[2] = "#eeee00";  // yellow
	//m_colors[3] = "#99cc01";  // green
	//m_colors[4] = "#bbddff";  // light blue
	//m_colors[5] = "#88aaff";  // dark blue
	//m_colors[6] = "#cc88ff";  // purple

	m_colorQ = getBoolean("color");
	m_colorQ = true;  // default behavior for now.
}



//////////////////////////////
//
// Tool_pline::processFile --
//

void Tool_pline::processFile(HumdrumFile& infile) {
	getPlineInterpretations(infile, m_ptokens);
	fillLineInfo(infile, m_lineInfo);
	if (m_colorQ) {
		plineToColor(infile, m_ptokens);
	}
	infile.createLinesFromTokens();
	m_humdrum_text << infile;
	if (m_colorQ) {
		m_humdrum_text << "!!!RDF**kern: 😀 = marked note, color=black" << endl;
	}
}



//////////////////////////////
//
// Tool_pline::markRests --
//

void Tool_pline::markRests(HumdrumFile& infile) {
	vector<HTp> spinestops;
	infile.getSpineStopList(spinestops);
	for (int i=0; i<(int)spinestops.size(); i++) {
		if (!spinestops[i]->isKern()) {
			continue;
		}
		markSpineRests(spinestops[i]);
	}
}


//////////////////////////////
//
// Tool_pline::markSpineRests --
//

void Tool_pline::markSpineRests(HTp spineStop) {
	string marker = "😀";
	int track = spineStop->getTrack();
	int lastValue = -1;
	HTp current = spineStop->getPreviousToken();
	int  line;
	int  cvalue;
	while (current) {
		if (!current->isData()) {
			current = current->getPreviousToken();
			continue;
		}
		if (current->isNull()) {
			current = current->getPreviousToken();
			continue;
		}

		line = current->getLineIndex();
		cvalue = m_lineInfo.at(line).at(track);

		if (current->isRest() && (cvalue != lastValue)) {
			string text = *current;
			text += marker;
			current->setText(text);
		} else {
			lastValue = cvalue;
			string text = *current;
			text += "@" + to_string(cvalue);
			current->setText(text);
		}
		current = current->getPreviousToken();
	}
}



//////////////////////////////
//
// Tool_pline::fillLineInfo --
//

void Tool_pline::fillLineInfo(HumdrumFile& infile, vector<vector<int>>& lineinfo) {
	lineinfo.clear();
	lineinfo.resize(infile.getLineCount());
	int maxtrack = infile.getMaxTrack();
	HumRegex hre;
	for (int i=0; i<infile.getLineCount(); i++) {
		lineinfo[i].resize(maxtrack + 1);
		fill(lineinfo[i].begin(), lineinfo[i].end(), 0);
		if (!infile[i].isInterpretation()) {
			continue;
		}
		for (int j=0; j<infile[i].getFieldCount(); j++) {
			HTp token = infile.token(i, j);
			if (hre.search(token, "^\\*pline:\\s*(\\d+)")) {
				int digit = hre.getMatchInt(1);
				int track = token->getTrack();
				lineinfo[i][track] = digit;
			}
		}
	}

	for (int i=1; i<(int)lineinfo.size() - 1; i++) {
		for (int j=1; j<=maxtrack; j++) {
			if (lineinfo.at(i).at(j)) {
				continue;
			} else {
				lineinfo.at(i).at(j) = lineinfo.at(i-1).at(j);
			}
		}
	}

	// for (int i=0; i<(int)lineinfo.size() - 1; i++) {
	// 	for (int j=1; j<=maxtrack; j++) {
	// 		cerr << lineinfo[i][j] << "\t";
	// 	}
	// 	cerr << endl;
	// }

}



//////////////////////////////
//
// Tool_pline::plineToColor --
//

void Tool_pline::plineToColor(HumdrumFile& infile, vector<HTp>& tokens) {
	HumRegex hre;
	markRests(infile);
	for (int i=0; i<(int)tokens.size(); i++) {
		if (!hre.search(tokens[i], "^\\*pline:\\s*(\\d+)")) {
			continue;
		}
		int lineNum = hre.getMatchInt(1);
		int colorIndex = (lineNum - 1) % m_colors.size();
		string color = m_colors.at(colorIndex);
		string text = "*color:";
		text += color;
		tokens[i]->setText(text);
	}
}



//////////////////////////////
//
// Tool_pline::getPlineInterpretations --
//

void Tool_pline::getPlineInterpretations(HumdrumFile& infile, vector<HTp>& tokens) {
	HumRegex hre;
	for (int i=0; i<infile.getLineCount(); i++) {
		if (!infile[i].isInterpretation()) {
			continue;
		}
		for (int j=0; j<infile[i].getFieldCount(); j++) {
			HTp token = infile.token(i, j);
			if (!token->isKern()) {
				continue;
			}
			if (hre.search(token, "^\\*pline:\\s*(\\d+)")) {
				tokens.push_back(token);
			}
		}
	}
}





/////////////////////////////////
//
// Tool_gridtest::Tool_pnum -- Set the recognized options for the tool.
//

Tool_pnum::Tool_pnum(void) {
	define("b|base=i:midi",    "numeric base of pitch to extract");
	define("D|no-duration=b",  "do not include duration");
	define("c|pitch-class=b",  "give numeric pitch-class rather than pitch");
	define("o|octave=b",       "give octave rather than pitch");
	define("r|rest=s:0",       "representation string for rests");
	define("R|no-rests=b",     "do not include rests in conversion");
	define("x|attacks-only=b", "only mark lines with note attacks");
}



///////////////////////////////
//
// Tool_pnum::run -- Primary interfaces to the tool.
//

bool Tool_pnum::run(HumdrumFileSet& infiles) {
	bool status = true;
	for (int i=0; i<infiles.getCount(); i++) {
		status &= run(infiles[i]);
	}
	return status;
}


bool Tool_pnum::run(const string& indata, ostream& out) {
	HumdrumFile infile(indata);
	return run(infile, out);
}


bool Tool_pnum::run(HumdrumFile& infile, ostream& out) {
	bool status = run(infile);
	out << infile;
	return status;
}


bool Tool_pnum::run(HumdrumFile& infile) {
   initialize(infile);
	processFile(infile);
	infile.createLinesFromTokens();
	return true;
}



//////////////////////////////
//
// Tool_pnum::initialize --
//

void Tool_pnum::initialize(HumdrumFile& infile) {
	m_midiQ = false;
	if (getString("base") == "midi") {
		m_base = 12;
		m_midiQ = true;
	} else {
		// check base for valid numbers, but for now default to 12 if unknown
		m_base = getInteger("base");
	}

	m_durationQ = !getBoolean("no-duration");
	m_classQ    =  getBoolean("pitch-class");
	m_octaveQ   =  getBoolean("octave");
	m_attacksQ  =  getBoolean("attacks-only");
	m_rest      =  getString("rest");
	m_restQ     = !getBoolean("no-rests");
}



//////////////////////////////
//
// Tool_pnum::processFile --
//

void Tool_pnum::processFile(HumdrumFile& infile) {
	vector<HTp> kex;

	for (int i=0; i<infile.getLineCount(); i++) {
		if (!infile[i].hasSpines()) {
			continue;
		}
		for (int j=0; j<infile[i].getFieldCount(); j++) {
			HTp token = infile.token(i, j);
			if (!token->isKern()) {
				continue;
			}
			if (*token == "**kern") {
				kex.push_back(token);
				continue;
			}
			if (!token->isData()) {
				continue;
			}
			if (token->isNull()) {
				continue;
			}
			convertTokenToBase(token);
		}
	}

	string newex;
	for (int i=0; i<(int)kex.size(); i++) {
		if (m_midiQ) {
			newex = "**pmid";
		} else {
			newex = "**b" + to_string(m_base);
		}
		kex[i]->setText(newex);
	}
}



//////////////////////////////
//
// Tool_pnum::convertTokenToBase --
//

void Tool_pnum::convertTokenToBase(HTp token) {
	string output;
	int scount = token->getSubtokenCount();
	for (int i=0; i<scount; i++) {
		string subtok = token->getSubtoken(i);
		output += convertSubtokenToBase(subtok);
		if (i < scount - 1) {
			output += " ";
		}
	}
	token->setText(output);
}



//////////////////////////////
//
// Tool_pnum::convertSubtokenToBase --
//

string Tool_pnum::convertSubtokenToBase(const string& text) {
	int pitch = 0;
	if (text.find("r") == string::npos) {
		switch (m_base) {
			case 7:
				pitch = Convert::kernToBase7(text);
				break;
			case 40:
				pitch = Convert::kernToBase40(text);
				break;
			default:
				pitch = Convert::kernToBase12(text);
		}
	} else if (!m_restQ) {
		return ".";
	}
	string recip;
	if (m_durationQ) {
		HumRegex hre;
		if (hre.search(text, "(\\d+%?\\d*\\.*)")) {
			recip = hre.getMatch(1);
		}
	}

	string output;

	int pc = pitch % m_base;
	int oct = pitch / m_base;

	if (m_midiQ) {
		// MIDI numbers use 5 for middle-C octave.
		pitch += 12;
	}

	int tie = 1;
	if (text.find("_") != string::npos) {
		tie = -1;
	}
	if (text.find("]") != string::npos) {
		tie = -1;
	}
	pitch *= tie;
	if (m_attacksQ && pitch < 0) {
		return ".";
	}

	if (m_durationQ) {
		output += recip;
		output += "/";
	}

	if (text.find("r") != string::npos) {
		output += m_rest;
	} else {
		if (!m_octaveQ && !m_classQ) {
			output += to_string(pitch);
		} else {
			if (m_classQ) {
				if (pitch < 0) {
					output += "-";
				}
				output += to_string(pc);
			}
			if (m_classQ && m_octaveQ) {
				output += ":";
			}
			if (m_octaveQ) {
				output += to_string(oct);
			}
		}
	}

	return output;
}





#define OBJTAB "\t\t\t\t\t\t"
#define SVGTAG "_99%svg%";

#define SVGTEXT(out, text) \
	if (m_defineQ) { \
		out << "SVG "; \
	} else { \
		out << "t 1 1\n"; \
		out << SVGTAG; \
	} \
	printScoreEncodedText((out), (text)); \
	out << "\n";


//////////////////////////////
//
// _VoiceInfo::_VoiceInfo --
//

_VoiceInfo::_VoiceInfo(void) {
	clear();
}



//////////////////////////////
//
// _VoiceInfo::clear --
//

void _VoiceInfo::clear(void) {
	name = "";
	abbr = "";
	midibins.resize(128);
	fill(midibins.begin(), midibins.end(), 0.0);
	diatonic.resize(7 * 12);
	for (int i=0; i<(int)diatonic.size(); i++) {
		diatonic[i].resize(6);
		fill(diatonic[i].begin(), diatonic[i].end(), 0.0);
	}
	track = -1;
	kernQ = false;
	diafinal.clear();
	accfinal.clear();
	namfinal.clear();
	index = -1;
}


//////////////////////////////
//
// _VoiceInfo::print --
//

ostream& _VoiceInfo::print(ostream& out) {
	out << "==================================" << endl;
	out << "track:  " << track << endl;
	out << " name:  " << name << endl;
	out << " abbr:  " << abbr << endl;
	out << " kern:  " << kernQ << endl;
	out << " final:";
	for (int i=0; i<(int)diafinal.size(); i++) {
		out << " " << diafinal.at(i) << "/" << accfinal.at(i);
	}
	out << endl;
	out << " midi:  ";
	for (int i=0; i<(int)midibins.size(); i++) {
		if (midibins.at(i) > 0.0) {
			out << " " << i << ":" << midibins.at(i);
		}
	}
	out << endl;
	out << " diat:  ";
	for (int i=0; i<(int)diatonic.size(); i++) {
		if (diatonic.at(i).at(0) > 0.0) {
			out << " " << i << ":" << diatonic.at(i).at(0);
		}
	}
	out << endl;
	out << "==================================" << endl;
	return out;
}



/////////////////////////////////
//
// Tool_prange::Tool_prange -- Set the recognized options for the tool.
//

Tool_prange::Tool_prange(void) {

	define("A|acc|color-accidentals=b", "add color to accidentals in histogram");
	define("D|diatonic=b",              "diatonic counts ignore chormatic alteration");
	define("K|no-key=b",                "do not display key signature");
	define("N|norm=b",                  "normalize pitch counts");
	define("S|score=b",                 "convert range info to SCORE");
	define("T|no-title=b",              "do not display a title");
	define("a|all=b",                   "generate all-voice analysis");
	define("c|range|count=s:60-71",     "count notes in a particular MIDI note number range (inclusive)");
	define("debug=b",                   "trace input parsing");
	define("d|duration=b",              "weight pitches by duration");
	define("e|embed=b",                 "embed SCORE data in input Humdrum data");
	define("fill=b",                    "change color of fill only");
	define("finalis|final|last=b",      "include finalis note by voice");
	define("f|fraction=b",              "display histogram fractions");
	define("h|hover=b",                 "include svg hover capabilities");
	define("i|instrument=b",            "categorize multiple inputs by instrument");
	define("j|jrp=b",                   "set options for JRP style");
	define("l|local|local-maximum|local-maxima=b",  "use maximum values by voice rather than all voices");
	define("no-define=b",               "do not use defines in output SCORE data");
	define("pitch=b",                   "display pitch info in **pitch format");
	define("print=b",                   "count printed notes rather than sounding");
	define("p|percentile=d:0.0",        "display the xth percentile pitch");
	define("q|quartile=b",              "display quartile notes");
	define("r|reverse=b",               "reverse list of notes in analysis from high to low");
	define("x|extrema=b",               "highlight extrema notes in each part");
	define("sx|scorexml|score-xml|ScoreXML|scoreXML=b", "output ScoreXML format");
	define("title=s:",                  "title for SCORE display");

}


/////////////////////////////////
//
// Tool_prange::run -- Do the main work of the tool.
//

bool Tool_prange::run(HumdrumFileSet& infiles) {
	bool status = true;
	for (int i=0; i<infiles.getCount(); i++) {
		status &= run(infiles[i]);
	}
	return status;
}


bool Tool_prange::run(const string& indata, ostream& out) {
	HumdrumFile infile(indata);
	bool status = run(infile);
	if (hasAnyText()) {
		getAllText(out);
	} else {
		out << infile;
	}
	return status;
}


bool Tool_prange::run(HumdrumFile& infile, ostream& out) {
	bool status = run(infile);
	if (hasAnyText()) {
		getAllText(out);
	} else {
		out << infile;
	}
	return status;
}


bool Tool_prange::run(HumdrumFile& infile) {
	initialize();
	processFile(infile);
	return true;
}



//////////////////////////////
//
// Tool_prange::initialize --  Initializations that only have to be done once
//    for all HumdrumFile segments.
//

void Tool_prange::initialize(void) {
	m_accQ         = getBoolean("color-accidentals");
	m_addFractionQ = getBoolean("fraction");
	m_allQ         = getBoolean("all");
	m_debugQ       = getBoolean("debug");
	m_defineQ      = false;
	m_diatonicQ    = getBoolean("diatonic");
	m_durationQ    = getBoolean("duration");
	m_fillOnlyQ    = getBoolean("fill");
	m_finalisQ     = getBoolean("finalis");
	m_hoverQ       = getBoolean("hover");
	m_instrumentQ  = getBoolean("instrument");
	m_keyQ         = !getBoolean("no-key");
	m_listQ        = false;
	m_localQ       = getBoolean("local-maximum");
	m_normQ        = getBoolean("norm");
	m_notitleQ     = getBoolean("no-title");
	m_percentile   = getDouble("percentile");
	m_percentileQ  = getBoolean("percentile");
	m_pitchQ       = getBoolean("pitch");
	m_printQ       = getBoolean("print");
	m_quartileQ    = getBoolean("quartile");
	m_rangeQ       = getBoolean("range");
	m_reverseQ     = !getBoolean("reverse");
	m_scoreQ       = getBoolean("score");
	m_title        = getString("title");
	m_titleQ       = getBoolean("title");
	m_embedQ       = getBoolean("embed");
	m_extremaQ     = getBoolean("extrema");

	getRange(m_rangeL, m_rangeH, getString("range"));

	if (getBoolean("jrp")) {
		// default style settings for JRP range displays:
		m_scoreQ   = true;
		m_allQ     = true;
		m_hoverQ   = true;
		m_accQ     = true;
		m_finalisQ = true;
		m_notitleQ = true;
	}

	// The percentile is a fraction from 0.0 to 1.0.
	// if the percentile is above 1.0, then it is assumed
	// to be a percentage, in which case the value will be
	// divided by 100 to get it in the range from 0 to 1.
	if (m_percentile > 1) {
		m_percentile = m_percentile / 100.0;
	}

	#ifdef __EMSCRIPTEN__
		// Default styling for JavaScript version of program:
		m_accQ     = !getBoolean("color-accidentals");
		m_scoreQ   = !getBoolean("score");
		m_embedQ   = !getBoolean("embed");
		m_hoverQ   = !getBoolean("hover");
		m_notitleQ = !getBoolean("no-title");
	#endif
}



//////////////////////////////
//
// Tool_prange::processFile --
//

void Tool_prange::processFile(HumdrumFile& infile) {
	prepareRefmap(infile);
	vector<_VoiceInfo> voiceInfo;
	infile.fillMidiInfo(m_trackMidi);
	getVoiceInfo(voiceInfo, infile);
	fillHistograms(voiceInfo, infile);

	if (m_debugQ) {
		for (int i=0; i<(int)voiceInfo.size(); i++) {
			voiceInfo[i].print(cerr);
		}
	}

	if (m_scoreQ) {
		stringstream scoreout;
		printScoreFile(scoreout, voiceInfo, infile);
		if (m_embedQ) {
			if (m_extremaQ) {
				doExtremaMarkup(infile);
			}
			m_humdrum_text << infile;
			printEmbeddedScore(m_humdrum_text, scoreout, infile);
		} else {
			if (m_extremaQ) {
				doExtremaMarkup(infile);
			}
			m_humdrum_text << scoreout.str();
		}
	} else {
		printAnalysis(m_humdrum_text, voiceInfo[0].midibins);
	}
}



//////////////////////////////
//
// Tool_prange::doExtremaMarkup -- Mark highest and lowest note
//     in each **kern spine.
//
//

void Tool_prange::doExtremaMarkup(HumdrumFile& infile) {
	bool highQ = false;
	bool lowQ = false;
	for (int i=0; i<(int)m_trackMidi.size(); i++) {
		int maxindex = -1;
		int minindex = -1;

		for (int j=(int)m_trackMidi[i].size()-1; j>=0; j--) {
			if (m_trackMidi[i][j].empty()) {
				continue;
			}
			if (maxindex < 0) {
				maxindex = j;
				break;
			}
		}

		for (int j=1; j<(int)m_trackMidi[i].size(); j++) {
			if (m_trackMidi[i][j].empty()) {
				continue;
			}
			if (minindex < 0) {
				minindex = j;
				break;
			}
		}

		if ((maxindex < 0) || (minindex < 0)) {
			continue;
		}
		applyMarkup(m_trackMidi[i][maxindex], m_highMark);
		applyMarkup(m_trackMidi[i][minindex], m_lowMark);
		highQ = true;
		lowQ  = true;
	}
	if (highQ) {
		string highRdf = "!!!RDF**kern: " + m_highMark + " = marked note, color=\"hotpink\", highest note";
		infile.appendLine(highRdf);
	}
	if (lowQ) {
		string lowRdf = "!!!RDF**kern: " + m_lowMark + " = marked note, color=\"limegreen\", lowest note";
		infile.appendLine(lowRdf);
	}
	if (highQ || lowQ) {
		infile.createLinesFromTokens();
	}
}



//////////////////////////////
//
// Tool_prange::applyMarkup --
//

void Tool_prange::applyMarkup(vector<pair<HTp, int>>& notelist, const string& mark) {
	for (int i=0; i<(int)notelist.size(); i++) {
		HTp token = notelist[i].first;
		int subtoken = notelist[i].second;
		int tokenCount = token->getSubtokenCount();
		if (tokenCount == 1) {
			string text = *token;
			text += mark;
			token->setText(text);
		} else {
			string stok = token->getSubtoken(subtoken);
			stok = mark + stok;
			token->replaceSubtoken(subtoken, stok);
		}
	}
}



//////////////////////////////
//
// Tool_prange::printEmbeddedScore --
//

void Tool_prange::printEmbeddedScore(ostream& out, stringstream& scoredata, HumdrumFile& infile) {
	int id = getPrangeId(infile);

	out << "!!@@BEGIN: PREHTML\n";
	out << "!!@CONTENT: <div class=\"score-svg\" ";
	out <<    "style=\"margin-top:50px;text-align:center;\" ";
	out <<    " data-score=\"prange-" << id << "\"></div>\n";
	out << "!!@@END: PREHTML\n";
	out << "!!@@BEGIN: SCORE\n";
	out << "!!@ID: prange-" << id << "\n";
	out << "!!@OUTPUTFORMAT: svg\n";
	out << "!!@CROP: yes\n";
	out << "!!@PADDING: 10\n";
	out << "!!@SCALING: 1.5\n";
	out << "!!@SVGFORMAT: yes\n";
	out << "!!@TRANSPARENT: yes\n";
	out << "!!@ANTIALIAS: no\n";
	out << "!!@EMBEDPMX: yes\n";
	out << "!!@ANNOTATE: no\n";
	out << "!!@CONTENTS:\n";
	string line;
	while(getline(scoredata, line)) {
		out << "!!" << line << endl;
	}
	out << "!!@@END: SCORE\n";
}



//////////////////////////////
//
// Tool_prange::getPrangeId -- Find a line in this form
//          ^!!@ID: prange-(\d+)$
//      and return $1+1.  Searching backwards since the HTML section
//      will likely be at the bottom.  Assuming that the prange
//      SVG images are stored in sequence, with the highest ID last
//      in the file if there are more than one.
//

int Tool_prange::getPrangeId(HumdrumFile& infile) {
	string search = "!!@ID: prange-";
	int length = (int)search.length();
	for (int i=infile.getLineCount() - 1; i>=0; i--) {
		HTp token = infile.token(i, 0);
		if (token->compare(0, length, search) == 0) {
			HumRegex hre;
			if (hre.search(token, "prange-(\\d+)")) {
				return hre.getMatchInt(1) + 1;
			}
		}
	}
	return 1;
}



//////////////////////////////
//
// Tool_prange::mergeAllVoiceInfo --
//

void Tool_prange::mergeAllVoiceInfo(vector<_VoiceInfo>& voiceInfo) {
	voiceInfo.at(0).diafinal.clear();
	voiceInfo.at(0).accfinal.clear();

	for (int i=1; i<(int)voiceInfo.size(); i++) {
		if (!voiceInfo[i].kernQ) {
			continue;
		}
		for (int j=0; j<(int)voiceInfo.at(i).diafinal.size(); j++) {
			voiceInfo.at(0).diafinal.push_back(voiceInfo.at(i).diafinal.at(j));
			voiceInfo.at(0).accfinal.push_back(voiceInfo.at(i).accfinal.at(j));
			voiceInfo.at(0).namfinal.push_back(voiceInfo.at(i).name);
		}

		for (int j=0; j<(int)voiceInfo[i].midibins.size(); j++) {
			voiceInfo[0].midibins[j] += voiceInfo[i].midibins[j];
		}

		for (int j=0; j<(int)voiceInfo.at(i).diatonic.size(); j++) {
			for (int k=0; k<(int)voiceInfo.at(i).diatonic.at(k).size(); k++) {
				voiceInfo[0].diatonic.at(j).at(k) += voiceInfo.at(i).diatonic.at(j).at(k);
			}
		}
	}
}



//////////////////////////////
//
// Tool_prange::getVoiceInfo -- get names and track info for **kern spines.
//

void Tool_prange::getVoiceInfo(vector<_VoiceInfo>& voiceInfo, HumdrumFile& infile) {
	voiceInfo.clear();
	voiceInfo.resize(infile.getMaxTracks() + 1);
	for (int i=0; i<(int)voiceInfo.size(); i++) {
		voiceInfo.at(i).index = i;
	}

	vector<HTp> kstarts = infile.getKernSpineStartList();

	if (kstarts.size() == 2) {
		voiceInfo[0].name  = "both";
		voiceInfo[0].abbr  = "both";
		voiceInfo[0].track = 0;
	} else {
		voiceInfo[0].name  = "all";
		voiceInfo[0].abbr  = "all";
		voiceInfo[0].track = 0;
	}


	for (int i=0; i<infile.getLineCount(); i++) {
		if (infile[i].isData()) {
			break;
		}
		if (!infile[i].hasSpines()) {
			continue;
		}
		if (!infile[i].isInterpretation()) {
			continue;
		}
		for (int j=0; j<infile[i].getFieldCount(); j++) {
			HTp token = infile.token(i, j);
			int track = token->getTrack();
			voiceInfo[track].track = track;
			if (token->isKern()) {
				voiceInfo[track].kernQ = true;
			}
			if (!voiceInfo[track].kernQ) {
				continue;
			}
			if (token->isInstrumentName()) {
				voiceInfo[track].name = token->getInstrumentName();
			}
			if (token->isInstrumentAbbreviation()) {
				voiceInfo[track].abbr = token->getInstrumentAbbreviation();
			}
		}
	}


	// Check for piano/Grand Staff parts with LH/RH encoding.
	if (kstarts.size() == 2) {
		string bottomStaff = getHand(kstarts[0]);
		string topStaff    = getHand(kstarts[1]);
		if (!bottomStaff.empty() && !topStaff.empty()) {
			int track = kstarts[0]->getTrack();
			voiceInfo[track].name = "left hand";
			track = kstarts[1]->getTrack();
			voiceInfo[track].name = "right hand";
		}
	}
}



//////////////////////////////
//
// Tool_prange::getHand --
//

string Tool_prange::getHand(HTp sstart) {
	HTp current = sstart->getNextToken();
	HTp target = NULL;
	while (current) {
		if (current->isData()) {
			break;
		}
		if (*current == "*LH") {
			target = current;
			break;
		}
		if (*current == "*RH") {
			target = current;
			break;
		}
		current = current->getNextToken();
	}

	if (target) {
		if (*current == "*LH") {
			return "LH";
		} else if (*current == "*RH") {
			return "RH";
		} else {
			return "";
		}
	} else {
		return "";
	}
}



//////////////////////////////
//
// Tool_prange::getInstrumentNames --  Find any instrument names which are listed
//      before the first data line.  Instrument names are in the form:
//
//      *I"name
//

void Tool_prange::getInstrumentNames(vector<string>& nameByTrack, vector<int>& kernSpines,
		HumdrumFile& infile) {
	HumRegex hre;

	int track;
	string name;
	// nameByTrack.resize(kernSpines.size());
	nameByTrack.resize(infile.getMaxTrack() + 1);
	fill(nameByTrack.begin(), nameByTrack.end(), "");
	vector<HTp> kspines = infile.getKernSpineStartList();
	if (kspines.size() == 2) {
		nameByTrack.at(0) = "both";
	} else {
		nameByTrack.at(0) = "all";
	}

	for (int i=0; i<infile.getLineCount(); i++) {
		if (!infile[i].isInterpretation()) {
			continue;
		}
		for (int j=0; j<infile[i].getFieldCount(); j++) {
			HTp token = infile.token(i, j);
			if (hre.search(token, "^\\*I\"(.*)\\s*")) {
				name = hre.getMatch(1);
				track = token->getTrack();
				for (int k=0; k<(int)kernSpines.size(); k++) {
					if (track == kernSpines[k]) {
						nameByTrack[k] = name;
					}
				}
			}
		}
	}
}



//////////////////////////////
//
// Tool_prange::fillHistograms -- Store notes in score by MIDI note number.
//

void Tool_prange::fillHistograms(vector<_VoiceInfo>& voiceInfo, HumdrumFile& infile) {
	// storage for finals info:
	vector<vector<int>> diafinal;
	vector<vector<int>> accfinal;
	diafinal.resize(infile.getMaxTracks() + 1);
	accfinal.resize(infile.getMaxTracks() + 1);

	for (int i=0; i<infile.getLineCount(); i++) {
		if (!infile[i].isData()) {
			continue;
		}
		for (int j=0; j<infile[i].getFieldCount(); j++) {
			HTp token = infile.token(i, j);
			if (!token->isKern()) {
				continue;
			}
			if (token->isNull()) {
				continue;
			}
			int track = token->getTrack();

			diafinal.at(track).clear();
			accfinal.at(track).clear();

			vector<string> tokens = token->getSubtokens();
			for (int k=0; k<(int)tokens.size(); k++) {
				if (tokens[k].find("r") != string::npos) {
					continue;
				}
				if (tokens[k].find("R") != string::npos) {
					// non-pitched note
					continue;
				}
				bool hasPitch = false;
				for (int m=0; m<(int)tokens[k].size(); m++) {
					char test = tokens[k].at(m);
					if (!isalpha(test)) {
						continue;
					}
					test = tolower(test);
					if ((test >= 'a') && (test <= 'g')) {
						hasPitch = true;
						break;
					}
				}
				if (!hasPitch) {
					continue;
				}
				int octave = Convert::kernToOctaveNumber(tokens[k]) + 3;
				if (octave < 0) {
					cerr << "Note too low: " << tokens[k] << endl;
					continue;
				}
				if (octave >= 12) {
					cerr << "Note too high: " << tokens[k] << endl;
					continue;
				}
				int dpc    = Convert::kernToDiatonicPC(tokens[k]);
				int acc    = Convert::kernToAccidentalCount(tokens[k]);
				if (acc < -2) {
					cerr << "Accidental too flat: " << tokens[k] << endl;
					continue;
				}
				if (acc > +2) {
					cerr << "Accidental too sharp: " << tokens[k] << endl;
					continue;
				}
				int diatonic = dpc + 7 * octave;
				int realdiatonic = dpc + 7 * (octave-3);

				diafinal.at(track).push_back(realdiatonic);
				accfinal.at(track).push_back(acc);

				acc += 3;
				int midi = Convert::kernToMidiNoteNumber(tokens[k]);
				if (midi < 0) {
					cerr << "MIDI pitch too low: " << tokens[k] << endl;
				}
				if (midi > 127) {
					cerr << "MIDI pitch too high: " << tokens[k] << endl;
				}
				if (m_durationQ) {
					double duration = Convert::kernToDuration(tokens[k]).getFloat();
					voiceInfo[track].diatonic.at(diatonic).at(0) += duration;
					voiceInfo[track].diatonic.at(diatonic).at(acc) += duration;
					voiceInfo[track].midibins.at(midi) += duration;
				} else {
					if (tokens[k].find("]") != string::npos) {
						continue;
					}
					if (tokens[k].find("_") != string::npos) {
						continue;
					}
					voiceInfo[track].diatonic.at(diatonic).at(0)++;
					voiceInfo[track].diatonic.at(diatonic).at(acc)++;
					voiceInfo[track].midibins.at(midi)++;
				}
			}
		}
	}

	mergeFinals(voiceInfo, diafinal, accfinal);

	// Sum all voices into midibins and diatonic arrays of vector position 0:
	mergeAllVoiceInfo(voiceInfo);
}



//////////////////////////////
//
// Tool_prange::mergeFinals --
//

void Tool_prange::mergeFinals(vector<_VoiceInfo>& voiceInfo, vector<vector<int>>& diafinal,
		vector<vector<int>>& accfinal) {
	for (int i=0; i<(int)voiceInfo.size(); i++) {
		voiceInfo.at(i).diafinal = diafinal.at(i);
		voiceInfo.at(i).accfinal = accfinal.at(i);
	}
}



//////////////////////////////
//
// Tool_prange::printFilenameBase --
//

void Tool_prange::printFilenameBase(ostream& out, const string& filename) {
	HumRegex hre;
	if (hre.search(filename, "([^/]+)\\.([^.]*)", "")) {
		if (hre.getMatch(1).size() <= 8) {
			printXmlEncodedText(out, hre.getMatch(1));
		} else {
			// problem with too long a name (MS-DOS will have problems).
			// optimize to chop off everything after the dash in the
			// name (for Josquin catalog numbers).
			string shortname = hre.getMatch(1);
			if (hre.search(shortname, "-.*")) {
			   hre.replaceDestructive(shortname, "", "-.*");
				printXmlEncodedText(out, shortname);
			} else {
				printXmlEncodedText(out, shortname);
			}
		}
	}
}



//////////////////////////////
//
// Tool_prange::printReferenceRecords --
//

void Tool_prange::printReferenceRecords(ostream& out, HumdrumFile& infile) {
	for (int i=0; i<infile.getLineCount(); i++) {
		if (!infile[i].isReferenceRecord()) {
			continue;
		}
		out <<  "\t\t\t\t\t\t<?Humdrum key=\"";
		printXmlEncodedText(out, infile[i].getReferenceKey());
		out << "\" value=\"";
		printXmlEncodedText(out, infile[i].getReferenceValue());
		out << "\"?>\n";
	}
}



//////////////////////////////
//
// Tool_prange::printScoreEncodedText -- print SCORE text string
//    See SCORE 3.1 manual additions (page 19) for more.
//

void Tool_prange::printScoreEncodedText(ostream& out, const string& strang) {
	string newstring = strang;
	HumRegex hre;

	hre.replaceDestructive(newstring, "<<$1", "&([aeiou])acute;", "gi");
	hre.replaceDestructive(newstring, "<<$1", "([áéíóú])", "gi");

	hre.replaceDestructive(newstring, ">>$1", "&([aeiou])grave;", "gi");
	hre.replaceDestructive(newstring, ">>$1", "([àèìòù])", "gi");

	hre.replaceDestructive(newstring, "%%$1", "&([aeiou])uml;", "gi");
	hre.replaceDestructive(newstring, "%%$1", "([äëïöü])", "gi");

	hre.replaceDestructive(newstring, "^^$1", "&([aeiou])circ;", "gi");
	hre.replaceDestructive(newstring, "^^$1", "([âêîôû])", "gi");

	hre.replaceDestructive(newstring, "##c", "&ccedil;",  "g");
	hre.replaceDestructive(newstring, "##C", "&Ccedil;",  "g");
	hre.replaceDestructive(newstring, "?\\|", "\\|",      "g");
	hre.replaceDestructive(newstring, "?\\",  "\\\\",     "g");
	hre.replaceDestructive(newstring, "?m",   "---",      "g");
	hre.replaceDestructive(newstring, "?n",   "--",       "g");
	hre.replaceDestructive(newstring, "?2",   "-sharp",   "g");
	hre.replaceDestructive(newstring, "?1",   "-flat",    "g");
	hre.replaceDestructive(newstring, "?3",   "-natural", "g");
	hre.replaceDestructive(newstring, "\\",   "/",        "g");
	hre.replaceDestructive(newstring, "?[",   "\\[",      "g");
	hre.replaceDestructive(newstring, "?]",   "\\]",      "g");

	out << newstring;
}



//////////////////////////////
//
// Tool_prange::printXmlEncodedText -- convert
//    & to &amp;
//    " to &quot;
//    ' to &spos;
//    < to &lt;
//    > to &gt;
//

void Tool_prange::printXmlEncodedText(ostream& out, const string& strang) {
	HumRegex hre;
	string astring = strang;

	hre.replaceDestructive(astring, "&",  "&amp;",  "g");
	hre.replaceDestructive(astring, "'",  "&apos;", "g");
	hre.replaceDestructive(astring, "\"", "&quot;", "g");
	hre.replaceDestructive(astring, "<",  "&lt;",   "g");
	hre.replaceDestructive(astring, ">",  "&gt;",   "g");

	out << astring;
}



//////////////////////////////
//
// Tool_prange::printScoreFile --
//

void Tool_prange::printScoreFile(ostream& out, vector<_VoiceInfo>& voiceInfo, HumdrumFile& infile) {
	string titlestring = getTitle();

	if (m_defineQ) {
		out << "#define SVG t 1 1 \\n_99%svg%\n";
	}

	string acctext = "g.bar.doubleflat path&#123;color:darkorange;stroke:darkorange;&#125;g.bar.flat path&#123;color:brown;stroke:brown;&#125;g.bar.sharp path&#123;color:royalblue;stroke:royalblue;&#125;g.bar.doublesharp path&#123;color:aquamarine;stroke:aquamarine;&#125;";
	string hovertext = ".bar:hover path&#123;fill:red;color:red;stroke:red &#33;important&#125;";
	string hoverfilltext = hovertext;

	string text1 = "<style>";
	text1 += hoverfilltext;
	if (m_accQ) {
		text1 += acctext;
	}
	text1 += "g.labeltext&#123;color:gray;&#125;";
	text1 += "g.lastnote&#123;color:gray;&#125;";
	if (m_extremaQ) {
		text1 += "g.highest-pitch&#123;color:hotpink;&#125;";
		text1 += "g.lowest-pitch&#123;color:limegreen;&#125;";
	}
	text1 += "</style>";
	string text2 = text1;


	// print CSS style information if requested
	if (m_hoverQ) {
		SVGTEXT(out, text1);
	}

	int maxStaffPosition = getMaxStaffPosition(voiceInfo);

	if (!titlestring.empty()) {
		// print title
		int vpos = 54;
		if (maxStaffPosition > 12) {
			vpos = maxStaffPosition + 3;
		}
		out << "t 2 10 ";
		out << vpos;
		out << " 1 1 0 0 0 0 -1.35\n";
		// out << "_03";
		printScoreEncodedText(out, titlestring);
		out << "\n";
	}

	// print duration label if duration weighting is being used
	SVGTEXT(out, "<g class=\"labeltext\">");
	if (m_durationQ) {
		out << "t 2 185.075 14 1 0.738 0 0 0 0 0\n";
		out << "_00(durations)\n";
	} else {
		out << "t 2 185.075 14 1 0.738 0 0 0 0 0\n";
		out << "_00(attacks)\n";
	}
	SVGTEXT(out, "</g>");

	// print staff lines
	out << "8 1 0 0 0 200\n";   // staff 1
	out << "8 2 0 -6 0 200\n";   // staff 2

	int keysig = getKeySignature(infile);
	// print key signature
	if (keysig) {
		out << "17 1 10 0 " << keysig << " 101.0";
		printKeySigCompression(out, keysig, 0);
		out << endl;
		out << "17 2 10 0 " << keysig;
		printKeySigCompression(out, keysig, 1);
		out << endl;
	}

	// print barlines
	out << "14 1 0 2\n";         // starting barline
	out << "14 1 200 2\n";       // ending barline
	out << "14 1 0 2 8\n";       // curly brace at start

	// print clefs
	out << "3 2 2\n";            // treble clef
	out << "3 1 2 0 1\n";        // bass clef

	assignHorizontalPosition(voiceInfo, 25.0, 170.0);

	double maxvalue = 0.0;
	for (int i=1; i<(int)voiceInfo.size(); i++) {
		double tempvalue = getMaxValue(voiceInfo.at(i).diatonic);
		if (tempvalue > maxvalue) {
			maxvalue = tempvalue;
		}
	}
	for (int i=(int)voiceInfo.size()-1; i>0; i--) {
		if (voiceInfo.at(i).kernQ) {
			printScoreVoice(out, voiceInfo.at(i), maxvalue);
		}
	}
	if (m_allQ) {
		printScoreVoice(out, voiceInfo.at(0), maxvalue);
	}
}


//////////////////////////////
//
// Tool_prange::getMaxStaffPosition(vector<_VoiceInfo>& voiceinfo) {
//

int Tool_prange::getMaxStaffPosition(vector<_VoiceInfo>& voiceInfo) {
	int maxi = getMaxDiatonicIndex(voiceInfo[0].diatonic);
	int maxdiatonic = maxi - 3 * 7;
	int staffline = maxdiatonic - 27;
	return staffline;
}




//////////////////////////////
//
// Tool_prange::printKeySigCompression --
//

void Tool_prange::printKeySigCompression(ostream& out, int keysig, int extra) {
	double compression = 0.0;
	switch (abs(keysig)) {
		case 0: compression = 0.0; break;
		case 1: compression = 0.0; break;
		case 2: compression = 0.0; break;
		case 3: compression = 0.0; break;
		case 4: compression = 0.9; break;
		case 5: compression = 0.8; break;
		case 6: compression = 0.7; break;
		case 7: compression = 0.6; break;
	}
	if (compression <= 0.0) {
		return;
	}
	for (int i=0; i<extra; i++) {
		out << " 0";
	}
	out << " " << compression;
}



//////////////////////////////
//
// Tool_prange::assignHorizontalPosition --
//

void Tool_prange::assignHorizontalPosition(vector<_VoiceInfo>& voiceInfo, int minval, int maxval) {
	int count = 0;
	for (int i=1; i<(int)voiceInfo.size(); i++) {
		if (voiceInfo[i].kernQ) {
			count++;
		}
	}
	if (m_allQ) {
		count++;
	}

	vector<double> hpos(count, 0);
	if (count >= 2) {
		hpos[0] = maxval;
		hpos.back() = minval;
	}

	if (hpos.size() > 2) {
		for (int i=1; i<(int)hpos.size()-1; i++) {
			int ii = hpos.size() - i - 1;
			hpos[i] = (double)ii / (hpos.size()-1) * (maxval - minval) + minval;
		}
	}

	int position = 0;
	if (m_allQ) {
		position = 1;
		voiceInfo[0].hpos = hpos[0];
	}
	for (int i=0; i<(int)voiceInfo.size(); i++) {
		if (voiceInfo.at(i).kernQ) {
			voiceInfo.at(i).hpos = hpos.at(position++);
		}
	}
}



//////////////////////////////
//
// Tool_prange::getKeySignature -- find first key signature in file.
//

int Tool_prange::getKeySignature(HumdrumFile& infile) {
	for (int i=0; i<infile.getLineCount(); i++) {
		if (!infile[i].isInterpretation()) {
			if (infile[i].isData()) {
				break;
			}
			continue;
		}
		for (int j=0; j<infile[i].getFieldCount(); j++) {
			HTp token = infile.token(i, j);
			if (token->isKeySignature()) {
				return Convert::kernKeyToNumber(*token);
			}
		}
	}

	return 0; // C major key signature
}



//////////////////////////////
//
// Tool_prange::printScoreVoice -- print the range information for a particular voice (in SCORE format).
//

void Tool_prange::printScoreVoice(ostream& out, _VoiceInfo& voiceInfo, double maxvalue) {
	int mini = getMinDiatonicIndex(voiceInfo.diatonic);
	int maxi = getMaxDiatonicIndex(voiceInfo.diatonic);

	if ((mini < 0) || (maxi < 0)) {
		// no data for voice so skip
		return;
	}

	// int minacci = getMinDiatonicAcc(voiceInfo.diatonic, mini);
	// int maxacci = getMaxDiatonicAcc(voiceInfo.diatonic, maxi);
	int mindiatonic = mini - 3 * 7;
	int maxdiatonic = maxi - 3 * 7;
	// int minacc = minacci - 3;
	// int maxacc = maxacci - 3;

	int    staff;
	double vpos;

	int voicevpos = -3;
	staff = getStaffBase7(mindiatonic);
	int lowestvpos = getVpos(mindiatonic);
	if ((staff == 1) && (lowestvpos <= 0)) {
		voicevpos += lowestvpos - 2;
	}

	if (m_localQ || (voiceInfo.index == 0)) {
		double localmaxvalue = getMaxValue(voiceInfo.diatonic);
		maxvalue = localmaxvalue;
	}
	double width;
	double hoffset = 2.3333;
	double maxhist = 17.6;
	int i;
	int base7;

	// print histogram bars
	for (i=mini; i<=maxi; i++) {
		if (voiceInfo.diatonic.at(i).at(0) <= 0.0) {
			continue;
		}
		base7 = i - 3 * 7;
		staff = getStaffBase7(base7);
		vpos  = getVpos(base7);

		// staring positions of accidentals:
		vector<double> starthpos(6, 0.0);
		for (int j=1; j<(int)starthpos.size(); j++) {
			double width = maxhist * voiceInfo.diatonic.at(i).at(j)/maxvalue;
			starthpos[j] = starthpos[j-1] + width;
		}
		for (int j=(int)starthpos.size() - 1; j>0; j--) {
			starthpos[j] = starthpos[j-1];
		}

		// print chromatic alterations
		for (int j=(int)voiceInfo.diatonic.at(i).size()-1; j>0; j--) {
			if (voiceInfo.diatonic.at(i).at(j) <= 0.0) {
				continue;
			}
			int acc = 0;
			switch (j) {
				case 1: acc = -2; break;
				case 2: acc = -1; break;
				case 3: acc =  0; break;
				case 4: acc = +1; break;
				case 5: acc = +2; break;
			}

			width = maxhist * voiceInfo.diatonic.at(i).at(j)/maxvalue + hoffset;
			if (m_hoverQ) {
				string title = getNoteTitle((int)voiceInfo.diatonic.at(i).at(j), base7, acc);
				SVGTEXT(out, title);
			}
			out << "1 " << staff << " " << (voiceInfo.hpos + starthpos.at(j) + hoffset) << " " << vpos;
			out << " 0 -1 4 0 0 0 99 0 0 ";
			out << width << "\n";
			if (m_hoverQ) {
				SVGTEXT(out, "</g>");
			}
		}
	}

	string voicestring = voiceInfo.name;
	if (voicestring.empty()) {
		voicestring = voiceInfo.abbr;
	}
	if (!voicestring.empty()) {
		HumRegex hre;
		hre.replaceDestructive(voicestring, "", "(\\\\n)+$");
		vector<string> pieces;
		hre.split(pieces, voicestring, "\\\\n");

		if (pieces.size() > 1) {
			voicestring = "";
			for (int i=0; i<(int)pieces.size(); i++) {
				voicestring += pieces[i];
				if (i < (int)pieces.size() - 1) {
					voicestring += "/";
				}
			}
		}

		double increment = 4.0;
		for (int i=0; i<(int)pieces.size(); i++) {
			// print voice name
			double tvoffset = -4.0;
			out << "t 1 " << voiceInfo.hpos << " "
				<< (voicevpos - increment * i)
			  	<< " 1 1 0 0 0 0 " << tvoffset;
			out << "\n";

			if (pieces[i] == "all") {
				out << "_02";
			} else if (pieces[i] == "both") {
				out << "_02";
			} else {
				out << "_00";
			}
			printScoreEncodedText(out, pieces[i]);
			out << "\n";
		}
	}

	// print the lowest pitch in range
	staff = getStaffBase7(mindiatonic);
	vpos = getVpos(mindiatonic);
	if (m_hoverQ) {
		string content = "<g class=\"lowest-pitch\"><title>";
		content += getDiatonicPitchName(mindiatonic, 0);
		content += ": lowest note";
		if (!voicestring.empty()) {
			content += " of ";
			content += voicestring;
			content += "'s range";
		}
		content += "</title>";
		SVGTEXT(out, content);
	}
	out << "1 " << staff << " " << voiceInfo.hpos << " " << vpos
		  << " 0 0 4 0 0 -2\n";
	if (m_hoverQ) {
		SVGTEXT(out, "</g>");
	}

	// print the highest pitch in range
	staff = getStaffBase7(maxdiatonic);
	vpos = getVpos(maxdiatonic);
	if (m_hoverQ) {
		string content = "<g class=\"highest-pitch\"><title>";
		content += getDiatonicPitchName(maxdiatonic, 0);
		content += ": highest note";
		if (!voicestring.empty()) {
			content += " of ";
			content += voicestring;
			content += "'s range";
		}
		content += "</title>";
		SVGTEXT(out, content);
	}
	out << "1 " << staff << " " << voiceInfo.hpos << " " << vpos
		  << " 0 0 4 0 0 -2\n";
	if (m_hoverQ) {
		SVGTEXT(out, "</g>");
	}

	double goffset  = -1.66;
	double toffset  = 1.5;
	double median12 = getMedian12(voiceInfo.midibins);
	double median40 = Convert::base12ToBase40(median12);
	double median7  = Convert::base40ToDiatonic(median40);
	// int    acc      = Convert::base40ToAccidental(median40);

	staff = getStaffBase7(median7);
	vpos = getVpos(median7);

	// these offsets are useful when the quartile pitches are not shown...
	int vvpos = maxdiatonic - median7 + 1;
	int vvpos2 = median7 - mindiatonic + 1;
	double offset = goffset;
	if (vvpos <= 2) {
		offset += toffset;
	} else if (vvpos2 <= 2) {
		offset -= toffset;
	}

	if (m_hoverQ) {
		string content = "<g><title>";
		content += getDiatonicPitchName(median7, 0);
		content += ": median note";
		if (!voicestring.empty()) {
			content += " of ";
			content += voicestring;
			content += "'s range";
		}
		content += "</title>";
		SVGTEXT(out, content);
	}
	out << "1 " << staff << " " << voiceInfo.hpos << " ";
	if (vpos > 0) {
		out << vpos + 100;
	} else {
		out << vpos - 100;
	}
	out << " 0 1 4 0 0 " << offset << "\n";
	if (m_hoverQ) {
		SVGTEXT(out, "</g>");
	}

	if (m_finalisQ) {
		for (int f=0; f<(int)voiceInfo.diafinal.size(); f++) {
			int diafinalis = voiceInfo.diafinal.at(f);
			int accfinalis = voiceInfo.accfinal.at(f);
			int staff = getStaffBase7(diafinalis);
			int vpos = getVpos(diafinalis);
			double goffset = -1.66;
			double toffset = 3.5;

			// these offsets are useful when the quartile pitches are not shown...
			double offset = goffset;
			offset += toffset;

			if (m_hoverQ) {
				string content = "<g class=\"lastnote\"><title>";
				content += getDiatonicPitchName(diafinalis, accfinalis);
				content += ": last note";
				if (!voicestring.empty()) {
					content += " of ";
					if (voiceInfo.index == 0) {
						content += voiceInfo.namfinal.at(f);
					} else {
						content += voicestring;
					}
				}
				content += "</title>";
				SVGTEXT(out, content);
			}
			out << "1 " << staff << " " << voiceInfo.hpos << " ";
			if (vpos > 0) {
				out << vpos + 100;
			} else {
				out << vpos - 100;
			}
			out << " 0 0 4 0 0 " << offset << "\n";
			if (m_hoverQ) {
				SVGTEXT(out, "</g>");
			}
		}
	}

	/* Needs fixing
	int topquartile;
	if (m_quartileQ) {
		// print top quartile
		topquartile = getTopQuartile(voiceInfo.midibins);
		if (m_diatonicQ) {
			topquartile = Convert::base7ToBase12(topquartile);
		}
		staff = getStaffBase7(topquartile);
		vpos = getVpos(topquartile);
		vvpos = median7 - topquartile + 1;
		if (vvpos <= 2) {
			offset = goffset + toffset;
		} else {
			offset = goffset;
		}
		vvpos = maxdiatonic - topquartile + 1;
		if (vvpos <= 2) {
			offset = goffset + toffset;
		}

		if (m_hoverQ) {
			if (m_defineQ) {
				out << "SVG ";
			} else {
				out << "t 1 1\n";
				out << SVGTAG;
			}
			printScoreEncodedText(out, "<g><title>");
			printDiatonicPitchName(out, topquartile, 0);
			out << ": top quartile note";
			if (voicestring.size() > 0) {
				out <<  " of " << voicestring << "\'s range";
			}
			printScoreEncodedText(out, "</title>\n");
		}
		out << "1 " << staff << " " << voiceInfo.hpos << " ";
		if (vpos > 0) {
			out << vpos + 100;
		} else {
			out << vpos - 100;
		}
		out << " 0 0 4 0 0 " << offset << "\n";
		if (m_hoverQ) {
			SVGTEXT(out, "</g>");
		}
	}

	// print bottom quartile
	if (m_quartileQ) {
		int bottomquartile = getBottomQuartile(voiceInfo.midibins);
		if (m_diatonicQ) {
			bottomquartile = Convert::base7ToBase12(bottomquartile);
		}
		staff = getStaffBase7(bottomquartile);
		vpos = getVpos(bottomquartile);
		vvpos = median7 - bottomquartile + 1;
		if (vvpos <= 2) {
			offset = goffset + toffset;
		} else {
			offset = goffset;
		}
		vvpos = bottomquartile - mindiatonic + 1;
		if (vvpos <= 2) {
			offset = goffset - toffset;
		}
		if (m_hoverQ) {
			if (m_defineQ) {
				out << "SVG ";
			} else {
				out << "t 1 1\n";
				out << SVGTAG;
			}
			printScoreEncodedText(out, "<g><title>");
			printDiatonicPitchName(out, bottomquartile, 0);
			out << ": bottom quartile note";
			if (voicestring.size() > 0) {
				out <<  " of " << voicestring << "\'s range";
			}
			printScoreEncodedText(out, "</title>\n");
		}
		out << "1.0 " << staff << ".0 " << voiceInfo.hpos << " ";
		if (vpos > 0) {
			out << vpos + 100;
		} else {
			out << vpos - 100;
		}
		out << " 0 0 4 0 0 " << offset << "\n";
		if (m_hoverQ) {
			SVGTEXT(out, "</g>");
		}
	}
	*/

}



//////////////////////////////
//
// Tool_prange::printDiatonicPitchName --
//

void Tool_prange::printDiatonicPitchName(ostream& out, int base7, int acc) {
	out << getDiatonicPitchName(base7, acc);
}



//////////////////////////////
//
// Tool_prange::getDiatonicPitchName --
//

string Tool_prange::getDiatonicPitchName(int base7, int acc) {
	string output;
	int dpc = base7 % 7;
	char letter = (dpc + 2) % 7 + 'A';
	output += letter;
	switch (acc) {
		case -1: output += "&#9837;"; break;
		case +1: output += "&#9839;"; break;
		case -2: output += "&#119083;"; break;
		case +2: output += "&#119082;"; break;
	}
	int octave = base7 / 7;
	output += to_string(octave);
	return output;
}



//////////////////////////////
//
// Tool_prange::printHtmlStringEncodeSimple --
//

void Tool_prange::printHtmlStringEncodeSimple(ostream& out, const string& strang) {
	string newstring = strang;
	HumRegex hre;
	hre.replaceDestructive(newstring, "&", "&amp;", "g");
	hre.replaceDestructive(newstring, "<", "&lt;", "g");
	hre.replaceDestructive(newstring, ">", "&lt;", "g");
	out << newstring;
}



//////////////////////////////
//
// Tool_prange::getNoteTitle -- return the title of the histogram bar.
//    value = duration or count of notes
//    diatonic = base7 value for note
//    acc = accidental for diatonic note.
//

string Tool_prange::getNoteTitle(double value, int diatonic, int acc) {
	stringstream output;
	output << "<g class=\"bar";
	switch (acc) {
		case -2: output << " doubleflat";  break;
		case -1: output << " flat";        break;
		case  0: output << " natural";     break;
		case +1: output << " sharp";       break;
		case +2: output << " doublesharp"; break;
	}
	output << "\"";
	output << "><title>";
	if (m_durationQ) {
		output << value / 8.0;
		if (value/8.0 == 1.0) {
			output << " long on ";
		} else {
			output << " longs on ";
		}
		output << getDiatonicPitchName(diatonic, acc);
	} else {
		output << value;
		output << " ";
		output << getDiatonicPitchName(diatonic, acc);
		if (value != 1.0) {
			output << "s";
		}
	}
	output << "</title>";
	return output.str();
}



//////////////////////////////
//
// Tool_prange::getDiatonicInterval --
//

int Tool_prange::getDiatonicInterval(int note1, int note2) {
	int vpos1 = getVpos(note1);
	int vpos2 = getVpos(note2);
	return abs(vpos1 - vpos2) + 1;
}



//////////////////////////////
//
// Tool_prange::getTopQuartile --
//

int Tool_prange::getTopQuartile(vector<double>& midibins) {
	double sum = accumulate(midibins.begin(), midibins.end(), 0.0);

	double cumsum = 0.0;
	int i;
	for (i=midibins.size()-1; i>=0; i--) {
		if (midibins[i] <= 0.0) {
			continue;
		}
		cumsum += midibins[i]/sum;
		if (cumsum >= 0.25) {
			return i;
		}
	}

	return -1;
}



//////////////////////////////
//
// Tool_prange::getBottomQuartile --
//

int Tool_prange::getBottomQuartile(vector<double>& midibins) {
	double sum = accumulate(midibins.begin(), midibins.end(), 0.0);

	double cumsum = 0.0;
	int i;
	for (i=0; i<(int)midibins.size(); i++) {
		if (midibins[i] <= 0.0) {
			continue;
		}
		cumsum += midibins[i]/sum;
		if (cumsum >= 0.25) {
			return i;
		}
	}

	return -1;
}



//////////////////////////////
//
// Tool_prange::getMaxValue --
//

double Tool_prange::getMaxValue(vector<vector<double>>& bins) {
	double maxi = 0;
	for (int i=1; i<(int)bins.size(); i++) {
		if (bins.at(i).at(0) > bins.at(maxi).at(0)) {
			maxi = i;
		}
	}
	return bins.at(maxi).at(0);
}



//////////////////////////////
//
// Tool_prange::getVpos == return the position on the staff given the diatonic pitch.
//     and the staff. 1=bass, 2=treble.
//     3 = bottom line of clef, 0 = space below first ledger line.
//

double Tool_prange::getVpos(double base7) {
	double output = 0;
	if (base7 < 4 * 7) {
		// bass clef
		output = base7 - (1 + 2*7);  // D2
	} else {
		// treble clef
		output = base7 - (6 + 3*7);  // B3
	}
	return output;
}



//////////////////////////////
//
// Tool_prange::getStaffBase7 -- return 1 if less than middle C; otherwise return 2.
//

int Tool_prange::getStaffBase7(int base7) {
	if (base7 < 4 * 7) {
		return 1;
	} else {
		return 2;
	}
}


//////////////////////////////
//
// Tool_prange::getMaxDiatonicIndex -- return the highest non-zero content.
//

int Tool_prange::getMaxDiatonicIndex(vector<vector<double>>& diatonic) {
	for (int i=diatonic.size()-1; i>=0; i--) {
		if (diatonic.at(i).at(0) != 0.0) {
			return i;
		}
	}
	return -1;
}



//////////////////////////////
//
// Tool_prange::getMinDiatonicIndex -- return the lowest non-zero content.
//

int Tool_prange::getMinDiatonicIndex(vector<vector<double>>& diatonic) {
	for (int i=0; i<(int)diatonic.size(); i++) {
		if (diatonic.at(i).at(0) != 0.0) {
			return i;
		}
	}
	return -1;
}



//////////////////////////////
//
// Tool_prange::getMinDiatonicAcc -- return the lowest accidental.
//

int Tool_prange::getMinDiatonicAcc(vector<vector<double>>& diatonic, int index) {
	for (int i=1; i<(int)diatonic.at(index).size(); i++) {
		if (diatonic.at(index).at(i) != 0.0) {
			return i;
		}
	}
	return -1;
}



//////////////////////////////
//
// Tool_prange::getMaxDiatonicAcc -- return the highest accidental.
//

int Tool_prange::getMaxDiatonicAcc(vector<vector<double>>& diatonic, int index) {
	for (int i=(int)diatonic.at(index).size() - 1; i>0; i--) {
		if (diatonic.at(index).at(i) != 0.0) {
			return i;
		}
	}
	return -1;
}



//////////////////////////////
//
// Tool_prange::prepareRefmap --
//

void Tool_prange::prepareRefmap(HumdrumFile& infile) {
	vector<HLp> refrecords = infile.getGlobalReferenceRecords();
	m_refmap.clear();
	HumRegex hre;
	for (int i = (int)refrecords.size()-1; i>=0; i--) {
		string key = refrecords[i]->getReferenceKey();
		string value = refrecords[i]->getReferenceValue();
		m_refmap[key] = value;
		if (key.find("@") != string::npos) {
			// create default value
			hre.replaceDestructive(key, "", "@.*");
			if (m_refmap[key].empty()) {
				m_refmap[key] = value;
			}
		}
	}
	// fill in @{} templates (mostly for !!!title:)
	int counter = 0; // prevent recursions
	for (auto& entry : m_refmap) {

		if (entry.second.find("@") != string::npos) {
			while (hre.search(entry.second, "@\\{(.*?)\\}")) {
				string key = hre.getMatch(1);
				string value = m_refmap[key];
				hre.replaceDestructive(entry.second, value, "@\\{" + key + "\\}", "g");
				counter++;
				if (counter > 1000) {
					break;
				}
			}
		}

	}

	// prepare title
	if (m_refmap["title"].empty()) {
		m_refmap["title"] = m_refmap["OTL"];
	}
}



//////////////////////////////
//
// Tool_prange::getTitle --
//

string Tool_prange::getTitle(void) {
	string titlestring = "_00";
	HumRegex hre;
	if (m_notitleQ) {
		return "";
	} else if (m_titleQ) {
		titlestring = m_title;
		int counter = 0;

		if (titlestring.find("@") != string::npos) {
			while (hre.search(titlestring, "@\\{(.*?)\\}")) {
				string key = hre.getMatch(1);
				string value = m_refmap[key];
				hre.replaceDestructive(titlestring, value, "@\\{" + key + "\\}", "g");
				counter++;
				if (counter > 1000) {
					break;
				}
			}
		}
		if (!titlestring.empty()) {
			titlestring = "_00" + titlestring;
		}
	} else {
		titlestring = m_refmap["title"];
		if (!titlestring.empty()) {
			titlestring = "_00" + titlestring;
		}
	}
	return titlestring;
}



//////////////////////////////
//
// Tool_prange::clearHistograms --
//

void Tool_prange::clearHistograms(vector<vector<double> >& bins, int start) {
	int i;
	for (i=start; i<(int)bins.size(); i++) {
		bins[i].resize(40*11);
		fill(bins[i].begin(), bins[i].end(), 0.0);
		// bins[i].allowGrowth(0);
	}
	for (int i=0; i<(int)bins.size(); i++) {
		if (bins[i].size() == 0) {
			bins[i].resize(40*11);
			fill(bins[i].begin(), bins[i].end(), 0.0);
		}
	}
}




//////////////////////////////
//
// Tool_prange::printAnalysis --
//

void Tool_prange::printAnalysis(ostream& out, vector<double>& midibins) {
	if (m_percentileQ) {
		printPercentile(out, midibins, m_percentile);
		return;
	}  else if (m_rangeQ) {
		double notesinrange = countNotesInRange(midibins, m_rangeL, m_rangeH);
		out << notesinrange << endl;
		return;
	}

	int i;
	double normval = 1.0;

	// print the pitch histogram

	double fracL = 0.0;
	double fracH = 0.0;
	double fracA = 0.0;
	double sum = accumulate(midibins.begin(), midibins.end(), 0.0);
	if (m_normQ) {
		normval = sum;
	}
	double runningtotal = 0.0;


	out << "**keyno\t";
	if (m_pitchQ) {
		out << "**pitch";
	} else {
		out << "**kern";
	}
	out << "\t**count";
	if (m_addFractionQ) {
		out << "\t**fracL";
		out << "\t**fracA";
		out << "\t**fracH";
	}
	out << "\n";


	int base12;

	if (!m_reverseQ) {
		for (i=0; i<(int)midibins.size(); i++) {
			if (midibins[i] <= 0.0) {
				continue;
			}
			if (m_diatonicQ) {
				base12 = Convert::base7ToBase12(i);
			} else {
				base12 = i;
			}
			out << base12 << "\t";
			if (m_pitchQ) {
				out << Convert::base12ToPitch(base12);
			} else {
				out << Convert::base12ToKern(base12);
			}
			out << "\t";
			out << midibins[i] / normval;
			fracL = runningtotal/sum;
			runningtotal += midibins[i];
			fracH = runningtotal/sum;
			fracA = (fracH + fracL)/2.0;
			fracL = (int)(fracL * 10000.0 + 0.5)/10000.0;
			fracH = (int)(fracH * 10000.0 + 0.5)/10000.0;
			fracA = (int)(fracA * 10000.0 + 0.5)/10000.0;
			if (m_addFractionQ) {
				out << "\t" << fracL;
				out << "\t" << fracA;
				out << "\t" << fracH;
			}
			out << "\n";
		}
	} else {
		for (i=(int)midibins.size()-1; i>=0; i--) {
			if (midibins[i] <= 0.0) {
				continue;
			}
			if (m_diatonicQ) {
				base12 = Convert::base7ToBase12(i);
			} else {
				base12 = i;
			}
			out << base12 << "\t";
			if (m_pitchQ) {
				out << Convert::base12ToPitch(base12);
			} else {
				out << Convert::base12ToKern(base12);
			}
			out << "\t";
			out << midibins[i] / normval;
			fracL = runningtotal/sum;
			runningtotal += midibins[i];
			fracH = runningtotal/sum;
			fracA = (fracH + fracL)/2.0;
			fracL = (int)(fracL * 10000.0 + 0.5)/10000.0;
			fracH = (int)(fracH * 10000.0 + 0.5)/10000.0;
			fracA = (int)(fracA * 10000.0 + 0.5)/10000.0;
			if (m_addFractionQ) {
				out << "\t" << fracL;
				out << "\t" << fracA;
				out << "\t" << fracH;
			}
			out << "\n";
		}
	}

	out << "*-\t*-\t*-";
	if (m_addFractionQ) {
		out << "\t*-";
		out << "\t*-";
		out << "\t*-";
	}
	out << "\n";

	out << "!!tessitura:\t" << getTessitura(midibins) << " semitones\n";

	double mean = getMean12(midibins);
	if (m_diatonicQ && (mean > 0)) {
		mean = Convert::base7ToBase12(mean);
	}
	out << "!!mean:\t\t" << mean;
	out << " (";
	if (mean < 0) {
		out << "unpitched";
	} else {
		out << Convert::base12ToKern(int(mean+0.5));
	}
	out << ")" << "\n";

	int median12 = getMedian12(midibins);
	out << "!!median:\t" << median12;
	out << " (";
	if (median12 < 0) {
		out << "unpitched";
	} else {
		out << Convert::base12ToKern(median12);
	}
	out << ")" << "\n";

}



//////////////////////////////
//
// Tool_prange::getMedian12 -- return the pitch on which half of pitches are above
//     and half are below.
//

int Tool_prange::getMedian12(vector<double>& midibins) {
	double sum = accumulate(midibins.begin(), midibins.end(), 0.0);

	double cumsum = 0.0;
	int i;
	for (i=0; i<(int)midibins.size(); i++) {
		if (midibins[i] <= 0.0) {
			continue;
		}
		cumsum += midibins[i]/sum;
		if (cumsum >= 0.50) {
			return i;
		}
	}

	return -1000;
}



//////////////////////////////
//
// Tool_prange::getMean12 -- return the interval between the highest and lowest
//     pitch in terms if semitones.
//

double Tool_prange::getMean12(vector<double>& midibins) {
	double top    = 0.0;
	double bottom = 0.0;

	int i;
	for (i=0; i<(int)midibins.size(); i++) {
		if (midibins[i] <= 0.0) {
			continue;
		}
		top += midibins[i] * i;
		bottom += midibins[i];

	}

	if (bottom == 0) {
		return -1000;
	}
	return top / bottom;
}



//////////////////////////////
//
// Tool_prange::getTessitura -- return the interval between the highest and lowest
//     pitch in terms if semitones.
//

int Tool_prange::getTessitura(vector<double>& midibins) {
	int minn = -1000;
	int maxx = -1000;
	int i;

	for (i=0; i<(int)midibins.size(); i++) {
		if (midibins[i] <= 0.0) {
			continue;
		}
		if (minn < 0) {
			minn = i;
		}
		if (maxx < 0) {
			maxx = i;
		}
		if (minn > i) {
			minn = i;
		}
		if (maxx < i) {
			maxx = i;
		}
	}
	if (m_diatonicQ) {
		maxx = Convert::base7ToBase12(maxx);
		minn = Convert::base7ToBase12(minn);
	}

	return maxx - minn + 1;
}



//////////////////////////////
//
// Tool_prange::countNotesInRange --
//

double Tool_prange::countNotesInRange(vector<double>& midibins, int low, int high) {
	int i;
	double sum = 0;
	for (i=low; i<=high; i++) {
		sum += midibins[i];
	}
	return sum;
}



//////////////////////////////
//
// Tool_prange::printPercentile --
//

void Tool_prange::printPercentile(ostream& out, vector<double>& midibins, double m_percentile) {
	double sum = accumulate(midibins.begin(), midibins.end(), 0.0);
	double runningtotal = 0.0;
	int i;
	for (i=0; i<(int)midibins.size(); i++) {
		if (midibins[i] <= 0) {
			continue;
		}
		runningtotal += midibins[i] / sum;
		if (runningtotal >= m_percentile) {
			out << i << endl;
			return;
		}
	}

	out << "unknown" << endl;
}



//////////////////////////////
//
// Tool_prange::getRange --
//

void Tool_prange::getRange(int& rangeL, int& rangeH, const string& rangestring) {
	rangeL = -1; rangeH = -1;
	if (rangestring.empty()) {
		return;
	}
	int length = (int)rangestring.length();
	char* buffer = new char[length+1];
	strcpy(buffer, rangestring.c_str());
	char* ptr;
	if (std::isdigit(buffer[0])) {
		ptr = strtok(buffer, " \t\n:-");
		sscanf(ptr, "%d", &rangeL);
		ptr = strtok(NULL, " \t\n:-");
		if (ptr != NULL) {
			sscanf(ptr, "%d", &rangeH);
		}
	} else {
		ptr = strtok(buffer, " :");
		if (ptr != NULL) {
			rangeL = Convert::kernToMidiNoteNumber(ptr);
			ptr = strtok(NULL, " :");
			if (ptr != NULL) {
				rangeH = Convert::kernToMidiNoteNumber(ptr);
			}
		}
	}

	if (rangeH < 0) {
		rangeH = rangeL;
	}

	if (rangeL <   0) { rangeL =   0; }
	if (rangeH <   0) { rangeH =   0; }
	if (rangeL > 127) { rangeL = 127; }
	if (rangeH > 127) { rangeH = 127; }
	if (rangeL > rangeH) {
		int temp = rangeL;
		rangeL = rangeH;
		rangeH = temp;
	}

}





/////////////////////////////////
//
// Tool_gridtest::Tool_recip -- Set the recognized options for the tool.
//

Tool_recip::Tool_recip(void) {
	define("c|composite=b",          "do composite rhythm analysis");
	define("a|append=b",             "append composite analysis to input");
	define("p|prepend=b",            "prepend composite analysis to input");
	define("r|replace=b",            "replace **kern data with **recip data");
	define("x|attacks-only=b",       "only mark lines with note attacks");
	define("G|ignore-grace-notes=b", "ignore grace notes");
	define("k|kern-spine=i:1",       "analyze only given kern spine");
	define("K|all-spines=b",         "analyze each kern spine separately");
	define("e|exinterp=s:**recip",   "use the given exinterp for data output");
	define("n|kern-pitch=s:e",       "note to add for '-e kern' option");
	define("kern=b",                 "equivalent to '-e kern' option");
}



///////////////////////////////
//
// Tool_recip::run -- Primary interfaces to the tool.
//

bool Tool_recip::run(HumdrumFileSet& infiles) {
	bool status = true;
	for (int i=0; i<infiles.getCount(); i++) {
		status &= run(infiles[i]);
	}
	return status;
}


bool Tool_recip::run(const string& indata, ostream& out) {
	HumdrumFile infile(indata);
	return run(infile, out);
}


bool Tool_recip::run(HumdrumFile& infile, ostream& out) {
	bool status = run(infile);
	out << infile;
	return status;
}


bool Tool_recip::run(HumdrumFile& infile) {
   initialize(infile);

	int lineCount = infile.getLineCount();
	if (lineCount == 0) {
		m_error_text << "No input data";
		return false;
	}

	if (getBoolean("composite") || getBoolean("append") || getBoolean("prepend")) {
		doCompositeAnalysis(infile);
		infile.createLinesFromTokens();
		return true;
	} else if (getBoolean("replace")) {
		replaceKernWithRecip(infile);
		infile.createLinesFromTokens();
		return true;
	}
	HumdrumFile cfile = infile;
	cfile.analyzeStructure();
	replaceKernWithRecip(cfile);
	cfile.createLinesFromTokens();
	insertAnalysisSpines(infile, cfile);
	// infile.adjustMergeSpineLines();
	infile.createLinesFromTokens();
	return true;
}



//////////////////////////////
//
// Tool_recip::insertAnalysisSpines -- Could be more efficient than the
//     k-index loop...
//

void Tool_recip::insertAnalysisSpines(HumdrumFile& infile, HumdrumFile& cfile) {
	for (int i=0; i<infile.getLineCount(); i++) {
		if (!infile[i].hasSpines()) {
			continue;
		}
		for (int k=(int)m_kernspines.size()-1; k>=0; k--) {
			int fcount = infile[i].getFieldCount();
			int ktrack = m_kernspines[k]->getTrack();
			int insertj = -1;
			for (int j=fcount-1; j>=0; j--) {
				if (!infile.token(i, j)->isKern()) {
					continue;
				}
				int track = infile.token(i, j)->getTrack();
				if (track != ktrack) {
					continue;
				}
				if (insertj < 0) {
					insertj = j;
				}
				infile[i].appendToken(insertj, cfile.token(i, j)->getText());
				// infile.token(i, insertj+1)->setTrack(remapping[k]);
			}
		}
	}
}



//////////////////////////////
//
// Tool_recip::doCompositeAnalysis --
//

void Tool_recip::doCompositeAnalysis(HumdrumFile& infile) {

	// Calculate composite rhythm **recip spine:

	vector<HumNum> composite(infile.getLineCount());
	for (int i=0; i<(int)composite.size(); i++) {
		composite[i] = infile[i].getDuration();
	}

	int kernQ = false;
	if (m_exinterp.find("kern") != std::string::npos) {
		kernQ = true;
// cerr << "KERN ON" << endl;
	}

	// convert durations to **recip strings
	vector<string> recips(composite.size());
	for (int i=0; i<(int)recips.size(); i++) {
		if ((!m_graceQ) && (composite[i] == 0)) {
			continue;
		}
		recips[i] = Convert::durationToRecip(composite[i]);
		if (kernQ) {
			recips[i] += m_kernpitch;
// cerr << "ADDING PITCH " << m_kernpitch << endl;
		}
	}

	if (getBoolean("append")) {
		infile.appendDataSpine(recips, "", m_exinterp);
		return;
	} else if (getBoolean("prepend")) {
		infile.prependDataSpine(recips, "", m_exinterp);
		return;
	} else {
		infile.prependDataSpine(recips, "", m_exinterp);
		infile.printFieldIndex(0, m_humdrum_text);
		infile.clear();
		infile.readString(m_humdrum_text.str());
	}
}



//////////////////////////////
//
// Tool_recip::replaceKernWithRecip --
//

void Tool_recip::replaceKernWithRecip(HumdrumFile& infile) {
	vector<HTp> kspines = infile.getKernSpineStartList();
	HumRegex hre;
	string expression = "[^q\\d.%\\]\\[]+";
	for (int i=0; i<infile.getStrandCount(); i++) {
		HTp stok = infile.getStrandStart(i);
		if (!stok->isKern()) {
			continue;
		}
		HTp etok = infile.getStrandEnd(i);
		HTp tok = stok;
		while (tok && (tok != etok)) {
			if (!tok->isData()) {
				tok = tok->getNextToken();
				continue;
			}
			if (tok->isNull()) {
				tok = tok->getNextToken();
				continue;
			}
			if (tok->find('q') != string::npos) {
				if (m_graceQ) {
					tok->setText("q");
				} else {
					tok->setText(".");
				}
			} else {
				hre.replaceDestructive(*tok, "", expression, "g");
			}
			tok = tok->getNextToken();
		}
	}

	for (int i=0; i<(int)kspines.size(); i++) {
		kspines[i]->setText(m_exinterp);
	}

}




//////////////////////////////
//
// Tool_recip::initialize --
//

void Tool_recip::initialize(HumdrumFile& infile) {
	m_kernspines = infile.getKernSpineStartList();
	m_graceQ = !getBoolean("ignore-grace-notes");

	m_exinterp = getString("exinterp");
	if (m_exinterp.empty()) {
		m_exinterp = "**recip";
	} else if (m_exinterp[0] != '*') {
		m_exinterp.insert(0, "*");
	}
	if (m_exinterp[1] != '*') {
		m_exinterp.insert(0, "*");
	}

	m_kernpitch = getString("kern-pitch");

	if (getBoolean("kern")) {
		m_exinterp = "**kern";
	}

}





/////////////////////////////////
//
// Tool_restfill::Tool_restfill -- Set the recognized options for the tool.
//

Tool_restfill::Tool_restfill(void) {
	define("y|hidden-rests=b",  "hide inserted rests");
	define("i|exinterp=s:kern", "type of spine to fill with rests");
}



/////////////////////////////////
//
// Tool_restfill::run -- Do the main work of the tool.
//

bool Tool_restfill::run(HumdrumFileSet& infiles) {
	bool status = true;
	for (int i=0; i<infiles.getCount(); i++) {
		status &= run(infiles[i]);
	}
	return status;
}


bool Tool_restfill::run(const string& indata, ostream& out) {
	HumdrumFile infile;
	infile.readStringNoRhythm(indata);
	bool status = run(infile);
	if (hasAnyText()) {
		getAllText(out);
	} else {
		out << infile;
	}
	return status;
}


bool Tool_restfill::run(HumdrumFile& infile, ostream& out) {
	bool status = run(infile);
	if (hasAnyText()) {
		getAllText(out);
	} else {
		out << infile;
	}
	return status;
}


bool Tool_restfill::run(HumdrumFile& infile) {
	initialize();
	processFile(infile);
	infile.createLinesFromTokens();
	return true;
}



//////////////////////////////
//
// Tool_restfill::initialize --
//

void Tool_restfill::initialize(void) {
	m_hiddenQ = getBoolean("hidden-rests");
	m_exinterp = getString("exinterp");
	if (m_exinterp.empty()) {
		m_exinterp = "**kern";
	}
	if (m_exinterp.compare(0, 2, "**") != 0) {
		if (m_exinterp.compare(0, 1, "*") != 0) {
			m_exinterp = "**" + m_exinterp;
		} else {
			m_exinterp = "*" + m_exinterp;
		}
	}

}



//////////////////////////////
//
// Tool_restfill::processFile --
//

void Tool_restfill::processFile(HumdrumFile& infile) {

	vector<HTp> starts;
	infile.getSpineStartList(starts, m_exinterp);
	vector<bool> process(starts.size(), false);
	for (int i=0; i<(int)starts.size(); i++) {
		process[i] = hasBlankMeasure(starts[i]);
		if (process[i]) {
			starts[i]->setText("**temp-kern");
		}
	}
	infile.analyzeStructure();
	for (int i=0; i<(int)starts.size(); i++) {
		if (!process[i]) {
			continue;
		}
		starts[i]->setText("**kern");
		fillInRests(starts[i]);
	}
}



//////////////////////////////
//
// Tool_restfill::hasBlankMeasure --
//

bool Tool_restfill::hasBlankMeasure(HTp start) {
	bool foundcontent = false;
	HTp current = start;
	int founddata = false;
	while (current) {

		if (current->isBarline()) {
			if (founddata && !foundcontent) {
				return true;
			}
			foundcontent = false;
			founddata = false;
			current = current->getNextToken();
			continue;
		}
		if (!current->isData()) {
			current = current->getNextToken();
			continue;
		}
		founddata = true;
		if (!current->isNull()) {
			foundcontent = true;
		}
		current = current->getNextToken();

	}
	return false;
}



//////////////////////////////
//
// Tool_restfill::fillInRests --
//   Also deal with cases where the last measure does not end in a barline.
//

void Tool_restfill::fillInRests(HTp start) {
	HTp current = start;
	HTp firstcell = NULL;
	int founddata = false;
	bool foundcontent = false;
	HumNum lasttime = 0;
	HumNum currtime = 0;
	HumNum duration = 0;
	while (current) {
		if (current->isBarline()) {
			if (firstcell) {
				lasttime = firstcell->getDurationFromStart();
			}
			currtime = getNextTime(current);
			if (firstcell && founddata && !foundcontent) {
				duration = currtime - lasttime;
				addRest(firstcell, duration);
			}
			firstcell = NULL;
			founddata = false;
			foundcontent = false;
			current = current->getNextToken();
			lasttime = currtime;
			continue;
		}
		if (!current->isData()) {
			current = current->getNextToken();
			continue;
		}
		if (current->getDuration() == 0) {
			// grace-note line, so ignore
			current = current->getNextToken();
			continue;
		}
		founddata = true;
		if (!current->isNull()) {
			foundcontent = true;
		}
		if (!firstcell) {
			firstcell = current;
		}
		current = current->getNextToken();
	}
}



//////////////////////////////
//
// Tool_restfill::addRest --
//

void Tool_restfill::addRest(HTp cell, HumNum duration) {
	if (!cell) {
		return;
	}
	string text = Convert::durationToRecip(duration);
	text += "r";
	if (m_hiddenQ) {
		text += "yy";
	}
	cell->setText(text);
}



//////////////////////////////
//
// Tool_restfill::getNextTime --
//

HumNum Tool_restfill::getNextTime(HTp token) {
	HTp current = token;
	while (current) {
		if (current->isData()) {
			return current->getDurationFromStart();
		}
		current = current->getNextToken();
	}
	return token->getOwner()->getOwner()->getScoreDuration();
}






/////////////////////////////////
//
// Tool_rid::Tool_rid -- Set the recognized options for the tool.
//

Tool_rid::Tool_rid(void) {
   // Humdrum Toolkit classic rid options:
   define("D|all-data=b",                  "remove all data records");
   define("d|null-data=b",                 "remove null data records");
   define("G|all-global=b",                "remove all global comments");
   define("g|null-global=b",               "remove null global comments");
   define("I|all-interpretation=b",        "remove all interpretation records");
   define("i|null-interpretation=b",       "remove null interpretation records");
   define("L|all-local-comment=b",         "remove all local comments");
   define("l|1|null-local-comment=b",      "remove null local comments");
   define("T|all-tandem-interpretation=b", "remove all tandem interpretations");
   define("U|u=b",                         "remove unnecessary (duplicate ex. interps.");
   define("k|consider-kern-only=b",        "for -d, only consider **kern spines.");
   define("V=b",                           "negate filtering effect of program.");
   define("H|no-humdrum-syntax=b",         "equivalent to -GLIMd.");

   // additional options
   define("M|all-barlines=b",              "remove measure lines");
   define("C|all-comments=b",              "remove all comment lines");
   define("c=b",                           "remove global and local comment lines");
}



/////////////////////////////////
//
// Tool_rid::run -- Do the main work of the tool.
//

bool Tool_rid::run(HumdrumFileSet& infiles) {
	bool status = true;
	for (int i=0; i<infiles.getCount(); i++) {
		status &= run(infiles[i]);
	}
	return status;
}


bool Tool_rid::run(const string& indata, ostream& out) {
	HumdrumFile infile(indata);
	bool status = run(infile);
	if (hasAnyText()) {
		getAllText(out);
	} else {
		out << infile;
	}
	return status;
}


bool Tool_rid::run(HumdrumFile& infile, ostream& out) {
	bool status = run(infile);
	if (hasAnyText()) {
		getAllText(out);
	} else {
		out << infile;
	}
	return status;
}


bool Tool_rid::run(HumdrumFile& infile) {
	initialize();
	processFile(infile);
	return true;
}



//////////////////////////////
//
// Tool_rid::initialize --  Initializations that only have to be done once
//    for all HumdrumFile segments.
//

void Tool_rid::initialize(void) {
   option_D = getBoolean("D");
   option_d = getBoolean("d");
   option_G = getBoolean("G");
   option_g = getBoolean("g");
   option_I = getBoolean("I");
   option_i = getBoolean("i");
   option_L = getBoolean("L");
   option_l = getBoolean("l");
   option_T = getBoolean("T");
   option_U = getBoolean("U");
   option_M = getBoolean("M");
   option_C = getBoolean("C");
   option_c = getBoolean("c");
   option_k = getBoolean("k");
   option_V = getBoolean("V");

   if (getBoolean("no-humdrum-syntax")) {
      // remove all Humdrum file structure
      option_G = option_L = option_I = option_M = option_d = 1;
   }
}



//////////////////////////////
//
// Tool_rid::processFile --
//

void Tool_rid::processFile(HumdrumFile& infile) {
	int setcount = 1; // disabled for now.

   HumRegex hre;
   int revQ = option_V;

   // if bibliographic/reference records are not suppressed
   // print the !!!!SEGMENT: marker if present.
   if ((setcount > 1) && (!option_G)) {
      infile.printNonemptySegmentLabel(m_humdrum_text);
   }

   for (int i=0; i<infile.getLineCount(); i++) {
      if (option_D && (infile[i].isBarline() || infile[i].isData())) {
         // remove data lines if -D is specified
         if (revQ) {
            m_humdrum_text << infile[i] << "\n";
         }
         continue;
      }
      if (option_d) {
         // remove null data lines if -d is specified
         if (option_k && infile[i].isData() &&
               infile[i].equalFieldsQ("**kern", ".")) {
            // remove if only all **kern spines are null.
            if (revQ) {
               m_humdrum_text << infile[i] << "\n";
            }
            continue;
         } else if (!option_k && infile[i].isData() &&
               infile[i].isAllNull()) {
            // remove null data lines if all spines are null.
            if (revQ) {
               m_humdrum_text << infile[i] << "\n";
            }
            continue;
         }
      }
      if (option_G && (infile[i].isGlobalComment() ||
            infile[i].isReference())) {
         // remove global comments if -G is specified
         if (revQ) {
            m_humdrum_text << infile[i] << "\n";
         }
         continue;
      }
      if (option_g && hre.search(infile.token(i, 0), "^!!+\\s*$")) {
         // remove empty global comments if -g is specified
         if (revQ) {
            m_humdrum_text << infile[i] << "\n";
         }
         continue;
      }
      if (option_I && infile[i].isInterpretation()) {
         // remove all interpretation records
         if (revQ) {
            m_humdrum_text << infile[i] << "\n";
         }
         continue;
      }
      if (option_i && infile[i].isInterpretation() &&
            infile[i].isAllNull()) {
         // remove null interpretation records
         if (revQ) {
            m_humdrum_text << infile[i] << "\n";
         }
         continue;
      }
      if (option_L && infile[i].isLocalComment()) {
         // remove all local comments
         if (revQ) {
            m_humdrum_text << infile[i] << "\n";
         }
         continue;
      }
      if (option_l && infile[i].isLocalComment() &&
            infile[i].isAllNull()) {
         // remove null local comments
         if (revQ) {
            m_humdrum_text << infile[i] << "\n";
         }
         continue;
      }
      if (option_T && (infile[i].isInterpretation() && !infile[i].isManipulator())) {
         // remove tandem (non-manipulator) interpretations
         if (revQ) {
            m_humdrum_text << infile[i] << "\n";
         }
         continue;
      }
      if (option_U) {
         // remove unnecessary (duplicate exclusive) interpretations
         // HumdrumFile class does not allow duplicate ex. interps.
         // continue;
      }

      // non-classical options:

      if (option_M && infile[i].isBarline()) {
         // remove all measure lines
         if (revQ) {
            m_humdrum_text << infile[i] << "\n";
         }
         continue;
      }
      if (option_C && infile[i].isComment()) {
         // remove all comments (local & global)
         if (revQ) {
            m_humdrum_text << infile[i] << "\n";
         }
         continue;
      }
      if (option_c && (infile[i].isLocalComment() ||
            infile[i].isGlobalComment())) {
         // remove all comments (local & global)
         if (revQ) {
            m_humdrum_text << infile[i] << "\n";
         }
         continue;
      }

      // got past all test, so print the current line:
      if (!revQ) {
         m_humdrum_text << infile[i] << "\n";
      }
   }
}





/////////////////////////////////
//
// Tool_rphrase::Tool_rphrase -- Set the recognized options for the tool.
//

Tool_rphrase::Tool_rphrase(void) {
	define("a|average=b",            "calculate average length of rest-phrases by score");
	define("A|all-average=b",        "calculate average length of rest-phrases for all scores");
	define("B|no-breath=b",          "ignore breath interpretations");
	define("c|composite|collapse=b", "collapse all voices into single part");
	define("d|duration-unit=d:2.0",  "duration units, default: 2.0 (minims/half notes)");
	define("f|filename=b",           "include filename in output analysis");
	define("F|full-filename=b",      "include full filename location in output analysis");
	define("I|no-info=b",            "do not display summary info");
	define("l|longa=b",              "display minim length of longas");
	define("m|b|measure|barline=b",  "include barline numbers in output analysis");
	define("mark=b",                 "mark starts of phrases in score");
	define("s|sort=b",               "sort phrases by short to long length");
	define("S|reverse-sort=b",       "sort phrases by long to short length");
	define("u|url-type=s",           "URL type (jrp, 1520s) for hyperlink");
	define("z|squeeze=b",            "squeeze notation");
	define("close=b",                "close details element initially");
}



/////////////////////////////////
//
// Tool_rphrase::run -- Do the main work of the tool.
//

bool Tool_rphrase::run(HumdrumFileSet& infiles) {
	bool status = true;
	for (int i=0; i<infiles.getCount(); i++) {
		status &= run(infiles[i]);
	}
	return status;
}


bool Tool_rphrase::run(const string& indata, ostream& out) {
	HumdrumFile infile(indata);
	bool status = run(infile);
	if (hasAnyText()) {
		getAllText(out);
	} else {
		out << infile;
	}
	return status;
}


bool Tool_rphrase::run(HumdrumFile& infile, ostream& out) {
	bool status = run(infile);
	if (hasAnyText()) {
		getAllText(out);
	} else {
		out << infile;
	}
	return status;
}


bool Tool_rphrase::run(HumdrumFile& infile) {
	initialize();
	processFile(infile);
	return true;
}



//////////////////////////////
//
// Tool_rphrase::finally --
//

void Tool_rphrase::finally(void) {
	if (!m_markQ) {
		if (m_allAverageQ) {
			if (m_compositeQ) {
				double average = m_sumComposite / m_pcountComposite;
				m_free_text << "Composite average phrase length: " << average << " minims" << endl;
			} else {
				double average = m_sum / m_pcount;
				m_free_text << "All average phrase length: " << average << " minims" << endl;
			}
		}
	}
}



//////////////////////////////
//
// Tool_rphrase::initialize --
//

void Tool_rphrase::initialize(void) {
	m_barlineQ      = getBoolean("measure");
	m_allAverageQ   = getBoolean("all-average");
	m_breathQ       = !getBoolean("no-breath");
	m_compositeQ    = getBoolean("collapse");
	m_filenameQ     = getBoolean("filename");
	m_fullFilenameQ = getBoolean("full-filename");
	m_urlType       = getString("url-type");
	m_longaQ        = getBoolean("longa");
	#ifndef __EMSCRIPTEN__
		m_markQ         = getBoolean("mark");
		m_averageQ      = getBoolean("average");
	#else
		m_markQ         = !getBoolean("mark");
		m_averageQ      = !getBoolean("average");
	#endif
	m_sortQ         = getBoolean("sort");
	m_reverseSortQ  = getBoolean("reverse-sort");
	m_durUnit       = getDouble("duration-unit");
	m_infoQ         = !getDouble("no-info");
	m_squeezeQ      = getBoolean("squeeze");
	m_closeQ        = getBoolean("close");
}



//////////////////////////////
//
// Tool_rphrase::processFile --
//

void Tool_rphrase::processFile(HumdrumFile& infile) {
	if (m_filenameQ) {
		m_filename = infile.getFilename();
		HumRegex hre;
		hre.replaceDestructive(m_filename, "", ".*\\/");
		hre.replaceDestructive(m_filename, "", "\\.krn$");
	} else if (m_fullFilenameQ) {
		m_filename = infile.getFilename();
	}
	vector<HTp> kernStarts = infile.getKernSpineStartList();
	vector<Tool_rphrase::VoiceInfo> voiceInfo(kernStarts.size());
	Tool_rphrase::VoiceInfo compositeInfo;

	if (m_compositeQ) {
		fillCompositeInfo(compositeInfo, infile);
	} else {
		fillVoiceInfo(voiceInfo, kernStarts, infile);
	}

	if (m_longaQ) {
		markLongaDurations(infile);
	}

	if ((!m_allAverageQ) && (!m_markQ)) {
		if (m_line == 1) {
			if (m_compositeQ) {
				m_free_text << "Filename";
				if (!m_urlType.empty()) {
					m_free_text << "\tVHV";
				}
				m_free_text << "\tVoice";
				m_free_text << "\tComp seg count";
				if (m_averageQ) {
					m_free_text << "\tAvg comp seg dur";
				}
				m_free_text << "\tComposite seg durs";
				m_free_text << endl;
			} else {
				m_free_text << "Filename";
				if (!m_urlType.empty()) {
					m_free_text << "\tVHV";
				}
				m_free_text << "\tVoice";
				m_free_text << "\tSounding dur";
				m_free_text << "\tResting dur";
				m_free_text << "\tTotal dur";
				m_free_text << "\tSeg count";
				if (m_averageQ) {
					m_free_text << "\tSeg dur average";
				}
				m_free_text << "\tSegment durs";
				m_free_text << endl;
			}
		}
		if (m_compositeQ) {
			if (m_compositeQ) {
				m_line++;
			}
			printVoiceInfo(compositeInfo);
		} else {
			printVoiceInfo(voiceInfo);
		}
	}

	if (m_markQ) {
		outputMarkedFile(infile, voiceInfo, compositeInfo);
		if (m_squeezeQ) {
			m_humdrum_text << "!!!verovio: evenNoteSpacing" << endl;
		}
	}

}



//////////////////////////
//
// Tool_rphrase::markLongaDuratios --
//

void Tool_rphrase::markLongaDurations(HumdrumFile& infile) {
	string longrdf;
	for (int i=0; i<infile.getLineCount(); i++) {
		if (infile[i].hasSpines()) {
			continue;
		}
		if (!infile[i].isReferenceRecord()) {
			continue;
		}
		string key = infile[i].getReferenceKey();
		if (key != "RDF**kern") {
			continue;
		}
		string value = infile[i].getReferenceValue();
		HumRegex hre;
		if (hre.search(value, "^\\s*([^\\s=]+)\\s*=.*long")) {
			longrdf = hre.getMatch(1);
			break;
		}
	}

	if (longrdf.empty()) {
		return;
	}

	for (int i=0; i<infile.getLineCount(); i++) {
		if (!infile[i].hasSpines()) {
			continue;
		}
		if (!infile[i].isData()) {
			continue;
		}
		for (int j=0; j<infile[i].getFieldCount(); j++) {
			HTp token = infile.token(i, j);
			if (!token->isKern()) {
				continue;
			}
			if (token->find(longrdf) != string::npos) {
				HumNum duration = token->getTiedDuration();
				stringstream value;
				value.str("");
				value << duration.getFloat() / m_durUnit;
				token->setValue("auto", "rphrase-longa", value.str());
			}
		}
	}
}



//////////////////////////////
//
// Tool_rphrase::outputMarkedFile --
//

void Tool_rphrase::outputMarkedFile(HumdrumFile& infile, vector<Tool_rphrase::VoiceInfo>& voiceInfo,
		Tool_rphrase::VoiceInfo& compositeInfo) {
	m_free_text.clear();
	m_free_text.str("");
	for (int i=0; i<infile.getLineCount(); i++) {
		if (!infile[i].isData()) {
			m_humdrum_text << infile[i] << endl;
		} else {
			printDataLine(infile, i);
		}
	}

	if (m_infoQ) {
		printEmbeddedVoiceInfo(voiceInfo, compositeInfo, infile);
	}
}



//////////////////////////////
//
// Tool_rphrase::printDataLine --
//

void Tool_rphrase::printDataLine(HumdrumFile& infile, int index) {

	bool hasLonga = false;
	if (m_longaQ) {
		for (int j=0; j<infile[index].getFieldCount(); j++) {
			HTp token = infile.token(index, j);
			if (!token->isKern()) {
				continue;
			}
			string lotext = token->getValue("auto", "rphrase-longa");
			if (!lotext.empty()) {
				hasLonga = true;
				break;
			}
		}
	}


	bool hasLo = false;
	for (int j=0; j<infile[index].getFieldCount(); j++) {
		HTp token = infile.token(index, j);
		if (!token->isKern()) {
			continue;
		}
		string lotext = token->getValue("auto", "rphrase-start");
		if (!lotext.empty()) {
			hasLo = true;
			break;
		}
	}

	// search for composite phrase info
	bool hasGlo = false;
	if (infile[index].isData()) {
		string glotext = infile[index].getValue("auto", "rphrase-composite-start");
		if (!glotext.empty()) {
			hasGlo = true;
		}
	}

	if (hasGlo) {
		string glotext = infile[index].getValue("auto", "rphrase-composite-start");
		m_humdrum_text << "!!LO:TX:b:B:color=" << m_compositeLengthColor << ":t=" << glotext << endl;
	}

	if (hasLonga) {
		for (int j=0; j<infile[index].getFieldCount(); j++) {
			HTp token = infile.token(index, j);
			if (!token->isKern()) {
				m_humdrum_text << "!";
			} else {
				string value = token->getValue("auto", "rphrase-longa");
				if (value.empty()) {
					m_humdrum_text << "!";
				} else {
					m_humdrum_text << "!LO:TX:a:B:color=silver:t=" << value;
				}
			}
			if (j < infile[index].getFieldCount() - 1) {
				m_humdrum_text << "\t";
			}
		}
		m_humdrum_text << endl;
	}

	if (hasLo) {
		for (int j=0; j<infile[index].getFieldCount(); j++) {
			HTp token = infile.token(index, j);
			if (!token->isKern()) {
				m_humdrum_text << "!";
			} else {
				string value = token->getValue("auto", "rphrase-start");
				if (value.empty()) {
					m_humdrum_text << "!";
				} else {
					m_humdrum_text << "!LO:TX:a:B:color=" << m_voiceLengthColor << ":t=" << value;
				}
			}
			if (j < infile[index].getFieldCount() - 1) {
				m_humdrum_text << "\t";
			}
		}
		m_humdrum_text << endl;
	}

	m_humdrum_text << infile[index] << endl;
}



//////////////////////////////
//
// Tool_rphrase::getCompositeStates --
//

void Tool_rphrase::getCompositeStates(vector<int>& noteStates, HumdrumFile& infile) {
	noteStates.resize(infile.getLineCount());
	fill(noteStates.begin(), noteStates.end(), -1);
	for (int i=0; i<infile.getLineCount(); i++) {
		if (!infile[i].isData()) {
			continue;
		}
		int value = 0;
		for (int j=0; j<infile[i].getFieldCount(); j++) {
			HTp token = infile.token(i, j);
			if (!token->isKern()) {
				continue;
			}
			if (token->isRest()) {
				continue;
			} else if (token->isNull()) {
				HTp resolve = token->resolveNull();
				if (!resolve) {
					continue;
				} else if (resolve->isRest()) {
					continue;
				} else {
					value = 1;
					break;
				}
			} else {
				value = 1;
				break;
			}
		}
		noteStates[i] = value;
	}
}



//////////////////////////////
//
// Tool_rphrase::printVoiceInfo --
//

void Tool_rphrase::printVoiceInfo(vector<Tool_rphrase::VoiceInfo>& voiceInfo) {
	for (int i=(int)voiceInfo.size() - 1; i>=0; i--) {
		if (!m_compositeQ) {
			m_line++;
		}
		printVoiceInfo(voiceInfo[i]);
	}
}


//////////////////////////////
//
// Tool_rphrase::printHyperlink --
//

void Tool_rphrase::printHyperlink(const string& urlType) {
	string command = "rphrase";
	string options;
	options += "l";
	options+= "z";
	if (m_compositeQ) {
		options += "c";
	}
	if (m_sortQ) {
		options += "s";
	} else if (m_reverseSortQ) {
		options += "S";
	}
	if (!options.empty()) {
		command += "%20-";
		command += options;
	}

	if (urlType == "jrp") {
		m_free_text << "=HYPERLINK(\"https://verovio.humdrum.org/?file=jrp/\" & ";
		m_free_text << "LEFT(A" << m_line << ", 3) & \"/\" & A" << m_line << " & ";
		m_free_text << "\".krn&filter=" << command << "&k=ey\", LEFT(A" << m_line;
		m_free_text << ", FIND(\"-\", A" << m_line << ") - 1))";
	} else if (urlType == "1520s") {
		m_free_text << "=HYPERLINK(\"https://verovio.humdrum.org/?file=1520s/\" & A";
		m_free_text << m_line << " & \".krn&filter=" << command << "&k=ey\", LEFT(A";
		m_free_text << m_line << ", FIND(\"-\", A" << m_line << ") - 1))";
	}
}



//////////////////////////////
//
// Tool_rphrase::printVoiceInfo --
//

void Tool_rphrase::printVoiceInfo(Tool_rphrase::VoiceInfo& voiceInfo) {
	if (m_filenameQ) {
		m_free_text << m_filename << "\t";
	}
	if (!m_urlType.empty()) {
		printHyperlink(m_urlType);
		m_free_text << "\t";
	}
	m_free_text << voiceInfo.name << "\t";

	if (!m_compositeQ) {
		double sounding = 0.0;
		double resting = 0.0;
		for (int i=0; i<(int)voiceInfo.phraseDurs.size(); i++) {
			if (voiceInfo.phraseDurs[i] > 0.0) {
				sounding += voiceInfo.phraseDurs[i];
			}
			if (voiceInfo.restsBefore[i] > 0.0) {
				resting += voiceInfo.restsBefore[i];
			}
		}
		double total = sounding + resting;
		// double sounding_percent = int (sounding/total * 100.0 + 0.5);
		// double resting_percent = int (sounding/total * 100.0 + 0.5);

		m_free_text << twoDigitRound(sounding) << "\t";
		m_free_text << twoDigitRound(resting) << "\t";
		m_free_text << twoDigitRound(total) << "\t";
	}

	m_free_text << voiceInfo.phraseDurs.size() << "\t";

	if (m_averageQ) {
		double sum = 0;
		int count = 0;
		for (int i=0; i<(int)voiceInfo.phraseDurs.size(); i++) {
			count++;
			sum += voiceInfo.phraseDurs.at(i);
		}
		m_free_text << int(sum / count * 100.0 + 0.5)/100.0 << "\t";
	}

	if (m_sortQ || m_reverseSortQ) {
		vector<pair<double, int>> sortList;
		for (int i=0; i<(int)voiceInfo.phraseDurs.size(); i++) {
			sortList.emplace_back(voiceInfo.phraseDurs[i], i);
		}
		if (m_sortQ) {
			sort(sortList.begin(), sortList.end(),
				[](const std::pair<double, int>& a, const std::pair<double, int>& b) {
					return a.first < b.first;
			});
		} else if (m_reverseSortQ) {
			sort(sortList.begin(), sortList.end(),
				[](const std::pair<double, int>& a, const std::pair<double, int>& b) {
					return a.first > b.first;
			});
		}

		for (int i=0; i<(int)sortList.size(); i++) {
			int ii = sortList[i].second;
			if (m_barlineQ) {
				m_free_text << "m" << voiceInfo.barStarts.at(ii) << ":";
			}
			m_free_text << twoDigitRound(voiceInfo.phraseDurs.at(ii));
			if (i < (int)sortList.size() - 1) {
				m_free_text << " ";
			}
		}
	} else {
		for (int i=0; i<(int)voiceInfo.phraseDurs.size(); i++) {
			if (voiceInfo.restsBefore.at(i) > 0) {
				m_free_text << "r:" << twoDigitRound(voiceInfo.restsBefore.at(i)) << " ";
			} else if (i > 0) {
				// force display r:0 for section boundaries.
				m_free_text << "r:" << twoDigitRound(voiceInfo.restsBefore.at(i)) << " ";
			}
			if (m_barlineQ) {
				m_free_text << "m" << voiceInfo.barStarts.at(i) << ":";
			}
			m_free_text << twoDigitRound(voiceInfo.phraseDurs.at(i));
			if (i < (int)voiceInfo.phraseDurs.size() - 1) {
				m_free_text << " ";
			}
		}
	}

	m_free_text << endl;
}



//////////////////////////////
//
// Tool_rphrase::printEmbeddedVoiceInfo --
//

void Tool_rphrase::printEmbeddedVoiceInfo(vector<Tool_rphrase::VoiceInfo>& voiceInfo, Tool_rphrase::VoiceInfo& compositeInfo, HumdrumFile& infile) {

	m_humdrum_text << "!!@@BEGIN: PREHTML" << endl;;

	m_humdrum_text << "!!@SCRIPT:" << endl;
	m_humdrum_text << "!!   function rphraseGotoMeasure(measure) {" << endl;
	m_humdrum_text << "!!      let target = `svg .measure.m-${measure}`;" << endl;
	m_humdrum_text << "!!      let element = document.querySelector(target);" << endl;
	m_humdrum_text << "!!      if (element) {" << endl;
	m_humdrum_text << "!!         element.scrollIntoViewIfNeeded({ behavior: 'smooth' });" << endl;
	m_humdrum_text << "!!     }" << endl;
	m_humdrum_text << "!!   }" << endl;

	m_humdrum_text << "!!@CONTENT:\n";

	if (m_compositeQ) {
		m_humdrum_text << "!!<style> .PREHTML .composite ul .rest { color: #ccc; } </style>" << endl;
		m_humdrum_text << "!!<style> .PREHTML .composite ul .measure { color: #ccc; } </style>" << endl;
		m_humdrum_text << "!!<style> .PREHTML .composite ul .length { cursor: pointer; font-weight: bold; color: " << m_compositeLengthColor << "; } </style>" << endl;
	} else {
		m_humdrum_text << "!!<style> .PREHTML table.rphrase .rest { color: #ccc; } </style>" << endl;
		m_humdrum_text << "!!<style> .PREHTML table.rphrase .measure { color: #ccc; } </style>" << endl;
		m_humdrum_text << "!!<style> .PREHTML table.rphrase .length { cursor: pointer; font-weight: bold; color: " << m_voiceLengthColor << "; } </style>" << endl;
		m_humdrum_text << "!!<style> .PREHTML table.rphrase { border-collapse: collapse; </style>" << endl;
		m_humdrum_text << "!!<style> .PREHTML table.rphrase th, .PREHTML table.rphrase td { vertical-align: top; padding-right: 10px; } </style>" << endl;
		m_humdrum_text << "!!<style> .PREHTML table.rphrase tr { border-bottom: 1px solid #ccc; } </style>" << endl;
		m_humdrum_text << "!!<style> .PREHTML table.rphrase tr th:last-child, .PREHTML table.rphrase tr td:last-child { padding-right: 0; } </style>" << endl;

		m_humdrum_text << "!!<style> .PREHTML table.rphrase th.average { text-align: right; } </style>" << endl;
		m_humdrum_text << "!!<style> .PREHTML table.rphrase th.segments { text-align: right; } </style>" << endl;
		m_humdrum_text << "!!<style> .PREHTML table.rphrase th.sounding { text-align: right; } </style>" << endl;
		m_humdrum_text << "!!<style> .PREHTML table.rphrase th.resting { text-align: right; } </style>" << endl;
		m_humdrum_text << "!!<style> .PREHTML table.rphrase td.average { text-align: right; } </style>" << endl;
		m_humdrum_text << "!!<style> .PREHTML table.rphrase td.segments { text-align: right; } </style>" << endl;
		m_humdrum_text << "!!<style> .PREHTML table.rphrase td.sounding { text-align: right; } </style>" << endl;
		m_humdrum_text << "!!<style> .PREHTML table.rphrase td.resting { text-align: right; } </style>" << endl;
	}
	m_humdrum_text << "!!<style> .PREHTML details { position: relative; padding-left: 20px; } </style>" << endl;
	m_humdrum_text << "!!<style> .PREHTML summary { font-size: 1.5rem; cursor: pointer; list-style: none; } </style>" << endl;
	m_humdrum_text << "!!<style> .PREHTML summary::before { content: '▶'; display: inline-block; width: 2em; margin-left: -1.5em; text-align: center; } </style>" << endl;
	m_humdrum_text << "!!<style> .PREHTML details[open] summary::before { content: '▼'; } </style>" << endl;

	if (m_compositeQ) {
		m_humdrum_text << "!!<details";
		if (!m_closeQ) {
			m_humdrum_text << " open";
		}
		m_humdrum_text << "><summary>Composite rest phrasing</summary>\n";
	} else {
		m_humdrum_text << "!!<details";
		if (!m_closeQ) {
			m_humdrum_text << " open";
		}
		m_humdrum_text << "><summary>Voice rest phrasing</summary>\n";
	}
	if (m_compositeQ) {
		printEmbeddedCompositeInfo(compositeInfo, infile);
	} else {
		if (voiceInfo.size() > 0) {
			m_humdrum_text << "!!<table class='rphrase'>" << endl;
			m_humdrum_text << "!!<tr><th class='voice'>Voice</th><th class='sounding'>Sounding</th><th class='resting'>Resting</th><th class='segments'>Segments</th><th class='average'>Average</th><th class='segment-durations'>Segment durations</th></tr>" << endl;
			for (int i=(int)voiceInfo.size() - 1; i>=0; i--) {
				printEmbeddedIndividualVoiceInfo(voiceInfo[i], infile);
			}
			m_humdrum_text << "!!</table>" << endl;
			printEmbeddedVoiceInfoSummary(voiceInfo, infile);
		}
	}
	m_humdrum_text << "!!</details>" << endl;
	m_humdrum_text << "!!@@END: PREHTML" << endl;
}



//////////////////////////////
//
// Tool_rphrase::printEmbeddedCompositeInfo --
//

void Tool_rphrase::printEmbeddedCompositeInfo(Tool_rphrase::VoiceInfo& compositeInfo, HumdrumFile& infile) {

	m_humdrum_text << "!!<div class='composite'>" << endl;
	m_humdrum_text << "!!<ul>" << endl;
	m_humdrum_text << "!!<li>Composite segment count: " << compositeInfo.phraseDurs.size() << "</li>" << endl;

	if (!compositeInfo.phraseDurs.empty()) {
		m_humdrum_text << "!!<li>Composite segment duration";
		if (compositeInfo.phraseDurs.size() != 1) {
			m_humdrum_text << "s";
		}
		m_humdrum_text << ": ";
		if (m_sortQ || m_reverseSortQ) {
			vector<pair<double, int>> sortList;
			for (int i=0; i<(int)compositeInfo.phraseDurs.size(); i++) {
				sortList.emplace_back(compositeInfo.phraseDurs[i], i);
			}
			if (m_sortQ) {
				sort(sortList.begin(), sortList.end(),
					[](const std::pair<double, int>& a, const std::pair<double, int>& b) {
						return a.first < b.first;
				});
			} else if (m_reverseSortQ) {
				sort(sortList.begin(), sortList.end(),
					[](const std::pair<double, int>& a, const std::pair<double, int>& b) {
						return a.first > b.first;
				});
			}

			for (int i=0; i<(int)sortList.size(); i++) {
				int ii = sortList[i].second;
				if (m_barlineQ) {
					m_humdrum_text << "m" << compositeInfo.barStarts.at(ii) << ":";
				}
				m_humdrum_text << "<span class='length' title='measure " << compositeInfo.barStarts.at(ii)
				               << "' onclick='rphraseGotoMeasure(" << compositeInfo.barStarts.at(ii)
				               << ")' >" << twoDigitRound(compositeInfo.phraseDurs.at(ii)) << "</span>";
				if (i < (int)sortList.size() - 1) {
					m_humdrum_text << " ";
				}
			}
		} else {
			for (int i=0; i<(int)compositeInfo.phraseDurs.size(); i++) {
				if (compositeInfo.restsBefore.at(i) > 0) {
					m_humdrum_text << "<span title='inter-phrase rest' class='rest'>" << twoDigitRound(compositeInfo.restsBefore.at(i)) << "</span> ";
				} else if (i > 0) {
					// force display r:0 for section boundaries.
					m_humdrum_text << "<span title='inter-phrase rest' class='rest'>" << twoDigitRound(compositeInfo.restsBefore.at(i)) << "</span> ";
				}
				if (m_barlineQ) {
					m_humdrum_text << "<span class='measure'>m" << compositeInfo.barStarts.at(i) << ":</span>";
				}
				m_humdrum_text << "<span class='length' title='measure " << compositeInfo.barStarts.at(i)
				               << "' onclick='rphraseGotoMeasure(" << compositeInfo.barStarts.at(i)
				               << ")' >" << twoDigitRound(compositeInfo.phraseDurs.at(i)) << "</span>";
				if (i < (int)compositeInfo.phraseDurs.size() - 1) {
					m_humdrum_text << " ";
				}
			}
		}
		m_humdrum_text << "</li>" << endl;

		if (m_averageQ && (compositeInfo.phraseDurs.size() > 1)) {
			double sum = 0;
			int count = 0;
			for (int i=0; i<(int)compositeInfo.phraseDurs.size(); i++) {
				count++;
				sum += compositeInfo.phraseDurs.at(i);
			}
			double average = int(sum / count * 100.0 + 0.5)/100.0;
			m_humdrum_text << "!!<li>Average composite segment durations: " << average << "</li>" << endl;
		}

		m_humdrum_text << "!!<li>Voices: " << getVoiceInfo(infile) << "</li>" << endl;

		if (m_durUnit != 2.0) {
			m_humdrum_text << "!!<li>Duration unit: " << m_durUnit << "</li>" << endl;
		}
	}

	m_humdrum_text << "!!</ul>" << endl;
	m_humdrum_text << "!!</div>" << endl;
}



//////////////////////////////
//
// Tool_rphrase::getVoiceInfo --
//

string Tool_rphrase::getVoiceInfo(HumdrumFile& infile) {
	vector<HTp> kspines = infile.getKernSpineStartList();
	string vcount = to_string(kspines.size());
	string ocount;
	for (int i=0; i<infile.getLineCount(); i++) {
		if (infile[i].isData()) {
			break;
		}
		if (infile[i].isReferenceRecord()) {
			string key = infile[i].getReferenceKey();
			if (key == "voices") {
				ocount = infile[i].getReferenceValue();
			}
		}
	}

	if (ocount.empty()) {
		return vcount;
	}

	if (ocount != vcount) {
		string output = ocount;
		output += "(";
		output += vcount;
		output += ")";
		return output;
	} else {
		return vcount;
	}
}



//////////////////////////////
//
// Tool_rphrase::printEmbeddedVoiceInfoSummary --
//

void Tool_rphrase::printEmbeddedVoiceInfoSummary(vector<Tool_rphrase::VoiceInfo>& voiceInfo, HumdrumFile& infile) {
	m_humdrum_text << "!!<ul>" << endl;

	double total = 0.0;
	for (int i=0; i<(int)voiceInfo[0].phraseDurs.size(); i++) {
		if (voiceInfo[0].phraseDurs[i] > 0.0) {
			total += voiceInfo[0].phraseDurs[i];
		}
		if (voiceInfo[0].restsBefore[i] > 0.0) {
			total += voiceInfo[0].restsBefore[i];
		}
	}
	m_humdrum_text << "!!<li>Score duration: " << twoDigitRound(total) << "</li>" << endl;

	int countSum = 0;
	for (int i=0; i<(int)voiceInfo.size(); i++) {
		countSum += (int)voiceInfo[i].phraseDurs.size();
	}
	m_humdrum_text << "!!<li>Total segments: " << countSum << "</li>" << endl;

	double averageCount = countSum / (double)voiceInfo.size();
	averageCount = (int)(averageCount * 10 + 0.5) / 10.0;
	m_humdrum_text << "!!<li>Average voice segments: " << averageCount << "</li>" << endl;

	double durSum = 0.0;
	for (int i=0; i<(int)voiceInfo.size(); i++) {
		for (int j=0; j<(int)voiceInfo[i].phraseDurs.size(); j++) {
			durSum += voiceInfo[i].phraseDurs[j];
		}
	}
	double averageDur = durSum / countSum;
	averageDur = (int)(averageDur * 10 + 0.5) / 10.0;
	m_humdrum_text << "!!<li>Average segment duration: " << averageDur << "</li>" << endl;

	m_humdrum_text << "!!<li>Voices: " << getVoiceInfo(infile) << "</li>" << endl;

	m_humdrum_text << "!!</ul>" << endl;
}



//////////////////////////////
//
// Tool_rphrase::printEmbeddedIndividualVoiceInfo --
//

void Tool_rphrase::printEmbeddedIndividualVoiceInfo(Tool_rphrase::VoiceInfo& voiceInfo, HumdrumFile& infile) {
	m_humdrum_text << "!!<tr>" << endl;

	m_humdrum_text << "!!<td class='voice'>" << voiceInfo.name << "</td>" << endl;

	double sounding = 0.0;
	double resting = 0.0;
	for (int i=0; i<(int)voiceInfo.phraseDurs.size(); i++) {
		if (voiceInfo.phraseDurs[i] > 0.0) {
			sounding += voiceInfo.phraseDurs[i];
		}
		if (voiceInfo.restsBefore[i] > 0.0) {
			resting += voiceInfo.restsBefore[i];
		}
	}
	double total = sounding + resting;
	double spercent = int(sounding/total * 100.0 + 0.5);
	double rpercent = int(resting/total * 100.0 + 0.5);
	m_humdrum_text << "!!<td class='sounding'>" << sounding << "(" << spercent << "%)</td>" << endl;
	m_humdrum_text << "!!<td class='resting'>" << resting << "(" << rpercent << "%)</td>" << endl;

	// Segment count
	m_humdrum_text << "!!<td class='segments'>";
	m_humdrum_text << voiceInfo.phraseDurs.size();
	m_humdrum_text << "</td>" << endl;

	// Segment duration average
	m_humdrum_text << "!!<td class='average'>";
	double sum = 0;
	int count = 0;
	for (int i=0; i<(int)voiceInfo.phraseDurs.size(); i++) {
		count++;
		sum += voiceInfo.phraseDurs.at(i);
	}
	double average = int(sum / count * 100.0 + 0.5)/100.0;
	m_humdrum_text << average;
	m_humdrum_text << "</td>" << endl;

	// Segments
	m_humdrum_text << "!!<td class='segment-durations'>";
	if (m_sortQ || m_reverseSortQ) {
		vector<pair<double, int>> sortList;
		for (int i=0; i<(int)voiceInfo.phraseDurs.size(); i++) {
			sortList.emplace_back(voiceInfo.phraseDurs[i], i);
		}
		if (m_sortQ) {
			sort(sortList.begin(), sortList.end(),
				[](const std::pair<double, int>& a, const std::pair<double, int>& b) {
					return a.first < b.first;
			});
		} else if (m_reverseSortQ) {
			sort(sortList.begin(), sortList.end(),
				[](const std::pair<double, int>& a, const std::pair<double, int>& b) {
					return a.first > b.first;
			});
		}
		for (int i=0; i<(int)sortList.size(); i++) {
			int ii = sortList[i].second;
			if (m_barlineQ) {
				m_humdrum_text << "<span class='measure'>m" << voiceInfo.barStarts.at(ii) << ":</span>";
			}
			m_humdrum_text << "<span class='length' title='measure " << voiceInfo.barStarts.at(ii)
			               << ")' >" << twoDigitRound(voiceInfo.phraseDurs.at(ii)) << "</span>";
			if (i < (int)sortList.size() - 1) {
				m_humdrum_text << " ";
			}
		}
	} else {
		for (int i=0; i<(int)voiceInfo.phraseDurs.size(); i++) {
			if (voiceInfo.restsBefore.at(i) > 0) {
				m_humdrum_text << "<span title='inter-phrase rest' class='rest'>" << twoDigitRound(voiceInfo.restsBefore.at(i)) << "</span> ";
			} else if (i > 0) {
				// force display r:0 for section boundaries.
				m_humdrum_text << "<span title='inter-phrase rest' class='rest'>" << twoDigitRound(voiceInfo.restsBefore.at(i)) << "</span> ";
			}
			if (m_barlineQ) {
				m_humdrum_text << "<span class='measure'>m" << voiceInfo.barStarts.at(i) << ":</span>";
			}
			m_humdrum_text << "<span class='length' title='measure " << voiceInfo.barStarts.at(i)
			               << "' onclick='rphraseGotoMeasure(" << voiceInfo.barStarts.at(i)
			               << ")' >" << twoDigitRound(voiceInfo.phraseDurs.at(i)) << "</span>";
			if (i < (int)voiceInfo.phraseDurs.size() - 1) {
				m_humdrum_text << " ";
			}
		}
	}

	m_humdrum_text << "</td>" << endl;
	m_humdrum_text << "!!</tr>" << endl;
}



//////////////////////////////
//
// Tool_rphrase::fillCompositeInfo --
//

void Tool_rphrase::fillCompositeInfo(Tool_rphrase::VoiceInfo& compositeInfo, HumdrumFile& infile) {
	compositeInfo.name = getCompositeLabel(infile);
	vector<int> noteStates;
	getCompositeStates(noteStates, infile);

	bool inPhraseQ       = false;
	int currentBarline   = 0;
	int startBarline     = 1;
	HumNum startTime     = 0;
	HumNum restBefore    = 0;
	HumNum startTimeRest = 0;
	HumNum scoreDur      = infile.getScoreDuration();
	HTp phraseStartTok   = NULL;

	for (int i=0; i<infile.getLineCount(); i++) {

		// Split phrases at double barlines (medial cadences):
		if (infile[i].isBarline()) {
			HTp token = infile.token(i, 0);
			if (token->find("||") != string::npos) {
				HumNum tdur = token->getDurationFromStart();
				if (tdur != scoreDur) {
					// Only process if double barline is not at the end of the score.

					if (inPhraseQ) {
						// In phrase, so continue if still notes, otherwise
						// if a rest, then record the currently active phrase
						// that has ended.

						// ending a phrase
						HumNum endTime = infile[i].getDurationFromStart();
						HumNum duration = endTime - startTime;
						startTime = -1;
						inPhraseQ = false;
						double value = duration.getFloat() / m_durUnit;
						compositeInfo.phraseDurs.push_back(value);
						compositeInfo.barStarts.push_back(startBarline);
						compositeInfo.phraseStartToks.push_back(phraseStartTok);
						phraseStartTok = NULL;
						m_sumComposite += duration.getFloat() / m_durUnit;
						m_pcountComposite++;
						double rvalue = restBefore.getFloat() / m_durUnit;
						compositeInfo.restsBefore.push_back(rvalue);

						// record rest start
						startTimeRest = endTime;
					} else {
						// Not in phrase, so not splitting a rest region.
						// This case should be rare (starting a medial cadence
						// with rests and potentially starting new section with rests.
					}

				}
			}
		}

		if (infile[i].isBarline()) {
			HTp token = infile.token(i, 0);
			HumRegex hre;
			if (hre.search(token, "(\\d+)")) {
				currentBarline = hre.getMatchInt(1);
				continue;
			}
		}


		if (!infile[i].isData()) {
			continue;
		}

		if (inPhraseQ) {
			// In phrase, so continue if still notes, otherwise
			// if a rest, then record the currently active phrase
			// that has ended.
			if (noteStates[i] == 0) {
				// ending a phrase
				HumNum endTime = infile[i].getDurationFromStart();
				HumNum duration = endTime - startTime;
				startTime = -1;
				inPhraseQ = false;
				double value = duration.getFloat() / m_durUnit;
				compositeInfo.phraseDurs.push_back(value);
				compositeInfo.barStarts.push_back(startBarline);
				compositeInfo.phraseStartToks.push_back(phraseStartTok);
				phraseStartTok = NULL;
				m_sumComposite += duration.getFloat() / m_durUnit;
				m_pcountComposite++;
				double rvalue = restBefore.getFloat() / m_durUnit;
				compositeInfo.restsBefore.push_back(rvalue);
				// record rest start
				startTimeRest = endTime;
			} else {
				// continuing a phrase, so do nothing
			}
		} else {
			// Not in phrase, so continue if rest; otherwise,
			// if a note, then record a phrase start.
			if (noteStates[i] == 0) {
				// continuing a non-phrase, so do nothing
			} else {
				// starting a phrase
				startTime = infile[i].getDurationFromStart();
				startBarline = currentBarline;
				inPhraseQ = true;
				// check if there are rests before the phrase
				// The rest duration will be stored when the
				// end of the next phrase is encountered.
				if (startTimeRest >= 0) {
					restBefore = startTime - startTimeRest;
				} else {
					restBefore = 0;
				}
				phraseStartTok = infile.token(i, 0);
			}
		}

	}

	if (inPhraseQ) {
		// process last phrase
		HumNum endTime = infile.getScoreDuration();
		HumNum duration = endTime - startTime;
		double value = duration.getFloat() / m_durUnit;
		compositeInfo.phraseDurs.push_back(value);
		compositeInfo.barStarts.push_back(startBarline);
		compositeInfo.phraseStartToks.push_back(phraseStartTok);
		m_sumComposite += duration.getFloat() / m_durUnit;
		m_pcountComposite++;
		double rvalue = restBefore.getFloat() / m_durUnit;
		compositeInfo.restsBefore.push_back(rvalue);
	}

	if (m_markQ) {
		markCompositePhraseStartsInScore(infile, compositeInfo);
	}
}



//////////////////////////////
//
// Tool_rphrase::getCompositeLabel --
//

string Tool_rphrase::getCompositeLabel(HumdrumFile& infile) {
	string voices;
	for (int i=0; i<infile.getLineCount(); i++) {
		if (!infile[i].isReferenceRecord()) {
			continue;
		}
		string key = infile[i].getReferenceKey();
		if (key != "voices") {
			continue;
		}
		voices = infile[i].getReferenceValue();
		break;
	}

	if (voices.empty()) {
		return "composite";
	}

	vector<HTp> kstarts = infile.getKernSpineStartList();

	string output = "composite ";
	output += voices;


	HumRegex hre;

	if (hre.search(voices, "^\\d+$")) {
		int vint = stoi(voices);
		if (vint != (int)kstarts.size()) {
			output += "(";
			output += to_string(kstarts.size());
			output += ")";
		}
	} else {
		output += "(";
		output += to_string(kstarts.size());
		output += ")";
	}

	return output;
}



//////////////////////////////
//
// Tool_rphrase::fillVoiceInfo --
//

void Tool_rphrase::fillVoiceInfo(vector<Tool_rphrase::VoiceInfo>& voiceInfo,
		vector<HTp>& kstarts, HumdrumFile& infile) {
	for (int i=0; i<(int)kstarts.size(); i++) {
		fillVoiceInfo(voiceInfo.at(i), kstarts.at(i), infile);
	}
}


void Tool_rphrase::fillVoiceInfo(Tool_rphrase::VoiceInfo& voiceInfo, HTp& kstart, HumdrumFile& infile) {
	HTp current = kstart;

	bool inPhraseQ       = false;
	int currentBarline   = 0;
	int startBarline     = 1;
	HumNum startTime     = 0;

	HumNum restBefore    = 0;
	HumNum startTimeRest = 0;

	HumNum scoreDur = infile.getScoreDuration();
	HTp phraseStartTok = NULL;

	while (current) {

		// Split phrases at double barlines (medial cadences):
		if (infile[current->getLineIndex()].isBarline()) {
			HTp token = infile.token(current->getLineIndex(), 0);
			if (token->find("||") != string::npos) {
				HumNum tdur = token->getDurationFromStart();
				if (tdur != scoreDur) {
					// Only process if double barline is not at the end of the score.

					if (inPhraseQ) {
						// In phrase, so continue if still notes, otherwise
						// if a rest, then record the currently active phrase
						// that has ended.

						HumNum endTime = current->getDurationFromStart();
						HumNum duration = endTime - startTime;
						startTime = -1;
						inPhraseQ = false;
						double value = duration.getFloat() / m_durUnit;
						voiceInfo.phraseDurs.push_back(value);
						voiceInfo.barStarts.push_back(startBarline);
						voiceInfo.phraseStartToks.push_back(phraseStartTok);
						phraseStartTok = NULL;
						m_sum += duration.getFloat() / m_durUnit;
						m_pcount++;
						double rvalue = restBefore.getFloat() / m_durUnit;
						voiceInfo.restsBefore.push_back(rvalue);

						// record rest start
						startTimeRest = endTime;
					} else {
						// Not in phrase, so not splitting a rest region.
						// This case should be rare (starting a medial cadence
						// with rests and potentially starting new section with rests.
					}

				}
			}
		}

		if (current->isBarline()) {
			HumRegex hre;
			if (hre.search(current, "(\\d+)")) {
				currentBarline = hre.getMatchInt(1);
				current = current->getNextToken();
				continue;
			}
		}

		if (current->isInstrumentName()) {
			voiceInfo.name = current->substr(3);
		}
		if (!(current->isData() || (m_breathQ && (*current == "*breath")))) {
			current = current->getNextToken();
			continue;
		}
		if (current->isNull()) {
			current = current->getNextToken();
			continue;
		}

		if (inPhraseQ) {
			// In phrase, so continue if still notes, otherwise
			// if a rest, then record the currently active phrase
			// that has ended.
			if (current->isRest() || (*current == "*breath")) {
				// ending a phrase
				HumNum endTime = current->getDurationFromStart();
				HumNum duration = endTime - startTime;
				startTime = -1;
				inPhraseQ = false;
				double value = duration.getFloat() / m_durUnit;
				voiceInfo.phraseDurs.push_back(value);
				voiceInfo.barStarts.push_back(startBarline);
				voiceInfo.phraseStartToks.push_back(phraseStartTok);
				phraseStartTok = NULL;
				m_sum += duration.getFloat() / m_durUnit;
				m_pcount++;
				double rvalue = restBefore.getFloat() / m_durUnit;
				voiceInfo.restsBefore.push_back(rvalue);
				// record rest start
				startTimeRest = endTime;
			} else {
				// continuing a phrase, so do nothing
			}
		} else {
			// Not in phrase, so continue if rest; otherwise,
			// if a note, then record a phrase start.
			if (current->isRest() || (*current == "*breath")) {
				// continuing a non-phrase, so do nothing
			} else {
				// starting a phrase
				startTime = current->getDurationFromStart();
				startBarline = currentBarline;
				inPhraseQ = true;
				// check if there are rests before the phrase
				// The rest duration will be stored when the
				// end of the next phrase is encountered.
				if (startTimeRest >= 0) {
					restBefore = startTime - startTimeRest;
				} else {
					restBefore = 0;
				}
				phraseStartTok = current;
			}
		}

		current = current->getNextToken();
	}
	if (inPhraseQ) {
		// process last phrase
		HumNum endTime = kstart->getLine()->getOwner()->getScoreDuration();
		HumNum duration = endTime - startTime;
		double value = duration.getFloat() / m_durUnit;
		voiceInfo.phraseDurs.push_back(value);
		voiceInfo.barStarts.push_back(startBarline);
		voiceInfo.phraseStartToks.push_back(phraseStartTok);
		m_sum += duration.getFloat() / m_durUnit;
		m_pcount++;
		double rvalue = restBefore.getFloat() / m_durUnit;
		voiceInfo.restsBefore.push_back(rvalue);
	}

	if (m_markQ) {
		markPhraseStartsInScore(infile, voiceInfo);
	}
}



//////////////////////////////
//
// Tool_rphrase::markCompositePhraseStartsInScore --
//

void Tool_rphrase::markCompositePhraseStartsInScore(HumdrumFile& infile, Tool_rphrase::VoiceInfo& compositeInfo) {
	stringstream buffer;
	for (int i=0; i<(int)compositeInfo.phraseStartToks.size(); i++) {
		HTp tok = compositeInfo.phraseStartToks.at(i);
		string measure = "";
		if (m_barlineQ) {
			measure = to_string(compositeInfo.barStarts.at(i));
		}
		double duration = compositeInfo.phraseDurs.at(i);
		buffer.str("");
		if (!measure.empty()) {
			buffer << "m" << measure << "&colon;";
		}
		buffer << twoDigitRound(duration);
		int lineIndex = tok->getLineIndex();
		infile[lineIndex].setValue("auto", "rphrase-composite-start", buffer.str());
	}
}



//////////////////////////////
//
// Tool_rphrase::twoDigitRound --
//

double Tool_rphrase::twoDigitRound(double input) {
	return int(input * 100.0 + 0.499999) / 100.0;
}



//////////////////////////////
//
// Tool_rphrase::markPhraseStartsInScore --
//

void Tool_rphrase::markPhraseStartsInScore(HumdrumFile& infile, Tool_rphrase::VoiceInfo& voiceInfo) {
	stringstream buffer;
	for (int i=0; i<(int)voiceInfo.phraseStartToks.size(); i++) {
		HTp tok = voiceInfo.phraseStartToks.at(i);
		string measure = "";
		if (m_barlineQ) {
			measure = to_string(voiceInfo.barStarts.at(i));
		}
		double duration = voiceInfo.phraseDurs.at(i);
		buffer.str("");
		if (!measure.empty()) {
			buffer << "m" << measure << "&colon;";
		}
		buffer << duration;
		tok->setValue("auto", "rphrase-start", buffer.str());
	}
}




/////////////////////////////////
//
// Tool_ruthfix::Tool_ruthfix -- Set the recognized options for the tool.
//

Tool_ruthfix::Tool_ruthfix(void) {
	// add options here
}



/////////////////////////////////
//
// Tool_ruthfix::run -- Do the main work of the tool.
//

bool Tool_ruthfix::run(HumdrumFileSet& infiles) {
	bool status = true;
	for (int i=0; i<infiles.getCount(); i++) {
		status &= run(infiles[i]);
	}
	return status;
}


bool Tool_ruthfix::run(const string& indata, ostream& out) {
	HumdrumFile infile(indata);
	bool status = run(infile);
	if (hasAnyText()) {
		getAllText(out);
	} else {
		out << infile;
	}
	return status;
}


bool Tool_ruthfix::run(HumdrumFile& infile, ostream& out) {
	bool status = run(infile);
	if (hasAnyText()) {
		getAllText(out);
	} else {
		out << infile;
	}
	return status;
}


bool Tool_ruthfix::run(HumdrumFile& infile) {
	insertCrossBarTies(infile);
	return true;
}



//////////////////////////////
//
// Tool_ruthfix::insertCrossBarTies --
//

void Tool_ruthfix::insertCrossBarTies(HumdrumFile& infile) {
	int scount = infile.getStrandCount();
	if (scount == 0) {
		// The input file was not read from a file but was created
		// dynamically.  The easiest thing to do is to reload to get the
		// spine/strand information.
		stringstream ss;
		infile.createLinesFromTokens();
		ss << infile;
		infile.readString(ss.str());
	}
	scount = infile.getStrandCount();


	HTp token;
	for (int i=0; i<scount; i++) {
		token = infile.getStrandStart(i);
		if (!token->isKern()) {
			continue;
		}
		insertCrossBarTies(infile, i);
	}
}


void Tool_ruthfix::insertCrossBarTies(HumdrumFile& infile, int strand) {
	HTp sstart = infile.getStrandStart(strand);
	HTp send   = infile.getStrandEnd(strand);
	HTp s = sstart;
	HTp lastnote = NULL;
	bool barstart = true;
	while (s != send) {
		if (s->isBarline()) {
			barstart = true;
		} else if (s->isNote()) {
			if (lastnote && barstart && (s->find("yy") != string::npos)) {
				createTiedNote(lastnote, s);
			}
			barstart = false;
			lastnote = s;
		} else if (s->isRest()) {
			lastnote = NULL;
			barstart = false;
		}
		s = s->getNextToken();
		if (!s) {
			break;
		}
	}
}



//////////////////////////////
//
// Tool_ruthfix::createTiedNote -- Does not work for chords.
//  change  1E-X TO 2E-Xyy
//      to  [1E-X TO 2E-X]
//

void Tool_ruthfix::createTiedNote(HTp left, HTp right) {
	if (left->isChord() || right->isChord()) {
		return;
	}
	auto loc = right->find("yy");
	if (loc != string::npos) {
		left->insert(0, 1, '[');
		right->replace(loc, 2, "]");
	}
}





/////////////////////////////////
//
// Tool_sab2gs::Tool_sab2gs -- Set the recognized options for the tool.
//

Tool_sab2gs::Tool_sab2gs(void) {
	define("b|below=s:<", "Marker for displaying on next staff below");
	define("d|down=b",    "Use only *down/*Xdown interpretations");
}



/////////////////////////////////
//
// Tool_sab2gs::run -- Do the main work of the tool.
//

bool Tool_sab2gs::run(HumdrumFileSet& infiles) {
	bool status = true;
	for (int i=0; i<infiles.getCount(); i++) {
		status &= run(infiles[i]);
	}
	return status;
}


bool Tool_sab2gs::run(const string& indata, ostream& out) {
	HumdrumFile infile(indata);
	bool status = run(infile);
	if (hasAnyText()) {
		getAllText(out);
	} else {
		out << infile;
	}
	return status;
}


bool Tool_sab2gs::run(HumdrumFile& infile, ostream& out) {
	bool status = run(infile);
	if (hasAnyText()) {
		getAllText(out);
	} else {
		out << infile;
	}
	return status;
}


bool Tool_sab2gs::run(HumdrumFile& infile) {
	initialize();
	processFile(infile);
	return true;
}



//////////////////////////////
//
// Tool_sab2gs::initialize --  Initializations that only have to be done once
//    for all HumdrumFile segments.
//

void Tool_sab2gs::initialize(void) {
	m_belowMarker = getString("below");
	m_downQ       = getBoolean("down");
}



//////////////////////////////
//
// Tool_sab2gs::processFile --
//

void Tool_sab2gs::processFile(HumdrumFile& infile) {

	vector<HTp> spines;
	infile.getSpineStartList(spines);
	vector<HTp> kernSpines;
	for (int i=0; i<(int)spines.size(); i++) {
		if (spines[i]->isKern()) {
			kernSpines.push_back(spines[i]);
		}
	}
	if (kernSpines.size() != 3) {
		// Not valid for processing kern spines, so return original:
		m_humdrum_text << infile;
		return;
	}

	string belowMarker = hasBelowMarker(infile);
	if (!belowMarker.empty()) {
		m_hasBelowMarker = true;
		m_belowMarker = belowMarker;
	}

	adjustMiddleVoice(kernSpines[1]);
	printGrandStaff(infile, kernSpines);
}



/////////////////////////////
//
// Tool_sab2gs::hasBelowMarker -- Returns below marker if found; otherwise,
//     returns empty string.
//

string Tool_sab2gs::hasBelowMarker(HumdrumFile& infile) {
	string output;
	HumRegex hre;
	if (m_hasCrossStaff) {
		// Search backwards since if there is a below marker, it will be more
		// likely found at the bottom of the score.
		for (int i=infile.getLineCount()-1; i<=0; i--) {
			if (infile[i].hasSpines()) {
				continue;
			}
			if (hre.search(infile.token(i, 0), "^!!!RDF\\*\\*kern\\s*:\\s*([^\\s=]+)\\s*=\\s*below\\s*$")) {
				output = hre.getMatch(1);
				break;
			}
		}
	}
	return output;
}



///////////////////////////////
//
// Tool_sab2gs::printGrandStaff --
//

void Tool_sab2gs::printGrandStaff(HumdrumFile& infile, vector<HTp>& starts) {
	bool foundData = false;

	vector<int> ktracks(starts.size());
	for (int i=0; i<(int)starts.size(); i++) {
		ktracks.at(i) = starts.at(i)->getTrack();
	}

	for (int i=0; i<infile.getLineCount(); i++) {
		if (!infile[i].hasSpines()) {
			m_humdrum_text << infile[i] << endl;
			continue;
		}
		if (!foundData && (infile[i].isData() || infile[i].isBarline())) {
			printSpineSplit(infile, i, ktracks);
			foundData = true;
		}
		if (*infile.token(i, 0) == "*-") {
			printSpineMerge(infile, i, ktracks);
			foundData = false;
			printReducedLine(infile, i, ktracks);
			if (m_hasCrossStaff && !m_hasBelowMarker) {
				m_humdrum_text << "!!!RDF**kern: " << m_belowMarker << " = below" << endl;
			}
			continue;
		}
		if (foundData) {
			printSwappedLine(infile, i, ktracks);
		} else {
			printReducedLine(infile, i, ktracks);
		}
	}
}


//////////////////////////////
//
// Tool_sab2gs::printSpineSplit -- Split second and third spines, moving non-kern spines
//    after the second one to the end of the line (null interpretations);
//

void Tool_sab2gs::printSpineSplit(HumdrumFile& infile, int index, vector<int>& ktracks) {
	// First print all non-kern spines at the start of the line:
	int nextIndex = 0;
	int fcount = 0;
	for (int i=0; i<infile[index].getFieldCount(); i++) {
		HTp token = infile.token(index, i);
		if (token->isKern()) {
			break;
		}
		if (fcount > 0) {
			m_humdrum_text << "\t";
		}
		fcount++;
		m_humdrum_text << "*";
		nextIndex++;
	}
	// Must be on the first **kern spine:
	if (!infile.token(index, nextIndex)->isKern()) {
		cerr << "Something strange happend on line " << index+1 << ": " << infile[index] << endl;
		return;
	}
	// Print the first **kern spine:
	if (fcount > 0) {
		m_humdrum_text << "\t";
	}
	fcount++;
	m_humdrum_text << "*";
	nextIndex++;
	// Next print all non-kern spines after first **kern spine:
	for (int i=nextIndex; i<infile[index].getFieldCount(); i++) {
		HTp token = infile.token(index, i);
		if (token->isKern()) {
			break;
		}
		if (fcount > 0) {
			m_humdrum_text << "\t";
		}
		m_humdrum_text << "*";
		nextIndex++;
	}
	// Second **kern spine must be **kern data:
	if (!infile.token(index, nextIndex)->isKern()) {
		cerr << "Something strange happend B on line " << index+1 << ": " << infile[index] << endl;
		return;
	}
	// Ignore the second kern spine as it does not exist yet in the
	// output data.
	nextIndex++;
	// Then store any non-kern spines between the second and third kern spines to
	// append to the end of the data line later.
	string postData;
	for (int i=nextIndex; i<infile[index].getFieldCount(); i++) {
		HTp token = infile.token(index, i);
		if (token->isKern()) {
			break;
		}
		if (!postData.empty()) {
			postData += "\t";
		}
		nextIndex++;
		postData += "*";
	}
	// Third kern spine must be **kern data:
	if (!infile.token(index, nextIndex)->isKern()) {
		cerr << "Something strange happend C on line " << index+1 << ": " << infile[index] << endl;
		return;
	}
	// Now print the third kern spine:
	if (fcount > 0) {
		m_humdrum_text << "\t";
	}
	fcount++;
	nextIndex++;
	m_humdrum_text << "*^";
	// Now print the non-kern spines after the third **kern spine (or rather just
	// all spines including any other **kern spines, although current requirement
	// is that there are only three **kern spines.
	for (int i=nextIndex; i<infile[index].getFieldCount(); i++) {
		// HTp token = infile.token(index, nextIndex);
		if (fcount > 0) {
			m_humdrum_text << "\t";
		}
		fcount++;
		nextIndex++;
		m_humdrum_text << "*";
	}
	// Finally print any non-kern spines after the second **kern spine:
	if (!postData.empty()) {
		if (fcount > 0) {
			m_humdrum_text << "\t";
		}
		fcount++;
		m_humdrum_text << postData;
	}
	m_humdrum_text << endl;
}



//////////////////////////////
//
// Tool_sab2gs::printSpineMerge -- Merge second and third spines, moving non-kern spines
//    after the second one to the end of the line (null interpretations);
//

void Tool_sab2gs::printSpineMerge(HumdrumFile& infile, int index, vector<int>& ktracks) {
	// First print all non-kern spines at the start of the line:
	int nextIndex = 0;
	int fcount = 0;
	for (int i=0; i<infile[index].getFieldCount(); i++) {
		HTp token = infile.token(index, i);
		if (token->isKern()) {
			break;
		}
		if (fcount > 0) {
			m_humdrum_text << "\t";
		}
		fcount++;
		m_humdrum_text << "*";
		nextIndex++;
	}
	// Must be on the first **kern spine:
	if (!infile.token(index, nextIndex)->isKern()) {
		cerr << "Something strange happend on line " << index+1 << ": " << infile[index] << endl;
		return;
	}
	// Print the first **kern spine:
	if (fcount > 0) {
		m_humdrum_text << "\t";
	}
	fcount++;
	m_humdrum_text << "*";
	nextIndex++;
	// Next print all non-kern spines after first **kern spine:
	for (int i=nextIndex; i<infile[index].getFieldCount(); i++) {
		HTp token = infile.token(index, i);
		if (token->isKern()) {
			break;
		}
		if (fcount > 0) {
			m_humdrum_text << "\t";
		}
		m_humdrum_text << "*";
		nextIndex++;
	}
	// Second **kern spine must be **kern data:
	if (!infile.token(index, nextIndex)->isKern()) {
		cerr << "Something strange happend B on line " << index+1 << ": " << infile[index] << endl;
		return;
	}
	// Save the second kern spine as it does not exist yet in the
	// output data.
	// HTp savedKernToken = infile.token(index, nextIndex);
	nextIndex++;
	// Then store any non-kern spines between the second and third kern spines to
	// append to the end of the data line later.
	string postData;
	for (int i=nextIndex; i<infile[index].getFieldCount(); i++) {
		HTp token = infile.token(index, i);
		if (token->isKern()) {
			break;
		}
		if (!postData.empty()) {
			postData += "\t";
		}
		nextIndex++;
		postData += "*";
	}
	// Third kern spine must be **kern data:
	if (!infile.token(index, nextIndex)->isKern()) {
		cerr << "Something strange happend C on line " << index+1 << ": " << infile[index] << endl;
		return;
	}
	// Now print the third kern spine:
	if (fcount > 0) {
		m_humdrum_text << "\t";
	}
	fcount++;
	m_humdrum_text << "*v";
	nextIndex++;
	// Now printed the saved second **kern spine:
	if (fcount > 0) {
		m_humdrum_text << "\t";
	}
	m_humdrum_text << "*v";
	fcount++;
	// Now print the non-kern spines after the third **kern spine (or rather just
	// all spines including any other **kern spines, although current requirement
	// is that there are only three **kern spines.
	for (int i=nextIndex; i<infile[index].getFieldCount(); i++) {
		// HTp token = infile.token(index, nextIndex);
		if (fcount > 0) {
			m_humdrum_text << "\t";
		}
		fcount++;
		nextIndex++;
		m_humdrum_text << "*";
	}
	// Finally print any non-kern spines after the second **kern spine:
	if (!postData.empty()) {
		if (fcount > 0) {
			m_humdrum_text << "\t";
		}
		fcount++;
		m_humdrum_text << postData;
	}
	m_humdrum_text << endl;
}



//////////////////////////////
//
// Tool_sab2gs::printSwappedLine -- move the second **kern spine immediately after
//    the third one, and move any non-kern spines after then end of the line.
//

void Tool_sab2gs::printSwappedLine(HumdrumFile& infile, int index, vector<int>& ktracks) {
	// First print all non-kern spines at the start of the line:
	int nextIndex = 0;
	int fcount = 0;
	for (int i=0; i<infile[index].getFieldCount(); i++) {
		HTp token = infile.token(index, i);
		if (token->isKern()) {
			break;
		}
		if (fcount > 0) {
			m_humdrum_text << "\t";
		}
		fcount++;
		m_humdrum_text << token;
		nextIndex++;
	}
	// Must be on the first **kern spine:
	if (!infile.token(index, nextIndex)->isKern()) {
		cerr << "Something strange happend on line " << index+1 << ": " << infile[index] << endl;
		return;
	}
	// Print the first **kern spine:
	if (fcount > 0) {
		m_humdrum_text << "\t";
	}
	fcount++;
	m_humdrum_text << infile.token(index, nextIndex);
	nextIndex++;
	// Next print all non-kern spines after first **kern spine:
	for (int i=nextIndex; i<infile[index].getFieldCount(); i++) {
		HTp token = infile.token(index, i);
		if (token->isKern()) {
			break;
		}
		if (fcount > 0) {
			m_humdrum_text << "\t";
		}
		m_humdrum_text << token;
		nextIndex++;
	}
	// Second **kern spine must be **kern data:
	if (!infile.token(index, nextIndex)->isKern()) {
		cerr << "Something strange happend B on line " << index+1 << ": " << infile[index] << endl;
		return;
	}
	// Save the second kern spine as it does not exist yet in the
	// output data.
	HTp savedKernToken = infile.token(index, nextIndex++);
	// Then store any non-kern spines between the second and third kern spines to
	// append to the end of the data line later.
	string postData;
	for (int i=nextIndex; i<infile[index].getFieldCount(); i++) {
		HTp token = infile.token(index, i);
		if (token->isKern()) {
			break;
		}
		if (!postData.empty()) {
			postData += "\t";
		}
		nextIndex++;
		postData += *token;
	}
	// Third kern spine must be **kern data:
	if (!infile.token(index, nextIndex)->isKern()) {
		cerr << "Something strange happend C on line " << index+1 << ": " << infile[index] << endl;
		return;
	}
	// Now print the third kern spine:
	if (fcount > 0) {
		m_humdrum_text << "\t";
	}
	fcount++;
	m_humdrum_text << infile.token(index, nextIndex++);
	// Now printed the saved second **kern spine:
	if (fcount > 0) {
		m_humdrum_text << "\t";
	}
	m_humdrum_text << savedKernToken;
	fcount++;
	// Now print the non-kern spines after the third **kern spine (or rather just
	// all spines including any other **kern spines, although current requirement
	// is that there are only three **kern spines.
	for (int i=nextIndex; i<infile[index].getFieldCount(); i++) {
		HTp token = infile.token(index, nextIndex);
		if (fcount > 0) {
			m_humdrum_text << "\t";
		}
		fcount++;
		nextIndex++;
		m_humdrum_text << token;
	}
	// Finally print any non-kern spines after the second **kern spine:
	if (!postData.empty()) {
		if (fcount > 0) {
			m_humdrum_text << "\t";
		}
		fcount++;
		m_humdrum_text << postData;
	}
	m_humdrum_text << endl;
}



//////////////////////////////
//
// Tool_sab2gs::printReducedLine -- remove the contents of the second **kern
//    spine, and move any non-kernspines after it to become after the third **kern spine
//

void Tool_sab2gs::printReducedLine(HumdrumFile& infile, int index, vector<int>& ktracks) {
	// First print all non-kern spines at the start of the line:
	int nextIndex = 0;
	int fcount = 0;
	for (int i=0; i<infile[index].getFieldCount(); i++) {
		HTp token = infile.token(index, i);
		if (token->isKern()) {
			break;
		}
		if (fcount > 0) {
			m_humdrum_text << "\t";
		}
		fcount++;
		m_humdrum_text << token;
		nextIndex++;
	}
	// Must be on the first **kern spine:
	if (!infile.token(index, nextIndex)->isKern()) {
		cerr << "Something strange happend on line " << index+1 << ": " << infile[index] << endl;
		return;
	}
	// Print the first **kern spine:
	if (fcount > 0) {
		m_humdrum_text << "\t";
	}
	fcount++;
	m_humdrum_text << infile.token(index, nextIndex++);
	// Next print all non-kern spines after first **kern spine:
	for (int i=nextIndex; i<infile[index].getFieldCount(); i++) {
		HTp token = infile.token(index, i);
		if (token->isKern()) {
			break;
		}
		if (fcount > 0) {
			m_humdrum_text << "\t";
		}
		m_humdrum_text << token;
		nextIndex++;
	}
	// Second **kern spine must be **kern data:
	if (!infile.token(index, nextIndex)->isKern()) {
		cerr << "Something strange happend B on line " << index+1 << ": " << infile[index] << endl;
		return;
	}
	// Ignore the second kern spine as it does not exist yet in the
	// output data.
	nextIndex++;
	// Then store any non-kern spines between the second and third kern spines to
	// append to the end of the data line later.
	string postData;
	for (int i=nextIndex; i<infile[index].getFieldCount(); i++) {
		HTp token = infile.token(index, i);
		if (token->isKern()) {
			break;
		}
		if (!postData.empty()) {
			postData += "\t";
		}
		nextIndex++;
		postData += *token;
	}
	// Third kern spine must be **kern data:
	if (!infile.token(index, nextIndex)->isKern()) {
		cerr << "Something strange happend C on line " << index+1 << ": " << infile[index] << endl;
		return;
	}
	// Now print the third kern spine:
	if (fcount > 0) {
		m_humdrum_text << "\t";
	}
	fcount++;
	m_humdrum_text << infile.token(index, nextIndex++);
	// Now print the non-kern spines after the third **kern spine (or rather just
	// all spines including any other **kern spines, although current requirement
	// is that there are only three **kern spines.
	for (int i=nextIndex; i<infile[index].getFieldCount(); i++) {
		HTp token = infile.token(index, nextIndex);
		if (fcount > 0) {
			m_humdrum_text << "\t";
		}
		fcount++;
		nextIndex++;
		m_humdrum_text << token;
	}
	// Finally print any non-kern spines after the second **kern spine:
	if (!postData.empty()) {
		if (fcount > 0) {
			m_humdrum_text << "\t";
		}
		fcount++;
		m_humdrum_text << postData;
	}
	m_humdrum_text << endl;
}


//////////////////////////////
//
// Tool_sab2gs::adjustMiddleVoice --
//

void Tool_sab2gs::adjustMiddleVoice(HTp spineStart) {
	HTp current = spineStart;
	// staff: +1 = top staff, -1 = bottom staff
	// when on top staff, force stem down, or on bottom staff, force stem up
	// when on bottom staff add "<" marker after pitch (or rest) to move to
	// bottom staff.  Staff choice is selected by clef: clefG2 is for top staff
	// and staffF4 is for bottom staff. Chords are not expected.
	int staff = 0;
	string replacement = "$1" + m_belowMarker;
	HumRegex hre;
	while (current) {
		if (*current == "*-") {
			break;
		}
		if (!m_downQ && current->isClef()) {
			if (current->substr(0, 7) == "*clefG2") {
				staff = 1;
				// suppress clef:
				string text = "*x" + current->substr(1);
				current->setText(text);
			} else if (current->substr(0, 7) == "*clefF4") {
				staff = -1;
				// suppress clef:
				string text = "*x" + current->substr(1);
				current->setText(text);
			}
		} else if (current->isInterpretation()) {
			if (*current == "*down") {
				staff = -1;
			} else if (*current == "*Xdown") {
				staff = 1;
			}
		} else if ((staff != 0) && current->isData()) {
			if (current->isNull()) {
				// nothing to do with token
				current = current->getNextToken();
				continue;
			}
			if (staff > 0) {
				// force stems down or add stem down to non-rest notes
				if (hre.search(current, "[/\\\\]")) {
					string value = hre.replaceCopy(current, "\\", "/", "g");
					if (value != *current) {
						current->setText(value);
					}
					current = current->getNextToken();
					continue;
				} if (current->isRest()) {
					current = current->getNextToken();
					continue;
				} else {
					string value = *current;
					value += "\\";
					current->setText(value);
					current = current->getNextToken();
					continue;
				}

			} else if (staff < 0) {
				// force stems up or add stem up to non-rest notes
				if (hre.search(current, "[/\\\\]")) {
					string value = hre.replaceCopy(current, "\\", "/", "g");
					if (value != *current) {
						current->setText(value);
					}
					current = current->getNextToken();
					continue;
				} if (current->isRest()) {
					// Do not at stem direction to rests
				} else {
					// Force stem up (assuming not a chord, although it should not matter):
					string value = hre.replaceCopy(current, "/", "$");
					if (value != *current) {
						current->setText(value);
					}
				}
				// Add < after pitch (and accidental and qualifiers) to display
				// on staff below.
				m_hasCrossStaff = true;
				string output = hre.replaceCopy(current, replacement, "([A-Ga-gr]+[-#nXYxy]*)", "g");
				if (output != *current) {
					current->setText(output);
				}
			}
		}
		current = current->getNextToken();
	}
}




/////////////////////////////////
//
// Tool_satb2gs::Tool_satb2gs -- Set the recognized options for the tool.
//

Tool_satb2gs::Tool_satb2gs(void) {
	// no options
}



/////////////////////////////////
//
// Tool_satb2gs::run -- Do the main work of the tool.
//

bool Tool_satb2gs::run(HumdrumFileSet& infiles) {
	bool status = true;
	for (int i=0; i<infiles.getCount(); i++) {
		status &= run(infiles[i]);
	}
	return status;
}


bool Tool_satb2gs::run(const string& indata, ostream& out) {
	HumdrumFile infile(indata);
	bool status = run(infile);
	if (hasAnyText()) {
		getAllText(out);
	} else {
		out << infile;
	}
	return status;
}


bool Tool_satb2gs::run(HumdrumFile& infile, ostream& out) {
	bool status = run(infile);
	if (hasAnyText()) {
		getAllText(out);
	} else {
		out << infile;
	}
	return status;
}


bool Tool_satb2gs::run(HumdrumFile& infile) {
	initialize();
	processFile(infile);
	return true;
}



//////////////////////////////
//
// Tool_satb2gs::initialize --  Initializations that only have to be done once
//    for all HumdrumFile segments.
//

void Tool_satb2gs::initialize(void) {
	// do nothing
}



//////////////////////////////
//
// Tool_satb2gs::processFile --
//

void Tool_satb2gs::processFile(HumdrumFile& infile) {
	vector<vector<int>> tracks;
	getTrackInfo(tracks, infile);

	if ((tracks[1].size() != 2) || (tracks[3].size() != 2)) {
		cerr << "Warning: not processing data since there must be at least four **kern spines" << endl;
		return;
	}

	bool goodHeader = validateHeader(infile);
	if (!goodHeader) {
		cerr << "Warning: no spine manipulations allows within header, not processing file" << endl;
		return;
	}

	bool dataQ = false;
	for (int i=0; i<infile.getLineCount(); i++) {
		if (!infile[i].hasSpines()) {
			m_humdrum_text << infile[i] << endl;
			continue;
		}
		if (infile[i].isData()) {
			if (!dataQ) {
				printSpineSplitLine(tracks);
			}
			dataQ = true;
		}
		if (!dataQ) {
			printHeaderLine(infile, i, tracks);
			continue;
		}
		HTp token = infile.token(i, 0);
		if (*token == "*-") {
			printSpineMergeLine(tracks);
			printTerminatorLine(tracks);
			continue;
		}
		printRegularLine(infile, i, tracks);
	}
}



//////////////////////////////
//
// Tool_satb2gs::printRegularLine -- print a regular line
//   (between first data line and before terminator line).
//

void Tool_satb2gs::printRegularLine(HumdrumFile& infile, int line,
		vector<vector<int>>& tracks) {

	int spinecount = infile[line].getFieldCount();
	int track;
	HTp token;
	vector<vector<vector<HTp>>> tokens;
	tokens.resize(5);
	for (int i=0; i<(int)tracks.size(); i++) {
		tokens[i].resize(tracks[i].size());
	}

	// store tokens in output order:
	for (int i=0; i<(int)tracks.size(); i++) {
		for (int j=0; j<(int)tracks[i].size(); j++) {
			int target = tracks[i][j];
			for (int k=0; k<spinecount; k++) {
				token = infile.token(line, k);
				track = token->getTrack();
				if (track != target) {
					continue;
				}
				tokens[i][j].push_back(token);
			}
		}
	}

	int counter = 0;
	HTp top;
	HTp bot;
	HTp inner;
	HTp outer;
	bool suppressQ;

	// now print in output order, but hide fermatas
	// in the alto and tenor parts if there are fermatas
	// int the soprano and bass parts respectively.
	for (int i=0; i<(int)tokens.size(); i++) {
		for (int j=0; j<(int)tokens[i].size(); j++) {
			switch (i) {
				case 0:
				case 2:
				case 4:
					// non-kern spines
					for (int k=0; k<(int)tokens[i][j].size(); k++) {
						m_humdrum_text << tokens[i][j][k];
						counter++;
						if (counter < spinecount) {
							m_humdrum_text << "\t";
						}
					}
					break;

				case 1:
				case 3:
					top = tokens[i][0][0];
					bot = tokens[i][1][0];
					if (i == 1) {
						// tenor: top is inner
						inner = top;
						outer = bot;
					} else {
						// alto: bottom is inner
						inner = bot;
						outer = top;
					}
					if (inner->hasFermata() && outer->hasFermata()) {
						suppressQ = true;
					} else {
						suppressQ = false;
					}

					for (int k=0; k<(int)tokens[i][j].size(); k++) {
						token = tokens[i][j][k];
						if (suppressQ && ((void*)token == (void*)inner)) {
							string value = *token;
							// Make fermata invisible by adding 'y' after it:
							for (int m=0; m<(int)value.size(); m++) {
								m_humdrum_text << value[m];
								if (value[m] == ';') {
									if (m < (int)value.size() - 1) {
										if (value.at(m+1) != 'y') {
											m_humdrum_text << 'y';
										}
									} else {
											m_humdrum_text << 'y';
									}
								}
							}
						} else {
							m_humdrum_text << token;
						}
						counter++;
						if (counter < spinecount) {
							m_humdrum_text << "\t";
						}
					}
					break;
			}
		}
	}

	m_humdrum_text << endl;
}



//////////////////////////////
//
// Tool_satb2gs::printTerminatorLine --  Print the terminator line in the
//   output data.
//

void Tool_satb2gs::printTerminatorLine(vector<vector<int>>& tracks) {
	int count = getNewTrackCount(tracks);
	for (int i=0; i<count; i++) {
		m_humdrum_text << "*-";
		if (i < count - 1) {
			m_humdrum_text << "\t";
		}
	}
	m_humdrum_text << endl;
}



//////////////////////////////
//
// Tool_satb2gs::printSpineSplitLine --
//

void Tool_satb2gs::printSpineSplitLine(vector<vector<int>>& tracks) {
	int count = getNewTrackCount(tracks);
	int counter = 0;

	for (int i=0; i<(int)tracks.size(); i++) {
		switch (i) {
			case 0:
			case 2:
			case 4:
				for (int j=0; j<(int)tracks[i].size(); j++) {
					m_humdrum_text << "*";
					counter++;
					if (counter < count) {
						m_humdrum_text << "\t";
					}
				}
				break;
			case 1:
			case 3:
				m_humdrum_text << "*^";
				counter++;
				if (counter < count) {
					m_humdrum_text << "\t";
				}
				break;
		}
	}
	m_humdrum_text << endl;
}



//////////////////////////////
//
// Tool_satb2gs::printSpineMergeLine --
//

void Tool_satb2gs::printSpineMergeLine(vector<vector<int>>& tracks) {
	int count = getNewTrackCount(tracks);
	count += 2;
	int counter;

	if (!tracks[2].empty()) {
		// do not need to place merges on separate lines since they are
		// separated by non-kern spine(s) between bass and soprano subspines.

		counter = 0;
		for (int i=0; i<(int)tracks.size(); i++) {
			switch (i) {
				case 0:
				case 2:
				case 4:
					for (int j=0; j<(int)tracks[i].size(); j++) {
						m_humdrum_text << "*";
						counter++;
						if (counter < count) {
							m_humdrum_text << "\t";
						}
					}
					break;
				case 1:
				case 3:
					for (int j=0; j<(int)tracks[i].size(); j++) {
						m_humdrum_text << "*v";
						counter++;
						if (counter < count) {
							m_humdrum_text << "\t";
						}
					}
					break;
			}
		}
		m_humdrum_text << endl;

	} else {
		// Merges for tenor/bass and soprano/alto need to be placed
		// on separate lines.

		// First merge tenor/bass (tracks[1])
		counter = 0;
		for (int i=0; i<(int)tracks.size(); i++) {
			switch (i) {
				case 0:
				case 2:
				case 3:
				case 4:
					for (int j=0; j<(int)tracks[i].size(); j++) {
						m_humdrum_text << "*";
						counter++;
						if (counter < count) {
							m_humdrum_text << "\t";
						}
					}
					break;
				case 1:
					for (int j=0; j<(int)tracks[i].size(); j++) {
						m_humdrum_text << "*v";
						counter++;
						if (counter < count) {
							m_humdrum_text << "\t";
						}
					}
					break;
			}
		}
		m_humdrum_text << endl;

		// Now merge soprano/alto (tracks[3])
		count--;
		counter = 0;
		for (int i=0; i<(int)tracks.size(); i++) {
			switch (i) {
				case 0:
				case 2:
				case 4:
					for (int j=0; j<(int)tracks[i].size(); j++) {
						m_humdrum_text << "*";
						counter++;
						if (counter < count) {
							m_humdrum_text << "\t";
						}
					}
					break;
				case 1:
					m_humdrum_text << "*";
					m_humdrum_text << "\t";
					counter++;
					break;
				case 3:
					for (int j=0; j<(int)tracks[i].size(); j++) {
						m_humdrum_text << "*v";
						counter++;
						if (counter < count) {
							m_humdrum_text << "\t";
						}
					}
					break;
			}
		}
		m_humdrum_text << endl;
	}
}



//////////////////////////////
//
// Tool_satb2gs::getNewTrackCount -- Return the number of tracks (spines)
//   in the output data (not counting subspines).
//

int Tool_satb2gs::getNewTrackCount(vector<vector<int>>& tracks) {
	int sum = 0;
	for (int i=0; i<(int)tracks.size(); i++) {
		for (int j=0; j<(int)tracks[i].size(); j++) {
			sum++;
		}
	}
	// remove two spines that were merged into two others:
	sum -= 2;
	return sum;
}



//////////////////////////////
//
// Tool_satb2gs::printHeaderLine --
//

void Tool_satb2gs::printHeaderLine(HumdrumFile& infile, int line,
		vector<vector<int>>& tracks) {
	int count = infile.getMaxTrack() - 2;

	HTp token;
	int counter = 0;
	for (int i=0; i<(int)tracks.size(); i++) {
		switch (i) {
			case 0:
			case 2:
			case 4:
				for (int j=0; j<(int)tracks[i].size(); j++) {
					token = infile.token(line, tracks[i][j]-1);
					m_humdrum_text << token;
					counter++;
					if (counter < count) {
						m_humdrum_text << "\t";
					}
				}
				break;

			case 1:
			case 3:
				token = infile.token(line, tracks[i][0]-1);
				if (token->isInstrumentName()) {
					// suppress instrument names, but keep blank name
					// to force indent.
					m_humdrum_text << "*I\"";
				} else if (token->isInstrumentAbbreviation()) {
					// suppress instrument abbreviations
					m_humdrum_text << "*";
				} else if (token->isInstrumentDesignation()) {
					// suppress instrument designations (such as *Itenor)
					m_humdrum_text << "*";
				} else if (token->isClef()) {
					vector<HTp> clefs = getClefs(infile, line);
					if (i == 1) {
						if (clefs.size() == 4) {
							m_humdrum_text << clefs[0];
						} else {
							m_humdrum_text << "*clefF4";
						}
					} else {
						if (clefs.size() == 4) {
							m_humdrum_text << clefs.back();
						} else {
							m_humdrum_text << "*clefG2";
						}
					}
				} else {
					m_humdrum_text << token;
				}
				counter++;
				if (counter < count) {
					m_humdrum_text << "\t";
				}
				break;
		}
	}
	m_humdrum_text << endl;
}



//////////////////////////////
//
// Tool_satb2gs::getClefs -- get a list of the clefs on the current line.
//

vector<HTp> Tool_satb2gs::getClefs(HumdrumFile& infile, int line) {
	vector<HTp> output;
	for (int i=0; i<infile[line].getFieldCount(); i++) {
		HTp token = infile[line].token(i);
		if (!token->isKern()) {
			continue;
		}
		if (token->isClef()) {
			output.push_back(token);
		}
	}
	return output;
}



//////////////////////////////
//
// Tool_satb2gs::getTrackInfo --
//     tracks 0 = list of spines before bass **kern spine
//     tracks 1 = tenor and then bass **kern track numbers
//     tracks 2 = aux. spines after after tenor and then after bass
//     tracks 3 = soprano and then alto **kern track numbers
//     tracks 4 = aux. spines after after soprano and then after alto
//

void Tool_satb2gs::getTrackInfo(vector<vector<int>>& tracks, HumdrumFile& infile) {
	tracks.resize(5);
	for (int i=0; i<(int)tracks.size(); i++) {
		tracks[i].clear();
	}
	vector<HTp> sstarts;
	infile.getSpineStartList(sstarts);
	int track;

	// fill in tracks[0]: spines before first **kern spine
	for (int i=0; i<(int)sstarts.size(); i++) {
		if (sstarts[i]->isKern()) {
			break;
		}
		track = sstarts[i]->getTrack();
		tracks[0].push_back(track);
	}

	int kcount = 0;

	kcount = 0;
	// Store tracks related to the tenor part:
	for (int i=0; i<(int)sstarts.size(); i++) {
		if (sstarts[i]->isKern()) {
			kcount++;
		}
		if (kcount > 2) {
			break;
		}
		if (kcount < 2) {
			continue;
		}
		track = sstarts[i]->getTrack();
		if (sstarts[i]->isKern()) {
			tracks[1].push_back(track);
		} else {
			tracks[2].push_back(track);
		}
	}

	kcount = 0;
	// Store tracks related to the bass part:
	for (int i=0; i<(int)sstarts.size(); i++) {
		if (sstarts[i]->isKern()) {
			kcount++;
		}
		if (kcount > 1) {
			break;
		}
		if (kcount < 1) {
			continue;
		}
		track = sstarts[i]->getTrack();
		if (sstarts[i]->isKern()) {
			tracks[1].push_back(track);
		} else {
			tracks[2].push_back(track);
		}
	}

	kcount = 0;
	// Store tracks related to the soprano part:
	for (int i=0; i<(int)sstarts.size(); i++) {
		if (sstarts[i]->isKern()) {
			kcount++;
		}
		if (kcount > 4) {
			break;
		}
		if (kcount < 4) {
			continue;
		}
		track = sstarts[i]->getTrack();
		if (sstarts[i]->isKern()) {
			tracks[3].push_back(track);
		} else {
			tracks[4].push_back(track);
		}
	}

	kcount = 0;
	// Store tracks related to the alto part:
	for (int i=0; i<(int)sstarts.size(); i++) {
		if (sstarts[i]->isKern()) {
			kcount++;
		}
		if (kcount > 3) {
			break;
		}
		if (kcount < 3) {
			continue;
		}
		track = sstarts[i]->getTrack();
		if (sstarts[i]->isKern()) {
			tracks[3].push_back(track);
		} else {
			tracks[4].push_back(track);
		}
	}
}



//////////////////////////////
//
// Tool_satb2gs::validateHeader -- Header cannot contain
//   spine manipulators.
//

bool Tool_satb2gs::validateHeader(HumdrumFile& infile) {
	for (int i=0; i<infile.getLineCount(); i++) {
		if (infile[i].isData()) {
			break;
		}
		if (!infile[i].isInterpretation()) {
			continue;
		}
		HTp token = infile.token(i, 0);
		if (token->isExclusive()) {
			continue;
		}
		if (infile[i].isManipulator()) {
			return false;
		}
	}

	return true;
}





/////////////////////////////////
//
// Tool_scordatura::Tool_scordatura -- Set the recognized options for the tool.
//

Tool_scordatura::Tool_scordatura(void) {
	define("s|sounding=b",      "generate sounding score");
	define("w|written=b",       "generate written score");
	define("m|mark|marker=s:@", "marker to add to score");
	define("p|pitch|pitches=s", "list of pitches to mark");
	define("i|interval=s",      "musical interval of marked pitches");
	define("I|is-sounding=s",   "musical score is in sounding format for marks");
	define("c|chromatic=i:0",   "chromatic interval of marked pitches");
	define("d|diatonic=i:0",    "diatonic interval of marked pitches");
	define("color=s",           "color marked pitches");
	define("string=s",          "string number");
}



/////////////////////////////////
//
// Tool_scordatura::run -- Do the main work of the tool.
//

bool Tool_scordatura::run(HumdrumFileSet& infiles) {
	bool status = true;
	for (int i=0; i<infiles.getCount(); i++) {
		status &= run(infiles[i]);
	}
	return status;
}


bool Tool_scordatura::run(const string& indata, ostream& out) {
	HumdrumFile infile(indata);
	bool status = run(infile);
	if (hasAnyText()) {
		getAllText(out);
	} else {
		out << infile;
	}
	return status;
}


bool Tool_scordatura::run(HumdrumFile& infile, ostream& out) {
	bool status = run(infile);
	if (hasAnyText()) {
		getAllText(out);
	} else {
		out << infile;
	}
	return status;
}


bool Tool_scordatura::run(HumdrumFile& infile) {
	initialize();
	processFile(infile);
	return true;
}



//////////////////////////////
//
// Tool_scordatura::initialize --  Initializations that only have to be done once
//    for all HumdrumFile segments.
//

void Tool_scordatura::initialize(void) {
	m_writtenQ  = getBoolean("written");
	m_soundingQ = getBoolean("sounding");
	m_pitches.clear();
	m_marker = getString("mark");
	m_IQ = getBoolean("I");
	m_color = getString("color");
	if (getBoolean("pitches")) {
		m_pitches = parsePitches(getString("pitches"));
	}
	m_cd = getBoolean("diatonic") && getBoolean("chromatic");
	m_interval.clear();
	if (m_cd) {
		m_diatonic = getInteger("diatonic");
		m_chromatic = getInteger("chromatic");
	} else {
		if (getBoolean("interval")) {
			m_interval = getString("interval");
		}
	}
	if ((abs(m_diatonic) > 28) || (abs(m_chromatic) > 48)) {
		m_diatonic = 0;
		m_chromatic = 0;
		m_cd = false;
	}
	if (!m_pitches.empty()) {
		prepareTranspositionInterval();
	}
	m_string = getString("string");
}



//////////////////////////////
//
// Tool_scordatura::processFile --
//

void Tool_scordatura::processFile(HumdrumFile& infile) {
	m_modifiedQ = false;

	if (!m_pitches.empty()) {
		markPitches(infile);
		if (m_modifiedQ) {
			addMarkerRdf(infile);
		}
	}

	if (m_writtenQ || m_soundingQ) {
		vector<HTp> rdfs;
		getScordaturaRdfs(rdfs, infile);
		if (!rdfs.empty()) {
			processScordaturas(infile, rdfs);
		}
	}

	if (m_modifiedQ) {
		infile.createLinesFromTokens();
	}
}



//////////////////////////////
//
// Tool_scordatura::processScoredaturas --
//

void Tool_scordatura::processScordaturas(HumdrumFile& infile, vector<HTp>& rdfs) {
	for (int i=0; i<(int)rdfs.size(); i++) {
		processScordatura(infile, rdfs[i]);
	}
}



//////////////////////////////
//
// Tool_scordatura::processScordatura --
//

void Tool_scordatura::processScordatura(HumdrumFile& infile, HTp reference) {
	HumRegex hre;

	if (m_writtenQ) {
		if (!hre.search(reference, "^!!!RDF\\*\\*kern\\s*:\\s*([^\\s]+)\\s*=.*\\bscordatura\\s*=\\s*[\"']?\\s*ITrd(-?\\d+)c(-?\\d+)\\b")) {
			return;
		}
	} else if (m_soundingQ) {
		if (!hre.search(reference, "^!!!RDF\\*\\*kern\\s*:\\s*([^\\s]+)\\s*=.*\\bscordatura\\s*=\\s*[\"']?\\s*Trd(-?\\d+)c(-?\\d+)\\b")) {
			return;
		}
	}

	string marker = hre.getMatch(1);
	int diatonic = hre.getMatchInt(2);
	int chromatic = hre.getMatchInt(3);

	if (diatonic == 0 && chromatic == 0) {
		// nothing to do
		return;
	}

	flipScordaturaInfo(reference, diatonic, chromatic);
	transposeMarker(infile, marker, diatonic, chromatic);
}



//////////////////////////////
//
// Tool_scordatura::transposeMarker --
//


void Tool_scordatura::transposeMarker(HumdrumFile& infile, const string& marker, int diatonic, int chromatic) {
	m_transposer.setTranspositionDC(diatonic, chromatic);
	for (int i=0; i<infile.getStrandCount(); i++) {
		HTp sstart = infile.getStrandBegin(i);
		if (!sstart->isKern()) {
			continue;
		}
		HTp sstop = infile.getStrandEnd(i);
		transposeStrand(sstart, sstop, marker);
	}
}



//////////////////////////////
//
// Tool_scordatura::transposeStrand --
//

void Tool_scordatura::transposeStrand(HTp sstart, HTp sstop, const string& marker) {
	HTp current = sstart;
	while (current && current != sstop) {
		if (!current->isData()) {
			current = current->getNextToken();
			continue;
		}
		if (current->isNull() || current->isRest()) {
			current = current->getNextToken();
			continue;
		}
		if (current->find(marker) != string::npos) {
			transposeChord(current, marker);
		}
		current = current->getNextToken();
	}
}



//////////////////////////////
//
// Tool_scordatura::transposeChord --
//

void Tool_scordatura::transposeChord(HTp token, const string& marker) {
	int scount = token->getSubtokenCount();
	if (scount == 1) {
		string inputnote = *token;
		string newtoken;
		newtoken = transposeNote(inputnote);
		token->setText(newtoken);
		return;
	}
	vector<string> subtokens;
	subtokens = token->getSubtokens();
	for (int i=0; i<(int)subtokens.size(); i++) {
		if (subtokens[i].find(marker) == string::npos) {
			continue;
		}
		string newtoken = transposeNote(subtokens[i]);
		subtokens[i] = newtoken;
	}
	string newchord;
	for (int i=0; i<(int)subtokens.size(); i++) {
		newchord += subtokens[i];
		if (i<(int)subtokens.size() - 1) {
			newchord += ' ';
		}
	}
	token->setText(newchord);
}



//////////////////////////////
//
// Tool_scordatura::transposeNote --
//

string Tool_scordatura::transposeNote(const string& note) {
	HumRegex hre;
	if (!hre.search(note, "(.*?)([A-Ga-g]+[-#]*)(.*)")) {
		return note;
	}
	string pre = hre.getMatch(1);
	string pitch = hre.getMatch(2);
	string post = hre.getMatch(3);
	HumPitch hpitch;
	hpitch.setKernPitch(pitch);
	m_transposer.transpose(hpitch);
	string output;
	output = pre;
	output += hpitch.getKernPitch();
	output += post;
	return output;
}



//////////////////////////////
//
// Tool_scordatura::flipScordaturaInfo --
//

void Tool_scordatura::flipScordaturaInfo(HTp reference, int diatonic, int chromatic) {
	diatonic *= -1;
	chromatic *= -1;
	string output;
	if (m_writtenQ) {
		output = "Trd";
		output += to_string(diatonic);
		output += "c";
		output += to_string(chromatic);
	} else if (m_soundingQ) {
		output = "ITrd";
		output += to_string(diatonic);
		output += "c";
		output += to_string(chromatic);
	} else {
		return;
	}
	HumRegex hre;
	string token = *reference;
	hre.replaceDestructive(token, output, "I?Trd-?\\dc-?\\d");
	if (token != *reference) {
		m_modifiedQ = true;
		reference->setText(token);
	}
}



//////////////////////////////
//
// Tool_scordatura::getScoredaturaRdfs --
//

void Tool_scordatura::getScordaturaRdfs(vector<HTp>& rdfs, HumdrumFile& infile) {
	rdfs.clear();
	HumRegex hre;
	for (int i=0; i<infile.getLineCount(); i++) {
		if (!infile[i].isReference()) {
			continue;
		}
		HTp reference = infile.token(i, 0);
		if (m_writtenQ) {
			if (hre.search(reference, "^!!!RDF\\*\\*kern\\s*:\\s*[^\\s]+\\s*=.*\\bscordatura\\s*=\\s*[\"']?\\s*ITrd-?\\d+c-?\\d+\\b")) {
				rdfs.push_back(reference);
			}
		} else if (m_soundingQ) {
			if (hre.search(reference, "^!!!RDF\\*\\*kern\\s*:\\s*[^\\s]+\\s*=.*\\bscordatura\\s*=\\s*[\"']?\\s*Trd-?\\d+c-?\\d+\\b")) {
				rdfs.push_back(reference);
			}
		}
	}
}



//////////////////////////////
//
// Tool_scordatura::parsePitches --
//

set<int> Tool_scordatura::parsePitches(const string& input) {
	HumRegex hre;
	string value = input;
	hre.replaceDestructive(value, "-", "\\s*-\\s*", "g");

	vector<string> pieces;
	hre.split(pieces, value, "[^A-Ga-g0-9-]+");

	HumPitch pitcher;
	set<int> output;
	string p1;
	string p2;
	int d1;
	int d2;
	for (int i=0; i<(int)pieces.size(); i++) {
		if (hre.search(pieces[i], "(.*)-(.*)")) {
			// pitch range
			p1 = hre.getMatch(1);
			p2 = hre.getMatch(2);
			d1 = Convert::kernToBase7(p1);
			d2 = Convert::kernToBase7(p2);
			if ((d1 < 0) || (d2 < 0) || (d1 > d2) || (d1 > 127) || (d2 > 127)) {
				continue;
			}
			for (int j=d1; j<=d2; j++) {
				output.insert(j);
			}
		} else {
			// single pitch
			d1 = Convert::kernToBase7(pieces[i]);
			if ((d1 < 0) || (d1 > 127)) {
				continue;
			}
			output.insert(d1);
		}
	}
	return output;
}



//////////////////////////////
//
// Tool_scordatura::markPitches --
//

void Tool_scordatura::markPitches(HumdrumFile& infile) {
	for (int i=0; i<infile.getStrandCount(); i++) {
		HTp sstart = infile.getStrandStart(i);
		if (!sstart->isKern()) {
			continue;
		}
		HTp sstop = infile.getStrandStop(i);
		markPitches(sstart, sstop);
	}
}


void Tool_scordatura::markPitches(HTp sstart, HTp sstop) {
	HTp current = sstart;
	while (current && (current != sstop)) {
		if (current->isNull() || current->isRest()) {
			current = current->getNextToken();
			continue;
		}
		markPitches(current);
		current = current->getNextToken();
	}
}


void Tool_scordatura::markPitches(HTp token) {
	vector<string> subtokens = token->getSubtokens();
	int counter = 0;
	for (int i=0; i<(int)subtokens.size(); i++) {
		int dia = Convert::kernToBase7(subtokens[i]);
		if (m_pitches.find(dia) != m_pitches.end()) {
			counter++;
			subtokens[i] += m_marker;
		}
	}
	if (counter == 0) {
		return;
	}
	string newtoken;
	for (int i=0; i<(int)subtokens.size(); i++) {
		newtoken += subtokens[i];
		if (i < (int)subtokens.size() - 1) {
			newtoken += ' ';
		}
	}
	token->setText(newtoken);
	m_modifiedQ = true;
}



//////////////////////////////
//
// Tool_scordatura::addMarkerRdf --
//

void Tool_scordatura::addMarkerRdf(HumdrumFile& infile) {
	string line = "!!!RDF**kern: ";
	line += m_marker;
	line += " = ";
	if (!m_string.empty()) {
		line += "string=";
		line += m_string;
		line += " ";
	}
	line += "scordatura=";
	if (m_IQ) {
		line += "I";
	}
	line += "Tr";
	if (m_transposition.empty()) {
		line += "XXX";
	} else {
		line += m_transposition;
	}
	if (!m_color.empty()) {
		line += ", color=";
		line += m_color;
	}
	infile.appendLine(line);
	m_modifiedQ = true;
}



//////////////////////////////
//
// Tool_scordatura::prepareTranspositionInterval --
//

void Tool_scordatura::prepareTranspositionInterval(void) {
	m_transposition.clear();
	if (m_cd) {
		m_transposition = "d";
		m_transposition += to_string(m_diatonic);
		m_transposition += "c";
		m_transposition += to_string(m_chromatic);
		return;
	}

	if (m_interval.empty()) {
		return;
	}

	HumTransposer trans;
	trans.intervalToDiatonicChromatic(m_diatonic, m_chromatic, m_interval);
	m_transposition = "d";
	m_transposition += to_string(m_diatonic);
	m_transposition += "c";
	m_transposition += to_string(m_chromatic);
}





/////////////////////////////////
//
// Tool_semitones::Tool_semitones -- Set the recognized options for the tool.
//

Tool_semitones::Tool_semitones(void) {
	define("1|first=b",                   "mark only the first note of intervals");
	define("2|second=b",                  "mark only the second note of intervals");
	define("A|O|no-analysis|no-output=b", "do not print analysis spines");
	define("I|no-input=b",                "do not print input data spines");
	define("M|no-mark|no-marks=b",        "do not mark notes");
	define("R|no-rests=b",                "ignore rests");
	define("T|no-ties=b",                 "do not mark ties");
	define("X|include|only=s",            "include only **kern tokens with given pattern");
	define("color=s:red",                 "mark color");
	define("c|cdata=b",                   "store resulting data as **cdata (allowing display in VHV");
	define("d|down=b",                    "highlight notes that that have a negative semitone interval");
	define("j|jump=i:3",                  "starting interval defining leaps");
	define("l|leap=b",                    "highlight notes that have leap motion");
	define("mark=s:@",                    "mark character");
	define("m|midi=b",                    "show MIDI note number for pitches");
	define("n|count=b",                   "output count of intervals being marked");
	define("p|pc=b",                      "output pitch classes from C=0 instead of MIDI notes for -m option");
	define("r|same|repeat|repeated=b",    "highlight notes that are repeated ");
	define("s|step=b",                    "highlight notes that have step-wise motion");
	define("u|up=b",                      "highlight notes that that have a positive semitone interval");
	define("x|exclude=s",                 "exclude **kern tokens with given pattern");
}



/////////////////////////////////
//
// Tool_semitones::run -- Do the main work of the tool.
//

bool Tool_semitones::run(HumdrumFileSet& infiles) {
	bool status = true;
	for (int i=0; i<infiles.getCount(); i++) {
		status &= run(infiles[i]);
	}
	return status;
}


bool Tool_semitones::run(const string& indata, ostream& out) {
	HumdrumFile infile(indata);
	bool status = run(infile);
	if (hasAnyText()) {
		getAllText(out);
	} else {
		out << infile;
	}
	return status;
}


bool Tool_semitones::run(HumdrumFile& infile, ostream& out) {
	bool status = run(infile);
	if (hasAnyText()) {
		getAllText(out);
	} else {
		out << infile;
	}
	return status;
}


bool Tool_semitones::run(HumdrumFile& infile) {
	initialize();
	processFile(infile);
	return true;
}



//////////////////////////////
//
// Tool_semitones::initialize --  Initializations that only have to be done once
//    for all HumdrumFile segments.
//

void Tool_semitones::initialize(void) {
	// processing of options goes here

	m_cdataQ      = getBoolean("cdata");
	m_count       = getBoolean("count");
	m_downQ       = getBoolean("down");
	m_firstQ      = getBoolean("first");
	m_leapQ       = getBoolean("leap");
	m_midiQ       = getBoolean("midi");
	m_noanalysisQ = getBoolean("no-analysis");
	m_noinputQ    = getBoolean("no-input");
	m_nomarkQ     = getBoolean("no-marks");
	m_notiesQ     = getBoolean("no-ties");
	m_pcQ         = getBoolean("pc");
	m_repeatQ     = getBoolean("repeat");
	m_norestsQ    = getBoolean("no-rests");
	m_secondQ     = getBoolean("second");
	m_stepQ       = getBoolean("step");
	m_upQ         = getBoolean("up");

	m_leap        = getInteger("jump");

	m_color       = getString("color");
	m_exclude     = getString("exclude");
	m_include     = getString("include");
	m_marker      = getString("mark");

	if (!m_firstQ && !m_secondQ) {
		m_firstQ  = true;
		m_secondQ = true;
	}
}



//////////////////////////////
//
// Tool_semitones::processFile --
//

void Tool_semitones::processFile(HumdrumFile& infile) {
	m_markCount = 0;
	for (int i=0; i<infile.getLineCount(); i++) {
		analyzeLine(infile, i);
	}
	if ((m_markCount > 0) && !m_nomarkQ) {
		m_humdrum_text << "!!!RDF**kern: ";
		m_humdrum_text << m_marker;
		m_humdrum_text << " = marked note";
		if (getBoolean("color")) {
			m_humdrum_text << ", color=" << m_color;
		}
		m_humdrum_text << '\n';
	}
	if (m_count) {
		showCount();
	}
}



//////////////////////////////
//
// Tool_semitones::showCount -- Give a count for the number of
//     intervals that were marked.
//

void Tool_semitones::showCount(void) {
	m_humdrum_text << "!!semitone_count: " << m_markCount;
	if (m_repeatQ) {
		m_humdrum_text << " REPEAT";
	}
	if (m_upQ) {
		m_humdrum_text << " UP";
	}
	if (m_downQ) {
		m_humdrum_text << " DOWN";
	}
	if (m_stepQ) {
		m_humdrum_text << " STEP";
	}
	if (m_leapQ) {
		m_humdrum_text << " LEAP";
	}
	if ((m_stepQ || m_leapQ) && (m_leap != 3)) {
		m_humdrum_text << " JUMP:" << m_leap;
	}
	if (m_marker != "@") {
		m_humdrum_text << " MARK:" << m_marker;
	}
	m_humdrum_text << '\n';
}



//////////////////////////////
//
// Tool_semitones::analyzeLine --  Append analysis spines after every **kern
//   spine.
//

void Tool_semitones::analyzeLine(HumdrumFile& infile, int line) {
	int group = 0;
	if (!infile[line].hasSpines()) {
		m_humdrum_text << infile[line] << "\n";
		return;
	}
	for (int i=0; i<infile[line].getFieldCount(); i++) {
		HTp token = infile.token(line, i);
		if (!m_noinputQ) {
			if (!token->isKern()) {
				m_humdrum_text << token;
				if (i < infile[line].getFieldCount() - 1) {
					m_humdrum_text << '\t';
				}
				continue;
			}
		}
		i = processKernSpines(infile, line, i, group++);
		if (!m_noinputQ) {
			if (i < infile[line].getFieldCount() - 1) {
				m_humdrum_text << '\t';
			}
		}
	}
	m_humdrum_text << '\n';
}



//////////////////////////////
//
// Tool_semitones::processKernSpine --
//

int Tool_semitones::processKernSpines(HumdrumFile& infile, int line, int start, int kspine) {
	HTp token = infile.token(line, start);
	if (!token->isKern()) {
		return start;
	}
	int track = token->getTrack();
	vector<HTp> toks;
	toks.push_back(token);
	for (int i=start+1; i<infile[line].getFieldCount(); i++) {
		HTp newtok = infile.token(line, i);
		int newtrack = newtok->getTrack();
		if (newtrack == track) {
			toks.push_back(newtok);
			continue;
		}
		break;
	}

	int toksize = (int)toks.size();

	// calculate intervals/MIDI note numbers if appropriate
	bool allQ = m_stepQ || m_leapQ || m_upQ || m_downQ || m_repeatQ;
	bool dirQ = m_upQ || m_downQ;
	bool typeQ = m_stepQ || m_leapQ;
	vector<string> intervals(toksize);
	if (infile[line].isData()) {
		for (int i=0; i<toksize; i++) {
			intervals[i] = getTwelveToneIntervalString(toks[i]);
		}
		if (allQ && !m_midiQ) {
			for (int i=0; i<(int)intervals.size(); i++) {
				if (intervals[i].empty()) {
					continue;
				}
            if (!isdigit(intervals[i].back())) {
					continue;
				}
				int value = stoi(intervals[i]);
				if (m_upQ && m_stepQ && (value > 0) && (value < m_leap)) {
					markInterval(toks[i]);
				} else if (m_downQ && m_stepQ && (value < 0) && (value > -m_leap)) {
					markInterval(toks[i]);
				} else if (!dirQ && m_stepQ && (value != 0) && (abs(value) < m_leap)) {
					markInterval(toks[i]);

				} else if (m_upQ && m_leapQ && (value > 0) && (value >= m_leap)) {
					markInterval(toks[i]);
				} else if (m_downQ && m_leapQ && (value < 0) && (value <= -m_leap)) {
					markInterval(toks[i]);
				} else if (!dirQ && m_leapQ && (value != 0) && (abs(value) >= m_leap)) {
					markInterval(toks[i]);

				} else if (m_repeatQ && (value == 0)) {
					markInterval(toks[i]);
				} else if (!typeQ && m_upQ && (value > 0)) {
					markInterval(toks[i]);
				} else if (!typeQ && m_downQ && (value < 0)) {
					markInterval(toks[i]);
				}
			}
		}
	}

	// print the **kern fields
	if (!m_noinputQ) {
		for (int i=0; i<toksize; i++) {
			m_humdrum_text << toks[i];
			if (i < toksize - 1) {
				m_humdrum_text << '\t';
			}
		}
	}

	// then print the parallel analysis fields

	if (!m_noanalysisQ) {
		if (!m_noinputQ) {
			m_humdrum_text << '\t';
		} else if (m_noinputQ && (kspine != 0)) {
			m_humdrum_text << '\t';
		}
		if (!infile[line].isData()) {
			if (infile[line].isLocalComment()) {
				printTokens("!", toksize);
	 		} else if (infile[line].isInterpretation()) {
				if (toks[0]->compare(0, 2, "**") == 0) {
					if (m_cdataQ) {
						printTokens("**cdata", toksize);
					} else if (m_midiQ) {
						if (m_pcQ) {
							printTokens("**mpc", toksize);
						} else {
							printTokens("**mnn", toksize);
						}
					} else {
						printTokens("**tti", toksize);
					}
				} else {
					for (int i=0; i<toksize; i++) {
						m_humdrum_text << toks[i];
						if (i < toksize - 1) {
							m_humdrum_text << '\t';
						}
					}
				}
	 		} else if (infile[line].isBarline()) {
				printTokens(*toks[0], toksize);
			} else {
				cerr << "STRANGE ERROR " << toks[0] << endl;
			}
			return start + toksize - 1;
		}
		// print twelve-tone analyses.
		string value;
		for (int i=0; i<toksize; i++) {
			value = getTwelveToneIntervalString(toks[i]);
			m_humdrum_text << value;
			if (i < toksize - 1) {
				m_humdrum_text << '\t';
			}
		}
	}

	return start + toksize - 1;
}



//////////////////////////////
//
// Tool_semitones::markInterval -- mark the current note, any notes tied
//    after it, and then the next note and any tied notes attached to
//    that note.
//

void Tool_semitones::markInterval(HTp token) {
	if (!token->isData()) {
		return;
	}
	if (!token->isKern()) {
		return;
	}
	if (token->isNull()) {
		return;
	}
	if (token->isRest()) {
		return;
	}
	if (token->isUnpitched()) {
		return;
	}
	m_markCount++;
	token = markNote(token, m_firstQ);
	if (m_firstQ && !m_secondQ) {
		return;
	}
	// find next note
	HTp current = token->getNextToken();
	while (current) {
		if (!current->isData()) {
			current = current->getNextToken();
			continue;
		}
		if (current->isNull()) {
			current = current->getNextToken();
			continue;
		}
		markNote(current, m_secondQ);
		break;
	}
}



//////////////////////////////
//
// Tool_semitones::markNote -- make note and any tied notes after it.
//      Return the last note of a tied note (or the note if no tied notes
//      after it).
//

HTp Tool_semitones::markNote(HTp token, bool markQ) {
	string subtok = token->getSubtoken(0);
	bool hasTieEnd = false;
	if (subtok.find('_') != string::npos) {
		hasTieEnd = true;
	} else if (subtok.find(']') != string::npos) {
		hasTieEnd = true;
	}

	if (!(hasTieEnd && m_notiesQ)) {
		if (markQ) {
			addMarker(token);
		}
	}

	bool hasTie = false;
	if (subtok.find('[') != string::npos) {
		hasTie = true;
	} else if (subtok.find('_') != string::npos) {
		hasTie = true;
	}

	if (!hasTie) {
		return token;
	}
	HTp current = token->getNextToken();
	while (current) {
		if (!current->isData()) {
			current = current->getNextToken();
			continue;
		}
		if (current->isNull()) {
			current = current->getNextToken();
			continue;
		}
		subtok = current->getSubtoken(0);
		bool hasTie = false;
		if (subtok.find('[') != string::npos) {
			hasTie = true;
		} else if (subtok.find('_') != string::npos) {
			hasTie = true;
		}
		if (!hasTie) {
			if (subtok.find(']') != string::npos) {
				markNote(current, markQ);
			}
			return current;
		} else {
			return markNote(current, markQ);
		}
		break;
	}
	return NULL;
}



//////////////////////////////
//
// Tool_semitones::addMarker --
//

void Tool_semitones::addMarker(HTp token) {
	if (!m_nomarkQ) {
		string contents = m_marker;
		contents += token->getText();
		token->setText(contents);
	}
}



//////////////////////////////
//
// Tool_semitones::printTokens --
//

void Tool_semitones::printTokens(const string& value, int count) {
	for (int i=0; i<count; i++) {
		m_humdrum_text << value;
		if (i < count - 1) {
			m_humdrum_text << '\t';
		}
	}
}



///////////////////////////////
//
// Tool_semitones::getTwelveToneIntervalString --
//

string Tool_semitones::getTwelveToneIntervalString(HTp token) {
	if (token->isNull()) {
		return ".";
	}
	if (token->isRest()) {
		if (m_midiQ) {
			return "r";
		} else {
			return ".";
		}
	}
	if (token->isUnpitched()) {
		if (m_midiQ) {
			return "R";
		} else {
			return ".";
		}
	}
	if ((m_include.size() > 0) || (m_exclude.size() > 0)) {
		int status = filterData(token);
		if (status == 0) {
			return ".";
		} else if (status < 0) {
			return "x"; // excluded note
		}
	}
	string tok = token->getSubtoken(0);
	if (tok.find(']') != string::npos) {
		return ".";
	}
	if (tok.find('_') != string::npos) {
		return ".";
	}
	int value = Convert::kernToMidiNoteNumber(tok);

	if (m_midiQ) {
		string output;
		if (m_pcQ) {
			value = value % 12;
		}
		output = to_string(value);
		return output;
	}

	string nexttok = getNextNoteAttack(token);
	if (nexttok.empty()) {
		return ".";
	}
	if (nexttok.find('r') != string::npos) {
		// no interval since next note is a rest
		return "r";
	}
	int value2 = Convert::kernToMidiNoteNumber(nexttok);
	int interval =  value2 - value;
	string output = to_string(interval);
	return output;
}



///////////////////////////////
//
// Tool_semitones::getNextNoteAttack -- Or rest.
//

string Tool_semitones::getNextNoteAttack(HTp token) {
	HTp current = token;
	current = current->getNextToken();
	string tok;
	while (current) {
		if (!current->isData()) {
			current = current->getNextToken();
			continue;
		}
		if (current->isNull()) {
			current = current->getNextToken();
			continue;
		}
		if (current->isRest()) {
			if (!m_norestsQ) {
				return "r";
			} else {
				current = current->getNextToken();
				continue;
			}
		}
		if (current->isUnpitched()) {
			return "R";
		}
		string tok = current->getSubtoken(0);
		if (tok.find(']') != string::npos) {
			current = current->getNextToken();
			continue;
		}
		if (tok.find('_') != string::npos) {
			current = current->getNextToken();
			continue;
		}
		return tok;
	}

	if (!current) {
		return "";
	}
	if (!current->isData()) {
		return "";
	}
	// Some other strange problem.
	return ".";
}



//////////////////////////////
//
// Tool_semitones::filterData -- select or deselect an interval based
//    on regular expression pattern.  Return true if the note should
//    be kept; otherwise, return false.
//

int Tool_semitones::filterData(HTp token) {
	vector<HTp> toks = getTieGroup(token);
	HumRegex hre;
	if (!m_exclude.empty()) {
		for (int i=0; i<(int)toks.size(); i++) {
			if (hre.search(toks[i], m_exclude)) {
				return -1;
			}
		}
		return 1;
	} else if (!m_include.empty()) {
		for (int i=0; i<(int)toks.size(); i++) {
			if (hre.search(toks[i], m_include)) {
				return 1;
			}
		}
		return 0;
	}
	return 0;
}



//////////////////////////////
//
// Tool_semitones::getTieGroup --
//

vector<HTp> Tool_semitones::getTieGroup(HTp token) {
	vector<HTp> output;
	if (!token) {
		return output;
	}
	if (token->isNull()) {
		return output;
	}
	if (!token->isData()) {
		return output;
	}
	output.push_back(token);
	if (token->isRest()) {
		return output;
	}
	string subtok = token->getSubtoken(0);
	bool continues = hasTieContinue(subtok);
	HTp current = token;
	while (continues) {
		current = getNextNote(current);
		if (!current) {
			break;
		}
		string subtok = current->getSubtoken(0);
		if (subtok.find(']') != string::npos) {
			output.push_back(current);
			break;
		}
		continues = hasTieContinue(subtok);
	}
	return output;
}



//////////////////////////////
//
// Tool_semitones::hasTieContinue --
//

bool Tool_semitones::hasTieContinue(const string& value) {
	if (value.find('_') != string::npos) {
		return true;
	}
	if (value.find('[') != string::npos) {
		return true;
	}
	return false;
}



//////////////////////////////
//
// getNextNote --
//

HTp Tool_semitones::getNextNote(HTp token) {
	HTp current = token->getNextToken();
	while (current) {
		if (!current->isData()) {
			current = current->getNextToken();
			continue;
		}
		if (current->isNull()) {
			current = current->getNextToken();
			continue;
		}
		break;
	}
	return current;
}





/////////////////////////////////
//
// Tool_shed::Tool_shed -- Set the recognized options for the tool.
//

Tool_shed::Tool_shed(void) {
	define("s|spine|spines=s",              "list of spines to process");
	define("e|expression=s",                "regular expression");
	define("E|exclusion-expression=s",      "regular expression to skip");
	define("x|exclusive-interpretations=s", "apply only to spine types in list");
	define("k|kern=b",                      "apply only to **kern data");
	define("X=s",                           "defineable exclusive interpretation x");
	define("Y=s",                           "defineable exclusive interpretation y");
	define("Z=s",                           "defineable exclusive interpretation z");
}



/////////////////////////////////
//
// Tool_shed::run -- Do the main work of the tool.
//

bool Tool_shed::run(HumdrumFileSet& infiles) {
	bool status = true;
	for (int i=0; i<infiles.getCount(); i++) {
		status &= run(infiles[i]);
	}
	return status;
}


bool Tool_shed::run(const string& indata, ostream& out) {
	HumdrumFile infile(indata);
	bool status = run(infile);
	if (hasAnyText()) {
		getAllText(out);
	} else {
		out << infile;
	}
	return status;
}


bool Tool_shed::run(HumdrumFile& infile, ostream& out) {
	bool status = run(infile);
	if (hasAnyText()) {
		getAllText(out);
	} else {
		out << infile;
	}
	return status;
}


bool Tool_shed::run(HumdrumFile& infile) {
	initialize();
	initializeSegment(infile);
	if (m_options.empty()) {
		cerr << "Error: -e option is required" << endl;
		return false;
	}
	processFile(infile);
	return true;
}



//////////////////////////////
//
// Tool_shed::prepareSearch --
//

void Tool_shed::prepareSearch(int index) {
	// deal with command-line options (seprately for each search):
	m_exinterps.clear();

	if (getBoolean("kern")) {
		m_exinterps.push_back("**kern");
	} else if (getBoolean("exclusive-interpretations")) {
		vector<string> extra = addToExInterpList();
		for (int i=0; i<(int)extra.size(); i++) {
			m_exinterps.push_back(extra[i]);
		}
	}

	m_search  = m_searches.at(index);
	m_replace = m_replaces.at(index);
	m_option  = m_options.at(index);

	m_grepoptions = "";
	if (m_option.find("i") != std::string::npos) {
		m_grepoptions += "i";
	}
	if (m_option.find("g") != std::string::npos) {
		m_grepoptions += "g";
	}

	if (m_option.find("X") != std::string::npos) {
		if (m_xInterp != "") {
			m_exinterps.push_back(m_xInterp);
		}
	}
	if (m_option.find("Y") != std::string::npos) {
		if (m_yInterp != "") {
			m_exinterps.push_back(m_yInterp);
		}
	}
	if (m_option.find("Z") != std::string::npos) {
		if (m_zInterp != "") {
			m_exinterps.push_back(m_zInterp);
		}
	}

	m_data = true;             // process data
	m_barline = false;         // process barline
	m_exinterp = false;        // process exclusive interpretations
	m_interpretation = false;  // process interpretations (other than exinterp
	                           //     and spine manipulators).

	if (m_option.find("I") != std::string::npos) {
		m_interpretation = true;
		m_data = false;
	}
	if (m_option.find("X") != std::string::npos) {
		m_exinterp = true;
		m_data = false;
	}
	if (m_option.find("B") != std::string::npos) {
		m_barline = true;
		m_data = false;
	}
	if (m_option.find("M") != std::string::npos) {
		// measure is an alias for barline
		m_barline = true;
		m_data = false;
	}
	if (m_option.find("L") != std::string::npos) {
		m_localcomment = true;
		m_data = false;
	}
	if (m_option.find("G") != std::string::npos) {
		m_globalcomment = true;
		m_data = false;
	}
	if (m_option.find("K") != std::string::npos) {
		m_referencekey = true;
		m_data = false;
	}
	if (m_option.find("V") != std::string::npos) {
		m_referencevalue = true;
		m_data = false;
	}
	if (m_option.find("R") != std::string::npos) {
		m_reference = true;
		m_referencekey = false;
		m_referencevalue = false;
		m_data = false;
	}
	if (m_option.find("D") != std::string::npos) {
		m_data = true;
	}

}



//////////////////////////////
//
// Tool_shed::initialize --  Initializations that only have to be done once
//    for all HumdrumFile segments.
//

void Tool_shed::initialize(void) {
	if (getBoolean("expression")) {
		string value = getString("expression");
		parseExpression(value);
	}
	m_exclusion = getString("exclusion-expression");

	if (getBoolean("X")) {
		m_xInterp = getExInterp(getString("X"));
	}
	if (getBoolean("Y")) {
		m_yInterp = getExInterp(getString("Y"));
	}
	if (getBoolean("Z")) {
		m_zInterp = getExInterp(getString("Z"));
	}
}



//////////////////////////////
//
// Tool_shed::getExInterp --
//

string Tool_shed::getExInterp(const string& value) {
	if (value == "") {
		return "**";
	}
	if (value == "*") {
		return "**";
	}
	if (value.compare(0, 2, "**") == 0) {
		return value;
	}
	if (value.compare(0, 1, "*") == 0) {
		return "*" + value;
	}
	return "**" + value;
}



//////////////////////////////
//
// Tool_shed::parseExpression --
//     Form of string:
//        s/search/replace/options; s/search2/replace2/options2
//
//

void Tool_shed::parseExpression(const string& expression) {
	int state = 0;

	m_searches.clear();
	m_replaces.clear();
	m_options.clear();

	char divchar = '/';

	for (int i=0; i<(int)expression.size(); i++) {
		if (state == 0) {  // start of expression
			if (isspace(expression[i])) {
				continue;
			} else if (expression[i] == 's') {
				if (i >= (int)expression.size() - 1) {
					cerr << "Error: spurious s at end of expression: "
					     << expression << endl;
					return;
				} else {
					divchar = expression[i+1];
					i++;
					state++;
					m_searches.push_back("");
				}
			} else {
				cerr << "Error at position " << i
				     << " in expression: " << expression << endl;
				return;
			}
		} else if (state == 1) { // search string
			if (expression[i] == divchar) {
				state++;
				m_replaces.push_back("");
				continue;
			} if (expression[i] == '\\') {
				if (i >= (int)expression.size() - 1) {
					cerr << "Error: expression ends too soon: "
					     << expression << endl;
					return;
				} else {
					m_searches.back() += '\\';
					m_searches.back() += expression[i+1];
					i++;
				}
			} else {
				m_searches.back() += expression[i];
			}
		} else if (state == 2) { // replace string
			if (expression[i] == divchar) {
				state++;
				m_options.push_back("");
				continue;
			} if (expression[i] == '\\') {
				if (i >= (int)expression.size() - 1) {
					cerr << "Error: expression ends too soon: "
					     << expression << endl;
					return;
				} else {
					m_replaces.back() += '\\';
					m_replaces.back() += expression[i+1];
					i++;
				}
			} else {
				m_replaces.back() += expression[i];
			}
		} else if (state == 3) { // regular expression options
			if (expression[i] == ';') {
				state++;
			} else if (isspace(expression[i])) {
				state++;
			} else {
				m_options.back() += expression[i];
			}
		}
		if (state == 4) {
			state = 0;
		}
	}
}



//////////////////////////////
//
// Tool_shed::initializeSegment -- Recalculate variables for each Humdrum
//      input segment.
//

void Tool_shed::initializeSegment(HumdrumFile& infile) {
	m_spines.clear();
	if (getBoolean("spines")) {
		int maxtrack = infile.getMaxTrack();
		Convert::makeBooleanTrackList(m_spines, getString("spines"), maxtrack);
	}
}



//////////////////////////////
//
// Tool_shed::addToExInterpList --
//

vector<string> Tool_shed::addToExInterpList(void) {
	string elist = getString("exclusive-interpretations");
	elist = Convert::trimWhiteSpace(elist);
	HumRegex hre;
	hre.replaceDestructive(elist, "", "^[,;\\s*]+");
	hre.replaceDestructive(elist, "", "[,;\\s*]+$");
	vector<string> pieces;
	hre.split(pieces, elist, "[,;\\s*]+");

	vector<string> output;
	for (int i=0; i<(int)pieces.size(); i++) {
		if (pieces[i].empty()) {
			continue;
		}
		output.push_back("**" + pieces[i]);
	}
	return output;
}



//////////////////////////////
//
// Tool_shed::processFile --
//

void Tool_shed::processFile(HumdrumFile& infile) {
	for (int i=0; i<(int)m_options.size(); i++) {
		prepareSearch(i);
		processExpression(infile);
	}
	m_humdrum_text << infile;
}



//////////////////////////////
//
// Tool_shed::processExpression --
//

void Tool_shed::processExpression(HumdrumFile& infile) {
	if (m_search == "") {
		// nothing to do
		return;
	}
	m_modified = false;

	if (m_interpretation) {
		searchAndReplaceInterpretation(infile);
	}

	if (m_localcomment) {
		searchAndReplaceLocalComment(infile);
	}

	if (m_globalcomment) {
		searchAndReplaceGlobalComment(infile);
	}

	if (m_reference) {
		searchAndReplaceReferenceRecords(infile);
	}

	if (m_referencekey) {
		searchAndReplaceReferenceKeys(infile);
	}

	if (m_referencevalue) {
		searchAndReplaceReferenceValues(infile);
	}

	if (m_exinterp) {
		searchAndReplaceExinterp(infile);
	}

	if (m_barline) {
		searchAndReplaceBarline(infile);
	}

	if (m_data) {
		searchAndReplaceData(infile);
	}

	if (m_modified) {
		infile.createLinesFromTokens();
	}
}



//////////////////////////////
//
// Tool_shed::searchAndReplaceBarline --
//

void Tool_shed::searchAndReplaceBarline(HumdrumFile& infile) {
	string isearch;
	if (m_search[0] == '^') {
		isearch = "^=" + m_search.substr(1);
	} else {
		isearch = "^=.*" + m_search;
	}
	HumRegex hre;
	for (int i=0; i<infile.getLineCount(); i++) {
		if (!infile[i].isBarline()) {
			continue;
		}
		for (int j=0; j<infile[i].getFieldCount(); j++) {
			HTp token = infile.token(i, j);
			if (token->isNull()) {
				// Don't mess with null interpretations
				continue;
			}
			if (!isValid(token)) {
				continue;
			}
			if (hre.search(token, isearch, m_grepoptions)) {
				string text = token->getText().substr(1);
				hre.replaceDestructive(text, m_replace, m_search, m_grepoptions);
				hre.replaceDestructive(text, "", "^=+");
				text = "=" + text;
				token->setText(text);
				m_modified = true;
			}
		}
	}
}



//////////////////////////////
//
// Tool_shed::searchAndReplaceInterpretation --
//

void Tool_shed::searchAndReplaceInterpretation(HumdrumFile& infile) {
	string isearch;
	if (m_search[0] == '^') {
		isearch = "^\\*" + m_search.substr(1);
	} else {
		isearch = "^\\*.*" + m_search;
	}
	HumRegex hre;
	for (int i=0; i<infile.getLineCount(); i++) {
		if (!infile[i].isInterpretation()) {
			continue;
		} else if (infile[i].isExclusiveInterpretation()) {
			continue;
		} else if (infile[i].isManipulator()) {
			continue;
		}
		for (int j=0; j<infile[i].getFieldCount(); j++) {
			HTp token = infile.token(i, j);
			if (token->isNull()) {
				// Don't mess with null interpretations
				continue;
			}
			if (!isValid(token)) {
				continue;
			}
			if (hre.search(token, isearch, m_grepoptions)) {
				string text = token->getText().substr(1);
				hre.replaceDestructive(text, m_replace, m_search, m_grepoptions);
				hre.replaceDestructive(text, "", "^\\*+");
				text = "*" + text;
				token->setText(text);
				m_modified = true;
			}
		}
	}
}



//////////////////////////////
//
// Tool_shed::searchAndReplaceLocalComment --
//

void Tool_shed::searchAndReplaceLocalComment(HumdrumFile& infile) {
	string isearch;
	if (m_search[0] == '^') {
		isearch = "^!" + m_search.substr(1);
	} else {
		isearch = "^!.*" + m_search;
	}
	HumRegex hre;
	for (int i=0; i<infile.getLineCount(); i++) {
		if (!infile[i].isLocalComment()) {
			continue;
		}
		for (int j=0; j<infile[i].getFieldCount(); j++) {
			HTp token = infile.token(i, j);
			if (token->isNull()) {
				// Don't mess with null interpretations
				continue;
			}
			if (!isValid(token)) {
				continue;
			}
			if (hre.search(token, isearch, m_grepoptions)) {
				string text = token->getText().substr(1);
				hre.replaceDestructive(text, m_replace, m_search, m_grepoptions);
				hre.replaceDestructive(text, "", "^!+");
				text = "!" + text;
				token->setText(text);
				m_modified = true;
			}
		}
	}
}



//////////////////////////////
//
// Tool_shed::searchAndReplaceGlobalComment --
//

void Tool_shed::searchAndReplaceGlobalComment(HumdrumFile& infile) {
	string isearch;
	if (m_search[0] == '^') {
		isearch = "^!!" + m_search.substr(1);
	} else {
		isearch = "^!!.*" + m_search;
	}
	HumRegex hre;
	for (int i=0; i<infile.getLineCount(); i++) {
		if (!infile[i].isGlobalComment()) {
			continue;
		}
		HTp token = infile.token(i, 0);
		if (token->size() < 3) {
			// Don't mess with null comments
			continue;
		}
		if (hre.search(token, isearch, m_grepoptions)) {
			string text = token->getText().substr(2);
			hre.replaceDestructive(text, m_replace, m_search, m_grepoptions);
			hre.replaceDestructive(text, "", "^!+");
			text = "!!" + text;
			token->setText(text);
			m_modified = true;
		}
	}
}



//////////////////////////////
//
// Tool_shed::searchAndReplaceReferenceRecords --
//

void Tool_shed::searchAndReplaceReferenceRecords(HumdrumFile& infile) {
	string isearch;
	if (m_search[0] == '^') {
		isearch = "^!!!" + m_search.substr(1);
	} else {
		isearch = "^!!!.*" + m_search;
	}
	HumRegex hre;
	for (int i=0; i<infile.getLineCount(); i++) {
		if (!infile[i].isGlobalReference()) {
			continue;
		}
		HTp token = infile.token(i, 0);
		if (hre.search(token, isearch, m_grepoptions)) {
			string text = token->getText().substr(1);
			hre.replaceDestructive(text, m_replace, m_search, m_grepoptions);
			hre.replaceDestructive(text, "", "^!+");
			text = "!!!" + text;
			token->setText(text);
			m_modified = true;
		}
	}
}



//////////////////////////////
//
// Tool_shed::searchAndReplaceReferenceKeys --
//

void Tool_shed::searchAndReplaceReferenceKeys(HumdrumFile& infile) {
	string isearch = m_search;
	HumRegex hre;
	for (int i=0; i<infile.getLineCount(); i++) {
		if (!infile[i].isGlobalReference()) {
			continue;
		}
		HTp token = infile.token(i, 0);
		string key = infile[i].getReferenceKey();
		if (hre.search(key, isearch, m_grepoptions)) {
			hre.replaceDestructive(key, m_replace, m_search, m_grepoptions);
			hre.replaceDestructive(key, "", "^!+");
			hre.replaceDestructive(key, "", ":+$");
			string value = infile[i].getReferenceValue();
			string text = "!!!" + key + ": " + value;
			token->setText(text);
			m_modified = true;
		}
	}
}



//////////////////////////////
//
// Tool_shed::searchAndReplaceReferenceValues --
//

void Tool_shed::searchAndReplaceReferenceValues(HumdrumFile& infile) {
	string isearch = m_search;
	HumRegex hre;
	for (int i=0; i<infile.getLineCount(); i++) {
		if (!infile[i].isGlobalReference()) {
			continue;
		}
		HTp token = infile.token(i, 0);
		string value = infile[i].getReferenceValue();
		if (hre.search(value, isearch, m_grepoptions)) {
			hre.replaceDestructive(value, m_replace, m_search, m_grepoptions);
			hre.replaceDestructive(value, "", "^!+");
			hre.replaceDestructive(value, "", ":+$");
			string key = infile[i].getReferenceKey();
			string text = "!!!" + key + ": " + value;
			token->setText(text);
			m_modified = true;
		}
	}
}



//////////////////////////////
//
// Tool_shed::searchAndReplaceExinterp --
//

void Tool_shed::searchAndReplaceExinterp(HumdrumFile& infile) {
	string isearch;
	if (m_search[0] == '^') {
		isearch = "^\\*\\*" + m_search.substr(1);
	} else {
		isearch = "^\\*\\*.*" + m_search;
	}
	HumRegex hre;
	for (int i=0; i<infile.getLineCount(); i++) {
		if (!infile[i].isInterpretation()) {
			continue;
		} else if (!infile[i].isExclusiveInterpretation()) {
			// assuming a single line for all exclusive interpretations
			continue;
		}
		for (int j=0; j<infile[i].getFieldCount(); j++) {
			HTp token = infile.token(i, j);
			if (token->isNull()) {
				// Don't mess with null interpretations
				continue;
			}
			if (!isValid(token)) {
				continue;
			}
			if (hre.search(token, isearch, m_grepoptions)) {
				string text = token->getText().substr(2);
				hre.replaceDestructive(text, m_replace, m_search, m_grepoptions);
				hre.replaceDestructive(text, "", "^\\*+");
				text = "**" + text;
				token->setText(text);
				m_modified = true;
			}
		}
	}
}



//////////////////////////////
//
// Tool_shed::searchAndReplaceData --
//

void Tool_shed::searchAndReplaceData(HumdrumFile& infile) {
	string dsearch = m_search;

	HumRegex hre;
	for (int i=0; i<infile.getLineCount(); i++) {
		if (!infile[i].isData()) {
			continue;
		}
		for (int j=0; j<infile[i].getFieldCount(); j++) {
			HTp token = infile.token(i, j);
			if (token->isNull()) {
				// Don't mess with null interpretations
				continue;
			}
			if (!isValid(token)) {
				continue;
			}
			if (hre.search(token, dsearch, m_grepoptions)) {
				string text = token->getText();
				hre.replaceDestructive(text, m_replace, dsearch, m_grepoptions);
				if (text == "") {
					text = ".";
				}
				token->setText(text);
				m_modified = true;
			}
		}
	}
}



//////////////////////////////
//
// Tool_shed::isValidDataType -- usar with -x and -k options.
//

bool Tool_shed::isValidDataType(HTp token) {
	if (m_exinterps.empty()) {
		return true;
	}
	string datatype = token->getDataType();
	for (int i=0; i<(int)m_exinterps.size(); i++) {
		if (datatype == m_exinterps[i]) {
			return true;
		}
	}
	return false;
}



//////////////////////////////
//
// Tool_shed::isValidSpine -- used with -s option.
//

bool Tool_shed::isValidSpine(HTp token) {
	if (m_spines.empty()) {
		return true;
	}
	int track = token->getTrack();
	return m_spines.at(track);
}



//////////////////////////////
//
// Tool_shed::isValid --
//

bool Tool_shed::isValid(HTp token) {
	if (!m_exclusion.empty()) {
		HumRegex hre;
		if (hre.search(token, m_exclusion)) {
			return false;
		}
	}
	if (isValidDataType(token) && isValidSpine(token)) {
		return true;
	}
	return false;
}





/////////////////////////////////
//
// Tool_sic::Tool_sic -- Set the recognized options for the tool.
//

Tool_sic::Tool_sic(void) {
	define("s|substitution=b", "insert substitutions into music");
	define("o|original=b",     "insert originals into music");
	define("r|remove=b",       "remove sic layout tokens");
	define("v|verbose=b",      "add verbose parameter");
	define("q|quiet=b",        "remove verbose parameter");
}



/////////////////////////////////
//
// Tool_sic::run -- Do the main work of the tool.
//

bool Tool_sic::run(HumdrumFileSet& infiles) {
	bool status = true;
	for (int i=0; i<infiles.getCount(); i++) {
		status &= run(infiles[i]);
	}
	return status;
}


bool Tool_sic::run(const string& indata, ostream& out) {
	HumdrumFile infile(indata);
	bool status = run(infile);
	if (hasAnyText()) {
		getAllText(out);
	} else {
		out << infile;
	}
	return status;
}


bool Tool_sic::run(HumdrumFile& infile, ostream& out) {
	bool status = run(infile);
	if (hasAnyText()) {
		getAllText(out);
	} else {
		out << infile;
	}
	return status;
}


bool Tool_sic::run(HumdrumFile& infile) {
	initialize();
	if (!(m_substituteQ || m_originalQ || m_removeQ || m_verboseQ || m_quietQ)) {
		return true;
	}
	processFile(infile);
	return true;
}



//////////////////////////////
//
// Tool_sic::initialize --  Initializations that only have to be done once
//    for all HumdrumFile segments.
//

void Tool_sic::initialize(void) {
	m_substituteQ = getBoolean("substitution");
	m_originalQ   = getBoolean("original");
	m_removeQ     = getBoolean("remove");
	m_verboseQ    = getBoolean("verbose");
	m_quietQ      = getBoolean("quiet");
}



//////////////////////////////
//
// Tool_sic::processFile --
//

void Tool_sic::processFile(HumdrumFile& infile) {
	for (int i=0; i<infile.getLineCount(); i++) {
		if (!infile[i].isLocalComment()) {
			continue;
		}
		for (int j=0; j<infile[i].getFieldCount(); j++) {
			HTp token = infile[i].token(j);
			if (token->compare(0, 8, "!LO:SIC:") != 0) {
				continue;
			}
			if (m_verboseQ) {
				addVerboseParameter(token);
			} else if (m_quietQ) {
				removeVerboseParameter(token);
			}
			if (m_removeQ) {
				token->setText("!");
				m_modifiedQ = true;
			} else if (m_substituteQ) {
				insertSubstitutionToken(token);
			} else if (m_originalQ) {
				insertOriginalToken(token);
			}
		}
	}
	if (m_modifiedQ) {
		infile.createLinesFromTokens();
	}
	m_humdrum_text << infile;
}



//////////////////////////////
//
// Tool_sic::addVerboseParameter --
//

void Tool_sic::addVerboseParameter(HTp token) {
	HumRegex hre;
	string value = token->getText();
	if (hre.search(value, "(:v:)|(:v$)")) {
		return;
	}
	string newvalue = value + ":v";
	token->setText(newvalue);
	m_modifiedQ = true;
}



//////////////////////////////
//
// Tool_sic::removeVerboseParameter --
//

void Tool_sic::removeVerboseParameter(HTp token) {
	HumRegex hre;
	string value = token->getText();
	string newvalue = value;
	hre.replaceDestructive(newvalue, ":", ":v:", "g");
	hre.replaceDestructive(newvalue, "", ":v$", "");
	if (value == newvalue) {
		return;
	}
	token->setText(newvalue);
	m_modifiedQ = true;
}



//////////////////////////////
//
// Tool_sic::getTargetToken -- Get the token that the layout command
//    applies to.
//

HTp Tool_sic::getTargetToken(HTp stok) {
	HTp current = stok->getNextToken();
	while (current) {
		if (current->isNull()) {
			current = current->getNextToken();
			continue;
		}
		if (current->isManipulator()) {
			// Layout commands should not apply to manipulators nor be split
			// from their associated token.
			current = NULL;
			break;
		}
		if (current->isCommentLocal()) {
			current = current->getNextToken();
			continue;
		}
		break;
	}
	if (!current) {
		return NULL;
	}
	return current;
}



//////////////////////////////
//
// Tool_sic::insertSubstitutionToken --
//

void Tool_sic::insertSubstitutionToken(HTp sictok) {
	HTp target = getTargetToken(sictok);
	if (!target) {
		return;
	}
	HumRegex hre;
	vector<string> pieces;
	hre.split(pieces, *sictok, ":");
	string tstring = target->getText();
	string sstring;
	for (int i=2; i<(int)pieces.size(); i++) {
		if (pieces[i].compare(0, 2, "s=") == 0) {
			sstring = pieces[i].substr(2);
		}
	}
	if (sstring.empty()) {
		return;
	}
	target->setText(sstring);
	m_modifiedQ = true;
	string newsic = "!LO:SIC";
	for (int i=2; i<(int)pieces.size(); i++) {
		if (pieces[i].compare(0, 2, "s=") == 0) {
			newsic += ":o=" + tstring;
		} else {
			newsic += ":" + pieces[i];
		}
	}
	sictok->setText(newsic);
	m_modifiedQ = true;
}



//////////////////////////////
//
// Tool_sic::insertOriginalToken --
//

void Tool_sic::insertOriginalToken(HTp sictok) {
	HTp target = getTargetToken(sictok);
	if (!target) {
		return;
	}
	HumRegex hre;
	vector<string> pieces;
	hre.split(pieces, *sictok, ":");
	string tstring = target->getText();
	string sstring;
	for (int i=2; i<(int)pieces.size(); i++) {
		if (pieces[i].compare(0, 2, "o=") == 0) {
			sstring = pieces[i].substr(2);
		}
	}
	if (sstring.empty()) {
		return;
	}
	target->setText(sstring);
	m_modifiedQ = true;
	string newsic = "!LO:SIC";
	for (int i=2; i<(int)pieces.size(); i++) {
		if (pieces[i].compare(0, 2, "o=") == 0) {
			newsic += ":s=" + tstring;
		} else {
			newsic += ":" + pieces[i];
		}
	}
	sictok->setText(newsic);
	m_modifiedQ = true;
}





//////////////////////////////
//
// MeasureData::MeasureData --
//

MeasureData::MeasureData(void) {
	m_hist7pc.resize(7);
	std::fill(m_hist7pc.begin(), m_hist7pc.end(), 0.0);
}


MeasureData::MeasureData(HumdrumFile& infile, int startline, int stopline) {
	setStartLine(startline);
	setStopLine(stopline);
	setOwner(infile);
}


MeasureData::MeasureData(HumdrumFile* infile, int startline, int stopline) {
	setStartLine(startline);
	setStopLine(stopline);
	setOwner(infile);
}



//////////////////////////////
//
// MeasureData::~MeasureData --
//

MeasureData::~MeasureData() {
	clear();
}



//////////////////////////////
//
// MeasureData::setOwner --
//

void MeasureData::setOwner(HumdrumFile* infile) {
	m_owner = infile;
}


void MeasureData::setOwner(HumdrumFile& infile) {
	m_owner = &infile;
}



//////////////////////////////
//
// MeasureData::setStartLine --
//

void MeasureData::setStartLine(int startline) {
	m_startline = startline;
}



//////////////////////////////
//
// MeasureData::setStopLine --
//

void MeasureData::setStopLine(int stopline) {
	m_stopline = stopline;
}



//////////////////////////////
//
// MeasureData::getStartLine --
//

int MeasureData::getStartLine(void) {
	return m_startline;
}



//////////////////////////////
//
// MeasureData::getStopLine --
//

int MeasureData::getStopLine(void) {
	return m_stopline;
}



//////////////////////////////
//
// MeasureData::getStartTime -- return the start time in
//     quarter notes
//

double MeasureData::getStartTime(void) {
	if (m_owner == NULL) {
		return 0.0;
	}
	if (getStartLine() < 0) {
		return 0.0;
	}
	return (*m_owner)[getStartLine()].getDurationFromStart().getFloat();
}



//////////////////////////////
//
// MeasureData::getMeasure -- return the measure number of the measure.
//   return -1 if no measure number.
//

int MeasureData::getMeasure(void) {
	if (m_owner == NULL) {
		return -1;
	}
	if (getStartLine() < 0) {
		return -1;
	}
	HumdrumFile& infile = *m_owner;
	if (!infile[getStartLine()].isBarline()) {
		return -1;
	}
	HumRegex hre;
	if (hre.search(infile.token(getStartLine(), 0), "(\\d+)")) {
		return hre.getMatchInt(1);
	} else {
		return -1;
	}
}



//////////////////////////////
//
// MeasureData::getQon -- return the start time class id of the measure.
//

std::string MeasureData::getQon(void) {
	if (m_owner == NULL) {
		return "";
	}
	if (getStartLine() < 0) {
		return "";
	}
	HumdrumFile& infile = *m_owner;
	HumNum ts =  infile[getStartLine()].getDurationFromStart();
	string output = "qon" + to_string(ts.getNumerator());
	if (ts.getDenominator() != 1) {
		output += "-" + to_string(ts.getDenominator());
	}
	return output;
}



//////////////////////////////
//
// MeasureData::getQoff -- return the end time class id of the measure.
//

std::string MeasureData::getQoff(void) {
	if (m_owner == NULL) {
		return "";
	}
	if (getStopLine() < 0) {
		return "";
	}
	HumdrumFile& infile = *m_owner;
	HumNum ts =  infile[getStopLine()].getDurationFromStart();
	string output = "qoff" + to_string(ts.getNumerator());
	if (ts.getDenominator() != 1) {
		output += "-" + to_string(ts.getDenominator());
	}
	return output;
}



//////////////////////////////
//
// MeasureData::getStopTime -- return the stop time in
//     quarter notes
//

double MeasureData::getStopTime(void) {
	if (m_owner == NULL) {
		return 0.0;
	}
	if (getStopLine() < 0) {
		return 0.0;
	}
	return (*m_owner)[getStopLine()].getDurationFromStart().getFloat();
}



//////////////////////////////
//
// MeasureData::getDuration -- return the duration of the measure
//     int quarter notes
//

double MeasureData::getDuration(void) {
	return getStopTime() - getStartTime();
}



//////////////////////////////
//
// MeasureData::getScoreDuration --
//

double MeasureData::getScoreDuration(void) {
	if (m_owner == NULL) {
		return 0.0;
	}
	return m_owner->getScoreDuration().getFloat();
}



//////////////////////////////
//
// MeasureData::clear --
//

void MeasureData::clear(void) {
	m_owner = NULL;
	m_owner       = NULL;
	m_startline   = -1;
	m_startline   = -1;
	m_hist7pc.resize(7);
	std::fill(m_hist7pc.begin(), m_hist7pc.end(), 0.0);
	m_sum7pc      = 0.0;
}



//////////////////////////////
//
// MeasureData::getHistogram7pc --
//

std::vector<double>& MeasureData::getHistogram7pc(void) {
	return m_hist7pc;
}


//////////////////////////////
//
// MeasureData::getSum7pc --
//

double MeasureData::getSum7pc(void) {
	return m_sum7pc;
}



//////////////////////////////
//
// MeasureData::generateNoteHistogram --
//

void MeasureData::generateNoteHistogram(void) {
	m_hist7pc.resize(7);
	std::fill(m_hist7pc.begin(), m_hist7pc.end(), 0.0);
	m_sum7pc = 0;
	if (m_owner == NULL) {
		return;
	}
	if (m_startline < 0) {
		return;
	}
	if (m_stopline < 0) {
		return;
	}

	HumdrumFile& infile = *m_owner;
	for (int i=m_startline; i<m_stopline; i++) {
		if (!infile[i].isData()) {
			continue;
		}
		for (int j=0; j<infile[i].getFieldCount(); j++) {
			HTp token = infile.token(i, j);
			if (!token->isKern()) {
				continue;
			}
			if (token->isNull()) {
				continue;
			}
			if (token->isRest()) {
				continue;
			}
			double duration = token->getDuration().getFloat();
			int subtokcount = token->getSubtokenCount();
			for (int k=0; k<subtokcount; k++) {
				string subtok = token->getSubtoken(k);
				int pc = Convert::kernToBase7PC(subtok);
				if (pc < 0) {
					continue;
				}
				m_hist7pc.at(pc) += duration;
			}
		}
	}
	m_sum7pc = 0.0;
	for (int i=0; i<(int)m_hist7pc.size(); i++) {
		m_sum7pc += m_hist7pc[i];
	}
}


///////////////////////////////////////////////////////////////////////////


//////////////////////////////
//
// MeasureDataSet::MeasureDataSet --
//

MeasureDataSet::MeasureDataSet(void) {
	m_data.reserve(1000);
}



//////////////////////////////
//
// MeasureDataSet::MeasureDataSet --
//

MeasureDataSet::MeasureDataSet(HumdrumFile& infile) {
	parse(infile);
}



//////////////////////////////
//
// MeasureDataSet::~MeasureDataSet --
//

MeasureDataSet::~MeasureDataSet() {
	clear();
}



//////////////////////////////
//
// MeasureDataSet::clear --
//

void MeasureDataSet::clear(void) {
	for (int i=0; i<(int)m_data.size(); i++) {
		delete m_data[i];
	}
	m_data.clear();
}



//////////////////////////////
//
// MeasureDataSet::parse --
//

int MeasureDataSet::parse(HumdrumFile& infile) {
	int lastbar = 0;
	for (int i=0; i<infile.getLineCount(); i++) {
		if (!infile[i].isBarline()) {
			continue;
		}
		MeasureData* info = new MeasureData(infile, lastbar, i);
		info->generateNoteHistogram();
		m_data.push_back(info);
		lastbar = i;
	}
	MeasureData* info = new MeasureData(infile, lastbar, infile.getLineCount() - 1);
	m_data.push_back(info);
	return 1;
}



//////////////////////////////
//
// MeasureDataSet::operator[] --
//

MeasureData& MeasureDataSet::operator[](int index) {
	return *m_data[index];
}



//////////////////////////////
//
// MeasureDataSet::getScoreDuration --
//

double MeasureDataSet::getScoreDuration(void) {
	if (m_data.empty()) {
		return 0.0;
	}
	return m_data[0]->getScoreDuration();

}



///////////////////////////////////////////////////////////////////////////

//////////////////////////////
//
// MeasureComparison::MeasureComparison --
//

MeasureComparison::MeasureComparison() {
	// do nothing
}


MeasureComparison::MeasureComparison(MeasureData& data1, MeasureData& data2) {
	compare(data1, data2);
}


MeasureComparison::MeasureComparison(MeasureData* data1, MeasureData* data2) {
	compare(data1, data2);
}



//////////////////////////////
//
// MeasureComparison::~MeasureComparison --
//

MeasureComparison::~MeasureComparison() {
	clear();
}



//////////////////////////////
//
// MeasureComparison::clear --
//

void MeasureComparison::clear(void) {
	correlation7pc = 0.0;
}



//////////////////////////////
//
// MeasureComparison::compare --
//

void MeasureComparison::compare(MeasureData& data1, MeasureData& data2) {
	compare(&data1, &data2);
}


void MeasureComparison::compare(MeasureData* data1, MeasureData* data2) {
	double sum1 = data1->getSum7pc();
	double sum2 = data2->getSum7pc();
	if ((sum1 == sum2) && (sum1 == 0.0)) {
		correlation7pc = 1.0;
		return;
	}
	if (sum1 == 0.0) {
		correlation7pc = 0.0;
		return;
	}
	if (sum2 == 0.0) {
		correlation7pc = 0.0;
		return;
	}
	correlation7pc = Convert::pearsonCorrelation(data1->getHistogram7pc(), data2->getHistogram7pc());
	if (fabs(correlation7pc - 1.0) < 0.00000001) {
		correlation7pc = 1.0;
	}
}



//////////////////////////////
//
// MeasureComparison::getCorrelation7pc --
//

double MeasureComparison::getCorrelation7pc(void) {
	return correlation7pc;
}

//////////////////////////////////////////////////////////////////////////

//////////////////////////////
//
// MeasureComparisonGrid::MeasureComparisonGrid --
//

MeasureComparisonGrid::MeasureComparisonGrid(void) {
	// do nothing
}


MeasureComparisonGrid::MeasureComparisonGrid(MeasureDataSet& set1, MeasureDataSet& set2) {
	analyze(set1, set2);
}


MeasureComparisonGrid::MeasureComparisonGrid(MeasureDataSet* set1, MeasureDataSet* set2) {
	analyze(set1, set2);
}



//////////////////////////////
//
// MeasureComparisonGrid::~MeasureComparisonGrid --
//

MeasureComparisonGrid::~MeasureComparisonGrid() {
	// do nothing
}



//////////////////////////////
//
// MeasureComparisonGrid::clear --
//

void MeasureComparisonGrid::clear(void) {
	m_grid.clear();
}



//////////////////////////////
//
// MeasureComparisonGrid::analyze --
//

void MeasureComparisonGrid::analyze(MeasureDataSet* set1, MeasureDataSet* set2) {
	analyze(*set1, *set2);
}

void MeasureComparisonGrid::analyze(MeasureDataSet& set1, MeasureDataSet& set2) {
	m_grid.resize(set1.size());
	for (int i=0; i<(int)m_grid.size(); i++) {
		m_grid[i].resize(set2.size());
	}
	for (int i=0; i<(int)m_grid.size(); i++) {
		for (int j=0; j<(int)m_grid[i].size(); j++) {
			m_grid[i][j].compare(set1[i], set2[j]);
		}
	}
	m_set1 = &set1;
	m_set2 = &set2;
}



//////////////////////////////
//
// MeasureComparisonGrid::printCorrelationGrid --
//    default value: out = std::cout
//

ostream& MeasureComparisonGrid::printCorrelationGrid(ostream& out) {
	for (int i=0; i<(int)m_grid.size(); i++) {
		for (int j=0; j<(int)m_grid[i].size(); j++) {
			double correl = m_grid[i][j].getCorrelation7pc();
			if (correl > 0.0) {
				out << int(correl * 100.0 + 0.5)/100.0;
			} else {
				out << -int(-correl * 100.0 + 0.5)/100.0;
			}
			if (j < (int)m_grid[i].size() - 1) {
				out << '\t';
			}
		}
		out << endl;
	}
	return out;
}



//////////////////////////////
//
// MeasureComparisonGrid::printCorrelationDiagonal -- Assuming a square grid for now.
//    default value: out = std::cout
//

ostream& MeasureComparisonGrid::printCorrelationDiagonal(ostream& out) {
	for (int i=0; i<(int)m_grid.size(); i++) {
		for (int j=0; j<(int)m_grid[i].size(); j++) {
			if (i != j) {
				continue;
			}
			double correl = m_grid[i][j].getCorrelation7pc();
			if (correl > 0.0) {
				out << int(correl * 100.0 + 0.5)/100.0;
			} else {
				out << -int(-correl * 100.0 + 0.5)/100.0;
			}
			if (j < (int)m_grid[i].size() - 1) {
				out << '\t';
			}
		}
		out << endl;
	}
	return out;
}



//////////////////////////////
//
// MeasureComparisonGrid::getColorMapping --
//

void MeasureComparisonGrid::getColorMapping(double input, double& hue,
		double& saturation, double& lightness) {
	double maxhue = 0.75 * 360.0;
	hue = input;
	if (hue < 0.0) {
		hue = 0.0;
	}
	hue = hue * hue;
	if (hue != 1.0) {
		hue *= 0.95;
	}

	hue = (1.0 - hue) * 360.0;
	if (hue == 0.0) {
		// avoid -0.0;
		hue = 0.0;
	}

	if (hue > maxhue) {
		hue = maxhue;
	}
	if (hue < 0.0) {
		hue = maxhue;
	}

	saturation = 100.0;
	lightness = 50.0;

	if (hue > 60) {
		lightness = lightness - (hue-60) / (maxhue-60) * lightness / 1.5;
	}
}



//////////////////////////////
//
// MeasureComparisonGrid::getQoff1 -- return the end time class ID of the
//     current grid cell (for the first piece being compared).
//

std::string MeasureComparisonGrid::getQoff1(int index) {
	if (m_set1 == NULL) {
		return "";
	}
	return (*m_set1)[index].getQoff();
}



//////////////////////////////
//
// MeasureComparisonGrid::getQoff2 -- return the end time class ID of the
//     current grid cell (for the first piece being compared).
//

std::string MeasureComparisonGrid::getQoff2(int index) {
	if (m_set2 == NULL) {
		return "";
	}
	return (*m_set2)[index].getQoff();
}



//////////////////////////////
//
// MeasureComparisonGrid::getQon1 -- return the start time class ID of the
//     current grid cell (for the first piece being compared).
//

string MeasureComparisonGrid::getQon1(int index) {
	if (m_set1 == NULL) {
		return "";
	}
	return (*m_set1)[index].getQon();
}



//////////////////////////////
//
// MeasureComparisonGrid::getQon2 -- return the start time class ID of the
//     current grid cell (for the second piece being compared).
//

string MeasureComparisonGrid::getQon2(int index) {
	if (m_set2 == NULL) {
		return "";
	}
	return (*m_set2)[index].getQon();
}



//////////////////////////////
//
// MeasureComparisonGrid::getMeasure1 -- return the measure of the
//     current grid cell (for the first piece being compared).
//

int MeasureComparisonGrid::getMeasure1(int index) {
	if (m_set1 == NULL) {
		return 0.0;
	}
	return (*m_set1)[index].getMeasure();
}



//////////////////////////////
//
// MeasureComparisonGrid::getMeasure2 -- return the measure of the
//     current grid cell (for the second piece being compared).
//

int MeasureComparisonGrid::getMeasure2(int index) {
	if (m_set2 == NULL) {
		return 0.0;
	}
	return (*m_set2)[index].getMeasure();
}



//////////////////////////////
//
// MeasureComparisonGrid::getStartTime1 -- return the start time of the
//     measure at index position in the first compared score.
//

double MeasureComparisonGrid::getStartTime1(int index) {
	if (m_set1 == NULL) {
		return 0.0;
	}
	return (*m_set1)[index].getStartTime();
}



//////////////////////////////
//
// MeasureComparisonGrid::getScoreDuration1 --
//

double MeasureComparisonGrid::getScoreDuration1(void) {
	if (m_set1 == NULL) {
		return 0.0;
	}
	return m_set1->getScoreDuration();
}



//////////////////////////////
//
// MeasureComparisonGrid::getStartTime2 --
//

double MeasureComparisonGrid::getStartTime2(int index) {
	if (m_set2 == NULL) {
		return 0.0;
	}
	return (*m_set2)[index].getStartTime();
}



//////////////////////////////
//
// MeasureComparisonGrid::getStopTime1 --
//

double MeasureComparisonGrid::getStopTime1(int index) {
	if (m_set1 == NULL) {
		return 0.0;
	}
	return (*m_set1)[index].getStopTime();
}



//////////////////////////////
//
// MeasureComparisonGrid::getStopTime2 --
//

double MeasureComparisonGrid::getStopTime2(int index) {
	if (m_set2 == NULL) {
		return 0.0;
	}
	return (*m_set2)[index].getStopTime();
}



//////////////////////////////
//
// MeasureComparisonGrid::getDuration1 --
//

double MeasureComparisonGrid::getDuration1(int index) {
	if (m_set1 == NULL) {
		return 0.0;
	}
	return (*m_set1)[index].getDuration();
}



//////////////////////////////
//
// MeasureComparisonGrid::getDuration2 --
//

double MeasureComparisonGrid::getDuration2(int index) {
	if (m_set2 == NULL) {
		return 0.0;
	}
	return (*m_set2)[index].getDuration();
}



//////////////////////////////
//
// MeasureComparisonGrid::getScoreDuration2 --
//

double MeasureComparisonGrid::getScoreDuration2(void) {
	if (m_set2 == NULL) {
		return 0.0;
	}
	return m_set2->getScoreDuration();
}



//////////////////////////////
//
// MeasureComparisonGrid::printSvgGrid --
//    default value: out = std::cout
//

ostream& MeasureComparisonGrid::printSvgGrid(ostream& out) {
	pugi::xml_document image;
	auto declaration = image.prepend_child(pugi::node_declaration);
	declaration.append_attribute("version") = "1.0";
	declaration.append_attribute("encoding") = "UTF-8";
	declaration.append_attribute("standalone") = "no";

	auto svgnode = image.append_child("svg");
	svgnode.append_attribute("version") = "1.1";
	svgnode.append_attribute("xmlns") = "http://www.w3.org/2000/svg";
	svgnode.append_attribute("xmlns:xlink") = "http://www.w3.org/1999/xlink";
	svgnode.append_attribute("overflow") = "visible";
	svgnode.append_attribute("viewBox") = "0 0 1000 1000";
	svgnode.append_attribute("width") = "1000px";
	svgnode.append_attribute("height") = "1000px";

	auto grid = svgnode.append_child("g");
	grid.append_attribute("id") = "grid";

	double hue = 0.0;
	double saturation = 100;
	double lightness = 75;

	pugi::xml_node crect;
	double width;
	double height;

	stringstream ss;
	stringstream css;
	double x;
	double y;

	double imagewidth = 1000.0;
	double imageheight = 1000.0;

	double sdur1 = getScoreDuration1();
	double sdur2 = getScoreDuration2();

	for (int i=0; i<(int)m_grid.size(); i++) {
		for (int j=0; j<(int)m_grid[i].size(); j++) {
			width = getDuration2(j) / sdur2 * imagewidth;
			height = getDuration1(i) / sdur1 * imageheight;

			x = getStartTime2(j)/sdur2 * imageheight;
			y = getStartTime1(i)/sdur1 * imagewidth;

			getColorMapping(m_grid[i][j].getCorrelation7pc(), hue, saturation, lightness);
			ss << "hsl(" << hue << "," << saturation << "%," << lightness << "%)";
			crect = grid.append_child("rect");
			crect.append_attribute("x") = to_string(x).c_str();
			crect.append_attribute("y") = to_string(y).c_str();
			crect.append_attribute("width") = to_string(width*0.99).c_str();
			crect.append_attribute("height") = to_string(height*0.99).c_str();
			crect.append_attribute("fill") = ss.str().c_str();
			css << "Xm" << getMeasure1(i) << " Ym" << getMeasure2(j);
			css << " X" << getQon1(i)     << " Y" << getQon2(j);
			css << " X" << getQoff1(i)    << " Y" << getQoff2(j);
			crect.append_attribute("class") = css.str().c_str();
			ss.str("");
			css.str("");
		}
	}

	image.save(out);
	return out;
}


///////////////////////////////////////////////////////////////////////////


/////////////////////////////////
//
// Tool_simat::Tool_simat -- Set the recognized options for the tool.
//

Tool_simat::Tool_simat(void) {
	define("r|raw=b",      "output raw correlation matrix");
	define("d|diagonal=b", "output diagonal of correlation matrix");
}



/////////////////////////////////
//
// Tool_simat::run -- Primary interfaces to the tool.
//

bool Tool_simat::run(HumdrumFileSet& infiles) {
	bool status = true;
	if (infiles.getCount() == 1) {
		status = run(infiles[0], infiles[0]);
	} else if (infiles.getCount() > 1) {
		status = run(infiles[0], infiles[1]);
	} else {
		status = false;
	}
	return status;
}


bool Tool_simat::run(const string& indata1, const string& indata2, ostream& out) {
	HumdrumFile infile1(indata1);
	HumdrumFile infile2;
	bool status;
	if (indata2.empty()) {
		infile2.read(indata2);
		status = run(infile1, infile2);
	} else {
		status = run(infile1, infile1);
	}
	if (hasAnyText()) {
		getAllText(out);
	} else {
		out << infile1;
		out << infile2;
	}
	return status;
}


bool Tool_simat::run(HumdrumFile& infile1, HumdrumFile& infile2, ostream& out) {
	bool status;
	if (infile2.getLineCount() == 0) {
		status = run(infile1, infile1);
	} else {
		status = run(infile1, infile2);
	}
	if (hasAnyText()) {
		getAllText(out);
	} else {
		out << infile1;
		out << infile2;
	}
	return status;
}

//
// In-place processing of file:
//

bool Tool_simat::run(HumdrumFile& infile1, HumdrumFile& infile2) {
	if (infile2.getLineCount() == 0) {
		processFile(infile1, infile1);
	} else {
		processFile(infile1, infile2);
	}

	return true;
}



//////////////////////////////
//
// Tool_simat::processFile --
//

void Tool_simat::processFile(HumdrumFile& infile1, HumdrumFile& infile2) {
	m_data1.parse(infile1);
	m_data2.parse(infile2);
	m_grid.analyze(m_data1, m_data2);
	if (getBoolean("raw")) {
		m_grid.printCorrelationGrid(m_free_text);
		suppressHumdrumFileOutput();
	} else if (getBoolean("diagonal")) {
		m_grid.printCorrelationDiagonal(m_free_text);
		suppressHumdrumFileOutput();
	} else {
		m_grid.printSvgGrid(m_free_text);
		suppressHumdrumFileOutput();
	}
}





/////////////////////////////////
//
// Tool_slurcheck::Tool_slurcheck -- Set the recognized options for the tool.
//

Tool_slurcheck::Tool_slurcheck(void) {
	// add options here
	define("l|list=b",     "list locations of unclosed slur endings");
	define("c|count=b",    "count unclosed slur endings");
	define("Z|no-zeros=b", "do not list files that have zero unclosed slurs in counts");
	define("f|filename=b", "print filename for list and count options");
}



/////////////////////////////////
//
// Tool_slurcheck::run -- Do the main work of the tool.
//

bool Tool_slurcheck::run(HumdrumFileSet& infiles) {
	bool status = true;
	for (int i=0; i<infiles.getCount(); i++) {
		status &= run(infiles[i]);
	}
	return status;
}


bool Tool_slurcheck::run(const string& indata, ostream& out) {
	HumdrumFile infile(indata);
	bool status = run(infile);
	if (hasAnyText()) {
		getAllText(out);
	} else {
		out << infile;
	}
	return status;
}


bool Tool_slurcheck::run(HumdrumFile& infile, ostream& out) {
	bool status = run(infile);
	if (hasAnyText()) {
		getAllText(out);
	} else {
		out << infile;
	}
	return status;
}


bool Tool_slurcheck::run(HumdrumFile& infile) {
	initialize();
	processFile(infile);
	infile.createLinesFromTokens();
	return true;
}



//////////////////////////////
//
// Tool_slurcheck::initialize --
//

void Tool_slurcheck::initialize(void) {
}



//////////////////////////////
//
// Tool_slurcheck::processFile --
//

void Tool_slurcheck::processFile(HumdrumFile& infile) {
	infile.analyzeSlurs();
	int opencount = 0;
	int closecount = 0;
	int listQ  = getBoolean("list");
	int countQ = getBoolean("count");
	int zeroQ = !getBoolean("no-zeros");
	int filenameQ  = getBoolean("filename");
	if (listQ || countQ) {
		suppressHumdrumFileOutput();
	}
	for (int i=0; i<infile.getStrandCount(); i++) {
		HTp stok = infile.getStrandStart(i);
		if (!stok->isKern()) {
			continue;
		}
		HTp etok = infile.getStrandEnd(i);
		HTp tok = stok;
		while (tok && (tok != etok)) {
			if (!tok->isData()) {
				tok = tok->getNextToken();
				continue;
			}
			if (tok->isNull()) {
				tok = tok->getNextToken();
				continue;
			}
			string value = tok->getValue("auto", "hangingSlur");
			if (value == "true") {
				string side = tok->getValue("auto", "slurSide");
				if (side == "start") {
					opencount++;
					if (listQ) {
						if (filenameQ) {
							m_free_text << infile.getFilename() << ":\t";
						}
						m_free_text << "UNCLOSED SLUR\tline:" << tok->getLineIndex()+1
								<< "\tfield:" << tok->getFieldIndex()+1 << "\ttoken:" << tok << endl;
					} else if (!countQ) {
						string data = *tok;
						data += "i";
						tok->setText(data);
					}
				} else if (side == "stop") {
					closecount++;
					if (listQ) {
						if (filenameQ) {
							m_free_text << infile.getFilename() << ":\t";
						}
						m_free_text << "UNOPENED SLUR\tline:" << tok->getLineIndex()+1
								<< "\tfield:" << tok->getFieldIndex()+1 << "\ttoken:" << tok << endl;
					} else if (!countQ) {
						string data = *tok;
						data += "j";
						tok->setText(data);
					}
				}
			}
			tok = tok->getNextToken();
		}
	}

	if (countQ) {
		int sum = opencount + closecount;
		if ((!zeroQ) && (sum == 0)) {
			return;
		}
		if (filenameQ) {
			m_free_text << infile.getFilename() << ":\t";
		}
		m_free_text << (opencount + closecount) << "\t(:" << opencount << "\t):" << closecount << endl;
	}

	if (countQ || listQ) {
		return;
	}

	if (opencount + closecount == 0) {
		return;
	}

	if (opencount) {
		infile.appendLine("!!!RDF**kern: i = marked note, color=\"hotpink\", text=\"extra(\"");
	}

	if (closecount) {
		infile.appendLine("!!!RDF**kern: j = marked note, color=\"magenta\", text=\"extra)\"");
	}

	infile.createLinesFromTokens();
}





/////////////////////////////////
//
// Tool_gridtest::Tool_spinetrace -- Set the recognized options for the tool.
//

Tool_spinetrace::Tool_spinetrace(void) {
	define("a|append=b",  "append analysis to input data lines");
	define("p|prepend=b", "prepend analysis to input data lines");
}



///////////////////////////////
//
// Tool_spinetrace::run -- Primary interfaces to the tool.
//

bool Tool_spinetrace::run(HumdrumFileSet& infiles) {
	bool status = true;
	for (int i=0; i<infiles.getCount(); i++) {
		status &= run(infiles[i]);
	}
	return status;
}


bool Tool_spinetrace::run(const string& indata, ostream& out) {
	HumdrumFile infile(indata);
	return run(infile, out);
}


bool Tool_spinetrace::run(HumdrumFile& infile, ostream& out) {
	bool status = run(infile);
	return status;
}


bool Tool_spinetrace::run(HumdrumFile& infile) {
   initialize(infile);
	processFile(infile);
	return true;
}



//////////////////////////////
//
// Tool_spinetrace::initialize --
//

void Tool_spinetrace::initialize(HumdrumFile& infile) {
	// do nothing for now
}



//////////////////////////////
//
// Tool_spinetrace::processFile --
//

void Tool_spinetrace::processFile(HumdrumFile& infile) {
	bool appendQ = getBoolean("append");
	bool prependQ = getBoolean("prepend");

	int linecount = infile.getLineCount();
	for (int i=0; i<linecount; i++) {
		if (!infile[i].hasSpines()) {
			m_humdrum_text << infile[i] << endl;
			continue;
		}
		if (appendQ) {
			m_humdrum_text << infile[i] << "\t";
		}

		if (!infile[i].isData()) {
			if (infile[i].isInterpretation()) {
				int fieldcount = infile[i].getFieldCount();
				for (int j=0; j<fieldcount; j++) {
					HTp token = infile.token(i, j);
					if (token->compare(0, 2, "**") == 0) {
						m_humdrum_text << "**spine";
					} else {
						m_humdrum_text << token;
					}
					if (j < fieldcount - 1) {
						m_humdrum_text << "\t";
					}
				}
			} else {
				m_humdrum_text << infile[i];
			}
		} else {
			int fieldcount = infile[i].getFieldCount();
			for (int j=0; j<fieldcount; j++) {
				m_humdrum_text << infile[i].token(j)->getSpineInfo();
				if (j < fieldcount - 1) {
					m_humdrum_text << '\t';
				}
			}
		}

		if (prependQ) {
			m_humdrum_text << "\t" << infile[i];
		}
		m_humdrum_text << "\n";
	}
}




/////////////////////////////////
//
// Tool_strophe::Tool_strophe -- Set the recognized options for the tool.
//

Tool_strophe::Tool_strophe(void) {
	define("l|list=b",         "list all possible variants");
	define("m=b",              "mark strophe music");
	define("mark|marker=s:@",  "character to mark with");
	define("c|color=s:red",    "character to mark with");
}



/////////////////////////////////
//
// Tool_strophe::run -- Do the main work of the tool.
//

bool Tool_strophe::run(HumdrumFileSet& infiles) {
	bool status = true;
	for (int i=0; i<infiles.getCount(); i++) {
		status &= run(infiles[i]);
	}
	for (auto it = m_variants.begin(); it != m_variants.end(); ++it) {
		m_free_text << *it << endl;
	}
	return status;
}


bool Tool_strophe::run(const string& indata, ostream& out) {
	HumdrumFile infile(indata);
	bool status = run(infile);
	if (hasAnyText()) {
		getAllText(out);
	} else if (!m_listQ) {
		out << infile;
	}
	return status;
}


bool Tool_strophe::run(HumdrumFile& infile, ostream& out) {
	bool status = run(infile);
	if (hasAnyText()) {
		getAllText(out);
	} else if (!m_listQ) {
		out << infile;
	}
	return status;
}


bool Tool_strophe::run(HumdrumFile& infile) {
	initialize();
	processFile(infile);
	return true;
}



//////////////////////////////
//
// Tool_strophe::initialize --  Initializations that only have to be done once
//    for all HumdrumFile segments.
//

void Tool_strophe::initialize(void) {
	m_listQ     = getBoolean("list");
	m_markQ     = getBoolean("m");
	m_marker    = getString("marker");
	m_color     = getString("color");
}



//////////////////////////////
//
// Tool_strophe::processFile --
//

void Tool_strophe::processFile(HumdrumFile& infile) {
	infile.analyzeStrophes();
	if (m_listQ) {
		displayStropheVariants(infile);
	} else {
		markWithColor(infile);
	}
}



//////////////////////////////
//
// Tool_strophe::markWithColor --  Maybe give different colors
//     to different variants.  Currently only marking the primary
//     strophe.
//

void Tool_strophe::markWithColor(HumdrumFile& infile) {
	int counter = 0;
	for (int i=0; i<infile.getStropheCount(); i++) {
		HTp strophestart = infile.getStropheStart(i);
		HTp stropheend = infile.getStropheEnd(i);
		counter += markStrophe(strophestart, stropheend);
	}
	if (counter) {
		string rdf = "!!!RDF**kern: ";
		rdf += m_marker;
		rdf += " = marked note, strophe";
		if (m_color != "red") {
			rdf += ", color=\"";
			rdf += m_color;
			rdf += "\"";
		}
		infile.appendLine(rdf);
		infile.createLinesFromTokens();
	}
}



//////////////////////////////
//
// Tool_strophe::markStrophe -- Returns the number of marked notes/rests.
//

int Tool_strophe::markStrophe(HTp strophestart, HTp stropheend) {
	HTp current = strophestart;
	int output = 0;
	while (current && current != stropheend) {
		if (current->isData() && !current->isNull()) {
			// Think about multiple marking for individual notes in chords.
			string value = current->getText();
			value += m_marker;
			current->setText(value);
			output++;
		}
		current = current->getNextToken();
	}
	return output;
}



//////////////////////////////
//
// displayStropheVariants --
//

void Tool_strophe::displayStropheVariants(HumdrumFile& infile) {
	for (int i=0; i<infile.getLineCount(); i++) {
		if (!infile[i].isInterpretation()) {
			continue;
		}
		for (int j=0; j<infile[i].getFieldCount(); j++) {
			HTp token = infile.token(i, j);
			if (token->compare(0, 3, "*S/") != 0) {
				continue;
			}
			string variant = token->substr(3);
			m_variants.insert(variant);
		}
	}
}







/////////////////////////////////
//
// Tool_synco::Tool_synco -- Set the recognized options for the tool.
//

Tool_synco::Tool_synco(void) {
	define("c|color=s:skyblue", "SVG color to highlight syncopation notes");
	define("i|info=b",          "display only statistics info");
	define("f|filename=b",      "add filename to statistics info");
	define("a|all=b",           "average all statistics info");
}



/////////////////////////////////
//
// Tool_synco::run -- Do the main work of the tool.
//

bool Tool_synco::run(HumdrumFileSet& infiles) {
	bool status = true;
	for (int i=0; i<infiles.getCount(); i++) {
		status &= run(infiles[i]);
	}
	if (m_allQ) {
		m_free_text << m_scountTotal << "\t";
		m_free_text << m_notecountTotal << "\t";
		double percent =  (double)m_scountTotal / m_notecountTotal;
		percent = int(percent * 10000.0 + 0.5) / 100.0;
		m_free_text << percent << "\t";
		m_free_text << m_fileCount;
		if (m_fileCount == 1) {
			m_free_text << " file";
		} else {
			m_free_text << " files";
		}
		m_free_text << endl;
	}
	return status;
}



bool Tool_synco::run(const string& indata, ostream& out) {
	HumdrumFile infile(indata);
	bool status = run(infile);
	if (hasAnyText()) {
		getAllText(out);
	} else {
		out << infile;
	}
	return status;
}


bool Tool_synco::run(HumdrumFile& infile, ostream& out) {
	bool status = run(infile);
	if (hasAnyText()) {
		getAllText(out);
	} else {
		out << infile;
	}
	return status;
}


bool Tool_synco::run(HumdrumFile& infile) {
	initialize();
	processFile(infile);
	if (m_hasSyncoQ && !m_infoQ) {
		infile.createLinesFromTokens();
		m_humdrum_text << infile;
		m_humdrum_text << "!!!RDF**kern: | = marked note, color=" << m_color << endl;
	}
	double notecount = infile.getNoteCount();
	double density = m_scount / (double)notecount;
	double percent =  int(density * 10000.0 + 0.5) / 100.0;
	if (m_infoQ) {
		m_free_text << m_scount << "\t" << notecount << "\t" << percent << "%";
		if (m_fileQ) {
			m_free_text << "\t" << infile.getFilename();
		}
		m_free_text << endl;

		m_scountTotal    += m_scount;
		m_notecountTotal += notecount;
		m_fileCount++;
	} else {
		m_humdrum_text << "!!!syncopated_notes: " << m_scount << endl;
		m_humdrum_text << "!!!total_notes: " << notecount << endl;
		m_humdrum_text << "!!!syncopated_density: " << percent << "%" << endl;
	}

	return true;
}



//////////////////////////////
//
// Tool_synco::initialize --  Initializations that only have to be done once
//    for all HumdrumFile segments.
//

void Tool_synco::initialize(void) {
	m_infoQ = getBoolean("info");
	m_fileQ = getBoolean("filename");
	m_allQ  = getBoolean("all");
	m_color = getString("color");
}



//////////////////////////////
//
// Tool_synco::processFile --
//

void Tool_synco::processFile(HumdrumFile& infile) {
	int scount = infile.getStrandCount();
	m_scount = 0;
	for (int i=0; i<scount; i++) {
		HTp stok = infile.getStrandStart(i);
		if (!stok->isKern()) {
			continue;
		}
		HTp etok = infile.getStrandEnd(i);
		processStrand(stok, etok);
	}
}



//////////////////////////////
//
// Tool_synco::processStrand --
//

void Tool_synco::processStrand(HTp stok, HTp etok) {
	HTp current = stok;
	while (current && (current != etok)) {
		if (!current->isData()) {
			current = current->getNextToken();
			continue;
		}
		if (current->isNull()) {
			current = current->getNextToken();
			continue;
		}
		if (current->isRest()) {
			current = current->getNextToken();
			continue;
		}
		if (current->isSecondaryTiedNote()) {
			current = current->getNextToken();
			continue;
		}
		if (isSyncopated(current)) {
			m_hasSyncoQ = true;
			m_scount++;
			markNote(current);
		}
		current = current->getNextToken();
	}
}



//////////////////////////////
//
// Tool_synco::isSyncopated --
//

bool Tool_synco::isSyncopated(HTp token) {
	double metlev   = getMetricLevel(token);
	HumNum duration = token->getTiedDuration();
	double logDur   = log2(duration.getFloat());
	if (metlev == 2) {
		return false;
	}
	if (logDur > metlev) {
		return true;
	} else {
		return false;
	}
}



//////////////////////////////
//
// Tool_synco::getMetricLevel -- Assuming whole-note beats for now.
//

double Tool_synco::getMetricLevel(HTp token) {
	HumNum durbar = token->getDurationFromBarline();
	if (!durbar.isInteger()) {
		return -1.0;
	}
	if (durbar.getNumerator() % 4 == 0) {
		return 2.0;
	}
	if (durbar.getNumerator() % 2 == 0) {
		return 1.0;
	}
	return 0.0;
}



//////////////////////////////
//
// Tool_synco::markNote -- Currently ignoring chords.
//

void Tool_synco::markNote(HTp token) {
	token->setText(token->getText() + "|");
	if ((token->find('[') != string::npos) || (token->find('_') != string::npos)) {
		HTp current = token->getNextToken();
		while (current) {
			if (!current->isData()) {
				current = current->getNextToken();
				continue;
			}
			if (current->isNull()) {
				current = current->getNextToken();
				continue;
			}
			if (current->isRest()) {
				break;
			}
			if (current->find("_") != string::npos) {
				current->setText(current->getText() + "|");
			} else if (current->find("]") != string::npos) {
				current->setText(current->getText() + "|");
				break;
			}
			current = current->getNextToken();
		}
	}
}





/////////////////////////////////
//
// Tool_gridtest::Tool_tabber -- Set the recognized options for the tool.
//

Tool_tabber::Tool_tabber(void) {
	// do nothing for now.
	define("r|remove=b",    "remove any extra tabs");
}



///////////////////////////////
//
// Tool_tabber::run -- Primary interfaces to the tool.
//

bool Tool_tabber::run(HumdrumFileSet& infiles) {
	bool status = true;
	for (int i=0; i<infiles.getCount(); i++) {
		status &= run(infiles[i]);
	}
	return status;
}


bool Tool_tabber::run(const string& indata, ostream& out) {
	HumdrumFile infile(indata);
	return run(infile, out);
}


bool Tool_tabber::run(HumdrumFile& infile, ostream& out) {
	bool status = run(infile);
	out << m_free_text.str();
	return status;
}


bool Tool_tabber::run(HumdrumFile& infile) {
   initialize(infile);
	processFile(infile);
	return true;
}



//////////////////////////////
//
// Tool_tabber::initialize --
//

void Tool_tabber::initialize(HumdrumFile& infile) {
	// do nothing for now
}



//////////////////////////////
//
// Tool_tabber::processFile --
//

void Tool_tabber::processFile(HumdrumFile& infile) {
	if (getBoolean("remove")) {
		infile.removeExtraTabs();
	} else {
		infile.addExtraTabs();
	}
	infile.createLinesFromTokens();
}




/////////////////////////////////
//
// Tool_tandeminfo::Tool_tandeminfo -- Set the recognized options for the tool.
//

Tool_tandeminfo::Tool_tandeminfo(void) {

	define("c|count=b",                               "show only unique list of interpretations with counts");
	define("D|no-description|M|no-meaning=b",         "do not include descriptions of tandem interpretations in output");
	define("f|filename=b",                            "show filename");
	define("h|header-only=b",                         "only process interpretations before first data line");
	define("H|body-only=b",                           "only process interpretations after first data line");
	define("l|location=b",                            "show location of interpretation in file (row, column)");
	define("n|sort-by-count=b",                       "sort entries by unique counts from low to high (when -c is used)");
	define("N|sort-by-reverse-count=b",               "sort entries by unique counts from high to low (when -c is used)");
	define("s|sort=b",                                "sort entries alphabetically by tandem interpretation");
	define("t|table=b",                               "embed analysis withing input data");
	define("u|unknown-tandem-interpretations-only=b", "only list unknown interpretations");
	define("x|exclusive-interpretations=b",           "show exclusive interpretation context");
	define("z|zero-indexed-locations=b",              "locations are 0-indexed");

	define("close=b",                                 "close <details> by default in HTML output");
	define("humdrum|hmd=b",                           "textual output formatted with Humdrum syntax");

	m_entries.reserve(1000);
}



/////////////////////////////////
//
// Tool_tandeminfo::run -- Do the main work of the tool.
//

bool Tool_tandeminfo::run(HumdrumFileSet& infiles) {
	bool status = true;
	for (int i=0; i<infiles.getCount(); i++) {
		status &= run(infiles[i]);
	}
	return status;
}


bool Tool_tandeminfo::run(const string& indata, ostream& out) {
	HumdrumFile infile(indata);
	bool status = run(infile);
	if (hasAnyText()) {
		getAllText(out);
	} else {
		out << infile;
	}
	return status;
}


bool Tool_tandeminfo::run(HumdrumFile& infile, ostream& out) {
	bool status = run(infile);
	if (hasAnyText()) {
		getAllText(out);
	} else {
		out << infile;
	}
	return status;
}


bool Tool_tandeminfo::run(HumdrumFile& infile) {
	initialize();
	processFile(infile);
	return true;
}


//////////////////////////////
//
// Tool_tandeminfo::initialize --
//

void Tool_tandeminfo::initialize(void) {
	m_exclusiveQ   = getBoolean("exclusive-interpretations");
	m_unknownQ     = getBoolean("unknown-tandem-interpretations-only");
	m_filenameQ    = getBoolean("filename");
	m_locationQ    = getBoolean("location");
	m_countQ       = getBoolean("count");
	m_tableQ       = getBoolean("table");
	m_zeroQ        = getBoolean("zero-indexed-locations");
	m_sortQ        = getBoolean("sort");
	m_headerOnlyQ  = getBoolean("header-only");
	m_bodyOnlyQ    = getBoolean("body-only");
	m_closeQ       = getBoolean("close");
	m_humdrumQ     = getBoolean("humdrum");
	m_descriptionQ = !getBoolean("no-description");
	m_sortByCountQ = getBoolean("sort-by-count");
	m_sortByReverseCountQ = getBoolean("sort-by-reverse-count");

	if (m_headerOnlyQ && m_bodyOnlyQ) {
		m_headerOnlyQ = 0;
		m_bodyOnlyQ   = 0;
	}

	if (m_countQ && m_locationQ) {
		m_locationQ = false;
	}

	// table option turned on by default for web interfaces:
	#ifndef __EMSCRIPTEN__
		m_tableQ       = getBoolean("table");
	#else
		m_tableQ       = !getBoolean("table");
	#endif
}



//////////////////////////////
//
// Tool_tandeminfo::processFile --
//

void Tool_tandeminfo::processFile(HumdrumFile& infile) {
	m_entries.clear();
	m_count.clear();

	bool foundDataQ = false;
	for (int i=0; i<infile.getLineCount(); i++) {
		if (infile[i].isManipulator()) {
			continue;
		}
		if (infile[i].isData()) {
			foundDataQ = true;
		}
		if (!infile[i].isInterpretation()) {
			continue;
		}
		if (foundDataQ && m_headerOnlyQ) {
			break;
		}
		if (!foundDataQ && m_bodyOnlyQ) {
			continue;
		}
		for (int j=0; j<infile[i].getFieldCount(); j++) {
			HTp token = infile.token(i, j);
			if (*token == "*") {
				continue;
			}
			string description;
			if (m_descriptionQ || m_unknownQ) {
				description = getDescription(token);
				if (m_unknownQ) {
					HumRegex hre;
					if (!hre.search(description, m_unknown)) {
						continue;
					}
				}
			}
			m_entries.resize(m_entries.size() + 1);
			m_entries.back().token = token;
			m_entries.back().description = description;
		}
	}

	printEntries(infile);
}



//////////////////////////////
//
// Tool_tandeminfo::printEntries --
//

void Tool_tandeminfo::printEntries(HumdrumFile& infile) {
	if (m_sortQ) {
		sort(m_entries.begin(), m_entries.end(), [](const Entry &a, const Entry &b) {
			string aa = a.token->getText();
			string bb = b.token->getText();
			std::transform(aa.begin(), aa.end(), aa.begin(), ::tolower);
			std::transform(bb.begin(), bb.end(), bb.begin(), ::tolower);
			return (aa < bb);
		});
	}
	if (m_countQ) {
		doCountAnalysis();
	}

	if (m_sortByCountQ) {

		sort(m_entries.begin(), m_entries.end(), [](const Entry &a, const Entry &b) {
			int anum = a.count;
			int bnum = b.count;
			if (anum != bnum) {
				return anum < bnum;
			}
			string aa = a.token->getText();
			string bb = b.token->getText();
			std::transform(aa.begin(), aa.end(), aa.begin(), ::tolower);
			std::transform(bb.begin(), bb.end(), bb.begin(), ::tolower);
			return (aa < bb);
		});

	} else if (m_sortByReverseCountQ) {

		sort(m_entries.begin(), m_entries.end(), [](const Entry &a, const Entry &b) {
			int anum = a.count;
			int bnum = b.count;
			if (anum != bnum) {
				return anum > bnum;
			}
			string aa = a.token->getText();
			string bb = b.token->getText();
			std::transform(aa.begin(), aa.end(), aa.begin(), ::tolower);
			std::transform(bb.begin(), bb.end(), bb.begin(), ::tolower);
			return (aa < bb);
		});

	}

	if (m_tableQ) {
		printEntriesHtml(infile);
	} else {
		printEntriesText(infile);
	}
}



//////////////////////////////
//
// Tool_tandeminfo::doCountAnalysis --
//

void Tool_tandeminfo::doCountAnalysis(void) {
	m_count.clear();
	for (int i=0; i<(int)m_entries.size(); i++) {
		m_count[m_entries[i].token->getText()] += 1;
	}

	// store counts in entries:
	for (int i=0; i<(int)m_entries.size(); i++) {
		m_entries[i].count = m_count[m_entries[i].token->getText()];
	}
}



//////////////////////////////
//
// Tool_tandeminfo::printEntiesHtml -- Print as embedded HTML code at end of
//    input score.
//

void Tool_tandeminfo::printEntriesHtml(HumdrumFile& infile) {
	map<string, bool> processed;  // used for -c option

	m_humdrum_text << infile;

	m_humdrum_text << "!!@@BEGIN: PREHTML" << endl;

	m_humdrum_text << "!!@SCRIPT:" << endl;
	m_humdrum_text << "!!function gotoEditorCoordinate(row, col) {" << endl;
	m_humdrum_text << "!!   if ((typeof EDITOR == 'undefined') || !EDITOR) {" << endl;
	m_humdrum_text << "!!      return;" << endl;
	m_humdrum_text << "!!   }" << endl;
	m_humdrum_text << "!!   gotoLineFieldInEditor(row, col);" << endl;
	m_humdrum_text << "!!}" << endl;
	m_humdrum_text << "!!@CONTENT:" << endl;

	m_humdrum_text << "!!<style>" << endl;
	m_humdrum_text << "!!.PREHTML table.tandeminfo { border: 1px solid black; border-collapse: collapse; max-width: 98%; width:98%; }" << endl;
	m_humdrum_text << "!!.PREHTML table.tandeminfo th, .PREHTML table.tandeminfo td { vertical-align: top; padding-right: 10px; } " << endl;
	m_humdrum_text << "!!.PREHTML table.tandeminfo tr:hover td { background-color: #eee; } " << endl;
	m_humdrum_text << "!!.PREHTML table.tandeminfo th.tandem, .PREHTML table.tandeminfo td.tandem { white-space: nowrap; padding-right: 30px; } " << endl;
	m_humdrum_text << "!!.PREHTML table.tandeminfo .tandem { font-family:\"Courier New\", Courier, monospace; }" << endl;
	m_humdrum_text << "!!.PREHTML table.tandeminfo td.exclusive { font-family:\"Courier New\", Courier, monospace; } " << endl;
	m_humdrum_text << "!!.PREHTML table.tandeminfo th.location, .PREHTML table.tandeminfo td.location { white-space: nowrap; padding-right: 30px; } " << endl;
	m_humdrum_text << "!!.PREHTML table.tandeminfo th.count { padding-left: 20px; padding-right: 10px; } " << endl;
	m_humdrum_text << "!!.PREHTML table.tandeminfo td.count { text-align: right; padding-right: 30px; } " << endl;
	m_humdrum_text << "!!.PREHTML table.tandeminfo th.exclusive, .PREHTML table.tandeminfo td.exclusive { padding-right: 30px; } " << endl;
	m_humdrum_text << "!!.PREHTML table.tandeminfo tr th:last-child, .PREHTML table.tandeminfo tr td:last-child { width:100%; padding-right: 0; } " << endl;
	m_humdrum_text << "!!.PREHTML table.tandeminfo tr th:last-child, .PREHTML table.tandeminfo tr td:last-child { width:100%; padding-right: 0; } " << endl;
	m_humdrum_text << "!!.PREHTML table.tandeminfo tr th, .PREHTML table.tandeminfo tr td { padding:1px; padding-left:3px; } " << endl;
	m_humdrum_text << "!!.PREHTML span.unknown { color:crimson; font-weight:bold; }" << endl;
	m_humdrum_text << "!!.PREHTML td.squeeze { letter-spacing:-0.5px; }" << endl;

	m_humdrum_text << "!!.PREHTML details { position: relative; padding-left: 20px; }" << endl;
	m_humdrum_text << "!!.PREHTML summary { font-size: 1.5rem; cursor: pointer; list-style: none; }" << endl;
	m_humdrum_text << "!!.PREHTML summary::before { content: '▶'; display: inline-block; width: 2em; margin-left: -1.5em; text-align: center; }" << endl;
	m_humdrum_text << "!!.PREHTML details[open] summary::before { content: '▼'; }" << endl;

	m_humdrum_text << "!!</style>" << endl;

	m_humdrum_text << "!!<details class='tandeminfo' ";
	if (!m_closeQ) {
		m_humdrum_text << "open";
	}
	m_humdrum_text << ">" << endl;;
	m_humdrum_text << "!!<summary class='tandeminfo'>Tandem interpretation information</summary>" << endl;;
	if (!m_entries.empty()) {
		m_humdrum_text << "!!<table class='tandeminfo'>" << endl;

		// print table header
		m_humdrum_text << "!!<tr>" << endl;
		if (m_locationQ) {
			m_humdrum_text << "!!<th class='location'>Location</th>" << endl;
		} else if (m_countQ) {
			m_humdrum_text << "!!<th class='count'>Count</th>" << endl;
		}
		if (m_exclusiveQ) {
			m_humdrum_text << "!!<th class='exclusive'>Exclusive</th>" << endl;
		}
		m_humdrum_text << "!!<th class='tandem' >Tandem</th>" << endl;
		m_humdrum_text << "!!<th class='description'>Description</th>" << endl;
		m_humdrum_text << "!!</tr>" << endl;

		// print table entries
		for (int i=0; i<(int)m_entries.size(); i++) {
			HTp token = m_entries[i].token;
			if (m_countQ && processed[token->getText()]) {
				continue;
			}
			processed[token->getText()] = true;
			m_humdrum_text << "!!<tr" << " onclick='gotoEditorCoordinate(" << token->getLineNumber() << ", " << token->getFieldNumber() << ")'>" << endl;

			if (m_locationQ) {
				m_humdrum_text << "!!<td class='location'>" << endl;
				m_humdrum_text << "!!(" << token->getLineNumber() << ", " << token->getFieldNumber() << ")" << endl;
				m_humdrum_text << "!!</td>" << endl;
			} else if (m_countQ) {
				m_humdrum_text << "!!<td class='count'>";
				m_humdrum_text << m_count[token->getText()];
				m_humdrum_text << "</td>" << endl;
			}

			if (m_exclusiveQ) {
				m_humdrum_text << "!!<td class='exclusive'>" << endl;
				m_humdrum_text << "!!" << token->getDataType() << endl;
				m_humdrum_text << "!!</td>" << endl;
			}

			m_humdrum_text << "!!<td class='tandem";
			if (m_entries[i].token->size() > 15) {
				m_humdrum_text << " squeeze";
			}
			m_humdrum_text << "'>" << endl;
			m_humdrum_text << "!!" << m_entries[i].token << endl;
			m_humdrum_text << "!!</td>" << endl;

			HumRegex hre;
			m_humdrum_text << "!!<td class='description'>" << endl;
			string description = m_entries[i].description;
			// hre.replaceDestructive(description, "&lt;", "<", "g");
			// hre.replaceDestructive(description, "&gt;", ">", "g");
			hre.replaceDestructive(description, "<span class='tandeminfo unknown'>unknown</span>", "unknown");
			hre.replaceDestructive(description, "<span class='tandeminfo unknown'>non-standard</span>", "non-standard");
			m_humdrum_text << "!!" << description << endl;
			m_humdrum_text << "!!</td>" << endl;

			m_humdrum_text << "!!</tr>" << endl;
		}

		m_humdrum_text << "!!</table>" << endl;
	}

	// print relevant settings
	vector<string> settings;
	if (m_headerOnlyQ) {
		settings.push_back("Only processing header interpretations");
	}
	if (m_bodyOnlyQ) {
		settings.push_back("Only processing body interpretations");
	}
	if (m_unknownQ) {
		settings.push_back("Only processing unknown interpretations");
	}
	if (m_entries.empty()) {
		settings.push_back("No interpretations found");
	}
	if (!m_entries.empty()) {
		if ((!m_countQ) && m_sortQ && !m_entries.empty()) {
			settings.push_back("List sorted alphabetically by interpretation");
		} else if (m_countQ && m_sortByCountQ) {
			settings.push_back("List sorted low to high by count");
		} else if (m_countQ && m_sortByReverseCountQ) {
			settings.push_back("List sorted high to low by count");
		}
	}
	if (!settings.empty()) {
		m_humdrum_text << "!!<ul>" << endl;
		for (int i=0; i<(int)settings.size(); i++) {
			m_humdrum_text << "!!<li>";
			m_humdrum_text << settings[i];
			m_humdrum_text << "</li>" << endl;
		}
		m_humdrum_text << "!!</ul>" << endl;
	}

	m_humdrum_text << "!!</details>" << endl;
	m_humdrum_text << "!!@@END: PREHTML" << endl;
}



//////////////////////////////
//
// Tool_tandeminfo::printEntiesText --
//

void Tool_tandeminfo::printEntriesText(HumdrumFile& infile) {
	map<string, bool> processed;  // used for -c option

	if (m_humdrumQ) {
		if (m_locationQ) {
			m_free_text << "**loc" << "\t";
		} else if (m_countQ) {
			m_free_text << "**count" << "\t";
		}
		if (m_filenameQ) {
			m_free_text << "**file" << "\t";
		}
		if (m_exclusiveQ) {
			m_free_text << "**exinterp" << "\t";
		}
		m_free_text << "**tandem";
		if (m_descriptionQ) {
			m_free_text << "\t" << "**info";
		}
		m_free_text << endl;
	}

	for (int i=0; i<(int)m_entries.size(); i++) {
		HTp token          = m_entries[i].token;
		if (m_countQ && processed[token->getText()]) {
			continue;
		}
		processed[token->getText()] = true;

		string description = m_entries[i].description;
		HumRegex hre;
		hre.replaceDestructive(description, "", "</?span.*?>", "g");
		if (m_filenameQ) {
			m_free_text << infile.getFilename() << "\t";
		}
		if (m_locationQ) {
			if (m_zeroQ) {
				int row = token->getLineIndex();
				int col = token->getFieldIndex();
				m_free_text << "(" << row << ", " << col << ")" << "\t";
			} else {
				int row = token->getLineNumber();
				int col = token->getFieldNumber();
				m_free_text << "(" << row << ", " << col << ")" << "\t";
			}
		} else if (m_countQ) {
			m_free_text << m_count[token->getText()] << "\t";
		}
		if (m_exclusiveQ) {
			string exinterp = token->getDataType();
			if (m_humdrumQ) {
				exinterp = exinterp.substr(2);
			}
			m_free_text << exinterp << "\t";
		}
		if (m_humdrumQ) {
			string text = token->getText();
			text = text.substr(1);
			m_free_text << text;
		} else {
			m_free_text << token;
		}
		if (m_descriptionQ) {
			m_free_text << "\t" << description;
		}
		m_free_text << endl;
	}

	if (m_humdrumQ) {
		if (m_locationQ) {
			m_free_text << "*-" << "\t";
		} else if (m_countQ) {
			m_free_text << "*-" << "\t";
		}
		if (m_filenameQ) {
			m_free_text << "*-" << "\t";
		}
		if (m_exclusiveQ) {
			m_free_text << "*-" << "\t";
		}
		m_free_text << "*-";
		if (m_descriptionQ) {
			m_free_text << "\t" << "*-";
		}
		m_free_text << endl;
	}
}



//////////////////////////////
//
// Tool_tandeminfo::getDescription -- Return description of the input token; otherwise, return m_unknown.
//

string Tool_tandeminfo::getDescription(HTp token) {
	string tok = token->substr(1);
	string description;

	description = checkForKeySignature(tok);
	if (description != m_unknown) {
		return description;
	}

	description = checkForKeyDesignation(tok);
	if (description != m_unknown) {
		return description;
	}

	description = checkForInstrumentInfo(tok);
	if (description != m_unknown) {
		return description;
	}

	description = checkForLabelInfo(tok);
	if (description != m_unknown) {
		return description;
	}

	description = checkForTimeSignature(tok);
	if (description != m_unknown) {
		return description;
	}

	description = checkForMeter(tok);
	if (description != m_unknown) {
		return description;
	}

	description = checkForTempoMarking(tok);
	if (description != m_unknown) {
		return description;
	}

	description = checkForClef(tok);
	if (description != m_unknown) {
		return description;
	}

	description = checkForStaffPartGroup(tok);
	if (description != m_unknown) {
		return description;
	}

	description = checkForTuplet(tok);
	if (description != m_unknown) {
		return description;
	}

	description = checkForHands(tok);
	if (description != m_unknown) {
		return description;
	}

	description = checkForPosition(tok);
	if (description != m_unknown) {
		return description;
	}

	description = checkForCue(tok);
	if (description != m_unknown) {
		return description;
	}

	description = checkForFlip(tok);
	if (description != m_unknown) {
		return description;
	}

	description = checkForTremolo(tok);
	if (description != m_unknown) {
		return description;
	}

	description = checkForOttava(tok);
	if (description != m_unknown) {
		return description;
	}

	description = checkForPedal(tok);
	if (description != m_unknown) {
		return description;
	}

	description = checkForBracket(tok);
	if (description != m_unknown) {
		return description;
	}

	description = checkForRscale(tok);
	if (description != m_unknown) {
		return description;
	}

	description = checkForTimebase(tok);
	if (description != m_unknown) {
		return description;
	}

	description = checkForTransposition(tok);
	if (description != m_unknown) {
		return description;
	}

	description = checkForGrp(tok);
	if (description != m_unknown) {
		return description;
	}

	description = checkForStria(tok);
	if (description != m_unknown) {
		return description;
	}

	description = checkForFont(tok);
	if (description != m_unknown) {
		return description;
	}

	description = checkForVerseLabels(tok);
	if (description != m_unknown) {
		return description;
	}

	description = checkForLanguage(tok);
	if (description != m_unknown) {
		return description;
	}

	description = checkForStemInfo(tok);
	if (description != m_unknown) {
		return description;
	}

	description = checkForXywh(tok);
	if (description != m_unknown) {
		return description;
	}

	description = checkForCustos(tok);
	if (description != m_unknown) {
		return description;
	}

	description = checkForTextInterps(tok);
	if (description != m_unknown) {
		return description;
	}

	description = checkForRep(tok);
	if (description != m_unknown) {
		return description;
	}

	description = checkForPline(tok);
	if (description != m_unknown) {
		return description;
	}

	description = checkForTacet(tok);
	if (description != m_unknown) {
		return description;
	}

	description = checkForFb(tok);
	if (description != m_unknown) {
		return description;
	}

	description = checkForColor(tok);
	if (description != m_unknown) {
		return description;
	}

	description = checkForThru(tok);
	if (description != m_unknown) {
		return description;
	}

	HumRegex hre;
	if (hre.search(token, "\\s+$")) {
		return "unknown (space at end of interpretation may be the problem)";
	} else {
		return m_unknown;
	}
}



//////////////////////////////
//
// Tool_tandeminfo::checkForThru -- Humdrum Toolkit interpretations related to thru command.
//

string Tool_tandeminfo::checkForThru(const string& tok) {
	if (tok == "thru") {
		return "data processed by thru command (expansion lists processed)";
	}

	return m_unknown;
}



//////////////////////////////
//
// Tool_tandeminfo::checkForColor -- Extended interprerations for coloring notes in **kern data.
//     Used in verovio.
//

string Tool_tandeminfo::checkForColor(const string& tok) {
	HumRegex hre;
	if (hre.search(tok, "^color:(.*)")) {
		string color = hre.getMatch(1);
		string output;
		if (hre.search(tok, "^#[0-9A-Fa-f]{3}$")) {
			output = "3-digit hex ";
		} else if (hre.search(tok, "^#[0-9A-Fa-f]{6}$")) {
			output = "6-digit hex ";
		} else if (hre.search(tok, "^#[0-9A-Fa-f]{8}$")) {
			output = "8-digit hex  (RGB + transparency)";
		} else if (hre.search(tok, "^rgb(\\d+\\s*,\\s*\\d+\\s*,\\s*\\d+)$")) {
			output = "RGB integer";
		} else if (hre.search(tok, "^rgb(\\d+\\s*,\\s*\\d+\\s*,\\s*\\d+\\s*,[\\d.]+)$")) {
			output = "RGB integer with alpha";
		} else if (hre.search(tok, "^hsl(\\d+\\s*,\\s*\\d+%\\s*,\\s*\\d+%)$")) {
			output = "HSL";
		} else if (hre.search(tok, "^hsl(\\d+\\s*,\\s*\\d+%\\s*,\\s*\\d+%,\\s*[\\d.]+)$")) {
			output = "HSL with alpha";
		} else if (hre.search(tok, "^[a-z]+$")) {
			output = "named ";
		}
		output += " color";
		return output;
	}

	return m_unknown;
}



//////////////////////////////
//
// Tool_tandeminfo::checkForFb -- Extended interprerations especially for **fb (**fa) exclusive
//     interpretations.
//

string Tool_tandeminfo::checkForFb(const string& tok) {
	if (tok == "reverse") {
		return "reverse order of accidental and number in figured bass";
	}
	if (tok == "Xreverse") {
		return "stop reversing order of accidental and number in figured bass";
	}

	return m_unknown;
}



//////////////////////////////
//
// Tool_tandeminfo::checkForTacet -- Extended interprerations for marking parts that are not
//     playing (rests only) in a movement/movement subsection.
//

string Tool_tandeminfo::checkForTacet(const string& tok) {
	if (tok == "tacet") {
		return "part is tacet in movement/section";
	}
	if (tok == "Xtacet") {
		return "end of part tacet";
	}

	return m_unknown;
}



//////////////////////////////
//
// Tool_tandeminfo::checkForRep -- Extended interprerations for poetic line analysis related to pline tool.
//

string Tool_tandeminfo::checkForPline(const string& tok) {
	HumRegex hre;
	if (hre.search(tok, "^pline:(\\d+)([abcr]*)$")) {
		string number = hre.getMatch(1);
		string info = hre.getMatch(2);
		string output = "poetic line markup: " + number + info;
		return output;
	}

	return m_unknown;
}



//////////////////////////////
//
// Tool_tandeminfo::checkForRep -- Extended interprerations for adding repeat sign shorthand for
//     repeated music.
//

string Tool_tandeminfo::checkForRep(const string& tok) {
	if (tok == "rep") {
		return "start of repeat sign replacing notes/rests";
	}
	if (tok == "Xrep") {
		return "end of repeat sign replacing notes/rests";
	}

	return m_unknown;
}



//////////////////////////////
//
// Tool_tandeminfo::checkForTextInterps -- Extended interprerations for **text and **silbe
//

string Tool_tandeminfo::checkForTextInterps(const string& tok) {
	if (tok == "ij") {
		return "start of text repeat region";
	}
	if (tok == "Xij") {
		return "end of text repeat region";
	}
	if (tok == "edit") {
		return "start of editorial text region";
	}
	if (tok == "Xedit") {
		return "end of editorial text region";
	}

	return m_unknown;
}



//////////////////////////////
//
// Tool_tandeminfo::checkForCustos -- Extended interprerations for marker
//     at end of system for next note in part.
//

string Tool_tandeminfo::checkForCustos(const string& tok) {
	HumRegex hre;

	if (tok == "custos") {
		return "custos, pitch unspecified";
	}

	if (tok == "custos:") {
		return "custos, pitch unspecified";
	}

	if (hre.search(tok, "^custos:([A-G]+|[a-g]+)(#+|-+|n)?$")) {
		// also deal with chord custos
		string pitch = hre.getMatch(1);
		string accid = hre.getMatch(2);
		string output = "custos on pitch " + pitch + accid;
		return output;
	}

	return m_unknown;
}



//////////////////////////////
//
// Tool_tandeminfo::checkForStemInfo -- Extended interprerations
//      for visual display of stems (on left or right side of notes).
//

string Tool_tandeminfo::checkForXywh(const string& tok) {
	HumRegex hre;
	if (hre.search(tok, "^xywh-([^:\\s]+):(\\d+),(\\d+),(\\d+),(\\d+)$")) {
		string page = hre.getMatch(1);
		string x = hre.getMatch(2);
		string y = hre.getMatch(3);
		string w = hre.getMatch(4);
		string h = hre.getMatch(5);
		string output = "IIIF bounding box, page=";
		output += page;
		output += ", x=" + x;
		output += ", y=" + y;
		output += ", w=" + w;
		output += ", h=" + h;
		return output;
	}

	return m_unknown;
}



//////////////////////////////
//
// Tool_tandeminfo::checkForStemInfo -- Extended interprerations
//      for visual display of stems (on left or right side of notes).
//

string Tool_tandeminfo::checkForStemInfo(const string& tok) {
	HumRegex hre;

	if (hre.search(tok, "^(\\d+)/left$")) {
		string rhythm = hre.getMatch(1);
		string output = rhythm + "-rhythm notes always have stem up on the left";
		return output;
	}

	if (hre.search(tok, "^(\\d+)\\\\left$")) {
		string rhythm = hre.getMatch(1);
		string output = rhythm + "-rhythm notes always have stem down on the left";
		return output;
	}

	if (hre.search(tok, "^(\\d+)/right$")) {
		string rhythm = hre.getMatch(1);
		string output = rhythm + "-rhythm notes always have stem up on the right";
		return output;
	}

	if (hre.search(tok, "^(\\d+)\\\\right$")) {
		string rhythm = hre.getMatch(1);
		string output = rhythm + "-rhythm notes always have stem down on the right";
		return output;
	}

	if (tok == "all/right") {
		string output = "all notes always have stem up on the right";
		return output;
	}

	if (tok == "all\\right") {
		string output = "all notes always have stem down on the right";
		return output;
	}

	if (tok == "all/left") {
		string output = "all notes always have stem up on the left";
		return output;
	}

	if (tok == "all\\left") {
		string output = "all notes always have stem down on the left";
		return output;
	}

	if (tok == "all/center") {
		string output = "all notes always have stem up on notehead center";
		return output;
	}

	if (tok == "all\\center") {
		string output = "all notes always have stem down on notehead center";
		return output;
	}
	// there is also "middle" which is the same as "center";

	return m_unknown;
}



//////////////////////////////
//
// Tool_tandeminfo::checkForLanguage -- Humdrum Toolkit and extended interprerations
//      for langauages (for **text and **silbe).
//

string Tool_tandeminfo::checkForLanguage(const string& tok) {
	HumRegex hre;

	if (hre.search(tok, "^L([A-Z][^\\s]+)$")) {
		string language = hre.getMatch(1);
		string output = "Language, old style: " + language;
		return output;
	}

	if (hre.search(tok, "^lang:([A-Z]{2,3})$")) {
		string code = hre.getMatch(1);
		string name = Convert::getLanguageName(code);
		if (name.empty()) {
			string output = "language code <span class='tandem'>" + code +  "</span>=unknown";
			return output;
		}
		string output = "language code";
		if (code.size() == 2) {
			output = "ISO 639-3 two-letter language code: ";
		} else if (code.size() == 3) {
			output = "ISO 639-3 three-letter language code: ";
		}
		output += "<span class='tandem'>";
		output += code;
		output += "</span>=\"";
		output += name;
		output += "\"";
		return output;
	}

	return m_unknown;
}



//////////////////////////////
//
// Tool_tandeminfo::checkForVerseLabels -- Extended tandem interpretations (used by verovio
//      for visual rendeing of notation).
//

string Tool_tandeminfo::checkForVerseLabels(const string& tok) {
	HumRegex hre;
	if (hre.search(tok, "^v:(.*)$")) {
		string output = "verse label \"" + hre.getMatch(1) + "\"";
		return output;
	}
	if (hre.search(tok, "^vv:(.*)$")) {
		string output = "verse label \"" + hre.getMatch(1) + "\", repeated after each system break";
		return output;
	}

	return m_unknown;
}



//////////////////////////////
//
// Tool_tandeminfo::checkForFont -- Extended interprtations for styling **text and **silbe.
//

string Tool_tandeminfo::checkForFont(const string& tok) {
	if (tok == "italic") {
		return "use italic font style";
	}
	if (tok == "Xitalic") {
		return "stop using italic font style";
	}
	if (tok == "bold") {
		return "use bold font style";
	}
	if (tok == "Xbold") {
		return "stop using bold font style";
	}

	return m_unknown;
}



//////////////////////////////
//
// Tool_tandeminfo::checkForStria -- Humdrum Toolkit interpretation.
//

string Tool_tandeminfo::checkForStria(const string& tok) {
	HumRegex hre;
	if (hre.search(tok, "^stria(\\d+)$")) {
		string output = "number of staff lines:" + hre.getMatch(1);
		return output;
	}

	return m_unknown;
}



//////////////////////////////
//
// Tool_tandeminfo::checkForGrp -- Polyrhythm project interpretations for
//      polyrhythm group assignments.  Related to humlib composite tool.
//

string Tool_tandeminfo::checkForGrp(const string& tok) {
	HumRegex hre;
	if (hre.search(tok, "^grp:([AB])$")) {
		string output = "composite rhythm grouping label " + hre.getMatch(1);
		return output;
	}

	return m_unknown;
}



//////////////////////////////
//
// Tool_tandeminfo::checkForTransposition -- Humdrum Toolkit interpretations related
//      to pitch transposition.
//

string Tool_tandeminfo::checkForTransposition(const string& tok) {
	HumRegex hre;

	if (hre.search(tok, "ITrd(-?\\d+)c(-?\\d+)$")) {
		string diatonic = hre.getMatch(1);
		string chromatic = hre.getMatch(2);
		string output = "transposition for written part, diatonic: ";
		output += diatonic;
		output += ", chromatic: ";
		output += chromatic;
		return output;
	}

	if (hre.search(tok, "Trd(-?\\d+)c(-?\\d+)$")) {
		string diatonic = hre.getMatch(1);
		string chromatic = hre.getMatch(2);
		string output = "transposed by diatonic: ";
		output += diatonic;
		output += ", chromatic: ";
		output += chromatic;
		return output;
	}

	return m_unknown;
}



//////////////////////////////
//
// Tool_tandeminfo::checkForTimebase -- Humdrum Toolkit interpretations related
//      to the timebase tool.
//

string Tool_tandeminfo::checkForTimebase(const string& tok) {
	HumRegex hre;
	if (hre.search(tok, "^tb(\\d+)$")) {
		string number = hre.getMatch(1);
		string output = "timebase: all data lines (should) have a duration of " + number;
		return output;
	}

	return m_unknown;
}



//////////////////////////////
//
// Tool_tandeminfo::checkForRscale -- Extended interpretation for adjusting the visual
//     display of note durations when they do not match the logical
//     note durations (such as show a quarter note as if it were a
//     half note, which would be indicated by "*rscale:2". Or a
//     half note as if it were a quarter note with "*rscale:1/2".
//     Also related to the rscale tool from Humdrum Extras and humlib.
//     Used in verovio.
//

string Tool_tandeminfo::checkForRscale(const string& tok) {
	HumRegex hre;
	if (hre.search(tok, "^rscale:(\\d+)(/\\d+)?$")) {
		string fraction = hre.getMatch(1) + hre.getMatch(2);
		string output = "visual rhythmic scaling factor " + fraction;
		return output;
	}

	return m_unknown;
}



//////////////////////////////
//
// Tool_tandeminfo::checkForBracket -- Extended interpretations for displaying
//     various bracket lines in visual music notation.
//

string Tool_tandeminfo::checkForBracket(const string& tok) {
	// Coloration
	if (tok == "col") {
		return "start of coloration bracket";
	}
	if (tok == "Xcol") {
		return "end of coloration bracket";
	}

	// Ligatures
	if (tok == "lig") {
		return "start of ligature bracket";
	}
	if (tok == "Xlig") {
		return "end of ligature bracket";
	}

	// Schoenberg
	if (tok == "haupt") {
		return "start of Hauptstimme bracket";
	}
	if (tok == "Xhaupt") {
		return "end of Hauptstimme bracket";
	}
	if (tok == "neben") {
		return "start of Nebenstimme bracket";
	}
	if (tok == "Xneben") {
		return "end of Nebenstimme bracket";
	}
	if (tok == "rhaupt") {
		return "start of Hauptrhythm bracket";
	}
	if (tok == "Xrhaupt") {
		return "end of Hauptrhythm bracket";
	}

	return m_unknown;
}



//////////////////////////////
//
// Tool_tandeminfo::checkForPedal -- Extended interpretations for displaying
//     ottava lines in music notation.
//

string Tool_tandeminfo::checkForPedal(const string& tok) {
	if (tok == "ped") {
		return "sustain pedal down";
	}
	if (tok == "Xped") {
		return "sustain pedal up";
	}
	return m_unknown;
}



//////////////////////////////
//
// Tool_tandeminfo::checkForOttava -- Extended interpretations for displaying
//     ottava lines in music notation.
//

string Tool_tandeminfo::checkForOttava(const string& tok) {
	if (tok == "8va") {
		return "start of 8va line";
	}
	if (tok == "X8va") {
		return "end of 8va line";
	}
	if (tok == "8ba") {
		return "start of 8ba (ottava basso) line";
	}
	if (tok == "X8ba") {
		return "end of 8ba (ottava basso) line";
	}
	if (tok == "15ma") {
		return "start of 15ma line";
	}
	if (tok == "X15ma") {
		return "end of 15ma line";
	}
	if (tok == "coll8ba") {
		return "coll ottava basso start";
	}
	if (tok == "Xcoll8ba") {
		return "coll ottava basso end";
	}
	return m_unknown;
}




//////////////////////////////
//
// Tool_tandeminfo::checkForTremolo -- Extended interpretations for collapsing
//     repeated notes into tremolos in music notation rendering.
//     Used specifically by verovio.
//

string Tool_tandeminfo::checkForTremolo(const string& tok) {
	if (tok == "tremolo") {
		return "start of tremolo rendering of repeated notes";
	}
	if (tok == "Xtremolo") {
		return "end of tremolo rendering of repeated notes";
	}
	return m_unknown;
}


//////////////////////////////
//
// Tool_tandeminfo::checkForFlip -- Extended interpretations for use with the
//     flipper humlib command.
//

string Tool_tandeminfo::checkForFlip(const string& tok) {
	if (tok == "flip") {
		return "switch order of subspines, specific to flipper tool";
	}
	if (tok == "Xflip") {
		return "cancel flipping of subspine, specific to flipper tool";
	}
	return m_unknown;
}



//////////////////////////////
//
// Tool_tandeminfo::checkForCue -- Extended interpretations for visual rendering
//     *cue means display as cue-sized notes.  Probably change
//     this so that *cue means following notes are cue notes
//     and add *cuesz for cue-sized notes (that are not cues
//     from other instruments).
//

string Tool_tandeminfo::checkForCue(const string& tok) {
	if (tok == "cue") {
		return "cue-sized notation follows";
	}
	if (tok == "Xcue") {
		return "cancel cue-sized notation";
	}
	return m_unknown;
}



//////////////////////////////
//
// Tool_tandeminfo::checkForPosition -- Extended interpretations for visual rendering
//     data above/below staff.  Useful in particular for **dynam.
//     Staff number in part (relative to top staff) can be given
//     as a number following a colon after the placement.
//

string Tool_tandeminfo::checkForPosition(const string& tok) {
	if (tok == "above") {
		return "place items above staff";
	}
	if (tok == "above:1") {
		return "place items above first staff of part";
	}
	if (tok == "above:2") {
		return "place items above second staff of part";
	}
	if (tok == "below") {
		return "place items below staff";
	}
	if (tok == "below:1") {
		return "place items below first staff of part";
	}
	if (tok == "below:2") {
		return "place items below second staff of part";
	}
	if (tok == "center") {
		return "centered items between two staves";
	}
	return m_unknown;
}



//////////////////////////////
//
// Tool_tandeminfo::checkForHands -- Extended interpretations to indicate which
//     hand is playing the notes (for grand-staff keyboard in particular).
//

string Tool_tandeminfo::checkForHands(const string& tok) {
	if (tok == "LH") {
		return "notes played by left hand";
	}
	if (tok == "RH") {
		return "notes played by right hand";
	}
	return m_unknown;
}



//////////////////////////////
//
// Tool_tandeminfo::checkForTuplet -- Extended interpretations for **kern data to control
//     visual stylings of tuplet numbers and brackets.
//

string Tool_tandeminfo::checkForTuplet(const string& tok) {
	if (tok == "Xbrackettup") {
		return "suppress brackets for tuplets";
	}
	if (tok == "brackettup") {
		return "do not suppress brackets for tuplets (default)";
	}
	if (tok == "tuplet") {
		return "show tuplet numbers (default)";
	}
	if (tok == "Xtuplet") {
		return "do not show tuplet numbers";
	}
	if (tok == "tupbreak") {
		return "break tuplet at this point";
	}
	

	return m_unknown;
}



//////////////////////////////
//
// Tool_tandeminfo::checkForStaffPartGroup -- Humdrum Toolkit interpretation (*staff), and
//    extensions to *part to group multiple staves into a single part as
//    well as *group for grouping staves/parts into instrument class
//    groups (useful for controlling connecting barlines across multiple
//    staves).
//

string Tool_tandeminfo::checkForStaffPartGroup (const string& tok) {
	HumRegex hre;

	if (hre.search(tok, "^staff(\\d+)(/\\d+)*$")) {
		string number = hre.getMatch(1);
		string second = hre.getMatch(2);
		string output;
		if (second.empty()) {
			output = "staff " + number;
			return output;
		}
		output = "staves " + tok.substr(5);
		return output;
	}

	if (hre.search(tok, "^part(\\d+)(/\\d+)*$")) {
		string number = hre.getMatch(1);
		string second = hre.getMatch(2);
		string output;
		if (second.empty()) {
			output = "part " + number;
			return output;
		}
		output = "parts " + tok.substr(5);
		return output;
	}

	if (hre.search(tok, "^group(\\d+)(/\\d+)*$")) {
		string number = hre.getMatch(1);
		string second = hre.getMatch(2);
		string output;
		if (second.empty()) {
			output = "group " + number;
			return output;
		}
		output = "groups " + tok.substr(5);
		return output;
	}

	return m_unknown;
}



//////////////////////////////
//
// Tool_tandeminfo::checkForClef -- Humdrum Toolkit interpretations.  Extension
//     is "*clefX" for percussion clef (checked for below),
//     and *clefG2yy for an invisible clef (not visually rendered).
//

string Tool_tandeminfo::checkForClef(const string& tok) {
	HumRegex hre;
	if (hre.search(tok, "^(m|o)?clef([GFCX])(.*?)([12345])?(yy)?$")) {
		string modori = hre.getMatch(1);
		string ctype = hre.getMatch(2);
		string octave = hre.getMatch(3);
		string line = hre.getMatch(4);
		string invisible = hre.getMatch(5);
		string output = "clef: ";
		if (ctype == "X") {
			output += "percussion";
			if (!line.empty()) {
				output += ", line=" + line;
			}
			if (!octave.empty()) {
				return m_unknown;
			}
		} else {
			output += ctype;
			if (line.empty()) {
				return m_unknown;
			}
			output += ", line=" + line;
			if (!octave.empty()) {
				if (hre.search(octave, "^v+$")) {
					output += ", octave displacement -" + to_string(octave.size());
				} else if (hre.search(octave, "^\\^+$")) {
					output += ", octave displacement +" + to_string(octave.size());
				}
			}
		}
		if (!invisible.empty()) {
			output += ", invisible (not displayed in music rendering)";
		}
		return output;
	}

	return m_unknown;
}



//////////////////////////////
//
// Tool_tandeminfo::checkForTimeSignature -- Humdrum Toolkit interpretations.
//      Extended for use with rare non-notatable rhythm bases, such as
//      *M3/3%2 for three triplet whole notes to the measure (this
//      is equivalent in duration to *M2/1 but gives a more refined
//      version of what the beat is.  Maybe also allow "*M2/4." which
//      would be equivalent to an explicit compound *M6/8 time signature.
//      Other extensions could also be done such as *M4/4yy for an invisible
//      time signature.  And another extension could be *M2/8+3/8 for *M5/8
//      split into 2 + 3 beat groupings.
//

string Tool_tandeminfo::checkForTimeSignature(const string& tok) {
	HumRegex hre;
	if (tok == "MX") {
		return "unmeasured music time signature";
	}
	if (hre.search(tok, "^MX/(\\d+)(%\\d+)?(yy)?")) {
		string output = "unmeasured music with beat " + hre.getMatch(1) + hre.getMatch(2);
		if (hre.getMatch(3) == "yy") {
			output += ", invisible";
			return output;
		}
	}
	if (hre.search(tok, "^M(\\d+)/(\\d+)(%\\d+)?(yy)?$")) {
		string top = hre.getMatch(1);
		string bot = hre.getMatch(2) + hre.getMatch(3);
		string invisible = hre.getMatch(4);
		string output = "time signature: top=";
		output += "<span class='tandem'>";
		output += top;
		output += "</span>";
		output += ", bottom=";
		output += "<span class='tandem'>";
		output += bot;
		output += "</span>";
		if (bot == "3%2") {
			output += " (triplet semibreve)";
		} else if (bot == "3%4") {
			output += " (triplet breve)";
		}
		if (invisible == "yy") {
			output += ", invisible";
		}
		return output;
	}

	return m_unknown;
}



//////////////////////////////
//
// Tool_tandeminfo::checkForMeter -- Humdrum Toolkit interpretations. Extended for use
//    with mensural signs.
//

string Tool_tandeminfo::checkForMeter(const string& tok) {
	HumRegex hre;
	if (hre.search(tok, "^(m|o)?met\\((.*?)\\)$")) {
		string modori = hre.getMatch(1);
		string meter = hre.getMatch(2);
		if (meter == "c") {
			return "meter: common time";
		}
		if (meter == "c|") {
			return "meter: cut time";
		}
		if (meter == "") {
			return "meter: empty";
		}
		string output = "mensuration sign: ";
		if (meter == "O") {
			output += "circle";
		} else if (meter == "O|") {
			output += "cut-circle";
		} else if (meter == "C") {
			output += "c";
		} else if (meter == "C|") {
			output += "cut-c";
		} else if (meter == "Cr") {
			output += "reverse-c";
		} else if (meter == "C.") {
			output += "c-dot";
		} else if (meter == "O.") {
			output += "circle-dot";
		} else {
			output += meter;
		}
		return output;
	}

	return m_unknown;
}



//////////////////////////////
//
// Tool_tandeminfo::checkForTempoMarking -- Humdrum Toolit interpretations.
//

string Tool_tandeminfo::checkForTempoMarking(const string& tok) {
	HumRegex hre;
	if (hre.search(tok, "^MM(\\d+)(\\.\\d*)?$")) {
		string tempo = hre.getMatch(1) + hre.getMatch(2);
		string output = "tempo: " + tempo + " quarter notes per minute";
		return output;
	}

	if (hre.search(tok, "^MM\\[(.*?)\\]$")) {
		string text = hre.getMatch(1);
		string output = "text-based tempo: " + text;
		return output;
	}

	return m_unknown;
}



//////////////////////////////
//
// Tool_tandeminfo::checkForLabelInfo -- Humdrum Toolkit interpretations.
//     Used by the thru command.
//

string Tool_tandeminfo::checkForLabelInfo(const string& tok) {
	HumRegex hre;
	if (!hre.search(tok, "^>")) {
		return m_unknown;
	}

	if (hre.search(tok, "^>(\\[.*\\]$)")) {
		string list = hre.getMatch(1);
		string output = "default expansion list: ";
		output += "<span class='tandem'>";
		output += list;
		output += "</span>";
		return output;
	}

	if (hre.search(tok, "^>([^[\\[\\]]+)(\\[.*\\]$)")) {
		string expansionName = hre.getMatch(1);
		string list = hre.getMatch(2);
		string output = "alternate expansion list: label=";
		output += "<span class='tandem'>" + expansionName + "</span>";
		if (expansionName == "norep") {
			output += " (meaning: no repeats, i.e., take only second endings)";
		}
		output += ", expansion list: " + list;
		return output;
	}

	if (hre.search(tok, "^>([^\\[\\]]+)$")) {
		string label = hre.getMatch(1);
		string output = "expansion label: ";
		output += "<span class='tandem'>";
		output += label;
		output += "</span>";
		return output;
	}

	return m_unknown;

}



//////////////////////////////
//
// Tool_tandeminfo::checkForInstrumentInfo -- Humdrum Toolkit and extended interpretations.
//     Humdrum Tookit:
//         instrument group	 *IG
//         instrument class	*IC
//         instrument code	*I
//     Extended:
//         instrument name *I"
//         instrument number *I#
//         instrument abbreviation *I'
//
//     modori tool extensions:
//         *mI == modernized
//         *oI == original
//

string Tool_tandeminfo::checkForInstrumentInfo(const string& tok) {
	HumRegex hre;

	if (hre.search(tok, "^(m|o)?I\"(.*)$")) {
		string modori = hre.getMatch(1);
		string name = hre.getMatch(2);
		string output = "text to display in fromt of staff on first system (usually instrument name): \"";
		output += name;
		output += "\"";
		if (modori == "o") {
			output += " (original)";
		} else if (modori == "m") {
			output += " (modern)";
		}
		if (hre.search(tok, "\\\\n")) {
			output += ", \"\\n\" means a line break";
		}
		return output;
	}

	if (hre.search(tok, "^(m|o)?I'(.*)$")) {
		string modori = hre.getMatch(1);
		string abbr = hre.getMatch(2);
		string output = "text to display in front of staff on secondary systems (usually instrument abbreviation): \"";
		output += abbr;
		output += "\"";
		if (modori == "o") {
			output += " (original)";
		} else if (modori == "m") {
			output += " (modern)";
		}
		if (hre.search(tok, "\\\\n")) {
			output += ", \"\\n\" means a line break";
		}
		return output;
	}


	if (hre.search(tok, "^(m|o)?IC([^\\s]*)$")) {
		string modori = hre.getMatch(1);
		string iclass = hre.getMatch(2);
		bool andy = false;
		bool ory  = false;
		vector<string> iclasses;
		string tok2 = tok;
		hre.replaceDestructive(tok2, "", "IC", "g");
		if (hre.search(tok2, "&")) {
			hre.split(iclasses, tok2, "&+");
			andy = true;
		} else if (hre.search(tok2, "\\|")) {
			hre.split(iclasses, tok2, "\\++");
			ory = true;
		} else {
			iclasses.push_back(tok2);
		}
		string output;
		if (modori == "o") {
			output += "(original) ";
		} else if (modori == "m") {
			output += "(modern) ";
		}
		output += "instrument class";
		if (iclasses.size() != 1) {
			output += "es";
		}
		output += ":";
		for (int i=0; i<(int)iclasses.size(); i++) {
			output += " ";
			output += "<span class='tandem'>";
			output += iclasses[i];
			output += "</span>";
			HumInstrument inst;
			inst.setHumdrum(iclasses[i]);
			string name;
			if (iclasses[i] == "bras") {
				name = "brass";
			} else if (iclasses[i] == "idio") {
				name = "percussion";
			} else if (iclasses[i] == "klav") {
				name = "keyboards";
			} else if (iclasses[i] == "str") {
				name = "strings";
			} else if (iclasses[i] == "vox") {
				name = "voices";
			} else if (iclasses[i] == "ww") {
				name = "woodwinds";
			} else if (iclasses[i] != "") {
				name = "unknown";
			}
			if (!name.empty()) {
				output += "=\"" + name + "\"";
			}
			if (i < (int)iclasses.size() - 1) {
				if (andy) {
					output += " and ";
				} else if (ory) {
					output += " or ";
				}
			}
		}
		if (modori == "o") {
			output += " (original)";
		} else if (modori == "m") {
			output += " (modern)";
		}
		return output;
	}


	if (hre.search(tok, "^(m|o)?IG([^\\s]*)$")) {
		string modori = hre.getMatch(1);
		string group = hre.getMatch(2);
		bool andy = false;
		bool ory  = false;
		vector<string> groups;
		string tok2 = tok;
		hre.replaceDestructive(tok2, "", "IG", "g");
		if (hre.search(tok2, "&")) {
			hre.split(groups, tok2, "&+");
			andy = true;
		} else if (hre.search(tok2, "\\|")) {
			hre.split(groups, tok2, "\\++");
			ory = true;
		} else {
			groups.push_back(tok2);
		}
		string output;
		if (modori == "o") {
			output += "(original) ";
		} else if (modori == "m") {
			output += "(modern) ";
		}
		output += "instrument group";
		if (groups.size() != 1) {
			output += "s";
		}
		output += ":";
		for (int i=0; i<(int)groups.size(); i++) {
			output += " ";
			output += groups[i];
			HumInstrument inst;
			inst.setHumdrum(groups[i]);
			string name;
			if (groups[i] == "acmp") {
				name = "=accompaniment";
			} else if (groups[i] == "solo") {
				name = "=solo";
			} else if (groups[i] == "cont") {
				name = "=basso-continuo";
			} else if (groups[i] == "ripn") {
				name = "=ripieno";
			} else if (groups[i] == "conc") {
				name = "=concertino";
			} else if (groups[i] != "") {
				name = "=unknown";
			}
			if (!name.empty()) {
				output += "=\"" + name + "\"";
			}
			if (i < (int)groups.size() - 1) {
				if (andy) {
					output += " and ";
				} else if (ory) {
					output += " or ";
				}
			}
		}
		if (modori == "o") {
			output += " (original)";
		} else if (modori == "m") {
			output += " (modern)";
		}
		return output;
	}

	if (hre.search(tok, "^(m|o)?I#(\\d+)$")) {
		string modori = hre.getMatch(1);
		string number = hre.getMatch(2);
		string output = "sub-instrument number: ";
		output += number;
		if (modori == "o") {
			output += " (original)";
		} else if (modori == "m") {
			output += " (modern)";
		}
		return output;
	}

	if (hre.search(tok, "^(m|o)?I([a-z][a-zA-Z0-9_|&-]+)$")) {
		string modori = hre.getMatch(1);
		string code = hre.getMatch(2);
		bool andy = false;
		bool ory  = false;
		vector<string> codes;
		string tok2 = tok;
		hre.replaceDestructive(tok2, "", "I", "g");
		if (hre.search(tok2, "&")) {
			hre.split(codes, tok2, "&+");
			andy = true;
		} else if (hre.search(tok2, "\\|")) {
			hre.split(codes, tok2, "\\++");
			ory = true;
		} else {
			codes.push_back(tok2);
		}
		string output;
		if (modori == "o") {
			output += "(original) ";
		} else if (modori == "m") {
			output += "(modern) ";
		}
		output += "instrument code";
		if (codes.size() != 1) {
			output += "s";
		}
		output += ":";
		for (int i=0; i<(int)codes.size(); i++) {
			output += " <span class='tandem'>";
			output += codes[i];
			output += "</span>";
			HumInstrument inst;
			inst.setHumdrum(codes[i]);
			string text = inst.getName();
			if (!text.empty()) {
				output += "= \"" + text + "\"";
			} else {
				output += "= unknown code";
			}
			output += "";
			if (i < (int)codes.size() - 1) {
				if (andy) {
					output += " and ";
				} else if (ory) {
					output += " or ";
				}
			}
		}

		return output;
	}

	return m_unknown;
}



//////////////////////////////
//
// Tool_tandeminfo::checkForKeySignature -- Standard Humdrum Toolkit interpretations.
//     Extended key signatures are possible (and detected by this function),
//     but typically the standard ones are in circle-of-fifths orderings.
//     This function also allows double sharps/flats in the key signature
//     which are very uncommon in real music.  Standard key signatures:
//
//     *k[f#c#g#d#a#e#b#]
//     *k[f#c#g#d#a#e#]
//     *k[f#c#g#d#a#]
//     *k[f#c#g#d#]
//     *k[f#c#g#]
//     *k[f#c#]
//     *k[f#]
//     *k[]
//     *k[b-]
//     *k[b-e-]
//     *k[b-e-a-]
//     *k[b-e-a-d-]
//     *k[b-e-a-d-g-]
//     *k[b-e-a-d-g-c-]
//     *k[b-e-a-d-g-c-f-]
//

string Tool_tandeminfo::checkForKeySignature(const string& tok) {

	// visual styling interpretations for key signatures:
	if (tok == "kcancel") {
		return "show cancellation naturals when changing key signatures";
	}
	if (tok == "Xkcancel") {
		return "do not show cancellation naturals when changing key signatures (default)";
	}

	if (tok == "k[]") {
		return "key signature: no sharps or flats";
	}
	if (tok == "ok[]") {
		return "original key signature: no sharps or flats";
	}
	if (tok == "mk[]") {
		return "modern key signature: no sharps or flats";
	}

	HumRegex hre;
	string modori;
	if (hre.search(tok, "^([m|o])k\\[")) {
		modori = hre.getMatch(1);
	}

	if (hre.search(tok, "^(?:m|o)?k\\[(([a-gA-G]+[n#-]{1,2})+)\\]$")) {
		string modori;
		string pcs = hre.getMatch(1);
		bool standardQ = false;
		if (pcs == "f#") {
			standardQ = true;
		} else if (pcs == "b-") {
			standardQ = true;
		} else if (pcs == "f#c#") {
			standardQ = true;
		} else if (pcs == "b-e-") {
			standardQ = true;
		} else if (pcs == "f#c#g#") {
			standardQ = true;
		} else if (pcs == "b-e-a-") {
			standardQ = true;
		} else if (pcs == "f#c#g#d#") {
			standardQ = true;
		} else if (pcs == "b-e-a-d-") {
			standardQ = true;
		} else if (pcs == "f#c#g#d#a#") {
			standardQ = true;
		} else if (pcs == "b-e-a-d-g-") {
			standardQ = true;
		} else if (pcs == "f#c#g#d#a#e#") {
			standardQ = true;
		} else if (pcs == "b-e-a-d-g-c-") {
			standardQ = true;
		} else if (pcs == "f#c#g#d#a#e#b#") {
			standardQ = true;
		} else if (pcs == "b-e-a-d-g-c-f-") {
			standardQ = true;
		}

		string output;
		if (modori == "m") {
			output = "modern ";
		} else if (modori == "o") {
			output = "original ";
		}
		output += "key signature";
		if (!standardQ) {
			output += " (non-standard)";
		}
		output += ": ";
		int flats = 0;
		int sharps = 0;
		int naturals = 0;
		int doubleflats = 0;
		int doublesharps = 0;
		vector<string> accidentals;
		hre.split(accidentals, pcs, "[a-gA-G]");
		for (int i=0; i<(int)accidentals.size(); i++) {
			if (accidentals[i] == "##") {
				doublesharps++;
			} else if (accidentals[i] == "--") {
				doubleflats++;
			} else if (accidentals[i] == "#") {
				sharps++;
			} else if (accidentals[i] == "-") {
				flats++;
			} else if (accidentals[i] == "n") {
				naturals++;
			}
		}

		bool foundQ = false;
		if (sharps) {
			if (foundQ) {
				output += ", ";
			}
			foundQ = true;
			if (sharps == 1) {
				output += "1 sharp";
			} else {
				output += to_string(sharps) + " sharps";
			}
		}

		if (flats) {
			if (foundQ) {
				output += ", ";
			}
			foundQ = true;
			if (flats == 1) {
				output += "1 flat";
			} else {
				output += to_string(flats) + " flats";
			}
		}

		if (naturals) {
			if (foundQ) {
				output += ", ";
			}
			foundQ = true;
			if (naturals == 1) {
				output += "1 natural";
			} else {
				output += to_string(naturals) + " naturals";
			}
		}

		if (doublesharps) {
			if (foundQ) {
				output += ", ";
			}
			foundQ = true;
			if (doublesharps == 1) {
				output += "1 double sharp";
			} else {
				output += to_string(doublesharps) + " double sharps";
			}
		}

		if (doubleflats) {
			if (foundQ) {
				output += ", ";
			}
			foundQ = true;
			if (doubleflats == 1) {
				output += "1 double flat";
			} else {
				output += to_string(doubleflats) + " double flats";
			}
		}

		return output;
	}
	return m_unknown;
}



//////////////////////////////
//
// Tool_tandeminfo::checkForKeyDesignation -- Standard Humdrum Toolkit interpretations, plus
//     modal extensions by Brett Arden.  Typically only used in **kern data.
//

string Tool_tandeminfo::checkForKeyDesignation(const string& tok) {
	HumRegex hre;
	if (tok == "?:") {
		return "key designation, unknown/unassigned key";
	}
	if (hre.search(tok, "^([a-gA-G])([-#]*):(ion|dor|phr|lyd|mix|aeo|loc)?(-hypo|-auth)?$")) {
		string tonic = hre.getMatch(1);
		string accid = hre.getMatch(2);
		string mode  = hre.getMatch(3);
		string older = hre.getMatch(4);
		bool isUpper = isupper(tonic[0]);
		string output = "key designation: ";
		if (mode.empty()) {
			output += toupper(tonic[0]);

			if (accid == "") {
				// do nothing
			} else if (accid == "#") {
				output += "-sharp";
			} else if (accid == "-") {
				output += "-flat";
			} else if (accid == "##") {
				output += "-double-sharp";
			} else if (accid == "--") {
				output += "-double-flat";
			} else if (accid == "###") {
				output += "-triple-sharp";
			} else if (accid == "---") {
				output += "-triple-flat";
			} else {
				return m_unknown;
			}

			if (isUpper) {
				output += " major";
			} else {
				output += " minor";
			}
			return output;
		} else {
			// Modal key
			if (isUpper && ((mode == "dor") || (mode == "phr") || (mode == "aeo") || (mode == "loc"))) {
				// need a lower-case letter for these modes (minor third above tonic)
				return m_unknown;
			}
			if ((!isUpper) && ((mode == "ion") || (mode == "lyd") || (mode == "mix"))) {
				// need an upper-case letter for these modes (major third above tonic)
				return m_unknown;
			}
			output += toupper(tonic[0]);

			if (accid == "") {
				// do nothing
			} else if (accid == "#") {
				output += "-sharp";
			} else if (accid == "-") {
				output += "-flat";
			} else if (accid == "##") {
				output += "-double-sharp";
			} else if (accid == "--") {
				output += "-double-flat";
			} else if (accid == "###") {
				output += "-triple-sharp";
			} else if (accid == "---") {
				output += "-triple-flat";
			} else {
				return m_unknown;
			}

			if (mode == "ion") {
				output += " ionian";
			} else if (mode == "dor") {
				output += " dorian";
			} else if (mode == "phr") {
				output += " phrygian";
			} else if (mode == "lyd") {
				output += " lydian";
			} else if (mode == "mix") {
				output += " mixolydian";
			} else if (mode == "aeo") {
				output += " aeolian";
			} else if (mode == "loc") {
				output += " locrian";
			} else {
				return m_unknown;
			}

			if (!older.empty()) {
				if (older == "-plag") {
					output += " (plagal)";
				} else if (older == "-auth") {
					output += " (authentic)";
				} else {
					output += " (unknown mode type: should be -auth (authentic), or -plag (plagal) for hypo modes)";
				}
			}

			return output;
		}
	}

	return m_unknown;
}




/////////////////////////////////
//
// Tool_tassoize::Tool_tassoize -- Set the recognized options for the tool.
//

Tool_tassoize::Tool_tassoize(void) {
	define("R|no-reference-records=b",                "do not add reference records");
	define("r|only-add-reference-records=b",          "only add reference records");

	define("B|do-not-delete-breaks=b",                "do not delete system/page break markers");
	define("b|only-delete-breaks=b",                  "only delete breaks");

	define("A|do-not-fix-instrument-abbreviations=b", "do not fix instrument abbreviations");
	define("a|only-fix-instrument-abbreviations=b",   "only fix instrument abbreviations");

	define("E|do-not-fix-editorial-accidentals=b",    "do not fix instrument abbreviations");
	define("e|only-fix-editorial-accidentals=b",      "only fix editorial accidentals");

	define("T|do-not-add-terminal-longs=b",           "do not add terminal long markers");
	define("t|only-add-terminal-longs=b",             "only add terminal longs");

	define("N|do-not-remove-empty-transpositions=b",  "do not remove empty transposition instructions");
	define ("n|only-remove-empty-transpositions=b",   "only remove empty transpositions");
}



/////////////////////////////////
//
// Tool_tassoize::run -- Primary interfaces to the tool.
//

bool Tool_tassoize::run(HumdrumFileSet& infiles) {
	bool status = true;
	for (int i=0; i<infiles.getCount(); i++) {
		status &= run(infiles[i]);
	}
	return status;
}


bool Tool_tassoize::run(const string& indata, ostream& out) {
	HumdrumFile infile(indata);
	bool status = run(infile);
	if (hasAnyText()) {
		getAllText(out);
	} else {
		out << infile;
	}
	return status;
}


bool Tool_tassoize::run(HumdrumFile& infile, ostream& out) {
	bool status = run(infile);
	if (hasAnyText()) {
		getAllText(out);
	} else {
		out << infile;
	}
	return status;
}

//
// In-place processing of file:
//

bool Tool_tassoize::run(HumdrumFile& infile) {
	processFile(infile);

	// Re-load the text for each line from their tokens.
	infile.createLinesFromTokens();

	// Need to adjust the line numbers for tokens for later
	// processing.
	m_humdrum_text << infile;
	return true;
}



//////////////////////////////
//
// Tool_tassoize::processFile --
//

void Tool_tassoize::processFile(HumdrumFile& infile) {

	bool abbreviationsQ  = true;
	bool accidentalsQ    = true;
	bool referencesQ     = true;
	bool terminalsQ      = true;
	bool breaksQ         = true;
	bool transpositionsQ = true;

	if (getBoolean("no-reference-records")) { referencesQ = false; }
	if (getBoolean("only-add-reference-records")) {
		abbreviationsQ  = false;
		accidentalsQ    = false;
		referencesQ     = true;
		terminalsQ      = false;
		breaksQ         = false;
		transpositionsQ = false;
	}

	if (getBoolean("do-not-delete-breaks")) { breaksQ = false; }
	if (getBoolean("only-delete-breaks")) {
		abbreviationsQ  = false;
		accidentalsQ    = false;
		referencesQ     = false;
		terminalsQ      = false;
		breaksQ         = true;
		transpositionsQ = false;
	}

	if (getBoolean("do-not-fix-instrument-abbreviations")) { abbreviationsQ = false; }
	if (getBoolean("only-fix-instrument-abbreviations")) {
		abbreviationsQ  = true;
		accidentalsQ    = false;
		referencesQ     = false;
		terminalsQ      = false;
		breaksQ         = false;
		transpositionsQ = false;
	}

	if (getBoolean("do-not-fix-editorial-accidentals")) { accidentalsQ = false; }
	if (getBoolean("only-fix-editorial-accidentals")) {
		abbreviationsQ  = false;
		accidentalsQ    = true;
		referencesQ     = false;
		terminalsQ      = false;
		breaksQ         = false;
		transpositionsQ = false;
	}

	if (getBoolean("do-not-add-terminal-longs")) { terminalsQ = false; }
	if (getBoolean("only-add-terminal-longs")) {
		abbreviationsQ  = false;
		accidentalsQ    = false;
		referencesQ     = false;
		terminalsQ      = true;
		breaksQ         = false;
		transpositionsQ = false;
	}

	if (getBoolean("do-not-remove-empty-transpositions")) { transpositionsQ = false; }
	if (getBoolean("only-remove-empty-transpositions")) {
		abbreviationsQ  = false;
		accidentalsQ    = false;
		referencesQ     = false;
		terminalsQ      = false;
		breaksQ         = false;
		transpositionsQ = true;
	}

	if (abbreviationsQ)  { fixInstrumentAbbreviations(infile); }
	if (accidentalsQ)    { fixEditorialAccidentals(infile); }
	if (referencesQ)     { addBibliographicRecords(infile); }
	if (terminalsQ)      { addTerminalLongs(infile); }
	if (breaksQ)         { deleteBreaks(infile); }
	if (transpositionsQ) { deleteDummyTranspositions(infile); }

	adjustSystemDecoration(infile);

	// Input lyrics may contain "=" signs which are to be converted into
	// spaces in **text data, and into elisions when displaying with verovio.
	Tool_shed shed;
	vector<string> argv;
	argv.push_back("shed");
	argv.push_back("-x");     // only apply to **text spines
	argv.push_back("text");
	argv.push_back("-e");
	argv.push_back("s/=/ /g");
	shed.process(argv);
	shed.run(infile);
}



//////////////////////////////
//
// Tool_tassoize::adjustSystemDecoration --
//    !!!system-decoration: [(s1)(s2)(s3)(s4)]
// to:
//    !!!system-decoration: [*]
//

void Tool_tassoize::adjustSystemDecoration(HumdrumFile& infile) {
	for (int i=infile.getLineCount() - 1; i>=0; i--) {
		if (!infile[i].isReference()) {
			continue;
		}
		HTp token = infile.token(i, 0);
		if (token->compare(0, 21, "!!!system-decoration:") == 0) {
			token->setText("!!!system-decoration: [*]");
			break;
		}
	}
}



//////////////////////////////
//
// Tool_tassoize::deleteDummyTranspositions -- Somehow empty
//    transpositions that go to the same pitch can appear in the
//    MusicXML data, so remove them here.  Example:
// 		*Trd0c0
//

void Tool_tassoize::deleteDummyTranspositions(HumdrumFile& infile) {
	std::vector<int> ldel;
	for (int i=0; i<infile.getLineCount(); i++) {
		if (!infile[i].hasSpines()) {
			continue;
		}
		if (!infile[i].isInterpretation()) {
			continue;
		}
		bool empty = true;
		for (int j=0; j<infile[i].getFieldCount(); j++) {
			HTp token = infile.token(i, j);
			if (*token == "*") {
				continue;
			}
			if (!token->isKern()) {
				empty = false;
				continue;
			}
			if (*token == "*Trd0c0") {
				token->setText("*");
			} else {
				empty = false;
			}
		}
		if (empty) {
			ldel.push_back(i);
		}
	}

	if (ldel.size() == 1) {
		infile.deleteLine(ldel[0]);
	} else if (ldel.size() > 1) {
		cerr << "Warning: multiple transposition lines, not deleting them" << endl;
	}

}


//////////////////////////////
//
// Tool_tassoize::fixEditorialAccidentals -- checkDataLine() does
//       all of the work for this function, which only manages
//       key signature and barline processing.
//    Rules for accidentals in Tasso in Music Project:
//    (1) Only note accidentals printed in the source editions
//        are displayed as regular accidentals.  These accidentals
//        are postfixed with an "X" in the **kern data.
//    (2) Editorial accidentals are given an "i" marker but not
//        a "X" marker in the **kern data.  This editorial accidental
//        is displayed above the note.
//    This algorithm makes adjustments to the input data because
//    Sibelius will drop editorial information after the frist
//    editorial accidental on that pitch in the measure.
//    (3) If a note is the same pitch as a previous note in the
//        measure and the previous note has an editorial accidental,
//        then make the note an editorial note.  However, if the
//        accidental state of the note matches the key-signature,
//        then do not add an editorial accidental, and there will be
//        no accidental displayed on the note.  In that case, add a "y"
//        after the accidental to indicate that it is interpreted
//        and not visible in the original score.
//

void Tool_tassoize::fixEditorialAccidentals(HumdrumFile& infile) {
	m_pstates.resize(infile.getMaxTrack() + 1);
	m_estates.resize(infile.getMaxTrack() + 1);
	m_kstates.resize(infile.getMaxTrack() + 1);

	for (int i=0; i<(int)m_pstates.size(); i++) {
		m_pstates[i].resize(70);
		fill(m_pstates[i].begin(), m_pstates[i].end(), 0);
		m_kstates[i].resize(70);
		fill(m_kstates[i].begin(), m_kstates[i].end(), 0);
		m_estates[i].resize(70);
		fill(m_estates[i].begin(), m_estates[i].end(), false);
	}

	for (int i=0; i<infile.getLineCount(); i++) {
		if (infile[i].isInterpretation()) {
			updateKeySignatures(infile, i);
			continue;
		} else if (infile[i].isBarline()) {
			clearStates();
			continue;
		} else if (infile[i].isData()) {
			checkDataLine(infile, i);
		}
	}
}



//////////////////////////////
//
// Tool_tassoize::addTerminalLongs -- Convert all last notes to terminal longs
//    Also probably add terminal longs before double barlines as in JRP.
//

void Tool_tassoize::addTerminalLongs(HumdrumFile& infile) {
	int scount = infile.getStrandCount();
	for (int i=0; i<scount; i++) {
		HTp cur = infile.getStrandEnd(i);
		if (*cur != "*-") {
			continue;
		}
		if (!cur->isKern()) {
			continue;
		}
		while (cur) {
			if (!cur->isData()) {
				cur = cur->getPreviousToken();
				continue;
			}
			if (cur->isNull()) {
				cur = cur->getPreviousToken();
				continue;
			}
			if (cur->isRest()) {
				cur = cur->getPreviousToken();
				continue;
			}
			if (cur->isSecondaryTiedNote()) {
				cur = cur->getPreviousToken();
				continue;
			}
			if (cur->find("l") != std::string::npos) {
				// already marked so do not do it again
				break;
			}
			// mark this note with "l"
			string newtext = *cur;
			newtext += "l";
			cur->setText(newtext);
			break;
		}
	}
}



//////////////////////////////
//
// Tool_tassoize::fixInstrumentAbbreviations --
//

void Tool_tassoize::fixInstrumentAbbreviations(HumdrumFile& infile) {
	int iline = -1;
	int aline = -1;

	std::vector<HTp> kerns = infile.getKernSpineStartList();
	if (kerns.empty()) {
		return;
	}

	HTp cur = kerns[0];
	while (cur) {
		if (cur->isData()) {
			break;
		}
		if (cur->compare(0, 3, "*I\"") == 0) {
			iline = cur->getLineIndex();
		} else if (cur->compare(0, 3, "*I'") == 0) {
			aline = cur->getLineIndex();
		}
		cur = cur->getNextToken();
	}

	if (iline < 0) {
		// no names to create abbreviations for
		return;
	}
	if (aline < 0) {
		// not creating a new abbreviation for now
		// (could add later).
		return;
	}
	if (infile[iline].getFieldCount() != infile[aline].getFieldCount()) {
		// no spine splitting between the two lines.
		return;
	}
	// Maybe also require them to be adjacent to each other.
	HumRegex hre;
	for (int j=0; j<(int)infile[iline].getFieldCount(); j++) {
		if (!infile.token(iline, j)->isKern()) {
			continue;
		}
		if (!hre.search(*infile.token(iline, j), "([A-Za-z][A-Za-z .0-9]+)")) {
			continue;
		}
		string name = hre.getMatch(1);
		string abbr = "*I'";
		if (name == "Basso Continuo") {
			abbr += "BC";
		} else if (name == "Basso continuo") {
			abbr += "BC";
		} else if (name == "basso continuo") {
			abbr += "BC";
		} else {
			abbr += toupper(name[0]);
		}
		// check for numbers after the end of the name and add to abbreviation
		infile.token(aline, j)->setText(abbr);
	}
}



//////////////////////////////
//
// Tool_tassoize::deleteBreaks --
//

void Tool_tassoize::deleteBreaks(HumdrumFile& infile) {
	HumRegex hre;
	for (int i=infile.getLineCount()-1; i>= 0; i--) {
		if (!infile[i].isGlobalComment()) {
			continue;
		}
		if (hre.search(*infile.token(i, 0), "linebreak\\s*:\\s*original")) {
			infile.deleteLine(i);
		}
		else if (hre.search(*infile.token(i, 0), "pagebreak\\s*:\\s*original")) {
			infile.deleteLine(i);
		}
	}
}



////////////////////////////////
//
// Tool_tassoize::addBibliographicRecords --
//
// !!!COM:
// !!!CDT:
// !!!PTL:
// !!!PPP:
// !!!PPR:
// !!!PDT:
// !!!RISM:
// !!!rime:
// !!!OTL:
// !!!AGN: Madrigal
// !!!SCT:
// !!!SCA:
// !!!voices:
//
// At end:
// !!!RDF**kern: l = terminal long
// !!!RDF**kern: i = editorial accidental
// !!!ENC: Emiliano Ricciardi
// !!!END:
// !!!EED: Emiliano Ricciardi
// !!!EEV: $DATE
// !!!YEC: Copyright 2018 Emiliano Ricciardi, All Rights Reserved
// !!!ONB: Translated from MusicXML and edited on $DATE by Craig Sapp
//

void Tool_tassoize::addBibliographicRecords(HumdrumFile& infile) {
	std::vector<HLp> refinfo = infile.getReferenceRecords();
	std::map<string, HLp> refs;
	for (int i=0; i<(int)refinfo.size(); i++) {
		string key = refinfo[i]->getReferenceKey();
		refs[key] = refinfo[i];
	}

	// header records
	if (refs.find("voices") == refs.end()) {
		if (infile.token(0, 0)->find("!!!OTL") != std::string::npos) {
			infile.insertLine(1, "!!!voices:");
		} else {
			infile.insertLine(0, "!!!voices:");
		}
	}
	if (refs.find("SCA") == refs.end()) {
		if (infile.token(0, 0)->find("!!!OTL") != std::string::npos) {
			infile.insertLine(1, "!!!SCA:");
		} else {
			infile.insertLine(0, "!!!SCA:");
		}
	}
	if (refs.find("SCT") == refs.end()) {
		if (infile.token(0, 0)->find("!!!OTL") != std::string::npos) {
			infile.insertLine(1, "!!!SCT:");
		} else {
			infile.insertLine(0, "!!!SCT:");
		}
	}
	if (refs.find("AGN") == refs.end()) {
		if (infile.token(0, 0)->find("!!!OTL") != std::string::npos) {
			infile.insertLine(1, "!!!AGN: Madrigal");
		} else {
			infile.insertLine(0, "!!!AGN: Madrigal");
		}
	}

	if (refs.find("OTL") == refs.end()) {
		infile.insertLine(0, "!!!OTL:");
	}
	if (refs.find("rime") == refs.end()) {
		infile.insertLine(0, "!!!rime:");
	}
	if (refs.find("RISM") == refs.end()) {
		infile.insertLine(0, "!!!RISM:");
	}
	if (refs.find("PDT") == refs.end()) {
		infile.insertLine(0, "!!!PDT:");
	}
	if (refs.find("PPR") == refs.end()) {
		infile.insertLine(0, "!!!PPR:");
	}
	if (refs.find("PPP") == refs.end()) {
		infile.insertLine(0, "!!!PPP:");
	}
	if (refs.find("PTL") == refs.end()) {
		infile.insertLine(0, "!!!PTL:");
	}
	if (refs.find("CDT") == refs.end()) {
		infile.insertLine(0, "!!!CDT:");
	}
	if (refs.find("COM") == refs.end()) {
		infile.insertLine(0, "!!!COM:");
	}

	// trailer records
	bool foundi = false;
	bool foundl = false;
	for (int i=0; i<infile.getLineCount(); i++) {
		if (!infile[i].isReference()) {
			continue;
		}
		if (infile.token(i, 0)->find("!!!RDF**kern:") == std::string::npos) {
			continue;
		}
		if (infile.token(i, 0)->find("terminal long") != std::string::npos) {
			foundl = true;
		} else if (infile.token(i, 0)->find("editorial accidental") != std::string::npos) {
			foundi = true;
		}
	}
	if (!foundi) {
		infile.appendLine("!!!RDF**kern: i = editorial accidental");
	}
	if (!foundl) {
		infile.appendLine("!!!RDF**kern: l = terminal long");
	}

	if (refs.find("ENC") == refs.end()) {
		infile.appendLine("!!!ENC: Emiliano Ricciardi");
	}
	if (refs.find("END") == refs.end()) {
		infile.appendLine("!!!END:");
	}
	if (refs.find("EED") == refs.end()) {
		infile.appendLine("!!!EED: Emiliano Ricciardi");
	}
	if (refs.find("EEV") == refs.end()) {
		string date = getDate();
		string line = "!!!EEV: " + date;
		infile.appendLine(line);
	}
	if (refs.find("YEC") == refs.end()) {
		infile.appendLine("!!!YEC: Copyright 2018 Emiliano Ricciardi, All Rights Reserved");
	}
	if (refs.find("ONB") == refs.end()) {
		string date = getDate();
		string line = "!!!ONB: Translated from MusicXML on " + date;
		infile.appendLine(line);
	}

}



////////////////////////////////
//
// Tool_tassoize::checkDataLine --
//

void Tool_tassoize::checkDataLine(HumdrumFile& infile, int lineindex) {
	HumdrumLine& line = infile[lineindex];

	HumRegex hre;
	HTp token;
	bool haseditQ;
	int base7;
	int accid;
	int track;
	bool removeQ;
	for (int i=0; i<line.getFieldCount(); i++) {
		token = line.token(i);
		track = token->getTrack();
		if (!token->isKern()) {
			continue;
		}
		if (token->isNull()) {
			continue;
		}
		if (token->isRest()) {
			continue;
		}
		if (token->isSecondaryTiedNote()) {
			continue;
		}

		base7 = Convert::kernToBase7(token);
		accid = Convert::kernToAccidentalCount(token);
		haseditQ = false;
		removeQ = false;

		// Hard-wired to "i" as editorial accidental marker
		if (token->find("ni") != string::npos) {
			haseditQ = true;
		} else if (token->find("-i") != string::npos) {
			haseditQ = true;
		} else if (token->find("#i") != string::npos) {
			haseditQ = true;
		} else if (token->find("nXi") != string::npos) {
			haseditQ = true;
			removeQ = true;
		} else if (token->find("-Xi") != string::npos) {
			haseditQ = true;
			removeQ = true;
		} else if (token->find("#Xi") != string::npos) {
			haseditQ = true;
			removeQ = true;
		}

		if (removeQ) {
			string temp = *token;
			hre.replaceDestructive(temp, "", "X");
			token->setText(temp);
		}

		bool explicitQ = false;
		if (token->find("#X") != string::npos) {
			explicitQ = true;
		} else if (token->find("-X") != string::npos) {
			explicitQ = true;
		} else if (token->find("nX") != string::npos) {
			explicitQ = true;
		} else if (token->find("n") != string::npos) {
			// add an explicit accidental marker
			explicitQ = true;
			string text = *token;
			hre.replaceDestructive(text, "nX", "n");
			token->setText(text);
		}

		if (haseditQ) {
			// Store new editorial pitch state.
			m_estates.at(track).at(base7) = true;
			m_pstates.at(track).at(base7) = accid;
			continue;
		}

		if (explicitQ) {
			// No need to make editorial since it is visible.
			m_estates.at(track).at(base7) = false;
			m_pstates.at(track).at(base7) = accid;
			continue;
		}

		if (accid == m_kstates.at(track).at(base7)) {
			// 	!m_estates.at(track).at(base7)) {
			// add !m_estates.at(track).at(base) as a condition if
			// you want editorial accidentals to be added to return the
			// note to the accidental in the key.
			//
			// The accidental matches the key-signature state,
			// so it should not be made editorial eventhough
			// it is not visible.
			m_pstates.at(track).at(base7) = accid;

			// Add a "y" marker of there is an interpreted accidental
			// state (flat or sharp) that is part of the key signature.
			int hasaccid = false;
			if (token->find("#") != std::string::npos) {
				hasaccid = true;
			} else if (token->find("-") != std::string::npos) {
				hasaccid = true;
			}
			int hashide = false;
			if (token->find("-y") != std::string::npos) {
				hashide = true;
			}
			else if (token->find("#y") != std::string::npos) {
				hashide = true;
			}
			if (hasaccid && !hashide) {
				string text = *token;
				hre.replaceDestructive(text, "#y", "#");
				hre.replaceDestructive(text, "-y", "-");
				token->setText(text);
			}

			continue;
		}

		// At this point the previous note with this pitch class
		// had an editorial accidental, and this note also has the
		// same accidental, or there was a previous visual accidental
		// outside of the key signature that will cause this note to have
		// an editorial accidental mark applied (Sibelius will drop
		// secondary editorial accidentals in a measure when exporting,
		// MusicXML, which is why this function is needed).

		m_estates[track][base7] = true;
		m_pstates[track][base7] = accid;

		string text = token->getText();
		string output = "";
		bool foundQ = false;
		for (int j=0; j<(int)text.size(); j++) {
			if (text[j] == 'n') {
				output += "ni";
				foundQ = true;
			} else if (text[j] == '#') {
				output += "#i";
				foundQ = true;
			} else if (text[j] == '-') {
				output += "-i";
				foundQ = true;
			} else {
				output += text[j];
			}
		}

		if (foundQ) {
			token->setText(output);
			continue;
		}

		// The note is natural, but has no natural sign.
		// add the natural sign and editorial mark.
		for (int j=(int)output.size()-1; j>=0; j--) {
			if ((tolower(output[j]) >= 'a') && (tolower(output[j]) <= 'g')) {
				output.insert(j+1, "ni");
				break;
			}
		}
		token->setText(output);
	}
}



////////////////////////////////
//
// Tool_tassoize::updateKeySignatures -- Fill in the accidental
//    states for each diatonic pitch.
//

void Tool_tassoize::updateKeySignatures(HumdrumFile& infile, int lineindex) {
	HumdrumLine& line = infile[lineindex];
	int track;
	for (int i=0; i<line.getFieldCount(); i++) {
		if (!line.token(i)->isKeySignature()) {
			continue;
		}
		HTp token = line.token(i);
		track = token->getTrack();
		string text = token->getText();
		fill(m_kstates[track].begin(), m_kstates[track].end(), 0);
		for (int j=3; j<(int)text.size()-1; j++) {
			if (text[j] == ']') {
				break;
			}
			switch (text[j]) {
				case 'a': case 'A':
					switch (text[j+1]) {
						case '#': m_kstates[track][5] = +1;
						break;
						case '-': m_kstates[track][5] = -1;
						break;
					}
					break;

				case 'b': case 'B':
					switch (text[j+1]) {
						case '#': m_kstates[track][6] = +1;
						break;
						case '-': m_kstates[track][6] = -1;
						break;
					}
					break;

				case 'c': case 'C':
					switch (text[j+1]) {
						case '#': m_kstates[track][0] = +1;
						break;
						case '-': m_kstates[track][0] = -1;
						break;
					}
					break;

				case 'd': case 'D':
					switch (text[j+1]) {
						case '#': m_kstates[track][1] = +1;
						break;
						case '-': m_kstates[track][1] = -1;
						break;
					}
					break;

				case 'e': case 'E':
					switch (text[j+1]) {
						case '#': m_kstates[track][2] = +1;
						break;
						case '-': m_kstates[track][2] = -1;
						break;
					}
					break;

				case 'f': case 'F':
					switch (text[j+1]) {
						case '#': m_kstates[track][3] = +1;
						break;
						case '-': m_kstates[track][3] = -1;
						break;
					}
					break;

				case 'g': case 'G':
					switch (text[j+1]) {
						case '#': m_kstates[track][4] = +1;
						break;
						case '-': m_kstates[track][4] = -1;
						break;
					}
					break;
			}
			for (int j=0; j<7; j++) {
				if (m_kstates[track][j] == 0) {
					continue;
				}
				for (int k=1; k<10; k++) {
					m_kstates[track][j+k*7] = m_kstates[track][j];
				}
			}
		}
	}

	// initialize m_pstates with contents of m_kstates
	for (int i=0; i<(int)m_kstates.size(); i++) {
		for (int j=0; j<(int)m_kstates[i].size(); j++) {
			m_pstates[i][j] = m_kstates[i][j];
		}
	}

}



////////////////////////////////
//
// Tool_tassoize::clearStates --
//

void Tool_tassoize::clearStates(void) {
	for (int i=0; i<(int)m_pstates.size(); i++) {
		fill(m_pstates[i].begin(), m_pstates[i].end(), 0);
	}
	for (int i=0; i<(int)m_estates.size(); i++) {
		fill(m_estates[i].begin(), m_estates[i].end(), false);
	}
}


//////////////////////////////
//
// Tool_tassoize::getDate --
//

string Tool_tassoize::getDate(void) {
	time_t t = time(NULL);
	tm* timeptr = localtime(&t);
	stringstream ss;
	int year = timeptr->tm_year + 1900;
	int month = timeptr->tm_mon + 1;
	int day = timeptr->tm_mday;
	ss << year << "/";
	if (month < 10) {
		ss << "0";
	}
	ss << month << "/";
	if (day < 10) {
		ss << "0";
	}
	ss << day;
	return ss.str();
}





/////////////////////////////////
//
// Tool_textdur::Tool_textdur -- Set the recognized options for the tool.
//

Tool_textdur::Tool_textdur(void) {
	// add command-line options here
	define("a|analysis=b",   "calculate and display analyses");
	define("m|melisma=b",    "count number of notes for each syllable");
	define("d|duration=b",   "duration of each syllable");
	define("i|interleave=b", "preserve original text, and place analyses below text");
}



/////////////////////////////////
//
// Tool_textdur::run -- Do the main work of the tool.
//

bool Tool_textdur::run(HumdrumFileSet& infiles) {
	bool status = true;
	for (int i=0; i<infiles.getCount(); i++) {
		status &= run(infiles[i]);
	}
	return status;
}


bool Tool_textdur::run(const string& indata, ostream& out) {
	HumdrumFile infile(indata);
	bool status = run(infile);
	if (hasAnyText()) {
		getAllText(out);
	} else {
		out << infile;
	}
	return status;
}


bool Tool_textdur::run(HumdrumFile& infile, ostream& out) {
	bool status = run(infile);
	if (hasAnyText()) {
		getAllText(out);
	} else {
		out << infile;
	}
	return status;
}


bool Tool_textdur::run(HumdrumFile& infile) {
	initialize();
	processFile(infile);
	return true;
}


//////////////////////////////
//
// Tool_textdur::initialize --
//

void Tool_textdur::initialize(void) {
	m_analysisQ = getBoolean("analysis");
	m_melismaQ = getBoolean("melisma");
	if (m_melismaQ) {
		m_durationQ = false;
	}
	if (getBoolean("duration")) {
		m_durationQ = true;
	}
	m_interleaveQ = getBoolean("interleave");
}



//////////////////////////////
//
// Tool_textdur::processFile --
//

void Tool_textdur::processFile(HumdrumFile& infile) {
	getTextSpineStarts(infile, m_textStarts);
	fillInstrumentNameInfo();

	if (m_textStarts.empty()) {
		return;
	}

	m_syllables.clear();
	m_syllables.resize(m_textStarts.size());

	m_durations.clear();
	m_durations.resize(m_textStarts.size());

	m_melismas.clear();
	m_melismas.resize(m_textStarts.size());

	for (int i=0; i<(int)m_textStarts.size(); i++) {
		processTextSpine(m_textStarts, i);
	}

	if (!m_interleaveQ) {
		for (int i=0; i<(int)m_textStarts.size(); i++) {
			string text = *m_textStarts[i];
			if (m_melismaQ) {
				text += "-melisma";
			} else {
				text += "-duration";
			}
			m_textStarts[i]->setText(text);
		}
	}

	if (!m_interleaveQ) {
		if (m_melismaQ) {
			printMelismas(infile);
		} else {
			printDurations(infile);
		}
	} else {
		printInterleaved(infile);
	}

	if (m_analysisQ) {
		printAnalysis();
	}

}



//////////////////////////////
//
// Tool_textdur::fillInstrumentNameInfo --
//

void Tool_textdur::fillInstrumentNameInfo(void) {
	m_columnName.clear();
	m_columnName.resize(m_textStarts.size());
	for (int i=0; i<(int)m_columnName.size(); i++) {
		m_columnName[i] = getColumnName(m_textStarts[i]);
	}
}



//////////////////////////////
//
// Tool_textdur::getColumnName --
//

string Tool_textdur::getColumnName(HTp token) {
	HTp kerntok = getTandemKernToken(token);
	if (!kerntok) {
		return "unknown";
	}
	int track = kerntok->getTrack();
	string output = "Track ";
	output += to_string(track);

	HTp current = kerntok->getNextToken();
	HumRegex hre;
	while (current) {
		if (current->isData()) {
			break;
		}
		if (hre.search(current, "^\\*I\\\"(.*)\\s*$")) {
			output = hre.getMatch(1);
			break;
		}
		current = current->getNextToken();
	}
	return output;
}




//////////////////////////////
//
// Tool_textdur::printAnalysis --
//

void Tool_textdur::printAnalysis(void) {
	if (m_melismaQ) {
		printMelismaAverage();
	}
	if (m_durationQ) {
		printDurationAverage();
	}
	printHtmlContent();
}



//////////////////////////////
//
// Tool_textdur::printHtmlContent --
//

void Tool_textdur::printHtmlContent(void) {

	m_humdrum_text << "!!@@BEGIN: PREHTML" << endl;
	m_humdrum_text << "!!@CONTENT: " << endl;
	m_humdrum_text << "!! <h1> Syllable length analysis </h1>" << endl;

	m_humdrum_text << "!! <details open> <summary> " << endl;
	m_humdrum_text << "!! <h3> Number of syllables: @{TOOL-textdur-total-syllables} </h3>" << endl;
	m_humdrum_text << "!! </summary>" << endl;
	m_humdrum_text << "!! <table style='margin-left:50px;width:400px;'> " << endl;

	int sum = 0;
	for (int i=0; i<(int)m_melismas.size(); i++) {
		sum += (int)m_melismas[i].size();
	}

	for (int i=(int)m_melismas.size() - 1; i>=0; i--) {
		double percent = 1.0 * m_melismas.at(i).size() / sum;
		percent = int(percent * 10000.0 + 0.5) / 100.0;
		m_humdrum_text << "!! <tr><td> " << m_columnName.at(i) << "</td>"
		               << "<td style='padding-left:20px; text-align:right;'>" << m_melismas.at(i).size()
		               << "</td><td style='width:100%;'> (" << percent << "%) </td></tr>" << endl;
	}
	m_humdrum_text << "!! </table> " << endl;
	m_humdrum_text << "!! </details>" << endl;

	if (m_melismaQ) {
		m_humdrum_text << "!! <h3> Average syllable note length: @{TOOL-textdur-average-notes-per-syllable} </h3>" << endl;
		m_humdrum_text << "!! <div style='margin-left:50px'></div>" << endl;
		printMelismaHtmlHistogram();
		m_humdrum_text << "!! </div>" << endl;
	}
	if (m_durationQ) {
		m_humdrum_text << "!! <div style='height:50px;'></div>" << endl;
		m_humdrum_text << "!! <h3> Average syllable duration: @{TOOL-textdur-average-syllable-duration} quarter notes </h3>" << endl;
		printDurationHtmlHistogram();
	}
	m_humdrum_text << "!!@@END: PREHTML" << endl;

}


//////////////////////////////
//
// Tool_textdur::printDurationHtmlHistogram --
//

void Tool_textdur::printDurationHtmlHistogram(void) {

	map<HumNum, int> durinfo;
	double total = 0;
	for (int i=0; i<(int)m_durations.size(); i++) {
		// -1 on next line for *- terminator?
		for (int j=0; j<(int)m_durations[i].size() -1 ; j++) {
			HumNum dur = m_durations[i][j];
			int value = durinfo[dur];
			durinfo[dur] = value + 1;
			total += 1.0;
		}
	}

	double maxlen = 0;
	for (auto it = durinfo.begin(); it != durinfo.end(); it++) {
		if (it->second > maxlen) {
			maxlen = it->second;
		}
	}

	m_humdrum_text << "!! <table class='duration-histogram'>" << endl;
	m_humdrum_text << "!! <tr> <th style='white-space:pre; text-align:center;'> Duration (quarter notes)</th> <th style='padding-left:10px; width:100%;'> Syllable count </th> </tr> " << endl;
	stringstream value;
	for (auto it = durinfo.begin(); it != durinfo.end(); it++) {
		double length = 1.0 * it->second / maxlen * 400;
		double percent = 1.0 * it->second / total * 100.0;
		percent = (int)(percent * 100.0 + 0.5) / 100.0;
		value.str("");
		it->first.printMixedFraction(value, "+");
		m_humdrum_text << "!! <tr><td style='padding-left:100px;'> " << value.str() << "</td><td style='padding-left:10px;white-space:pre;'>";
		m_humdrum_text << " <span style='display:inline-block;background-color:black;height:100%;width:" << length << "px;'>&nbsp;</span>";
		m_humdrum_text << "&nbsp;" << it->second << "&nbsp;(" << percent << "%)</td></tr>" << endl;
	}
	m_humdrum_text << "!! </table>" << endl;
}






//////////////////////////////
//
// Tool_textdur::printMelismaHtmlHistogram --
//    default value = -1 (meaning all voices);
//

void Tool_textdur::printMelismaHtmlHistogram(void) {

	map<int, int> melinfo;
	double total = 0;
	for (int i=0; i<(int)m_melismas.size(); i++) {
		// -1 on next line for *- terminator?
		for (int j=0; j<(int)m_melismas[i].size() -1 ; j++) {
			int count = m_melismas[i][j];
			int value = melinfo[count];
			melinfo[count] = value + 1;
			total += 1.0;
		}
	}

	double maxlen = 0;
	for (auto it = melinfo.begin(); it != melinfo.end(); it++) {
		if (it->second > maxlen) {
			maxlen = it->second;
		}
	}

	m_humdrum_text << "!! <table class='melisma-histogram'>" << endl;
	m_humdrum_text << "!! <tr> <th style='text-align:center;'> Syllable&nbsp;notes </th> <th style='padding-left:10px;'> Syllable count </th> </tr> " << endl;
	for (auto it = melinfo.begin(); it != melinfo.end(); it++) {
		double length = 1.0 * it->second / maxlen * 400;
		double percent = 1.0 * it->second / total * 100.0;
		percent = (int)(percent * 100.0 + 0.5) / 100.0;
		m_humdrum_text << "!! <tr><td style='text-align:center;'> " << it->first << "</td><td style='padding-left:10px;white-space:pre;'>";
		m_humdrum_text << " <span style='display:inline-block;background-color:black;height:100%;width:" << length << "px;'>&nbsp;</span>";
		m_humdrum_text << "&nbsp;" << it->second << "&nbsp;(" << percent << "%)</td></tr>" << endl;
	}
	m_humdrum_text << "!! </table>" << endl;
}


// Print individual histograms for each voice:

void printMelismaHtmlHistogram(int index, int maxVal) {

/*
	map<int, int> melinfo;
	double total = maxVal;
	double maxlen = maxVal;

// ggg

	m_humdrum_text << "!! <table class='melisma-histogram-" << index << "'>" << endl;
	m_humdrum_text << "!! <tr> <th style='text-align:center;'> Syllable&nbsp;notes </th> <th style='padding-left:10px;'> Syllable count </th> </tr> " << endl;
	for (auto it = melinfo.begin(); it != melinfo.end(); it++) {
		double length = 1.0 * it->second / maxlen * 400;
		double percent = 1.0 * it->second / total * 100.0;
		percent = (int)(percent * 100.0 + 0.5) / 100.0;
		m_humdrum_text << "!! <tr><td style='text-align:center;'> " << it->first << "</td><td style='padding-left:10px;white-space:pre;'>";
		m_humdrum_text << " <span style='display:inline-block;background-color:black;height:100%;width:" << length << "px;'>&nbsp;</span>";
		m_humdrum_text << "&nbsp;" << it->second << "&nbsp;(" << percent << "%)</td></tr>" << endl;
	}
	m_humdrum_text << "!! </table>" << endl;
}

*/

}



//////////////////////////////
//
// printMelismaAverage --
//

void Tool_textdur::printMelismaAverage() {
	double sum = 0.0;
	int counter = 0;
	for (int i=0; i<(int)m_melismas.size(); i++) {
		// -1 on next line for *- terminator?
		for (int j=0; j<(int)m_melismas[i].size() - 1; j++) {
			sum += m_melismas.at(i).at(j);
			counter++;
		}
	}
	if (!counter) {
		return;
	}
	double average = sum / counter;
	average = int(average * 100.0 + 0.5) / 100.0;
	m_humdrum_text << "!!!TOOL-textdur-average-notes-per-syllable: " << average << endl;
	m_humdrum_text << "!!!TOOL-textdur-total-syllables: " << counter << endl;
}



//////////////////////////////
//
// printDurationAverage --
//

void Tool_textdur::printDurationAverage(void) {
	HumNum sum = 0;
	int counter = 0;
	for (int i=0; i<(int)m_durations.size(); i++) {
		// -1 on next line for *- terminator?
		for (int j=0; j<(int)m_durations[i].size() - 1; j++) {
			sum += m_durations.at(i).at(j);
			counter++;
		}
	}
	if (!counter) {
		return;
	}
	double average = sum.getFloat() / counter;
	average = int(average * 100.0 + 0.5) / 100.0;
	m_humdrum_text << "!!!TOOL-textdur-average-syllable-duration: " << average << endl;
}


//////////////////////////////
//
// Tool_textdur::printInterleaved --
//

void Tool_textdur::printInterleaved(HumdrumFile& infile) {
	vector<bool> textTrack(infile.getMaxTrack() + 1, false);
	for (int i=0; i<(int)m_textStarts.size(); i++) {
		int track = m_textStarts[i]->getTrack();
		textTrack.at(track) = true;
	}


	for (int i=0; i<infile.getLineCount(); i++) {
		if (!infile[i].hasSpines()) {
			m_humdrum_text << infile[i] << endl;
			continue;
		}
		printInterleavedLine(infile[i], textTrack);
	}
}



//////////////////////////////
//
// Tool_textdur::printInterleavedLine --
//

void Tool_textdur::printInterleavedLine(HumdrumLine& line, vector<bool>& textTrack) {
	for (int i=0; i<line.getFieldCount(); i++) {
		HTp token = line.token(i);
		int track = token->getTrack();
		m_humdrum_text << token;
		if (textTrack.at(track)) {
			printTokenAnalysis(token);
		}
		if (i < line.getFieldCount() - 1) {
			m_humdrum_text << "\t";
		}
	}
	m_humdrum_text << "\n";
}



//////////////////////////////
//
// Tool_textdur::printTokenAnalysis --
//

void Tool_textdur::printTokenAnalysis(HTp token) {
	if (token->compare(0, 2, "**") == 0) {
		if (m_melismaQ) {
			m_humdrum_text << "\t**text-melisma";
		}
		if (m_durationQ) {
			m_humdrum_text << "\t**text-duration";
		}
		return;
	}

	if (*token == "*-") {
		if (m_melismaQ) {
			m_humdrum_text << "\t*-";
		}
		if (m_durationQ) {
			m_humdrum_text << "\t*-";
		}
		return;
	}

	if (token->compare(0, 1, "*") == 0) {

		HTp tandem = getTandemKernToken(token);
		if (tandem->compare(0, 3, "*I\"") == 0) {
			if (m_melismaQ) {
				m_humdrum_text << "\t*v:mel:";
			}
			if (m_durationQ) {
				m_humdrum_text << "\t*v:dur:";
			}
			return;
		}

		if (m_melismaQ) {
			m_humdrum_text << "\t*";
		}
		if (m_durationQ) {
			m_humdrum_text << "\t*";
		}
		return;
	}

	if (token->compare(0, 1, "!") == 0) {
		if (m_melismaQ) {
			m_humdrum_text << "\t!";
		}
		if (m_durationQ) {
			m_humdrum_text << "\t!";
		}
		return;
	}

	if (token->compare(0, 1, "=") == 0) {
		if (m_melismaQ) {
			m_humdrum_text << "\t" << token;
		}
		if (m_durationQ) {
			m_humdrum_text << "\t" << token;
		}
		return;
	}

	if (*token == ".") {
		if (m_melismaQ) {
			m_humdrum_text << "\t.";
		}
		if (m_durationQ) {
			m_humdrum_text << "\t.";
		}
		return;
	}

	if (!token->isData()) {
		cerr << "WARNING: DATA TOKEN IS NOT DATA: " << token << endl;
		return;
	}

	int index = -1;
	if (token->isDefined("auto", "index")) {
		index = token->getValueInt("auto", "index");
	} else {
		if (m_melismaQ) {
			m_humdrum_text << "\t.";
		}
		if (m_durationQ) {
			m_humdrum_text << "\t.";
		}
		return;
	}

	// Have data analysis to print:

	int track = token->getTrack();
	int column = m_track2column.at(track);

	if (m_melismaQ) {
		int melisma = m_melismas.at(column).at(index);
		m_humdrum_text << "\t" << melisma;
	}
	if (m_durationQ) {
		HumNum duration = m_durations.at(column).at(index);
		m_humdrum_text << "\t" << duration;
	}
}



//////////////////////////////
//
// Tool_textdur::printMelismas --
//

void Tool_textdur::printMelismas(HumdrumFile& infile) {
	// replace text with melisma data:
	for (int i=0; i<(int)m_syllables.size(); i++) {
		// -1 on next line for *- placeholder at end of spine.
		for (int j=0; j<(int)m_syllables.at(i).size() - 1; j++) {
			HTp token = m_syllables.at(i).at(j);
			int jj = token->getValueInt("auto", "index");
			string replacement = to_string(m_melismas.at(i).at(jj));
			token->setText(replacement);
		}
	}

	infile.createLinesFromTokens();
	m_humdrum_text << infile;
}



//////////////////////////////
//
// Tool_textdur::printDurations --
//

void Tool_textdur::printDurations(HumdrumFile& infile) {
	// replace text with duration data:
	stringstream replacement;
	for (int i=0; i<(int)m_syllables.size(); i++) {
		// -1 on next line for *- placeholder at end of spine.
		for (int j=0; j<(int)m_syllables.at(i).size() - 1; j++) {
			HTp token = m_syllables.at(i).at(j);
			int jj = token->getValueInt("auto", "index");
			replacement << m_durations.at(i).at(jj);
			token->setText(replacement.str());
			replacement.str("");
		}
	}

	infile.createLinesFromTokens();
	m_humdrum_text << infile;
}



//////////////////////////////
//
// Tool_textdur::getTextSpineStarts --
//

void Tool_textdur::getTextSpineStarts(HumdrumFile& infile, vector<HTp>& starts) {
	starts.clear();
	vector<HTp> allSpineStarts;
	infile.getSpineStartList(allSpineStarts);
	for (int i=0; i<(int)allSpineStarts.size(); i++) {
		HTp token = allSpineStarts.at(i);
		if (*token == "**text") {
			starts.push_back(token);
			token->setValue("auto", "index", i);
		} else if (*token == "**sylba") {
			starts.push_back(token);
			token->setValue("auto", "index", i);
		}
	}

	// Setup m_track2column
	m_track2column.resize(infile.getMaxTrack() + 1);
	fill(m_track2column.begin(), m_track2column.end(), -1);
	for (int i=0; i<(int)starts.size(); i++) {
		int track = starts[i]->getTrack();
		m_track2column.at(track) = i;
	}
}



//////////////////////////////
//
// Tool_textdur::processTextSpine --
//

void Tool_textdur::processTextSpine(vector<HTp>& starts, int index) {
	HTp current = starts.at(index);
	current->getNextToken();
	while (current) {
		if (!current->isData()) {
			if (*current == "*-") {
				// store data terminator (for calculating duration of last note):
				current->setValue("auto", "index", to_string(m_syllables.at(index).size()));
				m_syllables.at(index).push_back(current);
				m_durations.at(index).push_back(-1000);
				m_melismas.at(index).push_back(-1000);
				break;
			}
			current = current->getNextToken();
			continue;
		}

		if (current->isNull()) {
			current = current->getNextToken();
			continue;
		}

		current->setValue("auto", "index", to_string(m_syllables.at(index).size()));
		m_syllables.at(index).push_back(current);
		m_durations.at(index).push_back(-1);        // store dummy duration
		m_melismas.at(index).push_back(-1);         // store dummy melisma


		current = current->getNextToken();
	}

	for (int i=0; i<(int)m_syllables.size(); i++) {
		for (int j=0; j<(int)m_syllables[i].size() - 1; j++) {
			if (m_melismaQ) {
				m_melismas.at(i).at(j) = getMelisma(m_syllables.at(i).at(j), m_syllables.at(i).at(j+1));
			}
			if (m_durationQ) {
				m_durations.at(i).at(j) = getDuration(m_syllables.at(i).at(j), m_syllables.at(i).at(j+1));
			}
		}
	}
}



//////////////////////////////
//
// Tool_textdur::getMelisma --  Not counting syllable starts on secondary tied notes.
//

int Tool_textdur::getMelisma(HTp tok1, HTp tok2) {
	int stopIndex = tok2->getLineIndex();
	HTp current = getTandemKernToken(tok1);
	if (!current) {
		return 0;
	}
	if (current->isNull()) {
		cerr << "Strange case for syllable " << tok1 << " on line " << tok1->getLineNumber();
		cerr << ", field " << tok1->getFieldNumber() <<" which does not start on a note" << endl;
		return 0;
	}
	int cline = current->getLineIndex();
	int counter = 0;
	while (current && cline < stopIndex) {
		if (!current->isData()) {
			current = current->getNextToken();
			if (current) {
				cline = current->getLineIndex();
			}
			continue;
		}
		if (current->isNull()) {
			current = current->getNextToken();
			if (current) {
				cline = current->getLineIndex();
			}
			continue;
		}
		if (current->isNoteAttack()) {
			counter++;
		}
		current = current->getNextToken();
		if (current) {
			cline = current->getLineIndex();
		}
	}

	return counter;
}



//////////////////////////////
//
// Tool_textdur::getTandemKernToken --  Search to the left for the
//    first **kern spine.  Returns NULL if none found.
//

HTp Tool_textdur::getTandemKernToken(HTp token) {
	HTp current = token->getPreviousFieldToken();
	while (current && !current->isKern()) {
		current = current->getPreviousFieldToken();
	}
	return current;
}



//////////////////////////////
//
// Tool_textdur::getDuration --  Not counting rests at end of first syllable.
//

HumNum Tool_textdur::getDuration(HTp tok1, HTp tok2) {
	int startIndex = tok1->getLineIndex();
	HTp current = getTandemKernToken(tok2);
	if (!current) {
		return 0;
	}
	if (current->isNull()) {
		cerr << "Strange case for syllable " << tok1 << " on line " << tok1->getLineNumber();
		cerr << ", field " << tok1->getFieldNumber() <<" which does not start on a note" << endl;
		return 0;
	}
	HTp lastNoteEnd = current;
	current = current->getPreviousToken();
	int cline = current->getLineIndex();
	while (current && cline > startIndex) {
		if (!current->isData()) {
			current = current->getPreviousToken();
			continue;
		}
		if (current->isNull()) {
			current = current->getPreviousToken();
			continue;
		}
		cline = current->getLineIndex();
		if (!current->isRest()) {
			break;
		} else {
			lastNoteEnd = current;
		}
		current = current->getPreviousToken();
	}

	if (!lastNoteEnd) {
		return 0;
	}

	HumNum duration = lastNoteEnd->getDurationFromStart() - tok1->getDurationFromStart();

	return duration;
}





/////////////////////////////////
//
// Tool_thru::Tool_thru -- Set the recognized options for the tool.
//

Tool_thru::Tool_thru(void) {
	define("v|variant|variation=s:", "choose the expansion variant");
	define("l|list=b:",              "print list of labels in file");
	define("k|keep=b:",              "keep variation interpretations");
	define("i|info=b:",              "print info list of labels in file");
	define("r|realization=s:",       "alternate relaization label sequence");
}


/////////////////////////////////
//
// Tool_thru::run -- Do the main work of the tool.
//

bool Tool_thru::run(HumdrumFileSet& infiles) {
	bool status = true;
	for (int i=0; i<infiles.getCount(); i++) {
		status &= run(infiles[i]);
	}
	return status;
}


bool Tool_thru::run(const string& indata, ostream& out) {
	HumdrumFile infile(indata);
	bool status = run(infile);
	if (hasAnyText()) {
		getAllText(out);
	} else {
		out << infile;
	}
	return status;
}


bool Tool_thru::run(HumdrumFile& infile, ostream& out) {
	bool status = run(infile);
	if (hasAnyText()) {
		getAllText(out);
	} else {
		out << infile;
	}
	return status;
}


bool Tool_thru::run(HumdrumFile& infile) {
	initialize();
	processFile(infile);
	return true;
}



//////////////////////////////
//
// Tool_thru::initialize --  Initializations that only have to be done once
//    for all HumdrumFile segments.
//

void Tool_thru::initialize(void) {
	m_variation   = getString("variation");
	m_realization = getString("realization");
	m_listQ       = getBoolean("list");
	m_infoQ       = getBoolean("info");
	m_keepQ       = getBoolean("keep");
}



//////////////////////////////
//
// Tool_thru::processFile --
//

void Tool_thru::processFile(HumdrumFile& infile) {
	if (m_listQ) {
		printLabelList(infile);
		return;
	} else if (m_infoQ) {
		printLabelInfo(infile);
		return;
	}
	processData(infile);

	// analyze the input file according to command-line options
	// infiles[i].printNonemptySegmentLabel(m_humdrum_text);
}



//////////////////////////////
//
// Tool_thru::printLabelList -- print a list of the thru labels.
//

void Tool_thru::printLabelList(HumdrumFile& infile) {
	for (int i=0; i<infile.getLineCount(); i++) {
		if (!infile[i].isInterpretation()) {
			continue;
		}
		HTp token = infile.token(i, 0);
		if (token->compare(0, 2, "*>") != 0) {
			continue;   // ignore non-labels
		}
		//if (token->find('[') != NULL) {
		//   continue;   // ignore realizations
		//}
		m_humdrum_text << token->substr(2);
		m_humdrum_text << '\n';
	}
}



//////////////////////////////
//
// Tool_thru::printLabelInfo -- print a list of the thru labels.
//

void Tool_thru::printLabelInfo(HumdrumFile& infile) {
	// infile.analyzeRhythm();
	vector<int> labellines;
	labellines.reserve(1000);

	for (int i=0; i<infile.getLineCount(); i++) {
		if (!infile[i].isInterpretation()) {
			continue;
		}
		HTp token = infile.token(i, 0);

		if (token->compare(0, 2, "*>") != 0) {
			continue;   // ignore non-labels
		}
		if (token->find('[') != string::npos) {
			m_humdrum_text << "!!>";
			m_humdrum_text << token->substr(2) << endl;
			m_humdrum_text << '\n';
			continue;   // ignore realizations
		}
		labellines.push_back(i);
	}

	vector<int> barlines(1000, -1);
	for (int i=0; i<(int)labellines.size(); i++) {
		barlines[i] = getBarline(infile, labellines[i]);
	}

	if (barlines.size() > 0) {
		barlines[0] = adjustFirstBarline(infile);
	}

	int startline;
	int endline;
	HumNum startbeat;
	HumNum endbeat;
	HumNum duration;

	m_humdrum_text << "**label\t**sline\t**eline\t**sbeat\t**ebeat\t**dur\t**bar\n";
	for (int i=0; i<(int)labellines.size(); i++) {
		startline = labellines[i];
		if (i<(int)labellines.size()-1) {
			endline = labellines[i+1]-1;
		} else {
			endline = infile.getLineCount() - 1;
		}
		startbeat = infile[startline].getDurationFromStart();
		endbeat = infile[endline].getDurationFromStart();
		duration = endbeat - startbeat;
		duration = int(duration.getFloat() * 10000.0 + 0.5) / 10000.0;
		HTp token = infile.token(startline, 0);
		m_humdrum_text << token->substr(2);
		m_humdrum_text << '\t';
		m_humdrum_text << startline + 1;
		m_humdrum_text << '\t';
		m_humdrum_text << endline + 1;
		m_humdrum_text << '\t';
		m_humdrum_text << startbeat;
		m_humdrum_text << '\t';
		m_humdrum_text << endbeat;
		m_humdrum_text << '\t';
		m_humdrum_text << duration;
		m_humdrum_text << '\t';
		m_humdrum_text << barlines[i];
		m_humdrum_text << '\n';

	}
	m_humdrum_text << "*-\t*-\t*-\t*-\t*-\t*-\t*-\n";

}



//////////////////////////////
//
// Tool_thru::adjustFirstBarline --
//

int Tool_thru::adjustFirstBarline(HumdrumFile& infile) {
	int number = 0;
	HumRegex hre;
	for (int i=0; i<infile.getLineCount(); i++) {
		if (!infile[i].isBarline()) {
			continue;
		}
		if (infile[i].getDurationFromStart() > 0) {
			break;
		}
		HTp token = infile.token(i, 0);
		if (hre.search(token, "=.*(\\d+)")) {
			number = hre.getMatchInt(1);
		}
		break;
	}
	return number;
}



//////////////////////////////
//
// Tool_thru::getBarline --
//

int Tool_thru::getBarline(HumdrumFile& infile, int line) {

	if (infile[line].getDurationFromStart() == 0) {
		return 0;
	}

	int missingcount = 0;
	int number = -1;
	HumRegex hre;
	for (int i=line; i>0; i--) {
		if (!infile[i].isBarline()) {
			continue;
		}
		HTp token = infile.token(i, 0);

		if (hre.search(token, "=.*(\\d+)")) {
			number = hre.getMatchInt(1);
			break;
		} else {
			missingcount++;
		}
		if (missingcount > 1) {
			break;
		}
	}

	return number;
}



//////////////////////////////
//
// Tool_thru::processData --
//

void Tool_thru::processData(HumdrumFile& infile) {
	vector<string> labelsequence;
	labelsequence.reserve(1000);

	vector<string> labels;
	labels.reserve(1000);

	vector<int> startline;
	startline.reserve(1000);

	vector<int> stopline;
	stopline.reserve(1000);

	int header = -1;
	int footer = -1;
	string labelsearch;
	labelsearch = "*>";
	labelsearch += m_variation;
	labelsearch += "[";

	// check for label to expand
	int foundlabel = 0;
	string tempseq;
	if (m_realization.size()  == 0) {
		for (int i=0; i<infile.getLineCount(); i++) {
			if (!infile[i].isInterpretation()) {
				continue;
			}
			HTp token = infile.token(i, 0);
			if (token->compare(0, labelsearch.size(), labelsearch) != 0) {
				continue;
			}

			tempseq = token->substr(labelsearch.size());
			getLabelSequence(labelsequence, tempseq);
			foundlabel = 1;
			break;
		}
	} else {
		foundlabel = 1;
		getLabelSequence(labelsequence, m_realization);
	}

	if (foundlabel == 0) {
		// did not find the label to expand, so echo the data back
		for (int i=0; i<infile.getLineCount(); i++) {
			HTp token = infile.token(i, 0);
			if (*token == "*thru") {
				continue;
			}
			m_humdrum_text << infile[i] << "\n";
			if (token->compare(0, 2, "**") == 0) {
				for (int j=0; j<infile[i].getFieldCount(); j++) {
					m_humdrum_text << "*thru";
					if (j < infile[i].getFieldCount() - 1) {
						m_humdrum_text << "\t";
					}
				}
				m_humdrum_text << "\n";
			}
		}
		return;
	}

	// for (i=0; i<(int)labelsequence.size(); i++) {
	//    m_humdrum_text << i+1 << "\t=\t" << labelsequence[i] << endl;
	// }

	// search for the labeled sections in the music
	string label;
	int location;
	int index;
	for (int i=0; i<infile.getLineCount(); i++) {
		if (!infile[i].isInterpretation()) {
			continue;
		}
		HTp token = infile.token(i, 0);
		if (*token == "*-") {
			location = i-1;
			footer = i;
			stopline.push_back(location);
		}
		if (token->compare(0, 2, "*>") != 0) {
			continue;
		}
		if (token->find('[') != string::npos) {
			continue;
		}
		if (token->find(']') != string::npos) {
			continue;
		}

		if (labels.size() == 0) {
			header = i-1;
		}

		label = token->substr(2);
		index = (int)labels.size();
		location = i-1;
		if (startline.size() > 0) {
			stopline.push_back(location);
		}
		labels.resize(index+1);
		labels[index] = label;
		startline.push_back(i);
	}

	// m_humdrum_text << "FOOTER = " << footer << endl;
	// m_humdrum_text << "HEADER = " << header << endl;
	// for (i=0; i<(int)labels.size(); i++) {
	//    m_humdrum_text << "\t" << i << "\t=\t" << labels[i]
	//         << "\t" << startline[i] << "\t" << stopline[i]
	//         << endl;
	// }

	// now ready to copy the labeled segements into a final file.


	// print header:
	for (int i=0; i<=header; i++) {
		HTp token = infile.token(i, 0);
		if (*token == "*thru") {
			continue;
		}

		if (!m_keepQ) {
			if (infile[i].isInterpretation()) {
				if (token->compare(0, 2, "*>") == 0) {
					if (token->find('[') != string::npos) {
						continue;
					}
				}
			}
		}

		m_humdrum_text << infile[i] << "\n";
		if (token->compare(0, 2, "**") == 0) {
			for (int j=0; j<infile[i].getFieldCount(); j++) {
				m_humdrum_text << "*thru";
				if (j < infile[i].getFieldCount() - 1) {
					m_humdrum_text << "\t";
				}
			}
			m_humdrum_text << "\n";
		}
	}

	int start;
	int stop;
	for (int i=0; i<(int)labelsequence.size(); i++) {
		index = getLabelIndex(labels, labelsequence[i]);
		if (index < 0) {
			m_humdrum_text << "!! THRU ERROR: label " << labelsequence[i]
				  << " does not exist, skipping.\n";
		}
		start = startline[index];
		stop  = stopline[index];
		for (int j=start; j<=stop; j++) {
			if (!m_keepQ) {
				if (infile[j].isInterpretation()) {
					HTp token = infile.token(j, 0);
					if (token->compare(0, 2, "*>") == 0) {
						if (token->find('[') != string::npos) {
							continue;
						}
					}
				}
			}
			m_humdrum_text << infile[j] << "\n";
		}
	}

	// print footer:
	for (int i=footer; i<infile.getLineCount(); i++) {
		if (!m_keepQ) {
			if (infile[i].isInterpretation()) {
				HTp token = infile.token(i, 0);
				if (token->compare(0, 2, "*>") == 0) {
					if (token->find('[') != string::npos) {
						continue;
					}
				}
			}
		}
		m_humdrum_text << infile[i] << "\n";
	}

}



//////////////////////////////
//
// Tool_thru::getLabelIndex --
//

int Tool_thru::getLabelIndex(vector<string>& labels, string& key) {
	for (int i=0; i<(int)labels.size(); i++) {
		if (key == labels[i]) {
			return i;
		}
	}
	return -1;
}



//////////////////////////////
//
// Tool_thru::getLabelSequence --
//

void Tool_thru::getLabelSequence(vector<string>& labelsequence,
		const string& astring) {
	int slength = (int)astring.size();
	char* sdata = new char[slength+1];
	strcpy(sdata, astring.c_str());
	const char* ignorecharacters = ", [] ";
	int index;

	char* strptr = strtok(sdata, ignorecharacters);
	while (strptr != NULL) {
		labelsequence.resize((int)labelsequence.size() + 1);
		index = (int)labelsequence.size() - 1;
		labelsequence[index] = strptr;
		strptr = strtok(NULL, ignorecharacters);
	}

	delete [] sdata;
}




/////////////////////////////////
//
// Tool_tie::Tool_tie -- Set the recognized options for the tool.
//

Tool_tie::Tool_tie(void) {
	define("s|split=b",          "split overfill notes into tied notes across barlines.");
	define("m|merge=b",          "merge tied notes into a single note.");
	define("p|printable=b",      "merge tied notes only if single note is a printable note.");
	define("M|mark=b",           "mark overfill notes.");
	define("i|invisible=b",      "mark overfill barlines invisible.");
	define("I|skip-invisible=b", "skip invisible measures when splitting overfill durations.");
}



/////////////////////////////////
//
// Tool_tie::run -- Do the main work of the tool.
//

bool Tool_tie::run(HumdrumFileSet& infiles) {
	bool status = true;
	for (int i=0; i<infiles.getCount(); i++) {
		status &= run(infiles[i]);
	}
	return status;
}


bool Tool_tie::run(const string& indata, ostream& out) {
	HumdrumFile infile(indata);
	bool status = run(infile);
	if (hasAnyText()) {
		getAllText(out);
	} else {
		out << infile;
	}
	return status;
}


bool Tool_tie::run(HumdrumFile& infile, ostream& out) {
	bool status = run(infile);
	if (hasAnyText()) {
		getAllText(out);
	} else {
		out << infile;
	}
	return status;
}


bool Tool_tie::run(HumdrumFile& infile) {
	initialize();
	processFile(infile);
	infile.createLinesFromTokens();
	m_humdrum_text << infile;
	return true;
}



//////////////////////////////
//
// Tool_tie::initialize --
//

void Tool_tie::initialize(void) {
	m_printQ         = getBoolean("printable");
	m_mergeQ         = getBoolean("merge");
	m_splitQ         = getBoolean("split");
	m_markQ          = getBoolean("mark");
	m_invisibleQ     = getBoolean("invisible");
	m_skipInvisibleQ = getBoolean("skip-invisible");
}



//////////////////////////////
//
// Tool_tie::processFile --
//

void Tool_tie::processFile(HumdrumFile& infile) {
	if (m_mergeQ) {
		mergeTies(infile);
	} else if (m_splitQ) {
		splitOverfills(infile);
	} else if (m_markQ) {
		int count = markOverfills(infile);
		if (count > 0) {
			string rdfline = "!!!RDF**kern: ";
			rdfline += m_mark;
			rdfline += " = marked note, overfill (total: ";
			rdfline += to_string(count);
			rdfline += ")";
			infile.appendLine(rdfline);
		}
	}
}



//////////////////////////////
//
// Tool_tie::splitOverfills -- Both notes and rests that extend
//    past the end of the measure are split into two or more notes/rests,
//    with the notes connected with ties.
//

void Tool_tie::splitOverfills(HumdrumFile& infile) {

	for (int i=0; i<infile.getStrandCount(); i++) {
		HTp stok = infile.getStrandStart(i);
		if (!stok->isKern()) {
			continue;
		}
		HTp etok = infile.getStrandEnd(i);
		HTp tok = stok;
		while (tok && (tok != etok)) {
			if (!tok->isData()) {
				tok = tok->getNextToken();
				continue;
			}
			if (tok->isNull()) {
				tok = tok->getNextToken();
				continue;
			}
			bool overQ = checkForOverfill(tok);
			if (overQ) {
				splitToken(tok);
			}
			tok = tok->getNextToken();
		}
	}
	infile.createLinesFromTokens();


}



//////////////////////////////
//
// Tool_tie::splitToken --
//

void Tool_tie::splitToken(HTp tok) {
	HumNum duration = tok->getDuration();
	HumNum toBarline = getDurationToNextBarline(tok);
	HumNum newdur = toBarline;
	duration = duration - toBarline;
	string text = "[";
	text += tok->getText();
	HumRegex hre;
	string recip = Convert::durationToRecip(newdur);
	hre.replaceDestructive(text, recip, "\\d+(?:%\\d+)?\\.*", "g");
	tok->setText(text);
	carryForwardLeftoverDuration(duration, tok);
}



//////////////////////////////
//
// Tool_tie::carryForwardLeftoverDuration --
//

void Tool_tie::carryForwardLeftoverDuration(HumNum duration, HTp tok) {
	if (duration <= 0) {
		return;
	}
	HTp current = tok->getNextToken();
	// find next barline:
	while (current) {
		if (current->isBarline()) {
			if (m_skipInvisibleQ) {
				if (current->find("-") == string::npos) {
					break;
				}
			} else {
				break;
			}
		}
		current = current->getNextToken();
	}
	if (!current) {
		// strange problem: no next barline
		return;
	}
	if (!current->isBarline()) {
		// strange problem that cannot happen
		return;
	}
	HTp barline = current;
	if (m_invisibleQ && (barline->find('-') != string::npos)) {
		HumRegex hre;
		string text = *barline;
		hre.replaceDestructive(text, "", "-", "g");
		barline->setText(text);
	}
	HumNum bardur = getDurationToNextBarline(current);

	// find first null token after barline (that is not on a grace-note line)
	// if the original note is an overfill note, there must be
	// a null data token.
	current = current->getNextToken();
	bool foundQ = false;
	while (current) {
		if (current->isNull()) {
			HLp line = current->getOwner();
			if (!line) {
				// strange error
				return;
			}
			if (line->getDuration() > 0) {
				// non-grace note null token to exit loop
				foundQ = true;
				break;
			}
		}
		current = current->getNextToken();
	}
	if (!foundQ) {
		// strange error
		return;
	}
	if (!current->isNull()) {
		// strange error
		return;
	}
	HTp storage = current;
	// get next note or barline after null token
	current = current->getNextToken();
	foundQ = 0;
	while (current) {
		if (current->isBarline()) {
			if (m_skipInvisibleQ) {
				if (current->find("-") == string::npos) {
					foundQ = true;
					break;
				}
			} else {
				foundQ = true;
				break;
			}
		}
		if (current->isData()) {
			if (!current->isNull()) {
				foundQ = true;
				break;
			}
		}
		current = current->getNextToken();
	}

	if (!foundQ) {
		// strange error
		return;
	}
	HumNum barstart = barline->getDurationFromStart();
	HumNum nextstart = current->getDurationFromStart();
	HumNum available = nextstart - barstart;
	if (duration < available) {
		cerr << "DURATION " << duration << " IS LESS THAN AVAILABLE " << available << endl;
		// strange error
		return;
	}

	string text = *tok;
	HumRegex hre;
	hre.replaceDestructive(text, "", "[_[]", "g");
	string recip = Convert::durationToRecip(available);
	hre.replaceDestructive(text, recip, "\\d+(?:%\\d+)?\\.*", "g");

	if (available == duration) {
		// this is the last note in the tie group;
		text += ']';
		storage->setText(text);
		return;
	}

	// There is some more space for the remaining duration, but not
	// big enough for all of it.  Place the piece that can fit here
	// and then kick the can down the road for the remainder.
	text += '_';
	storage->setText(text);
	duration = duration - available;
	carryForwardLeftoverDuration(duration, storage);
}



//////////////////////////////
//
// Tool_tie::mergeTies --
//

void Tool_tie::mergeTies(HumdrumFile& infile) {
	// infile.analyzeKernTies();

	for (int i=0; i<infile.getStrandCount(); i++) {
		HTp stok = infile.getStrandStart(i);
		if (!stok->isKern()) {
			continue;
		}
		HTp etok = infile.getStrandEnd(i);
		HTp tok = stok;
		while (tok && (tok != etok)) {
			if (!tok->isData()) {
				tok = tok->getNextToken();
				continue;
			}
			if (tok->isNull()) {
				tok = tok->getNextToken();
				continue;
			}
			if (tok->find('[') == string::npos) {
				tok = tok->getNextToken();
				continue;
			}
			mergeTie(tok);
			tok = tok->getNextToken();
		}
	}
	infile.createLinesFromTokens();

}



//////////////////////////////
//
// Tool_tie::mergeTie --
//

void Tool_tie::mergeTie(HTp token) {
	if (token->find('[') == string::npos) {
		return;
	}

	vector<HTp> tiednotes;

	HumNum totaldur = token->getDuration();
	HTp current = token->getNextToken();
	while (current) {
		if (!current->isData()) {
			current = current->getNextToken();
			continue;
		}
		if (current->isNull()) {
			current = current->getNextToken();
			continue;
		}
		bool isMiddle = current->find('_') != string::npos;
		bool isEnd    = current->find(']') != string::npos;
		if (!(isMiddle ^ isEnd)) {
			// strange problem so don't merge any more notes
			break;
		}
		tiednotes.push_back(current);
		totaldur += current->getDuration();
		if (isEnd) {
			break;
		}
		current = current->getNextToken();
	}

	string recip = Convert::durationToRecip(totaldur);

	// cerr << "TOTAL DURATION OF " << token << " IS " << totaldur <<  " RECIP " << recip << endl;

	bool makeinvis = false;
	if (m_invisibleQ) {
		makeinvis = checkForInvisible(token);
	}

	for (int i=0; i<(int)tiednotes.size(); i++) {
		if (m_invisibleQ) {
			if (checkForInvisible(tiednotes[i])) {
				markNextBarlineInvisible(tiednotes[i]);
			}
		}
		tiednotes[i]->setText(".");
	}
	// set initial tied notes with updated recip.
	string text = *token;
	HumRegex hre;
	hre.replaceDestructive(text, recip, "\\d+(?:%\\d+)?\\.*", "g");
	hre.replaceDestructive(text, "", "\\[", "g");
	token->setText(text);
	if (makeinvis) {
		markNextBarlineInvisible(token);
	}

}



//////////////////////////////
//
// Tool_tie::markNextBarlineInvisible --  Multiple layers are not dealt with yet.
//

void Tool_tie::markNextBarlineInvisible(HTp tok) {
	HTp current = tok;
	while (current) {
		if (!current->isBarline()) {
			current = current->getNextToken();
			continue;
		}
		if (current->find('-') != string::npos) {
			// Already invisible
			break;
		}
		string text = *current;
		text += '-';
		current->setText(text);
		break;
	}
}



//////////////////////////////
//
// Tool_tie::markOverfills --
//

int Tool_tie::markOverfills(HumdrumFile& infile) {
	int counter = 0;

	for (int i=0; i<infile.getStrandCount(); i++) {
		HTp stok = infile.getStrandStart(i);
		if (!stok->isKern()) {
			continue;
		}
		HTp etok = infile.getStrandEnd(i);
		HTp tok = stok;
		while (tok && (tok != etok)) {
			if (!tok->isData()) {
				tok = tok->getNextToken();
				continue;
			}
			if (tok->isNull()) {
				tok = tok->getNextToken();
				continue;
			}
			bool overQ = checkForOverfill(tok);
			if (overQ) {
				string text = *tok;
				text += m_mark;
				tok->setText(text);
				counter++;
			}

			tok = tok->getNextToken();
		}
	}
	return counter;
}



//////////////////////////////
//
// Tool_tie::checkForInvisible --
//

bool Tool_tie::checkForInvisible(HTp tok) {
	HumNum duration = tok->getDuration();
	HumNum tobarline = tok->getDurationToBarline();
	if ((tok->find('[') != string::npos) ||
	   (tok->find('_') != string::npos)) {
		if (duration >= tobarline) {
			return true;
		} else {
			return false;
		}
	}
	if (duration > tobarline) {
		return true;
	} else {
		return false;
	}
}



//////////////////////////////
//
// Tool_tie::checkForOverfill --
//

bool Tool_tie::checkForOverfill(HTp tok) {
	HumNum duration = tok->getDuration();
	HumNum tobarline = getDurationToNextBarline(tok);
	if (duration > tobarline) {
		return true;
	} else {
		return false;
	}
}



//////////////////////////////
//
// Tool_tie::getDurationToNextVisibleBarline --
//

HumNum Tool_tie::getDurationToNextVisibleBarline(HTp tok) {
	HTp current = tok;
	HTp barline = NULL;
	while (current) {
		if (!current->isBarline()) {
			current = current->getNextToken();
			continue;
		}
		if (current->find("-") != string::npos) {
			// invisible so skip this barline
			current = current->getNextToken();
			continue;
		}
		barline = current;
		break;
	}

	if (!barline) {
		return tok->getDurationToEnd();
	}
	HumNum startpos   = tok->getDurationFromStart();
	HumNum endpos     = barline->getDurationFromStart();
	HumNum difference = endpos - startpos;
	return difference;
}



//////////////////////////////
//
// Tool_tie::getDurationToNextBarline --
//

HumNum Tool_tie::getDurationToNextBarline(HTp tok) {
	if (m_skipInvisibleQ) {
		return getDurationToNextVisibleBarline(tok);
	} else {
		return tok->getDurationToBarline();
	}
}




/////////////////////////////////
//
// Tool_timebase::Tool_timebase -- Set the recognized options for the tool.
//

Tool_timebase::Tool_timebase(void) {
	define("g|grace=b",       "keep grace notes");
	define("m|min=b",         "use minimum time in score for timebase");
	define("t|timebase=s:16", "timebase rhythm");
	define("q|quiet=b",       "quiet mode: Do not output warnings");
}



///////////////////////////////
//
// Tool_timebase::run -- Primary interfaces to the tool.
//

bool Tool_timebase::run(HumdrumFileSet& infiles) {
	bool status = true;
	for (int i=0; i<infiles.getCount(); i++) {
		status &= run(infiles[i]);
	}
	return status;
}

bool Tool_timebase::run(const string& indata, ostream& out) {
	HumdrumFile infile(indata);
	return run(infile, out);
}


bool Tool_timebase::run(HumdrumFile& infile, ostream& out) {
	bool status = run(infile);
	return status;
}


bool Tool_timebase::run(HumdrumFile& infile) {
	processFile(infile);
	return true;
}



///////////////////////////////
//
// Tool_timebase::processFile -- Adjust intervals of ornaments.
//

void Tool_timebase::processFile(HumdrumFile& infile) {
	m_grace   = getBoolean("grace");
	m_quiet   = getBoolean("quiet");
	if (!getBoolean("timebase")) {
		m_basedur = getMinimumTime(infile);
	} else {
		m_basedur = Convert::recipToDuration(getString("timebase"));
	}
	if (m_basedur == 0) {
		// some problem so don't do anything (return input data)
		return;
	}
	expandScore(infile, m_basedur);
}


//////////////////////////////
//
// Tool_timebase::expandScore --
//

void Tool_timebase::expandScore(HumdrumFile& infile, HumNum mindur) {
	for (int i=0; i<infile.getLineCount(); i++) {
		if (!infile[i].isData()) {
			m_humdrum_text << infile[i] << endl;
			continue;
		}
		HumNum duration = infile[i].getDuration();
		if (duration == 0) {
			if (m_grace) {
				m_humdrum_text << infile[i] << endl;
			}
			continue;
		}
		HumNum count = duration / mindur;
		if (count < 1) {
			if (!m_quiet) {
				m_humdrum_text << "!!Warning: following commented line was too short to be included in timebase output:\n";
				m_humdrum_text << "!! " << infile[i] << endl;
			}
			continue;
		} else if (count.getDenominator() != 1) {
			if (!m_quiet) {
				m_humdrum_text << "!!Warning: next line does not have proper duration for representing with timebase: " << count.getFloat() << endl;
			}
		}
		m_humdrum_text << infile[i] << endl;
		int repeats = int(count.getFloat()) - 1;
		for (int j=0; j<repeats; j++) {
			for (int k=0; k<infile[i].getFieldCount(); k++) {
				m_humdrum_text << ".";
				if (k < infile[i].getFieldCount() - 1) {
					m_humdrum_text << "\t";
				}
			}
			m_humdrum_text << endl;
		}
	}
	if (!m_quiet) {
		HumNum rhythm = Convert::durationToRecip(mindur);
		m_humdrum_text << "!!timebased: " << rhythm << endl;
	}

}




///////////////////////////////
//
// Tool_timebase::getMinimumTime -- Get the minimum time unit
//    in the file.  This is the smallest non-zero line duration.
//

HumNum Tool_timebase::getMinimumTime(HumdrumFile& infile) {
	HumNum minimum(0, 1);
	for (int i=0; i<infile.getLineCount(); i++) {
		if (!infile[i].isData()) {
			continue;
		}
		HumNum duration = infile[i].getDuration();
		if (minimum == 0) {
			minimum = duration;
			continue;
		}
		if (minimum > duration) {
			minimum = duration;
		}
	}
	// minimum is in units of quarter notes.
	return minimum;
}







#define STYLE_CONCERT 0
#define STYLE_WRITTEN 1

/////////////////////////////////
//
// Tool_transpose::Tool_transpose -- Set the recognized options for the tool.
//

Tool_transpose::Tool_transpose(void) {
	define("b|base40=i:0",    "the base-40 transposition value");
	define("d|diatonic=i:0",  "the diatonic transposition value");
	define("c|chromatic=i:0", "the chromatic transposition value");
	define("o|octave=i:0",    "the octave addition to tranpose value");
	define("t|transpose=s",   "musical interval transposition value");
	define("k|settonic=s",    "transpose to the given key/tonic (mode will not change)");
	define("auto=b",          "auto. trans. inst. parts to concert pitch");
	define("debug=b",         "print debugging statements");
	define("s|spines=s:",     "transpose only specified spines");
	// quiet reversed with -T option (actively need to request transposition code now)
	// define("q|quiet=b",       "suppress *Tr interpretations in output");
	define("T|transcode=b",   "include transposition code to reverse transposition");
	define("I|instrument=b",  "insert instrument code (*ITr) as well");
	define("C|concert=b",     "transpose written score to concert pitch");
	define("W|written=b",     "trans. concert pitch score to written score");
	define("n|negate=b",      "negate transposition indications");
	define("rotation=b",      "display transposition in half-steps");

	define("author=b",        "author of program");
	define("version=b",       "compilation info");
	define("example=b",       "example usages");
	define("help=b",          "short description");
}



/////////////////////////////////
//
// Tool_transpose::run -- Do the main work of the tool.
//

bool Tool_transpose::run(HumdrumFileSet& infiles) {
	bool status = true;
	for (int i=0; i<infiles.getCount(); i++) {
		status &= run(infiles[i]);
	}
	return status;
}


bool Tool_transpose::run(const string& indata, ostream& out) {
	HumdrumFile infile(indata);
	bool status = run(infile);
	if (hasAnyText()) {
		getAllText(out);
	} else {
		out << infile;
	}
	return status;
}


bool Tool_transpose::run(HumdrumFile& infile, ostream& out) {
	bool status = run(infile);
	if (hasAnyText()) {
		getAllText(out);
	} else {
		out << infile;
	}
	return status;
}

//
// In-place processing of file:
//

bool Tool_transpose::run(HumdrumFile& infile) {
	initialize(infile);

	if (ssettonicQ) {
		transval = calculateTranspositionFromKey(ssettonic, infile);
		transval = transval + octave * 40;
		if (debugQ) {
			m_humdrum_text << "!!Key TRANSVAL = " << transval;
		}
	}

	if (getBoolean("rotation")) {
		// returns the base-12 pitch transposition for use in conjunction
		// with the mkeyscape --rotate option
		int value = 60 - Convert::base40ToMidiNoteNumber(162 - transval);
		m_free_text << value << endl;
		return false;
	}

	if (concertQ) {
		convertScore(infile, STYLE_CONCERT);
	} else if (writtenQ) {
		convertScore(infile, STYLE_WRITTEN);
	} else if (autoQ) {
		doAutoTransposeAnalysis(infile);
	} else {
		vector<bool> spineprocess;
		infile.makeBooleanTrackList(spineprocess, spinestring);
		// filter out non-kern spines so they are not analyzed.
		// but now also allowing for *mxhm spines (musicxml harmony)
		for (int t=1; t<=infile.getMaxTrack(); t++) {
			if (!(infile.getTrackStart(t)->isKern() ||
					infile.getTrackStart(t)->isDataType("mxhm"))) {
				spineprocess[t] = false;
			}
		}
		processFile(infile, spineprocess);
	}

	return true;
}



//////////////////////////////
//
// Tool_transpose::convertScore -- create a concert pitch score from
//     a written pitch score.  The function will search for *Tr
//     interpretations in spines, and convert them to *ITr interpretations
//     as well as transposing notes, and transposing key signatures and
//     key interpretations.  Or create a written score from a
//     concert pitch score based on the style parameter.
//

void Tool_transpose::convertScore(HumdrumFile& infile, int style) {
	// transposition values for each spine
	vector<int> tvals(infile.getMaxTrack()+1, 0);

	int ptrack;
	int i, j;
	for (i=0; i<infile.getLineCount(); i++) {
		if (infile[i].isInterpretation()) {
				// scan the line for transposition codes
				// as well as key signatures and key markers
				processInterpretationLine(infile, i, tvals, style);
		} else if (infile[i].isData()) {
			// transpose notes according to tvals data
			for (j=0; j<infile[i].getFieldCount(); j++) {
				if (!infile.token(i, j)->isKern()) {
					m_humdrum_text << infile.token(i, j);
					if (j < infile[i].getFieldCount() - 1) {
					 		m_humdrum_text << "\t";
					}
					continue;
				}
				ptrack = infile.token(i, j)->getTrack();
				if (tvals[ptrack] == 0) {
					  m_humdrum_text << infile.token(i, j);
				} else {
					  printTransposedToken(infile, i, j, tvals[ptrack]);
				}
				if (j < infile[i].getFieldCount() - 1) {
					  m_humdrum_text << "\t";
				}
			}
			m_humdrum_text << "\n";
		} else {
			m_humdrum_text << infile[i] << "\n";
		}
	}
}



//////////////////////////////
//
// Tool_transpose::processInterpretationLine --  Used in converting between
//   concert pitch and written pitch scores.  Has some duplicate code that
//   is not used here.
//

void Tool_transpose::processInterpretationLine(HumdrumFile& infile, int line,
	  vector<int>& tvals, int style) {

	if (hasTrMarkers(infile, line)) {
		switch (style) {
			case STYLE_CONCERT:
				convertToConcertPitches(infile, line, tvals);
				break;
			case STYLE_WRITTEN:
				convertToWrittenPitches(infile, line, tvals);
				break;
			default: m_humdrum_text << infile[line];
		}
		m_humdrum_text << "\n";
		return;
	}

	for (int j=0; j<infile[line].getFieldCount(); j++) {
		int ptrack = infile.token(line, j)->getTrack();
		if (ptrack < 0) {
			cerr << "Track is negative on line " << (line+1) << ", spine " << (j+1) << endl;
			return;
		}

		// check for *ITr or *Tr markers
		// ignore *ITr markers when creating a Concert-pitch score
		// ignore *Tr  markers when creating a Written-pitch score
		HumRegex hre;
		if (hre.search(infile.token(line, j), "^\\*k\\[([a-gA-G\\#-]*)\\]", "")) {
			// transpose *k[] markers if necessary
			if (tvals.at(ptrack) != 0) {
				string value = hre.getMatch(1);
				printNewKeySignature(value, tvals[ptrack]);
			} else {
				m_humdrum_text << infile.token(line, j);
			}
		} else if (isKeyMarker(*infile.token(line, j))) {
			// transpose *C: markers and like if necessary
			if (tvals[ptrack] != 0) {
				printNewKeyInterpretation(infile[line], j, tvals[ptrack]);
			} else if (transval != 0) {
				// maybe not quite right for all possible cases
				printNewKeyInterpretation(infile[line], j, transval);
			} else {
				m_humdrum_text << infile.token(line, j);
			}
		} else {
			// other interpretations just echoed to output:
			m_humdrum_text << infile.token(line, j);
		}
		if (j<infile[line].getFieldCount()-1) {
			m_humdrum_text << "\t";
		}
	}
	m_humdrum_text << "\n";

}



//////////////////////////////
//
// Tool_transpose::convertToWrittenPitches --
//

void Tool_transpose::convertToWrittenPitches(HumdrumFile& infile, int line,
		vector<int>& tvals) {
	HumRegex hre;
	int j;
	int base;
	int ptrack;
	for (j=0; j<infile[line].getFieldCount(); j++) {
		if (!infile.token(line, j)->isKern()) {
			m_humdrum_text << infile.token(line, j);
			if (j < infile[line].getFieldCount() - 1) {
				m_humdrum_text << "\t";
			}
			continue;
		}
		if (hre.search(infile.token(line, j), "^\\*ITrd[+-]?\\d+c[+-]?\\d+$", "")) {
			base = Convert::transToBase40(*infile.token(line, j));

			string output = "*Tr";
			output += Convert::base40ToTrans(base);
			m_humdrum_text << output;
			ptrack = infile.token(line, j)->getTrack();
			tvals[ptrack] = base;
		} else {
			m_humdrum_text << infile.token(line, j);
		}
		if (j < infile[line].getFieldCount() - 1) {
			m_humdrum_text << "\t";
		}
	}
}



//////////////////////////////
//
// Tool_transpose::convertToConcertPitches --
//

void Tool_transpose::convertToConcertPitches(HumdrumFile& infile, int line, vector<int>& tvals) {
	HumRegex hre;
	int j;
	int base;
	int ptrack;
	for (j=0; j<infile[line].getFieldCount(); j++) {
		if (!infile.token(line, j)->isKern()) {
			m_humdrum_text << infile.token(line, j);
			if (j < infile[line].getFieldCount() - 1) {
				m_humdrum_text << "\t";
			}
			continue;
		}
		if (hre.search(infile.token(line, j),
				"^\\*Trd[+-]?\\d+c[+-]?\\d+$", "")) {
			base = Convert::transToBase40(*infile.token(line, j));
			string output = "*ITr";
			output += Convert::base40ToTrans(base);
			m_humdrum_text << output;
			ptrack = infile.token(line, j)->getTrack();
			tvals[ptrack] = -base;
		} else {
			m_humdrum_text << infile.token(line, j);
		}
		if (j < infile[line].getFieldCount() - 1) {
			m_humdrum_text << "\t";
		}
	}
}



//////////////////////////////
//
// Tool_transpose::hasTrMarkers -- returns true if there are any tokens
//    which start with *ITr or *Tr and contains c and d
//    with numbers after each of them.
//

int Tool_transpose::hasTrMarkers(HumdrumFile& infile, int line) {
	HumRegex hre;
	int j;
	for (j=0; j<infile[line].getFieldCount(); j++) {
		if (!infile.token(line, j)->isKern()) {
			continue;
		}
		if (hre.search(infile.token(line, j),
				"^\\*I?Trd[+-]?\\d+c[+-]?\\d+$", "")) {
			return 1;
		}
	}

	return 0;
}



//////////////////////////////
//
// Tool_transpose::isKeyMarker -- returns true if the interpretation is
//    a key description, such as *C: for C major, or *a:.
//

int Tool_transpose::isKeyMarker(const string& str) {
	HumRegex hre;
	return hre.search(str, "^\\*[a-g]?[\\#-]?:", "i");
}



//////////////////////////////
//
// Tool_transpose::printTransposedToken -- print a Humdrum token with the given
//    base-40 transposition value applied.  Only **kern data is
//    know now to transpose, other data types are currently not
//    allowed to be transposed (but could be added here later).
//

void Tool_transpose::printTransposedToken(HumdrumFile& infile, int row, int col, int transval) {
	if (!infile.token(row, col)->isKern()) {
		// don't know how to transpose this type of data, so leave it as is
		m_humdrum_text << infile.token(row, col);
	} else {
		printHumdrumKernToken(infile[row], col, transval);
	}
}



//////////////////////////////
//
// Tool_transpose::calculateTranspositionFromKey --
//

int Tool_transpose::calculateTranspositionFromKey(int targetkey,
		HumdrumFile& infile) {
	HumRegex hre;
	int base40 = 0;
	int currentkey = 0;
	int mode = 0;
	int found = 0;

	for (int i=0; i<infile.getLineCount(); i++) {
		if (infile[i].isData()) {
			// no initial key label was found, so don't transpose.
			// in the future, maybe allow an automatic key analysis
			// to be performed on the data if there is not explicit
			// key designation.
			return 0;
		}
		if (!infile[i].isInterpretation()) {
			continue;
		}
		for (int j=0; j<infile[i].getFieldCount(); j++) {
			if (!infile.token(i, j)->isKern()) {
				continue;
			}
			if (!hre.search(infile.token(i, j), "^\\*([A-G][#-]?):", "i")) {
				continue;
			}

			mode = 0;  // major key
			if (islower(infile.token(i, j)->at(1))) {
				mode = 1;  // minor key
			}
			base40 = Convert::kernToBase40(infile.token(i, j));
			// base40 = base40 + transval;
			base40 = base40 + 4000;
			base40 = base40 % 40;
			base40 = base40 + (3 + mode) * 40;
			currentkey = base40;
	 		found = 1;
			break;
		}
		if (found) {
			break;
		}
	}

	int trans = (targetkey%40 - currentkey%40);
	// base40 = targetkey + (3 + mode) * 40;
	if (trans > 40) {
		trans -= 40;
	}
	if (trans > 20) {
		trans = 40 - trans;
		trans = -trans;
	}
	if (trans < -40) {
		trans += 40;
	}
	if (trans < -20) {
		trans = -40 - trans;
		trans = -trans;
	}

	return trans;
}



//////////////////////////////
//
// Tool_transpose::printTransposeInformation -- collect and print *Tr interpretations
//      at the start of the spine.  Looks for *Tr markers at the start
//      of the file before any data.
//

void Tool_transpose::printTransposeInformation(HumdrumFile& infile,
		vector<bool>& spineprocess, int line, int transval) {
	int j;
	int ptrack;

	vector<int> startvalues(infile.getMaxTrack()+1);
	vector<int> finalvalues(infile.getMaxTrack()+1);

	for (j=0; j<infile[line].getFieldCount(); j++) {
		if (!infile.token(line, j)->isKern()) {
			continue;
		}
		ptrack = infile.token(line, j)->getTrack();
		startvalues[ptrack] = getTransposeInfo(infile, line, j);
		// m_humdrum_text << "Found transpose value " << startvalues[ptrack] << endl;
	}

	int entry = 0;
	// check if any spine will be transposed after final processing
	for (j=0; j<infile[line].getFieldCount(); j++) {
		if (!infile.token(line, j)->isKern()) {
			continue;
		}
		ptrack = infile.token(line, j)->getTrack();
		if (spineprocess[ptrack]) {
		finalvalues[ptrack] = transval;
		if (!instrumentQ) {
			finalvalues[ptrack] += startvalues[ptrack];
		}
		if (finalvalues[ptrack] != 0) {
			entry = 1;
		}
	} else {
			finalvalues[ptrack] = startvalues[ptrack];
			if (finalvalues[ptrack] != 0) {
				entry = 1;
			}
		}
	}

	if (!entry) {
		return;
	}

	for (j=0; j<infile[line].getFieldCount(); j++) {
		if (!infile.token(line, j)->isKern()) {
			m_humdrum_text << "*";
			if (j < infile[line].getFieldCount()-1) {
				m_humdrum_text << "\t";
			}
			continue;
		}
		ptrack = infile.token(line, j)->getTrack();
		if (finalvalues[ptrack] == 0) {
			m_humdrum_text << "*";
		} else {
			if (instrumentQ) {
				m_humdrum_text << "*ITr";
				m_humdrum_text << Convert::base40ToTrans(-finalvalues[ptrack]);
			} else {
				m_humdrum_text << "*Tr";
				m_humdrum_text << Convert::base40ToTrans(finalvalues[ptrack]);
			}
		}
		if (j < infile[line].getFieldCount()-1) {
			m_humdrum_text << "\t";
		}

	}
	m_humdrum_text << "\n";
}



//////////////////////////////
//
// getTransposeInfo -- returns the Transpose information found in
//    the specified spine starting at the current line, and searching
//    until data is found (or a *- record is found). Return value is a
//    base-40 number.
//

int Tool_transpose::getTransposeInfo(HumdrumFile& infile, int row, int col) {
	int track = infile.token(row, col)->getTrack();
	int ptrack;
	HumRegex hre;
	int base;
	int output = 0;

	for (int i=row; i<infile.getLineCount(); i++) {
		if (infile[i].isData()) {
			break;
		}
		if (!infile[i].isInterpretation()) {
			continue;
		}
		for (int j=0; j<infile[i].getFieldCount(); j++) {
			if (!infile.token(i, j)->isKern()) {
				continue;
			}
			ptrack = infile.token(i, j)->getTrack();
			if (ptrack != track) {
				continue;
			}
			if (hre.search(infile.token(i, j),
					"^\\*Trd[+-]?\\d+c[+-]?\\d+$", "")) {
				base = Convert::transToBase40(*infile.token(i, j));
				output += base;
				// erase the *Tr value because it will be printed elsewhere
				infile.token(i, j)->setText("*XTr");
				// ggg
			}
		}
	}

	return output;
}



//////////////////////////////
//
// Tool_transpose::checkForDeletedLine -- check to see if a "*deletedTr
//

int Tool_transpose::checkForDeletedLine(HumdrumFile& infile, int line) {
	int j;
	if (!infile[line].isInterpretation()) {
		return 0;
	}

	int present = 0;
	int composite = 0;
	for (j=0; j<infile[line].getFieldCount(); j++) {
		if (!infile.token(line, j)->isKern()) {
			continue;
		}
		if (infile.token(line, j)->find("deletedTr") != string::npos) {
			present = 1;
		} else if (infile.token(line, j)->isNull()) {
			// do nothing: not composite
		} else {
			// not a *deletedTr token or a * token, so have to print line later
			composite = 1;
		}
	}

	if (present == 0) {
		// no *deletedTr records found on the currnet line, so process normally
		return 0;
	}

	if (composite == 0) {
		// *deletedTr found, but no other important data found on line.
		return 1;
	}

	// print non-deleted elements in line.
	for (j=0; j<infile[line].getFieldCount(); j++) {;
		if ((string)(*infile.token(line, j)) == "deletedTr") {
			m_humdrum_text << "*";
		} else {
			m_humdrum_text << infile.token(line, j);
		}
		if (j < infile[line].getFieldCount() - 1) {
			m_humdrum_text << "\t";
		}
	}
	m_humdrum_text << "\n";

	return 1;
}



//////////////////////////////
//
// Tool_transpose::processFile --
//

void Tool_transpose::processFile(HumdrumFile& infile,
		vector<bool>& spineprocess) {
	int i;
	int diatonic;
	int j;
	HumRegex hre;
	int interpstart = 0;

	for (i=0; i<infile.getLineCount(); i++) {
		if (!quietQ && (interpstart == 1)) {
			interpstart = 2;
			printTransposeInformation(infile, spineprocess, i, transval);
		}
		if (checkForDeletedLine(infile, i)) {
			continue;
		}

		if (infile[i].isData()) {
			printHumdrumDataRecord(infile[i], spineprocess);
			m_humdrum_text << "\n";
		} else if (infile[i].isInterpretation()) {
			for (j=0; j<infile[i].getFieldCount(); j++) {

				if (infile.token(i, j)->compare(0, 2, "**") == 0) {
						interpstart = 1;
				}

				// check for key signature in a spine which is being
				// transposed, and adjust it.
				// Should also check tandem spines for updating
				// key signatures in non-kern spines.
				if (spineprocess[infile.token(i, j)->getTrack()] &&
						hre.search(infile.token(i, j),
							"^\\*k\\[([a-gA-G#-]*)\\]", "i")) {
						string value = hre.getMatch(1);
						printNewKeySignature(value, transval);
						if (j<infile[i].getFieldCount()-1) {
						m_humdrum_text << "\t";
						}
						continue;
				}

				// Check for key designations and tranpose
				// if the spine data is being transposed.
				// Should also check tandem spines for updating
				// key designations in non-kern spines.
				if (spineprocess[infile.token(i, j)->getTrack()] &&
						hre.search(infile.token(i, j), "^\\*([A-G])[#-]?:", "i")) {
					diatonic = tolower(hre.getMatch(1)[0]) - 'a';
					if (diatonic >= 0 && diatonic <= 6) {
					  	printNewKeyInterpretation(infile[i], j, transval);
					  	if (j<infile[i].getFieldCount()-1) {
							m_humdrum_text << "\t";
					  	}
					  	continue;
					}
				}
				m_humdrum_text << infile.token(i, j);
				if (j<infile[i].getFieldCount()-1) {
					m_humdrum_text << "\t";
				}
			}
			m_humdrum_text << "\n";

		} else {
			m_humdrum_text << infile[i] << "\n";
		}
	}
}



//////////////////////////////
//
// Tool_transpose::printNewKeySignature --
//

void Tool_transpose::printNewKeySignature(const string& keysig, int trans) {
	int counter = 0;
	int len = (int)keysig.size();
	for (int i=0; i<len; i++) {
		switch(keysig[i]) {
			case '-': counter--; break;
			case '#': counter++; break;
		}
	}

	int xxx = Convert::base40IntervalToLineOfFifths(trans);
	int newkey = xxx + counter;
	m_humdrum_text << Convert::keyNumberToKern(newkey);
}



//////////////////////////////
//
// Tool_transpose::printNewKeyInterpretation --
//

void Tool_transpose::printNewKeyInterpretation(HumdrumLine& aRecord,
		int index, int transval) {
	int mode = 0;
	if (islower(aRecord.token(index)->at(1))) {
		mode = 1;
	}
	int base40 = Convert::kernToBase40(*aRecord.token(index));
	currentkey = base40;
	base40 = base40 + transval;
	base40 = base40 + 4000;
	base40 = base40 % 40;
	base40 = base40 + (3 + mode) * 40;

	m_humdrum_text << "*" << Convert::base40ToKern(base40) << ":";

	HumRegex hre;
	string tvalue = *aRecord.token(index);
	if (hre.search(tvalue, ":(.+)$", "")) {
		string value = hre.getMatch(1);
		m_humdrum_text << value;
	}
}



//////////////////////////////
//
// Tool_transpose::printHumdrumDataRecord --
//

void Tool_transpose::printHumdrumDataRecord(HumdrumLine& record,
		vector<bool>& spineprocess) {
	int i;
	for (i=0; i<record.getFieldCount(); i++) {
		if (!(record.token(i)->isKern() ||
				record.token(i)->isDataType("mxhm"))) {
			// don't try to transpose non-kern and non-mxhm spines
			m_humdrum_text << record.token(i);
			if (i<record.getFieldCount()-1) {
				m_humdrum_text << "\t";
			}
			continue;
		}
		if (!spineprocess[record.token(i)->getTrack()]) {
			// don't try to transpose spines which were not indicated.
			m_humdrum_text << record.token(i);
			if (i<record.getFieldCount()-1) {
				m_humdrum_text << "\t";
			}
			continue;
		}

		if (record.token(i)->isKern()) {
			printHumdrumKernToken(record, i, transval);
		} else if (record.token(i)->isDataType("mxhm")) {
			printHumdrumMxhmToken(record, i, transval);
		} else {
			m_humdrum_text << record.token(i);
		}

		if (i<record.getFieldCount()-1) {
			m_humdrum_text << "\t";
		}
		continue;
	}

}



//////////////////////////////
//
// Tool_transpose::printHumdrumKernToken --
//

void Tool_transpose::printHumdrumKernToken(HumdrumLine& record, int index,
		int transval) {
	if (record.token(index)->isNull()) {
		// null record element (no pitch).
		m_humdrum_text << record.token(index);
		return;
	}
	if (!record.token(index)->isKern()) {
		m_humdrum_text << record.token(index);
		return;
	}
	string buffer;
	int tokencount = record.token(index)->getSubtokenCount();
	for (int k=0; k<tokencount; k++) {
		buffer = record.token(index)->getSubtoken(k);
		printNewKernString(buffer, transval);
		if (k<tokencount-1) {
			m_humdrum_text << " ";
		}
	}
}



//////////////////////////////
//
// Tool_transpose::printHumdrumMxhmToken --
//

void Tool_transpose::printHumdrumMxhmToken(HumdrumLine& record, int index,
		int transval) {
	if (record.token(index)->isNull()) {
		// null record element (nothing to do)
		m_humdrum_text << record.token(index);
		return;
	}
	if (!record.token(index)->isDataType("mxhm")) {
		m_humdrum_text << record.token(index);
		return;
	}
	HumRegex hre;
	if (hre.search(record.token(index), "N\\.C\\.")) {
		// no pitch information so just echo the text:
		m_humdrum_text << record.token(index);
	} else if (hre.search(record.token(index), "([A-Ga-g]+[n#-]{0,2})")) {
		string pitch = hre.getMatch(1);
		int b40 = Convert::kernToBase40(pitch) + transval;
cerr << "B40 = " << b40 << "\t" << pitch << endl;
		b40 = b40 % 40 + 120;
		pitch = Convert::base40ToKern(b40);
		string newtext = *record.token(index);
		hre.replaceDestructive(newtext, pitch, "([A-Ga-g]+[n#-]{0,2})");
		m_humdrum_text << newtext;
	} else {
		m_humdrum_text << record.token(index);
		return;
	}
}




//////////////////////////////
//
// Tool_transpose::printNewKernString --
//

void Tool_transpose::printNewKernString(const string& input, int transval) {

	HumRegex hre;
	if (input == ".") {
		// Don't transpose null tokens.
		m_humdrum_text << input;
		return;
	} else if (input.rfind('R') != string::npos) {
		// Don't transpose unpitched notes (percussion parts).
		m_humdrum_text << input;
		return;
	} else if (input.rfind('r') != string::npos) {
		// Transpose rests only if they contain a pitch component.
		string output = input;
		if (hre.search(input, "([A-Ga-g]+[#n-]*)")) {
			// Transpose pitch portion of rest (indicating vertical position).
			string pitch = hre.getMatch(1);
			int base40 = Convert::kernToBase40(pitch);
cerr << "XPITCH " << pitch << "\tbase40 = " << base40 << endl;
			string newpitch = Convert::base40ToKern(base40 + transval);
cerr << "\tNEWPITCH " << pitch << endl;
			hre.replaceDestructive(newpitch, "", "[-#n]+");
			hre.replaceDestructive(output, newpitch, "([A-Ga-g]+[#n-]*)");
		}
		// don't transpose rests...
		m_humdrum_text << output;
		return;
	} else if (!hre.search(input, "([A-Ga-g]+[#n-]*)")) {
		// This is a form of invisible rest with no "r", just **recip.
		m_humdrum_text << input;
		return;
	}

	// Now the only thing left are regular pitches.

	int base40 = Convert::kernToBase40(input);
	string newpitch = Convert::base40ToKern(base40 + transval);

	// consider interaction of #X -X n interaction vs. nX.
	string output;
	if (hre.search(input, "([A-Ga-g#n-]+)")) {
		string oldpitch = hre.getMatch(1);
		output = hre.replaceCopy(input, newpitch, oldpitch);
	}
	m_humdrum_text << output;
}



//////////////////////////////
//
// Tool_transpose::getBase40ValueFromInterval -- note: only ninth interval range allowed
//

int Tool_transpose::getBase40ValueFromInterval(const string& interval) {
	int sign = 1;
	if (interval.find('-') != string::npos) {
		sign = -1;
	}

	string icopy = interval;
	for (int i=0; i<(int)icopy.size(); i++) {
		if (icopy[i] == 'p') { icopy[i] = 'P'; }
		if (icopy[i] == 'a') { icopy[i] = 'A'; }
		if (icopy[i] == 'D') { icopy[i] = 'd'; }
	}

	int output = 0;

	if (icopy.find("dd1") != string::npos)      { output = -2; }
	else if (icopy.find("d1") != string::npos)  { output = -1; }
	else if (icopy.find("P1") != string::npos)  { output =  0; }
	else if (icopy.find("AA1") != string::npos) { output =  2; }
	else if (icopy.find("A1") != string::npos)  { output =  1; }

	else if (icopy.find("dd2") != string::npos) { output =  3; }
	else if (icopy.find("d2") != string::npos)  { output =  4; }
	else if (icopy.find("m2") != string::npos)  { output =  5; }
	else if (icopy.find("M2") != string::npos)  { output =  6; }
	else if (icopy.find("AA2") != string::npos) { output =  8; }
	else if (icopy.find("A2") != string::npos)  { output =  7; }

	else if (icopy.find("dd3") != string::npos) { output =  9; }
	else if (icopy.find("d3") != string::npos)  { output = 10; }
	else if (icopy.find("m3") != string::npos)  { output = 11; }
	else if (icopy.find("M3") != string::npos)  { output = 12; }
	else if (icopy.find("AA3") != string::npos) { output = 14; }
	else if (icopy.find("A3") != string::npos)  { output = 13; }

	else if (icopy.find("dd4") != string::npos) { output = 15; }
	else if (icopy.find("d4") != string::npos)  { output = 16; }
	else if (icopy.find("P4") != string::npos)  { output = 17; }
	else if (icopy.find("AA4") != string::npos) { output = 19; }
	else if (icopy.find("A4") != string::npos)  { output = 18; }

	else if (icopy.find("dd5") != string::npos) { output = 21; }
	else if (icopy.find("d5") != string::npos)  { output = 22; }
	else if (icopy.find("P5") != string::npos)  { output = 23; }
	else if (icopy.find("AA5") != string::npos) { output = 25; }
	else if (icopy.find("A5") != string::npos)  { output = 24; }

	else if (icopy.find("dd6") != string::npos) { output = 26; }
	else if (icopy.find("d6") != string::npos)  { output = 27; }
	else if (icopy.find("m6") != string::npos)  { output = 28; }
	else if (icopy.find("M6") != string::npos)  { output = 29; }
	else if (icopy.find("AA6") != string::npos) { output = 31; }
	else if (icopy.find("A6") != string::npos)  { output = 30; }

	else if (icopy.find("dd7") != string::npos) { output = 32; }
	else if (icopy.find("d7") != string::npos)  { output = 33; }
	else if (icopy.find("m7") != string::npos)  { output = 34; }
	else if (icopy.find("M7") != string::npos)  { output = 35; }
	else if (icopy.find("AA7") != string::npos) { output = 37; }
	else if (icopy.find("A7") != string::npos)  { output = 36; }

	else if (icopy.find("dd8") != string::npos) { output = 38; }
	else if (icopy.find("d8") != string::npos)  { output = 39; }
	else if (icopy.find("P8") != string::npos)  { output = 40; }
	else if (icopy.find("AA8") != string::npos) { output = 42; }
	else if (icopy.find("A8") != string::npos)  { output = 41; }

	else if (icopy.find("dd9") != string::npos) { output = 43; }
	else if (icopy.find("d9") != string::npos)  { output = 44; }
	else if (icopy.find("m9") != string::npos)  { output = 45; }
	else if (icopy.find("M9") != string::npos)  { output = 46; }
	else if (icopy.find("AA9") != string::npos) { output = 48; }
	else if (icopy.find("A9") != string::npos)  { output = 47; }

	return output * sign;
}



//////////////////////////////
//
// Tool_transpose::example --
//

void Tool_transpose::example(void) {


}



//////////////////////////////
//
// Tool_transpose::usage --
//

void Tool_transpose::usage(const string& command) {

}



///////////////////////////////////////////////////////////////////////////
//
// Automatic transposition functions
//


//////////////////////////////
//
// Tool_transpose::doAutoTransposeAnalysis --
//

void Tool_transpose::doAutoTransposeAnalysis(HumdrumFile& infile) {
	vector<int> ktracks(infile.getMaxTrack()+1, 0);

	vector<HTp> tracks;
	infile.getTrackStartList(tracks);
	int i;
	for (i=0; i<(int)tracks.size(); i++) {
		if (tracks[i]->isKern()) {
			ktracks[i] = tracks[i]->getTrack();
		} else {
			ktracks[i] = 0;
		}
	}

	int segments = int(infile.getScoreDuration().getFloat()+0.5);
	if (segments < 1) {
		segments = 1;
	}

	vector<vector<vector<double> > > trackhist;
	trackhist.resize(ktracks.size());

	for (i=1; i<(int)trackhist.size(); i++) {
		if (ktracks[i]) {
			storeHistogramForTrack(trackhist[i], infile, i, segments);
		}
	}

	if (debugQ) {
		m_free_text << "Segment pitch histograms: " << endl;
		printHistograms(segments, ktracks, trackhist);
	}

	int level = 16;
	int hop   = 8;
	int count = segments / hop;

	if (segments < count * level / (double)hop) {
		level = level / 2;
		hop   = hop / 2;
	}
	if (segments < count * level / (double)hop) {
		count = count / 2;
	}

	if (segments < count * level / (double)hop) {
		level = level / 2;
		hop   = hop / 2;
	}
	if (segments < count * level / (double)hop) {
		count = count / 2;
	}

	vector<vector<vector<double> > > analysis;

	doAutoKeyAnalysis(analysis, level, hop, count, segments, ktracks, trackhist);

	// print analyses raw results

	m_free_text << "Raw key analysis by track:" << endl;
	printRawTrackAnalysis(analysis, ktracks);

	doTranspositionAnalysis(analysis);
}



//////////////////////////////
//
// Tool_transpose::doTranspositionAnalysis --
//

void Tool_transpose::doTranspositionAnalysis(vector<vector<vector<double> > >& analysis) {
	int i, j, k;
	int value1;
	int value2;
	int value;

	for (i=0; i<1; i++) {
		for (j=2; j<3; j++) {
			for (k=0; k<(int)analysis[i].size(); k++) {
				if (analysis[i][k][24] >= 0 && analysis[j][k][24] >= 0) {
					value1 = (int)analysis[i][k][25];
	 				if (value1 >= 12) {
						  value1 = value1 - 12;
					}
					value2 = (int)analysis[j][k][25];
	 				if (value2 >= 12) {
						  value2 = value2 - 12;
					}
					value = value1 - value2;
					if (value < 0) {
						  value = value + 12;
					}
					if (value > 6) {
						  value = 12 - value;
					}
					m_free_text << value << endl;
				}
			}
		}
	}
}



//////////////////////////////
//
// Tool_transpose::printRawTrackAnalysis --
//

void Tool_transpose::printRawTrackAnalysis(vector<vector<vector<double> > >& analysis,
		vector<int>& ktracks) {

	int i, j;
	int value;
	int value2;

	for (i=0; i<(int)analysis[0].size(); i++) {
		m_free_text << "Frame\t" << i << ":";
		for (j=0; j<(int)analysis.size(); j++) {
			m_free_text << "\t";
	 		value = (int)analysis[j][i][24];
	 		if (value >= 12) {
				value = value - 12;
			}
	 		value2 = (int)analysis[j][i][25];
	 		if (value2 >= 12) {
				value2 = value2 - 12;
			}
			m_free_text << value;
	 		// if (value != value2) {
	 		//    m_free_text << "," << value2;
			// }
		}
		m_free_text << "\n";
	}
}



//////////////////////////////
//
// doAutoKeyAnalysis --
//

void Tool_transpose::doAutoKeyAnalysis(vector<vector<vector<double> > >& analysis, int level,
		int hop, int count, int segments, vector<int>& ktracks,
		vector<vector<vector<double> > >& trackhist) {

	vector<double> majorweights;
	vector<double> minorweights;
	fillWeightsWithKostkaPayne(majorweights, minorweights);

	int size = 0;
	int i;
	for (i=1; i<(int)ktracks.size(); i++) {
		if (ktracks[i]) {
			size++;
		}
	}

	analysis.resize(size);
	for (i=0; i<(int)analysis.size(); i++) {
		analysis[i].reserve(count);
	}

	int aindex = 0;
	for (i=1; i<(int)ktracks.size(); i++) {
		if (!ktracks[i]) {
			continue;
		}
		doTrackKeyAnalysis(analysis[aindex++], level, hop, count,
				trackhist[i], majorweights, minorweights);
	}
}



//////////////////////////////
//
// Tool_transpose::doTrackKeyAnalysis -- Do individual key analyses of sections of the
//   given track.
//

void Tool_transpose::doTrackKeyAnalysis(vector<vector<double> >& analysis, int level, int hop,
		int count, vector<vector<double> >& trackhist,
		vector<double>& majorweights, vector<double>& minorweights) {

	int i;
	for (i=0; i<count; i++) {
		if (i * hop + level > (int)trackhist.size()) {
			break;
		}
		analysis.resize(i+1);
		doSingleAnalysis(analysis[analysis.size()-1], i*hop+level, level,
				trackhist, majorweights, minorweights);
	}
}



//////////////////////////////
//
// Tool_transpose::doSingleAnalysis --
//

void Tool_transpose::doSingleAnalysis(vector<double>& analysis, int startindex, int length,
		vector<vector<double> >& trackhist, vector<double>& majorweights,
		vector<double>& minorweights) {
	vector<double> histsum(12, 0);

	for (int i=0; (i<length) && (startindex+i+length<(int)trackhist.size()); i++) {
		for (int k=0; k<12; k++) {
			histsum[k] += trackhist[i+startindex][k];
		}
	}

	identifyKey(analysis, histsum, majorweights, minorweights);
}



///////////////////////////////
//
// Tool_transpose::fillWeightsWithKostkaPayne --
//

void Tool_transpose::fillWeightsWithKostkaPayne(vector<double>& maj, vector<double>& min) {
	maj.resize(12);
	min.resize(12);

	// found in David Temperley: Music and Probability 2006
	maj[0]  = 0.748;	// C major weights
	maj[1]  = 0.060;	// C#
	maj[2]  = 0.488;	// D
	maj[3]  = 0.082;	// D#
	maj[4]  = 0.670;	// E
	maj[5]  = 0.460;	// F
	maj[6]  = 0.096;	// F#
	maj[7]  = 0.715;	// G
	maj[8]  = 0.104;	// G#
	maj[9]  = 0.366;	// A
	maj[10] = 0.057;	// A#
	maj[11] = 0.400;	// B
	min[0]  = 0.712;	// c minor weights
	min[1]  = 0.084;	// c#
	min[2]  = 0.474;	// d
	min[3]  = 0.618;	// d#
	min[4]  = 0.049;	// e
	min[5]  = 0.460;	// f
	min[6]  = 0.105;	// f#
	min[7]  = 0.747;	// g
	min[8]  = 0.404;	// g#
	min[9]  = 0.067;	// a
	min[10] = 0.133;	// a#
	min[11] = 0.330;	// b
}



////////////////////////////////////////
//
// identifyKey -- correls contains the 12 major key correlation
//      values, then the 12 minor key correlation values, then two
//      more values: index=24 is the best key, and index=25 is the
//      second best key.  If [24] or [25] is -1, then that means that
//      all entries in the original histogram were zero (all rests).
//

void Tool_transpose::identifyKey(vector<double>& correls,
		vector<double>& histogram, vector<double>& majorweights,
		vector<double>& minorweights) {

	correls.clear();
	correls.reserve(26);

	double testsum = 0.0;
	for (int i=0; i<12; i++) {
		testsum += histogram[i];
	}
	if (testsum == 0.0) {
		correls.resize(26);
		fill(correls.begin(), correls.end(), -1);
		correls[24] = -1;
		correls[25] = -1;
		return;
	}

	vector<double> majorcorrels;
	vector<double> minorcorrels;
	for (int i=0; i<12; i++) {
		majorcorrels[i] = Convert::pearsonCorrelation(majorweights, histogram);
		minorcorrels[i] = Convert::pearsonCorrelation(minorweights, histogram);
	}

	// find max value
	int besti;
	int majorbesti = 0;
	int minorbesti = 0;
	for (int i=1; i<12; i++) {
		if (majorcorrels[i] > majorcorrels[majorbesti]) {
			majorbesti = i;
		}
		if (minorcorrels[i] > minorcorrels[minorbesti]) {
			minorbesti = i;
		}
	}
	besti = majorbesti;
	if (majorcorrels[majorbesti] < minorcorrels[minorbesti]) {
		besti = minorbesti + 12;
	}

	// find second best major key
	int majorsecondbesti = 0;
	if (majorbesti == 0) {
		majorsecondbesti = 1;
	}
	for (int i=1; i<12; i++) {
		if (i == majorbesti) {
			continue;
		}
		if (majorcorrels[i] > majorcorrels[majorsecondbesti]) {
			majorsecondbesti = i;
		}
	}

	// find second best minor key
	int minorsecondbesti = 0;
	if (minorbesti == 0) {
		minorsecondbesti = 1;
	}
	for (int i=1; i<12; i++) {
		if (i == minorbesti) {
			continue;
		}
		if (minorcorrels[i] > minorcorrels[minorsecondbesti]) {
			minorsecondbesti = i;
		}
	}

	int secondbesti = majorsecondbesti;
	if (majorcorrels[majorsecondbesti] < minorcorrels[minorsecondbesti]) {
		secondbesti = minorsecondbesti;
	}
	secondbesti += 12;

	correls = majorcorrels;
	correls.insert(correls.end(), minorcorrels.begin(), minorcorrels.end());
	correls.push_back(besti);
	correls.push_back(secondbesti);
}



//////////////////////////////
//
// Tool_transpose::printHistograms --
//

void Tool_transpose::printHistograms(int segments, vector<int> ktracks,
vector<vector<vector<double> > >& trackhist) {
	int i, j, k;
	int start;

	for (i=0; i<segments; i++) {
//m_free_text << "i=" << i << endl;
		m_free_text << "segment " << i
				<< " ==========================================\n";
		for (j=0; j<12; j++) {
			start = 0;
//m_free_text << "j=" << i << endl;
			for (k=1; k<(int)ktracks.size(); k++) {
//m_free_text << "k=" << i << endl;
				if (!ktracks[k]) {
					continue;
				}
				if (!start) {
					m_free_text << j;
					start = 1;
				}
				m_free_text << "\t";
				m_free_text << trackhist[k][i][j];
			}
	 if (start) {
				m_free_text << "\n";
			}
		}
	}
	m_free_text << "==========================================\n";
}



//////////////////////////////
//
// Tool_transpose::storeHistogramForTrack --
//

double Tool_transpose::storeHistogramForTrack(vector<vector<double> >& histogram,
		HumdrumFile& infile, int track, int segments) {

	histogram.clear();
	histogram.reserve(segments);

	int i;
	int j;
	int k;

	for (i=0; i<(int)histogram.size(); i++) {
		histogram[i].resize(12);
		fill(histogram[i].begin(), histogram[i].end(), 0);
	}

	double totalduration = infile.getScoreDuration().getFloat();

	double duration;
	string buffer;
	int pitch;
	double start;
	int tokencount;
	for (i=0; i<infile.getLineCount(); i++) {
		if (!infile[i].isData()) {
			continue;
		}
		start = infile[i].getDurationFromStart().getFloat();
		for (j=0; j<infile[i].getFieldCount(); j++) {
			if (!infile.token(i, j)->isKern()) {
				continue;
			}
	 		if (infile.token(i, j)->getTrack() != track) {
				continue;
			}
			if (!infile.token(i, j)->isKern()) {
				continue;
			}
			if (!infile.token(i, j)->isNull()) {
				continue;
			}
			tokencount = infile.token(i, j)->getSubtokenCount();
			for (k=0; k<tokencount; k++) {
				buffer = *infile.token(j, k);
				if (buffer == ".") {
					continue;  // ignore illegal inline null tokens
				}
				pitch = Convert::kernToMidiNoteNumber(buffer);
				if (pitch < 0) {
					continue;  // ignore rests or strange objects
				}
				pitch = pitch % 12;  // convert to chromatic pitch-class
				duration = Convert::recipToDuration(buffer).getFloat();
				if (duration <= 0.0) {
					continue;   // ignore grace notes and strange objects
				}
				addToHistogramDouble(histogram, pitch,
						  start, duration, totalduration, segments);
			}
		}
	}

	return totalduration;
}



//////////////////////////////
//
// Tool_transpose::addToHistogramDouble -- fill the pitch histogram in the right spots.
//

void Tool_transpose::addToHistogramDouble(vector<vector<double> >& histogram, int pc,
		double start, double dur, double tdur, int segments) {

	pc = (pc + 12) % 12;

	double startseg = start / tdur * segments;
	double startfrac = startseg - (int)startseg;

	double segdur = dur / tdur * segments;

	if (segdur <= 1.0 - startfrac) {
		histogram[(int)startseg][pc] += segdur;
		return;
	} else if (1.0 - startfrac > 0.0) {
		histogram[(int)startseg][pc] += (1.0 - startfrac);
		segdur -= (1.0 - startfrac);
	}

	int i = (int)(startseg + 1);
	while (segdur > 0.0 && i < (int)histogram.size()) {
		if (segdur < 1.0) {
			histogram[i][pc] += segdur;
			segdur = 0.0;
		} else {
			histogram[i][pc] += 1.0;
			segdur -= 1.0;
		}
		i++;
	}
}



//////////////////////////////
//
// Tool_transpose::initialize --
//

void Tool_transpose::initialize(HumdrumFile& infile) {

	// handle basic options:
	if (getBoolean("author")) {
		m_free_text << "Written by Craig Stuart Sapp, "
			  << "craig@ccrma.stanford.edu, 12 Apr 2004" << endl;
		exit(0);
	} else if (getBoolean("version")) {
		m_free_text << getArg(0) << ", version: 10 Dec 2016" << endl;
		m_free_text << "compiled: " << __DATE__ << endl;
		exit(0);
	} else if (getBoolean("help")) {
		usage(getArg(0));
		exit(0);
	} else if (getBoolean("example")) {
		example();
		exit(0);
	}

	transval     =  getInteger("base40");
	ssettonicQ   =  getBoolean("settonic");
	ssettonic    =  Convert::kernToBase40(getString("settonic").c_str());
	autoQ        =  getBoolean("auto");
	debugQ       =  getBoolean("debug");
	spinestring  =  getString("spines");
	octave       =  getInteger("octave");
	concertQ     =  getBoolean("concert");
	writtenQ     =  getBoolean("written");
	quietQ       = !getBoolean("transcode");
	instrumentQ  =  getBoolean("instrument");

	switch (getBoolean("diatonic") + getBoolean("chromatic")) {
		case 1:
			cerr << "Error: both -d and -c options must be specified" << endl;
			exit(1);
			break;
		case 2:
			{
				char buffer[128] = {0};
				snprintf(buffer, 128, "d%dc%d", getInt("d"), getInt("c"));
				transval = Convert::transToBase40(buffer);
			}
			break;
	}

	ssettonic = ssettonic % 40;

	if (getBoolean("transpose")) {
		transval = getBase40ValueFromInterval(getString("transpose").c_str());
	}

	transval += 40 * octave;
}




/////////////////////////////////
//
// Tool_tremolo::Tool_tremolo -- Set the recognized options for the tool.
//

Tool_tremolo::Tool_tremolo(void) {
	define("k|keep=b",                      "keep tremolo rhythm markup");
	define("F|no-fill=b",                   "do not fill in tremolo spaces");
	define("T|no-tremolo-interpretation=b", "do not add *tremolo/*Xtremolo marks");
}



/////////////////////////////////
//
// Tool_tremolo::run -- Do the main work of the tool.
//

bool Tool_tremolo::run(HumdrumFileSet& infiles) {
	bool status = true;
	for (int i=0; i<infiles.getCount(); i++) {
		status &= run(infiles[i]);
	}
	return status;
}


bool Tool_tremolo::run(const string& indata, ostream& out) {
	HumdrumFile infile(indata);
	bool status = run(infile);
	if (hasAnyText()) {
		getAllText(out);
	} else {
		out << infile;
	}
	return status;
}


bool Tool_tremolo::run(HumdrumFile& infile, ostream& out) {
	bool status = run(infile);
	if (hasAnyText()) {
		getAllText(out);
	} else {
		out << infile;
	}
	return status;
}


bool Tool_tremolo::run(HumdrumFile& infile) {
	processFile(infile);

	// Force reprocessing of file for now (does not seem to be
	// completely updated in javascript):
	stringstream ss;
	ss << infile;
	infile.readString(ss.str());

	return true;
}



//////////////////////////////
//
// Tool_tremolo::processFile --
//

void Tool_tremolo::processFile(HumdrumFile& infile) {
	m_keepQ = getBoolean("keep");
	m_first_tremolo_time.clear();
	m_last_tremolo_time.clear();
	int maxtrack = infile.getMaxTrack();
	m_first_tremolo_time.resize(maxtrack+1);
	m_last_tremolo_time.resize(maxtrack+1);
	fill(m_first_tremolo_time.begin(), m_first_tremolo_time.end(), -1);
	fill(m_last_tremolo_time.begin(), m_last_tremolo_time.end(), -1);
	HumRegex hre;
	m_markup_tokens.reserve(1000);
	for (int i=infile.getLineCount()-1; i>=0; i--) {
		if (!infile[i].isData()) {
			continue;
		}
		if (infile[i].getDuration() == 0) {
			// don't deal with grace notes
			continue;
		}
		for (int j=0; j<infile[i].getFieldCount(); j++) {
			HTp token = infile.token(i, j);
			if (!token->isKern()) {
				continue;
			}
			if (token->isNull()) {
				continue;
			}

			if (hre.search(token, "@(\\d+)@")) {
				m_markup_tokens.push_back(token);
				int value = hre.getMatchInt(1);
				HumNum duration = Convert::recipToDuration(token);
				HumNum count = duration;
				count *= value;
				count /= 4;
				HumNum increment = 4;
				increment /= value;

				if (token->find("@@") != string::npos) {
					count *= 2;
				}

				if (!count.isInteger()) {
					cerr << "Error: time value cannot be used: " << value << endl;
					continue;
				}
				int kcount = count.getNumerator();
				HumNum starttime = token->getDurationFromStart();
				HumNum timestamp;
				for (int k=1; k<kcount; k++) {
					timestamp = starttime + (increment * k);
					infile.insertNullDataLine(timestamp);
				}
			}

		}
	}

	if (!getBoolean("no-fill")) {
		expandTremolos();
		if (!getBoolean("no-tremolo-interpretation")) {
			addTremoloInterpretations(infile);
		}
	} else if (!m_keepQ) {
		removeMarkup();
	}

	if (m_modifiedQ) {
		infile.createLinesFromTokens();
	}

	// m_humdrum_text << infile;
}



//////////////////////////////
//
// Tool_tremolo::addTremoloInterpretations --
//

void Tool_tremolo::addTremoloInterpretations(HumdrumFile& infile) {

	// Insert starting *tremolo
	for (int i=0; i<(int)m_first_tremolo_time.size(); i++) {
		if (m_first_tremolo_time[i] < 0) {
			continue;
		}
		HLp line = infile.insertNullInterpretationLine(m_first_tremolo_time[i]);
		if (line != NULL) {
			for (int j=0; j<line->getFieldCount(); j++) {
				HTp token = line->token(j);
				int track = token->getTrack();
				int subtrack = token->getSubtrack();
				if (subtrack > 1) {
					// Currently *tremolo affects all subtracks, but this
					// will probably change in the future.
					continue;
				}
				if (track == i) {
					token->setText("*tremolo");
					line->createLineFromTokens();
				}
			}
		}
	}

	// Insert ending *Xtremolo
	for (int i=0; i<(int)m_last_tremolo_time.size(); i++) {
		if (m_last_tremolo_time[i] < 0) {
			continue;
		}
		HLp line = infile.insertNullInterpretationLineAbove(m_last_tremolo_time[i]);
		if (line != NULL) {
			for (int j=0; j<line->getFieldCount(); j++) {
				HTp token = line->token(j);
				int track = token->getTrack();
				int subtrack = token->getSubtrack();
				if (subtrack > 1) {
					// Currently *tremolo affects all subtracks, but this
					// will probably change in the future.
					continue;
				}
				if (track == i) {
					token->setText("*Xtremolo");
					line->createLineFromTokens();
				}
			}
		}
	}
}



//////////////////////////////
//
// Tool_tremolo::expandTremolos --
//

void Tool_tremolo::expandTremolos(void) {
	for (int i=0; i<(int)m_markup_tokens.size(); i++) {
		if (m_markup_tokens[i]->find("@@") != string::npos) {
			expandFingerTremolo(m_markup_tokens[i]);
		} else {
			expandTremolo(m_markup_tokens[i]);
		}
	}
}



//////////////////////////////
//
// Tool_tremolo::expandTremolo --
//

void Tool_tremolo::expandTremolo(HTp token) {
	HumRegex hre;
	HumNum value = 0;
	HumNum duration;
	HumNum repeat;
	HumNum increment;
	bool addBeam = false;
	int tnotes = -1;
	if (hre.search(token, "@(\\d+)@")) {
		value = hre.getMatchInt(1);
		duration = Convert::recipToDuration(token);
		HumNum count = duration * value / 4;
		if (!count.isInteger()) {
			cerr << "Error: non-integer number of tremolo notes: " << token << endl;
			return;
		}
		if (value < 8) {
			cerr << "Error: tremolo notes can only be eighth-notes or shorter" << endl;
			return;
		}
		if (duration.getFloat() > 0.5) {
			// needs to be less that one for tuplet quarter note tremolos
			addBeam = true;
		}

		// There are cases where duration < 1 need added beams
		// when the note is not already in a beam.  Such as
		// a plain 8th note with a slash.  This needs to be
		// converted into two 16th notes with a beam so that
		// *tremolo can reduce it back into a tremolo, since
		// it will only reduce beam groups.

		repeat = duration;
		repeat *= value;
		repeat /= 4;
		increment = 4;
		increment /= value;
		if (!repeat.isInteger()) {
			cerr << "Error: tremolo repetition count must be an integer: " << token << endl;
			return;
		}
		tnotes = repeat.getNumerator();
	} else {
		return;
	}

	storeFirstTremoloNoteInfo(token);

	int beams = log((double)(value.getFloat()))/log(2.0) - 2;
	string markup = "@" + to_string(value.getNumerator()) + "@";
	string base = token->getText();
	hre.replaceDestructive(base, "", markup, "g");

	// complicated beamings are not allowed yet (no internal L/J markers in tremolo beam)
	bool hasBeamStart = base.find('L') != string::npos;
	bool hasBeamStop  = base.find('J') != string::npos;

	if (addBeam) {
		hasBeamStart = true;
		hasBeamStop = true;
	}

	// Currently not allowed to add tremolo to beamed notes, so remove all beaming:
	hre.replaceDestructive(base, "", "[LJKk]+", "g");
	string startbeam;
	string endbeam;
	for (int i=0; i<beams; i++) {
		startbeam += 'L';
		endbeam   += 'J';
	}
	// Set the rhythm of the tremolo notes.
	// Augmentation dot is expected adjacent to regular rhythm value.
	// Maybe allow anywhere?
	hre.replaceDestructive(base, to_string(value.getNumerator()), "\\d+%?\\d*\\.*", "g");
	string initial = base;
	if (hasBeamStart) {
		initial += startbeam;
	}
	string terminal = base;
	if (hasBeamStop) {
		terminal += endbeam;
	}
	// remove slur end from start of tremolo:
	hre.replaceDestructive(initial, "", "[)]+[<>]?", "g");
	if (m_keepQ) {
		initial += markup;
	}
	// remove slur start information from end of tremolo:
	hre.replaceDestructive(terminal, "", "[(]+[<>]?", "g");

	// remove slur information from middle of tremolo:
	hre.replaceDestructive(base, "", "[()]+[<>]?", "g");

	token->setText(initial);
	token->getOwner()->createLineFromTokens();

	// Now fill in the rest of the tremolos.
	HumNum starttime = token->getDurationFromStart();
	HumNum timestamp = starttime + increment;
	HTp current = token->getNextToken();
	int counter = 1;
	while (current) {
		if (!current->isData()) {
			current = current->getNextToken();
			continue;
		}
		HumNum duration = current->getOwner()->getDuration();
		if (duration == 0) {
			// grace note line, so skip
			current = current->getNextToken();
			continue;
		}
		HumNum cstamp = current->getDurationFromStart();
		if (cstamp < timestamp) {
			current = current->getNextToken();
			continue;
		}
		if (cstamp > timestamp) {
			cerr << "\tWarning: terminating tremolo insertion early" << endl;
			cerr << "\tCSTAMP : " << cstamp << " TSTAMP " << timestamp << endl;
			break;
		}
		counter++;
		if (tnotes == counter) {
			current->setText(terminal);
			storeLastTremoloNoteInfo(current);
		} else {
			current->setText(base);
		}
		current->getOwner()->createLineFromTokens();
		if (counter >= tnotes) {
			// done with inserting of tremolo notes.
			break;
		}
		timestamp += increment;
		current = current->getNextToken();
	}
}


//////////////////////////////
//
// Tool_tremolo::getNextNote --
//

HTp Tool_tremolo::getNextNote(HTp token) {
	HTp output = NULL;
	HTp current = token->getNextToken();
	while (current) {
		if (!current->isData()) {
			current = current->getNextToken();
			continue;
		}
		if (current->getDuration() == 0) {
			// ignore grace notes
			current = current->getNextToken();
			continue;
		}
		if (current->isNull() || current->isRest()) {
			current = current->getNextToken();
			continue;
		}
		output = current;
		break;
	}

	return output;
}


//////////////////////////////
//
// Tool_tremolo::expandFingerTremolos --
//

void Tool_tremolo::expandFingerTremolo(HTp token1) {
	HTp token2 = getNextNote(token1);
	if (token2 == NULL) {
		return;
	}
	HumRegex hre;
	int value = 0;
	HumNum duration;
	HumNum repeat;
	HumNum increment;
	int tnotes = -1;
	if (hre.search(token1, "@@(\\d+)@@")) {
		value = hre.getMatchInt(1);
		if (!Convert::isPowerOfTwo(value)) {
			cerr << "Error: not a power of two: " << token1 << endl;
			return;
		}
		if (value < 8) {
			cerr << "Error: tremolo can only be eighth-notes or shorter" << endl;
			return;
		}
		duration = Convert::recipToDuration(token1);
		HumNum count = duration;

		count *= value;
		count /= 4;
		if (!count.isInteger()) {
			cerr << "Error: tremolo repetition count must be an integer: " << token1 << endl;
			return;
		}
		increment = 4;
		increment /= value;

		tnotes = count.getNumerator() * 2;
	} else {
		return;
	}

	storeFirstTremoloNoteInfo(token1);

	int beams = log((double)(value))/log(2.0) - 2;
	string markup = "@@" + to_string(value) + "@@";

	string base1 = token1->getText();
	hre.replaceDestructive(base1, "", markup, "g");
	// Currently not allowed to add tremolo to beamed notes, so remove all beaming:
	hre.replaceDestructive(base1, "", "[LJKk]+", "g");
	string startbeam;
	string endbeam;
	for (int i=0; i<beams; i++) {
		startbeam += 'L';
		endbeam   += 'J';
	}

	// Set the rhythm of the tremolo notes.
	// Augmentation dot is expected adjacent to regular rhythm value.
	// Maybe allow anywhere?
	hre.replaceDestructive(base1, to_string(value), "\\d+%?\\d*\\.*", "g");
	string initial = base1 + startbeam;
	// remove slur end from start of tremolo:
	hre.replaceDestructive(initial, "", "[)]+[<>]?", "g");
	if (m_keepQ) {
		initial += markup;
	}

	// remove slur information from middle of tremolo:
	hre.replaceDestructive(base1, "", "[()]+[<>]?", "g");

	token1->setText(initial);
	token1->getOwner()->createLineFromTokens();

	string base2 = token2->getText();
	hre.replaceDestructive(base2, "", "[LJKk]+", "g");
	hre.replaceDestructive(base2, to_string(value), "\\d+%?\\d*\\.*", "g");

	string terminal = base2 + endbeam;
	// remove slur start information from end of tremolo:
	hre.replaceDestructive(terminal, "", "[(]+[<>]?", "g");

	bool state = false;

	// Now fill in the rest of the tremolos.
	HumNum starttime = token1->getDurationFromStart();
	HumNum timestamp = starttime + increment;
	HTp current = token1->getNextToken();
	int counter = 1;
	while (current) {
		if (!current->isData()) {
			// Also check if line is non-zero duration (not a grace-note line).
			current = current->getNextToken();
			continue;
		}
		HumNum cstamp = current->getDurationFromStart();
		if (cstamp < timestamp) {
			current = current->getNextToken();
			continue;
		}
		if (cstamp > timestamp) {
			cerr << "\tWarning: terminating tremolo insertion early" << endl;
			cerr << "\tCSTAMP : " << cstamp << " TSTAMP " << timestamp << endl;
			break;
		}
		counter++;
		if (tnotes == counter) {
			current->setText(terminal);
			storeLastTremoloNoteInfo(current);
		} else {
			if (state) {
				current->setText(base1);
			} else {
				current->setText(base2);
			}
			state = !state;
		}
		current->getOwner()->createLineFromTokens();
		if (counter >= tnotes) {
			// done with inserting of tremolo notes.
			break;
		}
		timestamp += increment;
		current = current->getNextToken();
	}
}



//////////////////////////////
//
// Tool_tremolo::removeMarkup --  Remove markup such as "@16@" from tokens.
//

void Tool_tremolo::removeMarkup(void) {
	if (m_markup_tokens.empty()) {
		return;
	}
	HumRegex hre;
	for (int i=0; i<(int)m_markup_tokens.size(); i++) {
		HTp token = m_markup_tokens[i];
		string text = *token;
		hre.replaceDestructive(text, "", "@+\\d+@+");
		token->setText(text);
		token->getOwner()->createLineFromTokens();
	}
}



//////////////////////////////
//
// Tool_tremolo::storeFirstTremoloNote --
//

void Tool_tremolo::storeFirstTremoloNoteInfo(HTp token) {
	int track = token->getTrack();
	HumNum timestamp = token->getDurationFromStart();
	if (m_first_tremolo_time.at(track) < 0) {
		m_first_tremolo_time.at(track) = timestamp;
	} else if (timestamp < m_first_tremolo_time.at(track)) {
		// This case is probably not necessary.
		m_first_tremolo_time.at(track) = timestamp;
	}
}



//////////////////////////////
//
// Tool_tremolo::storeLastTremoloNote --
//

void Tool_tremolo::storeLastTremoloNoteInfo(HTp token) {
	if (!token) {
		return;
	}
	int track = token->getTrack();
	if (track < 1) {
		cerr << "Track is not set for token: " << track << endl;
		return;
	}
	HumNum timestamp = token->getDurationFromStart();
	timestamp += Convert::recipToDuration(token);
	if (m_last_tremolo_time.at(track) < 0) {
		m_last_tremolo_time.at(track) = timestamp;
	} else if (timestamp > m_last_tremolo_time.at(track)) {
		m_last_tremolo_time.at(track) = timestamp;
	}
}





/////////////////////////////////
//
// Tool_trillspell::Tool_trillspell -- Set the recognized options for the tool.
//

Tool_trillspell::Tool_trillspell(void) {
	define("x=b", "mark trills with x (interpretation)");
}



///////////////////////////////
//
// Tool_trillspell::run -- Primary interfaces to the tool.
//

bool Tool_trillspell::run(HumdrumFileSet& infiles) {
	bool status = true;
	for (int i=0; i<infiles.getCount(); i++) {
		status &= run(infiles[i]);
	}
	return status;
}

bool Tool_trillspell::run(const string& indata, ostream& out) {
	HumdrumFile infile(indata);
	return run(infile, out);
}


bool Tool_trillspell::run(HumdrumFile& infile, ostream& out) {
	bool status = run(infile);
	return status;
}


bool Tool_trillspell::run(HumdrumFile& infile) {
	processFile(infile);
	infile.createLinesFromTokens();
	return true;
}



///////////////////////////////
//
// Tool_trillspell::processFile -- Adjust intervals of ornaments.
//

void Tool_trillspell::processFile(HumdrumFile& infile) {
	m_xmark = getBoolean("x");
	analyzeOrnamentAccidentals(infile);
}



//////////////////////////////
//
// Tool_trillspell::analyzeOrnamentAccidentals --
//

bool Tool_trillspell::analyzeOrnamentAccidentals(HumdrumFile& infile) {
	int i, j, k;
	int kindex;
	int track;

	// ktracks == List of **kern spines in data.
	// rtracks == Reverse mapping from track to ktrack index (part/staff index).
	vector<HTp> ktracks = infile.getKernSpineStartList();
	vector<int> rtracks(infile.getMaxTrack()+1, -1);
	for (i=0; i<(int)ktracks.size(); i++) {
		track = ktracks[i]->getTrack();
		rtracks[track] = i;
	}
	int kcount = (int)ktracks.size();

	// keysigs == key signature spellings of diatonic pitch classes.  This array
	// is duplicated into dstates after each barline.
	vector<vector<int> > keysigs;
	keysigs.resize(kcount);
	for (i=0; i<kcount; i++) {
		keysigs[i].resize(7);
		std::fill(keysigs[i].begin(), keysigs[i].end(), 0);
	}

	// dstates == diatonic states for every pitch in a spine.
	// sub-spines are considered as a single unit, although there are
	// score conventions which would keep a separate voices on a staff
	// with different accidental states (i.e., two parts superimposed
	// on the same staff, but treated as if on separate staves).
	// Eventually this algorithm should be adjusted for dealing with
	// cross-staff notes, where the cross-staff notes should be following
	// the accidentals of a different spine...
	vector<vector<int> > dstates; // diatonic states
	dstates.resize(kcount);
	for (i=0; i<kcount; i++) {
		dstates[i].resize(70);     // 10 octave limit for analysis
			                        // may cause problems; maybe fix later.
		std::fill(dstates[i].begin(), dstates[i].end(), 0);
	}

	for (i=0; i<infile.getLineCount(); i++) {
		if (!infile[i].hasSpines()) {
			continue;
		}
		if (infile[i].isInterpretation()) {
			for (j=0; j<infile[i].getFieldCount(); j++) {
				HTp token = infile.token(i, j);
				if (!token->isKern()) {
					continue;
				}
				if (infile[i].token(j)->compare(0, 3, "*k[") == 0) {
					track = infile[i].token(j)->getTrack();
					kindex = rtracks[track];
					fillKeySignature(keysigs[kindex], *infile[i].token(j));
					// resetting key states of current measure.  What to do if this
					// key signature is in the middle of a measure?
					resetDiatonicStatesWithKeySignature(dstates[kindex],
							keysigs[kindex]);
				}
			}
		} else if (infile[i].isBarline()) {
			for (j=0; j<infile[i].getFieldCount(); j++) {
				if (!infile[i].token(j)->isKern()) {
					continue;
				}
				if (infile[i].token(j)->isInvisible()) {
					continue;
				}
				track = infile[i].token(j)->getTrack();
				kindex = rtracks[track];
				// reset the accidental states in dstates to match keysigs.
				resetDiatonicStatesWithKeySignature(dstates[kindex],
						keysigs[kindex]);
			}
		}

		if (!infile[i].isData()) {
			continue;
		}

		for (j=0; j<infile[i].getFieldCount(); j++) {
			HTp token = infile.token(i, j);
			if (!token->isKern()) {
				continue;
			}
			if (token->isNull()) {
				continue;
			}
			if (token->isRest()) {
				continue;
			}

			int subcount = token->getSubtokenCount();
			track = token->getTrack();

			HumRegex hre;
			int rindex = rtracks[track];
			for (k=0; k<subcount; k++) {
				string subtok = token->getSubtoken(k);
				int b40 = Convert::kernToBase40(subtok);
				int diatonic = Convert::kernToBase7(subtok);
				if (diatonic < 0) {
					// Deal with extra-low notes later.
					continue;
				}
				int accid = Convert::kernToAccidentalCount(subtok);
				dstates.at(rindex).at(diatonic) = accid;

				// check for accidentals on trills, mordents and turns.
				// N.B.: augmented-second intervals are not considered.

				if ((subtok.find("t") != string::npos) && !hre.search(subtok, "[tT]x")) {
					int nextup = getBase40(diatonic + 1, dstates[rindex][diatonic+1]);
					int interval = nextup - b40;
					if (interval == 6) {
						// Set to major-second trill
						hre.replaceDestructive(subtok, "T", "t", "g");
						if (m_xmark) {
							hre.replaceDestructive(subtok, "$1x", "([Tt]+)", "g");
						}
						token->replaceSubtoken(k, subtok);
					} else {
						if (m_xmark) {
							hre.replaceDestructive(subtok, "$1x", "([Tt]+)", "g");
						}
						token->replaceSubtoken(k, subtok);
					}
				} else if ((subtok.find("T") != string::npos) && !hre.search(subtok, "[tT]x")) {
					int nextup = getBase40(diatonic + 1, dstates[rindex][diatonic+1]);
					int interval = nextup - b40;
					if (interval == 5) {
						// Set to minor-second trill
						hre.replaceDestructive(subtok, "t", "T", "g");
						if (m_xmark) {
							hre.replaceDestructive(subtok, "$1x", "([Tt]+)", "g");
						}
						token->replaceSubtoken(k, subtok);
					} else {
						if (m_xmark) {
							hre.replaceDestructive(subtok, "$1x", "([Tt]+)", "g");
						}
						token->replaceSubtoken(k, subtok);
					}
				} else if ((subtok.find("M") != string::npos) && !hre.search(subtok, "[Mm]x")) {
					// major-second upper mordent
					int nextup = getBase40(diatonic + 1, dstates[rindex][diatonic+1]);
					int interval = nextup - b40;
					if (interval == 5) {
						// Set to minor-second upper mordent
						hre.replaceDestructive(subtok, "m", "M", "g");
						if (m_xmark) {
							hre.replaceDestructive(subtok, "$1x", "([Mm]+)", "g");
						}
						token->replaceSubtoken(k, subtok);
					} else {
						if (m_xmark) {
							hre.replaceDestructive(subtok, "$1x", "([Mm]+)", "g");
						}
						token->replaceSubtoken(k, subtok);
					}
				} else if ((subtok.find("m") != string::npos) && !hre.search(subtok, "[Mm]x")) {
					// minor-second upper mordent
					int nextup = getBase40(diatonic + 1, dstates[rindex][diatonic+1]);
					int interval = nextup - b40;
					if (interval == 6) {
						// Set to major-second upper mordent
						hre.replaceDestructive(subtok, "M", "m", "g");
						if (m_xmark) {
							hre.replaceDestructive(subtok, "$1x", "([Mm]+)", "g");
						}
						token->replaceSubtoken(k, subtok);
					} else {
						if (m_xmark) {
							hre.replaceDestructive(subtok, "$1x", "([Mm]+)", "g");
						}
						token->replaceSubtoken(k, subtok);
					}
				} else if ((subtok.find("W") != string::npos) && !hre.search(subtok, "[Ww]x")) {
					// major-second lower mordent
					int nextdn = getBase40(diatonic - 1, dstates[rindex][diatonic-1]);
					int interval = b40 - nextdn;
					if (interval == 5) {
						// Set to minor-second lower mordent
						hre.replaceDestructive(subtok, "w", "W", "g");
						if (m_xmark) {
							hre.replaceDestructive(subtok, "$1x", "([Ww]+)", "g");
						}
						token->replaceSubtoken(k, subtok);
					} else {
						if (m_xmark) {
							hre.replaceDestructive(subtok, "$1x", "([Ww]+)", "g");
						}
						token->replaceSubtoken(k, subtok);
					}
				} else if ((subtok.find("w") != string::npos) && !hre.search(subtok, "[Ww]x")) {
					// minor-second lower mordent
					int nextdn = getBase40(diatonic - 1, dstates[rindex][diatonic-1]);
					int interval = b40 - nextdn;
					if (interval == 6) {
						// Set to major-second lower mordent
						hre.replaceDestructive(subtok, "W", "w", "g");
						if (m_xmark) {
							hre.replaceDestructive(subtok, "$1x", "([Ww]+)", "g");
						}
						token->replaceSubtoken(k, subtok);
					} else {
						if (m_xmark) {
							hre.replaceDestructive(subtok, "$1x", "([Ww]+)", "g");
						}
						token->replaceSubtoken(k, subtok);
					}
				}
				// deal with turns and inverted turns here.

			}
		}
	}

	return true;
}



//////////////////////////////
//
// Tool_trillspell::resetDiatonicStatesWithKeySignature -- Only used in
//     Tool_trillspell::analyzeKernAccidentals().  Resets the accidental
//     states for notes
//

void Tool_trillspell::resetDiatonicStatesWithKeySignature(vector<int>&
		states, vector<int>& signature) {
	for (int i=0; i<(int)states.size(); i++) {
		states[i] = signature[i % 7];
	}
}



//////////////////////////////
//
// Tool_trillspell::fillKeySignature -- Read key signature notes and
//    assign +1 to sharps, -1 to flats in the diatonic input array.  Used
//    only by Tool_trillspell::analyzeOrnamentAccidentals().
//

void Tool_trillspell::fillKeySignature(vector<int>& states,
		const string& keysig) {
	std::fill(states.begin(), states.end(), 0);
	if (keysig.find("f#") != string::npos) { states[3] = +1; }
	if (keysig.find("c#") != string::npos) { states[0] = +1; }
	if (keysig.find("g#") != string::npos) { states[4] = +1; }
	if (keysig.find("d#") != string::npos) { states[1] = +1; }
	if (keysig.find("a#") != string::npos) { states[5] = +1; }
	if (keysig.find("e#") != string::npos) { states[2] = +1; }
	if (keysig.find("b#") != string::npos) { states[6] = +1; }
	if (keysig.find("b-") != string::npos) { states[6] = -1; }
	if (keysig.find("e-") != string::npos) { states[2] = -1; }
	if (keysig.find("a-") != string::npos) { states[5] = -1; }
	if (keysig.find("d-") != string::npos) { states[1] = -1; }
	if (keysig.find("g-") != string::npos) { states[4] = -1; }
	if (keysig.find("c-") != string::npos) { states[0] = -1; }
	if (keysig.find("f-") != string::npos) { states[3] = -1; }
}



//////////////////////////////
//
// Tool_trillspell::getBase40 --
//

int Tool_trillspell::getBase40(int diatonic, int accidental) {
	return Convert::base7ToBase40(diatonic) + accidental;
}




/////////////////////////////////
//
// Tool_tspos::Tool_tspos -- Set the recognized options for the tool.
//

Tool_tspos::Tool_tspos(void) {
	define("d|double=b",        "highlight only doubled notes in triads");
	define("3|no-thirds=b",     "do not color thirds");
	define("5|no-fifths=b",     "do not color fifths");
	define("T|no-triads=b",     "do not color full triads");
	define("m|minor-triads=b",  "only analyze major triad");
	define("M|major-triads=b",  "only analyze minor triads");
	define("x|attacks=b",       "only process sonorities with three unique triadic pitch classes attacking at once (sustains in additional voices are allowed)");
	define("v|voice-count=i:0", "only analyze sonorities with given voice count");
	define("c|compressed=b",    "compress music to see more on each system");
	define("top=b",             "mark top voice in analysis output");
	define("t|table=b",         "add analysis table above score");
	define("V|all-voices=b",    "require all voices in score to be sounding");
	define("Q|no-question=b",   "do not show question mark in table header");
}



/////////////////////////////////
//
// Tool_tspos::run -- Do the main work of the tool.
//

bool Tool_tspos::run(HumdrumFileSet& infiles) {
	bool status = true;
	for (int i=0; i<infiles.getCount(); i++) {
		status &= run(infiles[i]);
	}
	return status;
}


bool Tool_tspos::run(const string& indata, ostream& out) {
	HumdrumFile infile(indata);
	bool status = run(infile);
	if (hasAnyText()) {
		getAllText(out);
	} else {
		out << infile;
	}
	return status;
}


bool Tool_tspos::run(HumdrumFile& infile, ostream& out) {
	bool status = run(infile);
	if (hasAnyText()) {
		getAllText(out);
	} else {
		out << infile;
	}
	return status;
}


bool Tool_tspos::run(HumdrumFile& infile) {
	initialize(infile);
	processFile(infile);
	return true;
}



//////////////////////////////
//
// Tool_tspos::initialize -- Setup to do before processing a file.
//

void Tool_tspos::initialize(HumdrumFile& infile) {
	m_colorThirds = !getBoolean("no-thirds");
	m_colorFifths = !getBoolean("no-fifths");
	m_colorTriads = !getBoolean("no-triads");
	m_doubleQ = getBoolean("double");
	m_topQ = getBoolean("top");
	m_tableQ = getBoolean("table");
	m_compressedQ = getBoolean("compressed");
	m_voice = getInteger("voice-count");
	if (getBoolean("all-voices")) {
		vector<HTp> kernSpines =  infile.getKernSpineStartList();
		m_voice = (int)kernSpines.size();
	}
	m_triadAttack = getBoolean("attacks");
	m_majorQ = true;
	m_minorQ = true;
	if (getBoolean("major-triads")) {
		m_majorQ = true;
		m_minorQ = false;
	}
	if (getBoolean("minor-triads")) {
		m_majorQ = false;
		m_minorQ = true;
	}
	m_questionQ = !getBoolean("no-question");
}



//////////////////////////////
//
// Tool_tspos::processFile -- Analyze an input file.
//

void Tool_tspos::processFile(HumdrumFile& infile) {
	// Algorithm go line by line in the infile, extracting the notes that are active
	// check to see if the list of notes form a triad
	// label the root third and fifth notes of the triad

	m_toolCount = getToolCounter(infile);

	m_used_markers.resize(7);
	fill(m_used_markers.begin(), m_used_markers.end(), 0);

	avoidRdfCollisions(infile);
	analyzeVoiceCount(infile);

	m_partTriadPositions.resize(infile.getMaxTrack() + 1);
	for (int i = 0; i < (int)infile.getMaxTrack() + 1; i++) {
		m_partTriadPositions.at(i).resize(m_positionCount);
		fill(m_partTriadPositions.at(i).begin(), m_partTriadPositions.at(i).end(), 0);
	}

	m_triadState.clear();
	m_triadState.resize(infile.getLineCount());

	vector<HTp> kernNotes;
	vector<int> midiNotes;
	vector<int> chordPositions;
	vector<int> thirdPositions;
	vector<int> fifthPositions;

	for (int i=0; i<infile.getLineCount(); i++) {
		if (!infile[i].isData()) { // if no notes in the line
			continue;
		}
		if ((m_voice > 0)&& (m_voiceCount.at(i) != m_voice)) {
			// Ignore sonorities that do not have the required number of
			// voices.  m_voices==0 means consider all voice counts.
			continue;
		}
		if (m_triadAttack) {
			if (!hasFullTriadAttack(infile[i])) {
				continue;
			}
		}

		// iterate along the line looking at each field, and creating a
		//     list of tokens that are notes.
		kernNotes.clear();
		midiNotes.clear();
		chordPositions.clear();
		thirdPositions.clear();
		fifthPositions.clear();
		for (int j=0; j<infile[i].getFieldCount(); j++) {
			HTp token = infile.token(i, j); // get ptr to cell
			// if not a **kern token, then skip:
			if (!token->isKern()) {
				continue;
			}

			if (token->isRest()) {
				continue;
			}

			HTp resolvedToken = NULL;
			if (token->isNull()) {
				resolvedToken = token->resolveNull();
				if (!resolvedToken || resolvedToken->isRest()) {
					continue;
				}
			}
			if (resolvedToken) {
				kernNotes.push_back(resolvedToken);
			} else {
				kernNotes.push_back(token);
			}
		}
		midiNotes = getMidiNotes(kernNotes);

		if (m_colorThirds) { // thirds
			thirdPositions = getThirds(midiNotes);
			checkForTriadicSonority(thirdPositions, i);

			if (m_doubleQ) { // label only doubles if prompted
				keepOnlyDoubles(thirdPositions);
			}

			labelThirds(kernNotes, thirdPositions);
		}

		if (m_colorFifths) { // fifths
			fifthPositions = getFifths(midiNotes);
			checkForTriadicSonority(fifthPositions, i);

			if (m_doubleQ) { // label only doubles if prompted
				keepOnlyDoubles(fifthPositions);
			}

			labelFifths(kernNotes, fifthPositions);
		}

		if (m_colorTriads) { // triads
		  	chordPositions = getChordPositions(midiNotes);
			checkForTriadicSonority(chordPositions, i);
			if (m_doubleQ) { // label only doubles if prompted
				keepOnlyDoubles(chordPositions);
			}
	 		labelChordPositions(kernNotes, chordPositions);
		}
	}

	infile.createLinesFromTokens();

	m_humdrum_text << infile;

	printUsedMarkers();

	string statistics = generateStatistics(infile);
	m_humdrum_text << statistics;
	if (m_compressedQ) {
		m_humdrum_text << "!!!verovio: evenNoteSpacing\n";
	}
}



//////////////////////////////
//
// Tool_tspos::printUsedMarkers --
//

void Tool_tspos::printUsedMarkers(void) {
	for (int i=0; i<(int)m_used_markers.size(); i++) {
		if (!m_used_markers[i]) {
			continue;
		}
		switch (i) {

			case 0:
				m_humdrum_text << "!!!RDF**kern: " << m_root_marker
		 		<< " = marked note, "
				<< "type=\"root position\" "
				<< "count=\"" << m_used_markers[i] << " "
				<< "color=\"" << m_root_color << "\"" << endl;
				break;

			case 1:
				m_humdrum_text << "!!!RDF**kern: " << m_third_marker
		 		<< " = marked note, "
				<< "type=\"third position\" "
				<< "count=\"" << m_used_markers[i] << " "
				<< "color=\"" << m_third_color << "\"" << endl;
				break;

			case 2:
				m_humdrum_text << "!!!RDF**kern: " << m_fifth_marker
		 		<< " = marked note, "
				<< "type=\"fifth position\" "
				<< "count=\"" << m_used_markers[i] << " "
				<< "color=\"" << m_fifth_color << "\"" << endl;
				break;

			case 3:
				m_humdrum_text << "!!!RDF**kern: " << m_3rd_root_marker
		 		<< " = marked note, "
				<< "type=\"open third root position\" "
				<< "count=\"" << m_used_markers[i] << " "
				<< "color=\"" << m_3rd_root_color << "\"" << endl;
				break;

			case 4:
				m_humdrum_text << "!!!RDF**kern: " << m_3rd_third_marker
		 		<< " = marked note, "
				<< "type=\"open third, third position\" "
				<< "count=\"" << m_used_markers[i] << " "
				<< "color=\"" << m_3rd_third_color << "\"" << endl;
				break;

			case 5:
				m_humdrum_text << "!!!RDF**kern: " << m_5th_root_marker
		 		<< " = marked note, "
				<< "type=\"open fifth, root position\" "
				<< "count=\"" << m_used_markers[i] << " "
				<< "color=\"" << m_5th_root_color << "\"" << endl;
				break;

			case 6:
				m_humdrum_text << "!!!RDF**kern: " << m_5th_fifth_marker
		 		<< " = marked note, "
				<< "type=\"open fifth, fifth position\" "
				<< "count=\"" << m_used_markers[i] << " "
				<< "color=\"" << m_5th_fifth_color << "\"" << endl;
				break;
		}
	}
}



//////////////////////////////
//
// Tool_tspos::hasFullTriadAttack --
//

bool Tool_tspos::hasFullTriadAttack(HumdrumLine& line) {
	vector<int> midiPitches = line.getMidiPitchesSortLH();
	vector<int> positive(24, 0);
	int count = 0;
	for (int i=0; i<(int)midiPitches.size(); i++) {
		if (midiPitches[i] > 0) {
			if (!positive[midiPitches[i] % 12]) {
				positive[midiPitches[i] % 12]++;
				positive[midiPitches[i] % 12 + 12]++;
				count++;
			}
		}
	}

	if (count != 3) {
		return false;
	}

	for (int i=0; i<12; i++) {
		if (!positive[i]) {
			continue;
		}
		if (positive[i+3]) {
			if (positive[i+3+3]) { // diminished triad pitch-classes present
				return true;
			} else if (positive[i+3+4]) { // minor
				return true;
			}
		} else if (positive[i+4]) {
			if (positive[i+4+3]) { // major
				return true;
			} else if (positive[i+4+4]) { // augmented
				return true;
			}
		}
	}

	return false;
}




//////////////////////////////
//
// Tool_tspos:analyzeVoiceCount -- Chords count as a single voice.
//

void Tool_tspos::analyzeVoiceCount(HumdrumFile& infile) {
	vector<int>& voices = m_voiceCount;
	voices.resize(infile.getLineCount());
	for (int i=0; i<infile.getLineCount(); i++) {
		if (!infile[i].isData()) {
			voices[i] = 0;
			continue;
		}
		voices[i] = countVoicesOnLine(infile, i);
	}
}



//////////////////////////////
//
// Tool_tspos::checkForTriadicSonority -- Mark the given line in the file as a triadic sonority
//     of all MIDI note numbers are positive.
//

void Tool_tspos::checkForTriadicSonority(vector<int>& positions, int line) {
	for (int i=0; i<(int)positions.size(); i++) {
		if (positions[i] > 0) {
			m_triadState.at(line) = true;
			break;
		}
	}
}



//////////////////////////////
//
// Tool_tspos::countVoicesOnLine --
//

int Tool_tspos::countVoicesOnLine(HumdrumFile& infile, int line) {
	int output = 0;
	for (int j=0; j<infile[line].getFieldCount(); j++) {
		HTp token = infile.token(line, j);
		if (!token->isKern()) {
			continue;
		}
		if (token->isNull()) {
			token = token->resolveNull();
			if (!token) {
				continue;
			}
			if (token->isNull()) {
				continue;
			}
		}
		if (token->isRest()) {
			continue;
		}
		output++;
	}
	return output;
}



//////////////////////////////
//
// Tool_tspos::generateStatistics --
//

string Tool_tspos::generateStatistics(HumdrumFile& infile) {
	int sonorityCount = 0;  // total number of sonorities
	int triadCount = 0;     // sonorities that are triads
	HumNum triadDuration = 0;
	for (int i=0; i<infile.getLineCount(); i++) {
		if (!infile[i].isData()) {
			continue;
		}
		if (m_triadState.at(i)) {
			triadDuration += infile[i].getDuration();
		}
		int attacks = infile[i].getKernNoteAttacks();
		if (attacks == 0) {
			continue;
		}
		sonorityCount++;
		if (m_triadState.at(i)) {
			triadCount++;
		}
	}

	string toolPrefix = "!!!TOOL-";
	if (m_toolCount > 0) {
		toolPrefix += to_string(m_toolCount) + "-";
	}
	toolPrefix += m_toolName + "-";

	stringstream out;
	out << toolPrefix << "sonority-count: " << sonorityCount << endl;
	out << toolPrefix << "sonority-duration: " << infile.getScoreDuration().getFloat() << endl;
	out << toolPrefix << "triadic-count: " << triadCount << endl;
	out << toolPrefix << "triadic-duration: " << triadDuration.getFloat() << endl;

	double percentage = 100.0 * (double)triadCount / (double)sonorityCount;
	percentage = int(percentage * 100.0 + 0.5) / 100.0;
	out << toolPrefix << "count-ratio: " << percentage << "%" << endl;

	percentage = 100.0 * triadDuration.getFloat() / infile.getScoreDuration().getFloat();
	percentage = int(percentage * 100.0 + 0.5) / 100.0;
	out << toolPrefix << "duration-ratio: " << percentage << "%" << endl;

	// Report triads positions by voice:
	vector<string> names = getTrackNames(infile);

	for (int i=1; i<(int)names.size(); i++) {
		out << toolPrefix << m_toolName << "track-name-" << to_string(i) << ": " << names[i] << endl;
	}
	vector<HTp> kernstarts;
	infile.getKernSpineStartList(kernstarts);
	if (!kernstarts.empty()) {
		out << toolPrefix << "first-kern-track: " << kernstarts[0]->getTrack() << endl;
		out << toolPrefix << "last-kern-track: " << kernstarts.back()->getTrack() << endl;
	}
	out << toolPrefix << "kern-count: " << kernstarts.size() << endl;
	out << toolPrefix << "kern-tracks: ";
	for (int i=0; i<(int)kernstarts.size(); i++) {
		out << kernstarts[i]->getTrack();
		if (i < (int)kernstarts.size() - 1) {
			out << " ";
		}
	}
	out << endl;
	int topTrack = kernstarts.back()->getTrack();

	for (int i=1; i<(int)m_partTriadPositions.size(); i++) {
		vector<int>& entry = m_partTriadPositions[i];
		int sum = getVectorSum(entry);
		if (sum == 0) {
			continue;
		}
		string name = names[i];
		int rootCount = 0;
		int thirdCount = 0;
		int fifthCount = 0;
		rootCount += entry[0] + entry[3] + entry[5];
		thirdCount += entry[1] + entry[4];
		fifthCount += entry[2] + entry[6];
		double rootPercent = int(rootCount * 1000.0 / sum + 0.5) / 10.0;
		double thirdPercent = int(thirdCount * 1000.0 / sum + 0.5) / 10.0;
		double fifthPercent = int(fifthCount * 1000.0 / sum + 0.5) / 10.0;
		if (m_topQ && (topTrack == i)) {
			name = "top-" + name;
		}
		out << toolPrefix << "count-sum-" << i << "-" << name << ": " << sum << endl;
		out << toolPrefix << "root-count-" << i << "-" << name << ": " << rootCount << " (" << rootPercent << "%)" << endl;
		out << toolPrefix << "third-count-" << i << "-" << name << ": " << thirdCount << " (" << thirdPercent << "%)" << endl;
		out << toolPrefix << "fifth-count-" << i << "-" << name << ": " << fifthCount << " (" << fifthPercent << "%)" << endl;
	}

	if (m_tableQ) {
		out << generateTable(infile, names);
	}

	return out.str();
}



//////////////////////////////
//
// Tool_tspos::generateTable -- Create an HTML table that the statistics
//     will be filled into.
//

string Tool_tspos::generateTable(HumdrumFile& infile, vector<string>& names) {

	string toolPrefix = "TOOL-";
	if (m_toolCount > 0) {
		toolPrefix += to_string(m_toolCount) + "-";
	}
	toolPrefix += m_toolName + "-";

	vector<HTp> spinestarts;
	infile.getSpineStartList(spinestarts);
	stringstream out;
	out << "!!@@BEGIN: PREHTML\n";
	out << "!!@CONTENT:\n";
	out << "!!<div class='tool-" << m_toolName << "'>\n";
	out << "!!<h2 class='header-tool-" << m_toolName << "'>";
	if (m_majorQ && !m_minorQ) {
		out << "Major triadic";
	} else if (!m_majorQ && m_minorQ) {
		out << "Minor triadic";
	} else {
		out << "Triadic";
	}
	out << " sonority position analysis";
	if (m_questionQ) {
		out <<  " <a class='info-" << m_toolName
				<< "' target='_blank' href='https://doc.verovio.humdrum.org/filter/"
				<< m_toolName
				<< "'>?</a>";
	}
	out << "</h2>\n";
	out << "!!<table class='analysis'>\n";
	out << "!!<tr><th class='voice'>Voice</th>"
			<< "<th class='root'>Root</th><th class='third'>Third</th>"
			<< "<th class='fifth'>Fifth</th><th class='total'>Total</th></td>\n";

	int rootSum = 0;
	int thirdSum = 0;
	int fifthSum = 0;

	std::vector<vector<int>>& pt =  m_partTriadPositions;
	for (int i=(int)names.size() - 1; i>0; i--) {
		int track = i;
		if (!spinestarts.at(i-1)->isKern()) {
			continue;
		}
		out << "!!<tr>\n";
		int sum = 0;
		for (int j=0; j<(int)pt[i].size(); j++) {
			sum += pt[i][j];
		}

		int rootCount  = pt.at(i)[0] + pt.at(i)[3] + pt.at(i)[5];
		int thirdCount = pt.at(i)[1] + pt.at(i)[4];
		int fifthCount = pt.at(i)[2] + pt.at(i)[6];

		rootSum  += rootCount;
		thirdSum += thirdCount;
		fifthSum += fifthCount;

		// Requires the colors to be hex:
		string oc1 = makeOpacityColor(m_root_color,  rootCount,  sum);
		string oc3 = makeOpacityColor(m_third_color, thirdCount, sum);
		string oc5 = makeOpacityColor(m_fifth_color, fifthCount, sum);

		out << "!!   <td class='voice'>" << m_fullNames[i] << "</td>\n";
		out << "!!   <td class='root'  style='background-color:" << oc1
			<< ";'>@{" << toolPrefix << "root-count-"  << to_string(track)
			<< "-" << names[track] << "}</td>\n";
		out << "!!   <td class='third' style='background-color:" << oc3
			<< ";'>@{" << toolPrefix << "third-count-" << to_string(track)
			<< "-" << names[track] << "}</td>\n";
		out << "!!   <td class='fifth' style='background-color:" << oc5
				<< ";'>@{" << toolPrefix << "fifth-count-" << to_string(track)
				<< "-" << names[track] << "}</td>\n";
		out << "!!   <td class='total'>@{" << toolPrefix << "count-sum-"
				<< to_string(track) << "-" << names[track] << "}</td>\n";
		out << "!!</tr>\n";
	}

	int totalSum = rootSum + thirdSum + fifthSum;

	string p1 = makePercentString(rootSum,  totalSum, 1);
	string p3 = makePercentString(thirdSum, totalSum, 1);
	string p5 = makePercentString(fifthSum, totalSum, 1);

	string oc1 = makeOpacityColor(m_root_color,  rootSum,  totalSum);
	string oc3 = makeOpacityColor(m_third_color, thirdSum, totalSum);
	string oc5 = makeOpacityColor(m_fifth_color, fifthSum, totalSum);

	// print column sum table
	out << "!!<tr>\n";
	out << "!!<td style='text-align:right; font-style:italic;'>\n";
	out << "!!   sum:\n";
	out << "!!</td>\n";

	// root sum:
	out << "!!<td class='root' style='background-color:" << oc1 << ";'>\n";
	out << "!!   " << rootSum << " (" << p1 << "%)" << endl;
	out << "!!</td>\n";

	// third sum:
	out << "!!<td class='third' style='background-color:" << oc3 << ";'>\n";
	out << "!!   " << thirdSum << " (" << p3 << "%)" << endl;
	out << "!!</td>\n";

	// fifth sum:
	out << "!!<td class='fifth' style='background-color:" << oc5 << ";'>\n";
	out << "!!   " << fifthSum << " (" << p5 << "%)" << endl;
	out << "!!</td>\n";

	// total sum:
	out << "!!<td class='total'>\n";
	out << "!!   " << totalSum << endl;
	out << "!!</td>\n";

	out << "!!</tr>\n";

	out << "!!</table>\n";

	stringstream options;
	int licount = 0;
	options << "!!<ul>\n";

	// Print summary of options used:
	if (m_voice > 0) {
		options << "!!<li> Only sonorities with " << m_voice << " sounding voices analyzed</li>\n";
		licount++;
	}

	if (m_triadAttack) {
		options << "!!<li> Only sonorities with at least three unique pitch-class attacks are analyzed</li>\n";
		licount++;
	}

	if (!(m_colorThirds && m_colorFifths && m_colorTriads)) {
		if (m_colorTriads && m_colorThirds && m_colorFifths) {
			options << "!!<li> Sonorities with two or three triadic positions analyzed</li>\n";
			licount++;
		} else if (m_colorTriads && !m_colorThirds && !m_colorFifths) {
			options << "!!<li> Only sonorities with all three triadic positions analyzed</li>\n";
			licount++;
		} else if (!m_colorTriads && m_colorThirds && m_colorFifths) {
			options << "!!<li> Only sonorities with two triadic positions analyzed</li>\n";
			licount++;
		} else if (!m_colorTriads && m_colorThirds && !m_colorFifths) {
			options << "!!<li> Only sonorities with open thirds analyzed</li>\n";
			licount++;
		} else if (!m_colorTriads && !m_colorThirds && m_colorFifths) {
			options << "!!<li> Only sonorities with open fifths analyzed</li>\n";
			licount++;
		} else if (m_colorTriads && m_colorThirds && !m_colorFifths) {
			options << "!!<li> Ignoring open fifths </li>\n";
			licount++;
		} else if (m_colorTriads && !m_colorThirds && m_colorFifths) {
			options << "!!<li> Ignoring open thirds </li>\n";
			licount++;
		}
	} else {
		// print a message about both two- and three-pitch-class sonorities?
	}

	options << "!!</ul>\n";
	if (licount) {
		out << options.str();
	}

	out << "!!</div>\n";

	// Styling of table:
	out << "!!<style>\n";
	out << "!!div.tool-" << m_toolName << " { padding-left: 20px;}\n";
	out << "!!div.tool-" << m_toolName << " table tr:hover { background: orange; }\n";
	out << "!!div.tool-" << m_toolName << " table {\n";
	out << "!!   border-collapse: collapse;\n";
	out << "!!   border: 1px solid orange;\n";
	out << "!!   margin: 10px;\n";
	out << "!!   max-width: 800px;\n";
	out << "!!}\n";
	out << "!!div.tool-" << m_toolName << " table td.total, div.tool-" << m_toolName
			<< " table th.total { text-align: center; }\n";
	out << "!!th { background-color: bisque; }\n";
	out << "!!div.tool-" << m_toolName << "table th, div.tool-" << m_toolName
			<< " table td { padding: 5px; }\n";
	out << "!!a.info-" << m_toolName << " {\n"
			 << "!!   text-decoration:none;\n"
			 << "!!   font-size:70%;\n"
			 << "!!   display:inline-block;\n"
			 << "!!   background:#ffaa0070;\n"
			 << "!!   text-align:center;\n"
			 << "!!   border: 1px solid orange;\n"
			 << "!!   border-radius:12px;\n"
			 << "!!   width:25px;\n"
			 << "!!   height:25px;\n"
			 << "!!}\n";
	out << "!! a.info-" << m_toolName << ":link, a.info-"
			<< m_toolName << ":visited, a.info-"
			<< m_toolName << ":active { background:orange; }\n";
	out << "!! a.info-" << m_toolName
			<< ":hover { color: white; background-color: purple; }\n";
	out << "!!</style>\n";

	out << "!!@@END: PREHTML\n";
	return out.str();
}



//////////////////////////////
//
// Tool_tspos::makeOpacityColor --
//

string Tool_tspos::makeOpacityColor(string& color, double value, double total, bool enhance) {
	stringstream output;
	int opacity;
	if (enhance) {
		opacity = logisticColorMap(value, total);
	} else {
		opacity  = int(value / total * 255.49 + 0.5);
	}
	if (opacity < 0) {
		opacity = 0;
	} else if (opacity > 255) {
		opacity = 255;
	}
	output << color << std::hex << std::setw(2) << std::setfill('0') << opacity << std::dec;
	return output.str();
}



//////////////////////////////
//
// Tool_tspos::makePercentString --
//

string Tool_tspos::makePercentString(double value, double total, int digits) {
	double percent = int(value / total * 100.0 * std::pow(10, digits) + 0.5) / std::pow(10, digits);
	stringstream output;
	output << percent;
	if ((percent - int(percent)) < 1.0 / std::pow(10, digits) / 10.0) {
		output << ".0";
	}
	return output.str();
}



//////////////////////////////
//
// Tool_tspos::getVectorSum --
//

int Tool_tspos::getVectorSum(vector<int>& input) {
	int sum = 0;
	for (int i=0; i<(int)input.size(); i++) {
		sum += input[i];
	}
	return sum;
}



//////////////////////////////
//
// Tool_tspos::getTrackNames --
//

vector<string> Tool_tspos::getTrackNames(HumdrumFile& infile) {
	int tracks = infile.getTrackCount();
	m_fullNames.clear();
	m_fullNames.resize(tracks+1);
	vector<string> output(tracks+1);
	for (int i=1; i<(int)output.size(); i++) {
		output[i] = "Spine " + to_string(i);
	}
	for (int i=0; i<infile.getLineCount(); i++) {
		if (infile[i].isData()) {
			break;
		}
		for (int j=0; j<infile[i].getFieldCount(); j++) {
			HTp token = infile.token(i, j);
			if (token->compare(0, 3, "*I\"") == 0) {
				string value = token->substr(3);
				if (!value.empty()) {
					int track = token->getTrack();
					output.at(track) = value;
				}
			}
		}
	}
	// Remove colons from names and convert spaces into underscores:
	HumRegex hre;
	for (int i=1; i<(int)output.size(); i++) {
		m_fullNames[i] = output[i];
		hre.replaceDestructive(output[i], "", "^\\s+");
		hre.replaceDestructive(output[i], "", "\\s+$");
		hre.replaceDestructive(output[i], "_", "\\s+", "g");
		hre.replaceDestructive(output[i], "", ":", "g");
	}
	return output;
}



//////////////////////////////
//
// Tool_tspos::labelChordPositions -- Mark the scale degree of notes:
//       0: not in triadic sonority so no label.
//       1 == @: root (such as C in C major triad)
//       3 == N third (such as E in C major triad)
//       5 == Z fifth (such as G in C major triad)
//

void Tool_tspos::labelChordPositions(vector<HTp>& kernNotes, vector<int>& chordPositions) {
	for (int i=0; i<(int)kernNotes.size(); i++) {
		int position = chordPositions.at(i);
		if (position == 0) {
			continue;
		}

		int track = kernNotes[i]->getTrack(); // get part
		string label;
		switch (position) {
			case 1:
				label = m_root_marker;
				m_used_markers[0]++;
				m_partTriadPositions.at(track).at(0)++;
				break;
			case 3:
				label = m_third_marker;
				m_used_markers[1]++;
				m_partTriadPositions.at(track).at(1)++;
				break;
			case 5:
				label = m_fifth_marker;
				m_used_markers[2]++;
				m_partTriadPositions.at(track).at(2)++;
				break;
		}
		if (label.empty()) {
			continue;
		}
		string text = *kernNotes.at(i);
		text += label;
		kernNotes.at(i)->setText(text);
	}
}



//////////////////////////////
//
// Tool_tspos::labelThirds -- Mark the scale degree of notes:
//       0: not in interval sonority so no label.
//       1 == @: root (bottom of the interval)
//       3 == N: third (top of the interval)

void Tool_tspos::labelThirds(vector<HTp>& kernNotes, vector<int>& thirdPositions) {
	for (int i = 0; i < (int)kernNotes.size(); i++) {
		int position = thirdPositions.at(i);
		if (position == 0) {
			continue;
		}

		int track = kernNotes.at(i)->getTrack(); // get part
		string label;
		switch (position) {
			case 1:
				label = m_3rd_root_marker;
				m_used_markers[3]++;
				m_partTriadPositions.at(track).at(3)++;
				break;
			case 3:
				label = m_3rd_third_marker;
				m_used_markers[4]++;
				m_partTriadPositions.at(track).at(4)++;
				break;
		}

		if (label.empty()) {
			continue;
		}
		string text = *kernNotes.at(i);
		text += label;
		kernNotes.at(i)->setText(text);
	}
}



//////////////////////////////
//
// Tool_tspos::labelFifths -- Mark the scale degree of notes:
//       0: not in interval sonority so no label.
//       1 == @: root (bottom of the interval)
//       5 == Z: fifth (top of the interval)

void Tool_tspos::labelFifths(vector<HTp>& kernNotes, vector<int>& fifthPositions) {
	for (int i = 0; i < (int)kernNotes.size(); i++) {
		int position = fifthPositions.at(i);
		if (position == 0) {
			continue;
		}

		int track = kernNotes.at(i)->getTrack(); // get part
		string label;
		switch (position) {
			case 1:
				label = m_5th_root_marker;
				m_used_markers[5]++;
				m_partTriadPositions.at(track).at(5)++;
				break;
			case 5:
				label = m_5th_fifth_marker;
				m_used_markers[6]++;
				m_partTriadPositions.at(track).at(6)++;
				break;
		}

		if (label.empty()) {
			continue;
		}
		string text = *kernNotes.at(i);
		text += label;
		kernNotes.at(i)->setText(text);
	}
}



//////////////////////////////
//
// Tool_tspos::getNoteMods -- Identify if the sonority is a triad, and if so, place
//    the position of the note in the chord:
//       0: not in triadic sonority.
//       1: root (such as C in C major triad)
//       3: third (such as E in C major triad)
//       5: fifth (such as G in C major triad)
//

vector<int> Tool_tspos::getNoteMods(vector<int>& midiNotes) {
	// create modulo 12 arr
	vector<int> pitchClasses(12, 0);
	for (int i = 0; i < (int)midiNotes.size(); i++) {
		int index = midiNotes.at(i) % 12;
		pitchClasses.at(index)++; // add one to the pitch
	}

	vector<int> noteMods;

	for (int i = 0; i < (int)pitchClasses.size(); i++) {
		if (pitchClasses.at(i) != 0) { // not zero
			noteMods.push_back(i); // add the index
		}
	}

	return noteMods;
}



//////////////////////////////
//
// Tool_tspos::getThirds -- Identify if the sonority is a third interval, and if so,
//    place the position of the note in the output.
//       0: not in the sonority
//       1: root (bottom of the interval)
//       3: third (top of the interval)

vector<int> Tool_tspos::getThirds(vector<int>& midiNotes) {
	vector<int> output(midiNotes.size(), 0);

	if (midiNotes.empty()) {
		return output;
	}

	vector<int> noteMods = getNoteMods(midiNotes); // we know noteMods is sorted
	if (noteMods.size() != 2) {
		return output;
	}

	int interval = noteMods[1] - noteMods[0];
	int rootClass = -1; // currently uninitialized
	int thirdClass = -1;

	if (m_majorQ && m_minorQ) {

		if (interval == 3 || interval == 4) { // third is found
			rootClass = noteMods.at(0);
			thirdClass = noteMods.at(1);
		}
		else if (interval == 8 || interval == 9) { // third is found (inversion)
			rootClass = noteMods.at(1);
			thirdClass = noteMods.at(0);
		}

	}

	else if (!m_majorQ && m_minorQ) {
		if (interval == 3) { // minor third is found
			rootClass = noteMods.at(0);
			thirdClass = noteMods.at(1);
		}
		else if (interval == 9) { // major sixth is found (inversion)
			rootClass = noteMods.at(1);
			thirdClass = noteMods.at(0);
		}
	}

	else if (m_majorQ && !m_minorQ) {

		if (interval == 4) { // major third is found
			rootClass = noteMods.at(0);
			thirdClass = noteMods.at(1);
		}
		else if (interval == 8) { // minor sixth is found (inversion)
			rootClass = noteMods.at(1);
			thirdClass = noteMods.at(0);
		}

	}

	if (rootClass == -1) { // third was not found
		return output;
	}

	// populate output
	for (int i = 0; i < (int)midiNotes.size(); i++) {
		if (midiNotes.at(i) % 12 == rootClass) {
		output.at(i) = 1;
		} else if (midiNotes.at(i) % 12 == thirdClass) {
			output.at(i) = 3;
		}
	}

	return output;
}



//////////////////////////////
//
// Tool_tspos::getFifths -- Identify if the sonority is a fifth interval, and if so,
//    place the position of the note in the output.
//       0: not in the sonority
//       1: root (bottom of the interval)
//       5: fifth (top of the interval)

vector<int> Tool_tspos::getFifths(vector<int>& midiNotes) {
	vector<int> output(midiNotes.size(), 0);

	if (midiNotes.empty()) {
		return output;
	}

	vector<int> noteMods = getNoteMods(midiNotes);
	if (noteMods.size() != 2) {
		return output;
	}

	int interval = noteMods.at(1) - noteMods.at(0);
	int rootClass = -1; // currently uninitialized
	int fifthClass = -1;

	if ((interval == 7) || (interval == 6)) { // fifth found
		rootClass = noteMods.at(0);
		fifthClass = noteMods.at(1);
	}

	if (interval == 5) { // inverted fifth found
		rootClass = noteMods.at(1);
		fifthClass = noteMods.at(0);
	}

	if (rootClass == -1) { // fifth not found
		return output;
	}

	// populate output
	for (int i = 0; i < (int)midiNotes.size(); i++) {
		if (midiNotes.at(i) % 12 == rootClass) {
			output.at(i) = 1;
		}
		else if (midiNotes.at(i) % 12 == fifthClass) {
			output.at(i) = 5;
		}
	}

	return output;
}



///////////////////////////////
//
// Tool_tspos::getChordPositions --
//

vector<int> Tool_tspos::getChordPositions(vector<int>& midiNotes) {
	vector<int> output(midiNotes.size(), 0);
	if (midiNotes.empty()) {
		return output;
	}

	vector<int> noteMods = getNoteMods(midiNotes);

	if (noteMods.size() != 3) { // not a triad
		return output;
	}

	int bint = noteMods.at(1) - noteMods.at(0);
	int tint = noteMods.at(2) - noteMods.at(1);

	int rootClass = -1; // curr uninitialized
	int thirdClass = -1;
	int fifthClass = -1;

	if (m_majorQ && m_minorQ) {

		if ((bint == 3 && tint == 4) || (bint == 4 && tint == 3) || (bint == 3 && tint == 3)) { // root pos
			rootClass = noteMods.at(0);
			thirdClass = noteMods.at(1);
			fifthClass = noteMods.at(2);
		} else if ((bint == 4 && tint == 5) || (bint == 3 && tint == 5) || (bint == 3 && tint == 6)) { // first inv
			rootClass = noteMods.at(2);
			thirdClass = noteMods.at(0);
			fifthClass = noteMods.at(1);
		} else if ((bint == 5 && tint == 3) || (bint == 5 && tint == 4) || (bint == 6 && tint == 3)) { // sec inv
			rootClass = noteMods.at(1);
			thirdClass = noteMods.at(2);
			fifthClass = noteMods.at(0);
		}
	}

	else if (!m_majorQ && m_minorQ) {
		if (bint == 3 && tint == 4) {// minor root pos
			rootClass = noteMods.at(0);
			thirdClass = noteMods.at(1);
			fifthClass = noteMods.at(2);
		} else if (bint == 4 && tint == 5) { // minor first inv
			rootClass = noteMods.at(2);
			thirdClass = noteMods.at(0);
			fifthClass = noteMods.at(1);
		} else if (bint == 5 && tint == 3) { // minor sec inv
			rootClass = noteMods.at(1);
			thirdClass = noteMods.at(2);
			fifthClass = noteMods.at(0);
		}
	}

	else if (m_majorQ && !m_minorQ) {
		if (bint == 4 && tint == 3) {// major root pos
			rootClass = noteMods.at(0);
			thirdClass = noteMods.at(1);
			fifthClass = noteMods.at(2);
		} else if (bint == 3 && tint == 5) { // major first inv
			rootClass = noteMods.at(2);
			thirdClass = noteMods.at(0);
			fifthClass = noteMods.at(1);
		} else if (bint == 5 && tint == 4) { // major sec inv
			rootClass = noteMods.at(1);
			thirdClass = noteMods.at(2);
			fifthClass = noteMods.at(0);
		}
	}

	if (rootClass == -1) {
		return output;
	}

	for (int i = 0; i < (int)midiNotes.size(); i++) {
		if (midiNotes.at(i) % 12 == rootClass) {
			output.at(i) = 1;
		}
		else if (midiNotes.at(i) % 12 == thirdClass) {
			output.at(i) = 3;
		}
		else if (midiNotes.at(i) % 12 == fifthClass) {
			output.at(i) = 5;
		}
	}

	if (m_doubleQ) {
		keepOnlyDoubles(output); // call some function that marks only doubles
	}

	return output;
}



//////////////////////////////
//
// Tool_tspos::keepOnlyDoubles --
//

void Tool_tspos::keepOnlyDoubles(vector<int>& output) {
	map<int, int> positionCounts = {{1, 0}, {3, 0}, {5, 0}};

	for (int i = 0; i < (int)output.size(); i++) { // create hashmap of counts
		if (output[i] == 1) {
			positionCounts[1]++;
		}
		else if (output[i] == 3) {
			positionCounts[3]++;
		}
		else if (output[i] == 5) {
			positionCounts[5]++;
		}
	}

	for (auto positionCount : positionCounts) {
		if (positionCount.second == 1) { // if only appears once
		replace(output.begin(), output.end(), positionCount.first, 0); // replace with 0
		}
	}

	return;
}



//////////////////////////////
//
// Tool_tspos::getMidiNotes -- Convert kern notes to MIDI note numbers.
//    If the note is sustaining, then make the MIDI note number negative.
//

vector<int> Tool_tspos::getMidiNotes(vector<HTp>& kernNotes) {
	vector<int> output(kernNotes.size());
	if (kernNotes.empty()) {
		return output;
	}
	for (int i=0; i<(int)kernNotes.size(); i++) {
		int midiNote = kernNotes.at(i)->getMidiPitch();
		if (midiNote < 0) {
			// negative values indicate sustained note.
			midiNote = -midiNote;
		}
		output.at(i) = midiNote;
	}
	return output;
}



//////////////////////////////
//
// Tool_tspos::avoidRdfCollisions -- Adjust markers if they are already
//      defined in the file.
//

void Tool_tspos::avoidRdfCollisions(HumdrumFile& infile) {
	map<string, bool> rdfs;
	for (int i=0; i<infile.getLineCount(); i++) {
		if (!infile[i].isReferenceRecord()) {
			continue;
		}
		string value = infile[i].getReferenceValue();
		HumRegex hre;
		if (hre.search(value, "^\\s*([^\\s]+)\\s*=\\s*(.*)\\s*$")) {
			string rdf = hre.getMatch(1);
			rdfs[rdf] = true;
		}
	}

	vector<string> replacement(7);
	replacement[0] = "😀";
	replacement[1] = "😄";
	replacement[2] = "😉";
	replacement[3] = "😍";
	replacement[4] = "😂";
	replacement[5] = "😎";
	replacement[6] = "😗";

	if (rdfs[m_root_marker]) {
		m_root_marker = replacement[0];
	}
	if (rdfs[m_third_marker]) {
		m_third_marker = replacement[1];
	}
	if (rdfs[m_fifth_marker]) {
		m_fifth_marker = replacement[2];
	}
	if (rdfs[m_3rd_root_marker]) {
		m_3rd_root_marker = replacement[3];
	}
	if (rdfs[m_3rd_third_marker]) {
		m_3rd_third_marker = replacement[4];
	}
	if (rdfs[m_5th_root_marker]) {
		m_5th_root_marker = replacement[5];
	}
	if (rdfs[m_5th_fifth_marker]) {
		m_5th_fifth_marker = replacement[6];
	}

}



//////////////////////////////
//
// Tool_tspos::getToolCounter --
//

int Tool_tspos::getToolCounter(HumdrumFile& infile) {
	int counter = 0;
	HumRegex hre;
	for (int i=0; i<infile.getLineCount(); i++) {
		if (hre.search(infile[i], "^!!@@BEGIN:\\s*PREHTML\\s*$")) {
			counter++;
		}
	}
	return counter;
}



//////////////////////////////
//
// Tool_tspos::logisticColorMap -- Increase sensitivity of color mapping
//    around 40%.
//

#ifndef M_E
#define M_E 2.71828182845904523536
#endif

int Tool_tspos::logisticColorMap(double input, double max) {
	double center = max * 0.40;
	double k = 0.04;
	int output = max / (1.0 + pow(M_E, -k * (input + center) - (max + center)/2));
	output -= 11.4209;
	output = output * 255.0 / 243.377;
	if (output < 0) {
		output = 0;
	}
	if (output > 255) {
		output = 255;
	}
	return output;
}




/////////////////////////////////
//
// Tool_vcross::Tool_vcross -- Set the recognized options for the tool.
//

Tool_vcross::Tool_vcross(void) {
}



/////////////////////////////////
//
// Tool_vcross::run -- Do the main work of the tool.
//

bool Tool_vcross::run(HumdrumFileSet& infiles) {
	bool status = true;
	for (int i=0; i<infiles.getCount(); i++) {
		status &= run(infiles[i]);
	}
	return status;
}


bool Tool_vcross::run(const string& indata, ostream& out) {
	HumdrumFile infile(indata);
	bool status = run(infile);
	if (hasAnyText()) {
		getAllText(out);
	} else {
		out << infile;
	}
	return status;
}


bool Tool_vcross::run(HumdrumFile& infile, ostream& out) {
	bool status = run(infile);
	if (hasAnyText()) {
		getAllText(out);
	} else {
		out << infile;
	}
	return status;
}


bool Tool_vcross::run(HumdrumFile& infile) {
	processFile(infile);
	return true;
}



//////////////////////////////
//
// Tool_vcross::initialize --  Initializations that only have to be done once
//    for all HumdrumFile segments.
//

void Tool_vcross::initialize(void) {
	m_redQ   = false;
	m_greenQ = false;
	m_blueQ  = false;
}



//////////////////////////////
//
// Tool_vcross::processFile --
//

void Tool_vcross::processFile(HumdrumFile& infile) {
	initialize();
	for (int i=0; i<infile.getLineCount(); i++) {
		if (!infile[i].isData()) {
			continue;
		}
		processLine(infile, i);
	}
	if ((!m_redQ) && (!m_greenQ) && (!m_blueQ)) {
		return;
	}
	infile.generateLinesFromTokens();
	m_humdrum_text << infile;
	if (m_redQ) {
		m_humdrum_text << "!!!RDF**kern: 🟥 = marked note, color=\"crimson\", lower part's note is higher than higher part's note" << endl;
	}
	if (m_greenQ) {
		m_humdrum_text << "!!!RDF**kern: 🟩 = marked note, color=\"limegreen\", lower part's note is equal to higher part's note" << endl;
	}
	if (m_blueQ) {
		m_humdrum_text << "!!!RDF**kern: 🟦 = marked note, color=\"dodgerblue\", higher part's note is lower than lower part's note" << endl;
	}
}



//////////////////////////////
//
// Tool_vcross::processLine --
//

void Tool_vcross::processLine(HumdrumFile& infile, int index) {
	vector<vector<HTp>> tokens;
	int ctrack = -1;
	for (int j=0; j<infile[index].getFieldCount(); j++) {
		HTp token = infile.token(index, j);
		if (!token->isKern()) {
			continue;
		}
		int track = token->getTrack();
		if (ctrack == -1) {
			tokens.resize(tokens.size() + 1);
			ctrack = track;
		} else if (ctrack != track) {
			tokens.resize(tokens.size() + 1);
			ctrack = track;
		}
		tokens.back().push_back(token);
	}
	for (int i=1; i<(int)tokens.size(); i++) {
		compareVoices(tokens.at(i-1), tokens.at(i));
	}
}



//////////////////////////////
//
// Tool_vcross::compareVoices --
//

void Tool_vcross::compareVoices(vector<HTp>& lower, vector<HTp>& higher) {
	vector<vector<int>> midihi(higher.size());
	vector<vector<int>> midilo(lower.size());

	for (int i=0; i<(int)higher.size(); i++) {
		getMidiInfo(midihi.at(i), higher.at(i));
	}

	for (int i=0; i<(int)lower.size(); i++) {
		getMidiInfo(midilo.at(i), lower.at(i));
	}

	int highestLo = -1;
	int lowestHi  = -1;

	for (int i=0; i<(int)midihi.size(); i++) {
		for (int j=0; j<(int)midihi.at(i).size(); j++) {
			if (midihi[i][j] < 0) {
				continue;
			}
			if (midihi[i][j] < 0) {
				continue;
			} else if (lowestHi < 0) {
				lowestHi = midihi[i][j];
			} else if (midihi[i][j] < lowestHi) {
				lowestHi = midihi[i][j];
			}
		}
	}

	for (int i=0; i<(int)midilo.size(); i++) {
		for (int j=0; j<(int)midilo.at(i).size(); j++) {
			if (midilo[i][j] < 0) {
				continue;
			}
			if (midilo[i][j] > highestLo) {
				highestLo = midilo[i][j];
			}
		}
	}

	if (highestLo < 0) {
		return;
	}
	if (lowestHi < 0) {
		return;
	}
	if (highestLo < lowestHi) {
		return;
	}

	for (int i=0; i<(int)midihi.size(); i++) {
		for (int j=0; j<(int)midihi.at(i).size(); j++) {
			if (midihi[i][j] < 0) {
				continue;
			}
			if (midihi[i][j] < highestLo) {
				if (!higher.at(i)->isNull()) {
					string subtok = higher.at(i)->getSubtoken(j);
					subtok += "🟦";
					m_blueQ = true;
					higher.at(i)->replaceSubtoken(j, subtok);
				}
			} else if (midihi[i][j] == highestLo) {
				if (!higher.at(i)->isNull()) {
					string subtok = higher.at(i)->getSubtoken(j);
					subtok += "🟩";
					m_greenQ = true;
					higher.at(i)->replaceSubtoken(j, subtok);
				}
			}
		}
	}

	for (int i=0; i<(int)midilo.size(); i++) {
		for (int j=0; j<(int)midilo.at(i).size(); j++) {
			if (midilo[i][j] < 0) {
				continue;
			}
			if (midilo[i][j] > lowestHi) {
				if (!lower.at(i)->isNull()) {
					string subtok = lower.at(i)->getSubtoken(j);
					subtok += "🟥";
					m_redQ = true;
					lower.at(i)->replaceSubtoken(j, subtok);
				}
			} else if (midilo[i][j] == lowestHi) {
				if (!lower.at(i)->isNull()) {
					string subtok = lower.at(i)->getSubtoken(j);
					subtok += "🟩";
					m_greenQ = true;
					lower.at(i)->replaceSubtoken(j, subtok);
				}
			}
		}
	}
}



//////////////////////////////
//
// Tool_vcross::getMidiInfo --
//

void Tool_vcross::getMidiInfo(vector<int>& midis, HTp token) {
	if (token->isNull()) {
		token = token->resolveNull();
		if (!token) {
			midis.clear();
			return;
		}
	}
	vector<string> subtokens = token->getSubtokens();
	midis.resize(subtokens.size());
	for (int i=0; i<(int)subtokens.size(); i++) {
		if (subtokens[i].find("r") != string::npos) {
			midis.at(i) = -1;
			continue;
		}
		midis.at(i) = Convert::kernToMidiNoteNumber(subtokens.at(i));
	}
}



} // end namespace hum
