function intToPitchClass(a){
//Accepts any integer. Returns a pitch class in the set {0123456789te}
//Based on C4=0. Thus, '-1' will return pitch class 'e' (B3).
	a=Number(a);
	while(a<0){a+=12;}
	while(a>11){a-=12;}
	if(a==10){
		return 't';
	}
	if(a==11){
		return 'e';
	}
	return String(a);
}

function pitchClassToInt(a){
//Accepts a pitch class in the set {0123456789te}.
//Returns an integer (essentially converting t to 10 and e to 11).
//A negative sign, if present, is ignored.
	a=String(a);
	a=a.replace(/t/gi,"10");
	a=a.replace(/e/gi,"11");
	return Number(a);
}

function pitchClassAdd(a,b){
//Accepts two pitch classes. Returns their sum, also a pitch class.
	return intToPitchClass(pitchClassToInt(a)+pitchClassToInt(b));
}

function orderedPitchInterval(a,b){
//Accepts two integer pitches. Returns the ordered (signed) pitch interval.
//Passing a pitch class (i.e. containing 't' or 'e') will return NaN!
	return Number(b)-Number(a);
}

function unorderedPitchInterval(a,b){
//Accepts two integer pitches. Returns the unordered (unsigned) pitch interval.
//Passing a pitch class (i.e. containing 't' or 'e') will return NaN!
	return Math.abs(Number(b)-Number(a));
}

function orderedPitchClassInterval(a,b){
//Accepts two pitch classes. Returns the ordered pitch class interval {0123456789te}
	return intToPitchClass(pitchClassToInt(b)-pitchClassToInt(a));
}

function unorderedPitchClassInterval(a,b){
//Accepts two pitch classes. Returns the unordered pitch class interval {0123456}
	c = orderedPitchClassInterval(a,b);
	d = orderedPitchClassInterval(b,a);
	if (pitchClassToInt(c)<pitchClassToInt(d)){return c;}
	return d;
}
function validateCollection(a){
//Accepts a string. Returns a string of pitch classes with no extraneous characters.
	a=a.split("");
	al=a.length
	for(var y=0;y<al;y++){
	for(var x=0;x<al;x++){
		if(a[x]!='0' && a[x]!='1' && a[x]!='2' && a[x]!='3' && a[x]!='4' && a[x]!='5' && a[x]!='6' && a[x]!='7' && a[x]!='8' && a[x]!='9' && a[x]!='t' && a[x]!='T' && a[x]!='e' && a[x]!='E'){
			a.splice(x,1);
		}
	}
	}
	return a.join("");
}

function removeDuplicates(a){
//Accepts a string. Returns a string with no duplicates characters but in the same order.
a=validateCollection(a);
a=a.split("");
a=unique(a);
return a.join("");
}

function simplifyCollection(a){
//Accepts a string of pitch classes.
//Returns a string of pitch classes ordered and with no duplicates.
//This function ignores any characters that are not pitch classes.
//e.g. passing 'Test 321' returns '123te'
	a=String(a);
	pitcharray=new Array();
	for(x=0;x<10;x++){
		if (a.indexOf(String(x))>=0)
		{
			pitcharray.push(String(x));
		}
	}
	if (a.indexOf('t')>=0 || a.indexOf('T')>=0)
	{
		pitcharray.push('t');
	}
	if (a.indexOf('e')>=0 || a.indexOf('E')>=0)
	{
		pitcharray.push('e');
	}
	return pitcharray.join("");
}

function intervalCount(a){
//Accepts a pitch collection string.
//Returns the number of intervals contained.
	a=simplifyCollection(a);
	return (a.length*(a.length-1))/2;
}

function intervalVector(a){
//Accepts a pitch collection string.
//Returns an array with the number of each interval class (including interval class 0, which is simply the number of pitches)
	a=simplifyCollection(a);
	arr=new Array(a.length,0,0,0,0,0,0);
	for(x=0;x<a.length;x++) {
		for (y=x+1;y<a.length;y++)
		{
			arr[unorderedPitchClassInterval(a.charAt(x),a.charAt(y))]++;
		}
	}
	return arr;
}
function orderedIntervals(a){
//Accepts an ordered pitch collection string.
//Returns an array with the ordered intervals between each pair of pitches.
	a=validateCollection(a);
	a=a.split("");
	b=new Array();
		for(i=0;i<a.length-1;i++){
			b.push(orderedPitchClassInterval(a[i],a[i+1]));
		}
	return b;
}

function retrograde(a){
//Acceps an ordered pitch collection string.
//Returns a string with the same characters in reverse order.
	a=validateCollection(a);
	b='';
		for(x=a.length-1;x>=0;x--){
			b+=a.charAt(x);
		}
	return b;
}

function setTranspose(a,b){
//Accepts a pitch collection string and a number by which to transpose. 
//Returns a transposed pitch collection string.
//Both the original string and the returned string are sorted but not normalized.
	a=simplifyCollection(a);
	b=pitchClassToInt(b);
	t=new String();
	for (x=0;x<a.length;x++)
	{
		t+=pitchClassAdd(a.charAt(x),b);
	}
	return simplifyCollection(t);
}

function literalTranspose(a,b){
//Accepts a pitch collection string and a number by which to transpose. 
//Returns a transposed pitch collection string.
//Does not remove duplicates, sort, or verify either the entered or returned string. Non-pitch class characters will cause errors.
	a=String(a);
	b=Number(b);
	t=new String();
	for (x=0;x<a.length;x++)
	{
		t+=pitchClassAdd(a.charAt(x),b);
	}
	return String(t);
}

function normalOrder(a){
//Accepts a pitch collection string.
//Returns the pitch collection string in normal form (untransposed).
	a=simplifyCollection(a);
	//set up an array with all possible rotations of the collection
	rotationarray=new Array();
	rotationarray[0]=a;
	temparr=new Array();
	temparr=a.split("");
	for(x=1;x<a.length;x++){
		temparr.push(temparr.shift());
		rotationarray[x]=temparr.join("");
	}
	//loop through to find the ordering most packed to the left, starting with the outermost pitch classes:
	for(x=a.length-1;x>0;x--){ //examine a smaller group of pitches on each iteration.
		//loop 1: find the minimum interval:
		minint=13;
		for(y=0;y<rotationarray.length;y++){
			thisint=pitchClassToInt(orderedPitchClassInterval(rotationarray[y].charAt(0),rotationarray[y].charAt(x)));
			if(thisint<minint){minint=thisint;}
		}
		//loop 2: mark all with interval > minimum:
		for(y=0;y<rotationarray.length;y++){
			thisint=pitchClassToInt(orderedPitchClassInterval(rotationarray[y].charAt(0),rotationarray[y].charAt(x)));
			if (thisint>minint){
				rotationarray[y]="x";
			}
		}
	}
	//remove all marked entries. I go through the array length*length times just to be sure.
	z=rotationarray.length;
	for(x=0;x<z;x++){
		for(y=0;y<z;y++){
			if (rotationarray[y]=='x')
			{
				rotationarray.splice(y,1);
			}
		}
	}
	//Because we did simplifyCollection, in the case of a tie it is safe to pick the first remaining option.
	return String(rotationarray[0]);
}

function normalize(a){
//Accepts a pitch collection string.
//Returns the pitch collection string in normal form, transposed to 0 (C).
	a=normalOrder(a);
	return setTranspose(a,12-pitchClassToInt(a.charAt(0)));
}

function checkTranspose(a,b){
//Accepts two pitch collections
//Returns the interval by which they are transpositionally related.
//Returns -1 if they are not transpositionally related, or if they are not the same length.
	a=normalOrder(a);
	b=normalOrder(b);
	if (a.length==b.length)
	{
		aint=new Array();
		bint=new Array();
		//build an array with the intervals in the first collection
		for(x=0;x<a.length-1;x++){
			aint[x]=orderedPitchClassInterval(a.charAt(x),a.charAt(x+1));
		}
		//build an array with the intervals in the second collection
		for(x=0;x<b.length-1;x++){
			bint[x]=orderedPitchClassInterval(b.charAt(x),b.charAt(x+1));
		}
		
		//compare the two arrays
		for(x=0;x<aint.length;x++){
			if(aint[x]!=bint[x]){return -1;}
		}
		//must be transpositionally related. Return the difference.
		return orderedPitchClassInterval(a.charAt(0),b.charAt(0));

	}
	return -1;
}

function setInvert(a,b){
//Accepts a pitch collection and an interval of transposition.
//Returns the pitch collection resulting from first inverting then transposing.
	a=simplifyCollection(a);
	//if(!b){b=0;}
	b=pitchClassToInt(b);
	arr=new Array(a.length);
	for(x=0;x<a.length;x++){
		arr[x]=intToPitchClass(12-pitchClassToInt(a.charAt(x)));
	}
	return literalTranspose(arr.join(""),b);
}

function seriesInvert(a,b){
//Accepts an ordered pitch collection and an interval of transposition.
//Returns the (still ordered) pitch collection resulting from first inverting then transposing.
	a=validateCollection(a);
	b=pitchClassToInt(b);
	arr=new Array(a.length);
	for(x=0;x<a.length;x++){
		arr[x]=intToPitchClass(12-pitchClassToInt(a.charAt(x)));
	}
	return literalTranspose(arr.join(""),b);
}

function checkInvert(a,b){
//Accepts two pitch collections
//Returns the interval by which they are related by inversion (the index number or sum).
//Returns -1 if they are not related, or if they are not the same length.

	a=normalOrder(a);
	b=normalOrder(b);
	if (a.length==b.length)
	{
		c=setInvert(b,0);
		return checkTranspose(a,c);
	}
	return -1;
}

function primeForm(a){
//Accepts a pitch collection
//Returns the prime form of the same pitch collection (according to Forte!)
	a=normalOrder(a);
	invarr=new Array(2);
	invarr[0]=setTranspose(a,12-pitchClassToInt(a.charAt(0)));
	inversion=normalOrder(setInvert(a,0));
	invarr[1]=setTranspose(inversion,12-pitchClassToInt(inversion.charAt(0)));

	for(x=a.length-1;x>0;x--){ //examine a smaller group of pitches on each iteration.
		//loop 1: find the minimum interval:
		minint=13;
		for(y=0;y<invarr.length;y++){
			thisint=pitchClassToInt(orderedPitchClassInterval(invarr[y].charAt(0),invarr[y].charAt(x)));
			if(thisint<minint){minint=thisint;}
		}
		//loop 2: mark all with interval > minimum:
		for(y=0;y<invarr.length;y++){
			thisint=pitchClassToInt(orderedPitchClassInterval(invarr[y].charAt(0),invarr[y].charAt(x)));
			if (thisint>minint){
				invarr[y]="x";
			}
		}
	}
	//remove all marked entries. I go through the array length*length times just to be sure.
	z=invarr.length;
	for(x=0;x<z;x++){
		for(y=0;y<z;y++){
			if (invarr[y]=='x')
			{
				invarr.splice(y,1);
			}
		}
	}
	return String(invarr[0]);
}

function transpositionalCommonTones(a){
//Accepts a pitch collection
//Returns an array showing the number of common tones at each transposition level.
	a=normalOrder(a);
	commonarr = new Array(a.length,0,0,0,0,0,0,0,0,0,0,0);
	intvec=intervalVector(a);
	for (x=1;x<=5;x++)
	{
		commonarr[x]=intvec[x];commonarr[12-x]=intvec[x];
	}
	commonarr[6]=intvec[6]*2
	return commonarr;
}

function transpositionalSymmetry(a){
//Accepts a pitch collection
//Returns an array showing the transpositional levels at which the set maps onto itself.
//I would like this function to be more generic - accepting two params (a,b) and listing the levels at which set 'a' maps onto set 'b' (which could be itself still)
	a=normalOrder(a);
	commonarr = new Array(1,0,0,0,0,0,0,0,0,0,0,0);
	intvec=intervalVector(a);
	for (x=1;x<=5;x++)
	{
		if(intvec[x]>=a.length){commonarr[x]++;commonarr[12-x]++;}
	}
	if(intvec[6]>=(a.length/2)){commonarr[6]++;}
	return commonarr;
}

function pCombinatoriality(a){
//Accepts a pitch collection.
//Returns an array showing the transpositional levels at which the set maps onto its complement.
	a=simplifyCollection(a);
	b=simplifyCollection(complement(a));
	arr=new Array(0,0,0,0,0,0,0,0,0,0,0,0);
	for(px=0;px<12;px++){
		if(setTranspose(a,px)==b){arr[px]++;}
	}
	return arr;
}
function rCombinatoriality(a){
//Accepts a pitch collection.
//Returns an array showing the transpositional levels at which the set maps onto its complement.
	a=simplifyCollection(a);
	b=simplifyCollection(complement(a));
	rarr=new Array(0,0,0,0,0,0,0,0,0,0,0,0);
	for(px=0;px<12;px++){
		if(simplifyCollection(setInvert(a,intToPitchClass(px)))==b){rarr[px]++;}
	}
	return rarr;
}

function inversionalCommonTones(a){
	a=normalOrder(a);
	ctarray= new Array(0,0,0,0,0,0,0,0,0,0,0,0);
	for(x=0;x<a.length;x++){
		for(y=0;y<a.length;y++){
			ctarray[pitchClassToInt(pitchClassAdd(a.charAt(x),a.charAt(y)))]++;
		}
	}
	return ctarray;
}
function inversionalSymmetry(a){
//I would like this function to be more generic - accepting two params (a,b) and listing the levels at which set 'a' maps onto set 'b'
	a=normalOrder(a);
	ctarray=inversionalCommonTones(a);
	symarray=new Array(0,0,0,0,0,0,0,0,0,0,0,0);
	for(x=0;x<12;x++){
		if(ctarray[x]==a.length){symarray[x]++};
	}
	return symarray;
}

function sameSet(a,b){
//accepts two pitch collections. Returns true if they have the same prime, false if they don't.
	a=primeForm(a);
	b=primeForm(b);
	if(a==b){return true;}
	return false;
}

function zRelation(a,b){
//accepts two pitch collections. Returns true if they are Z-related, false otherwise.
//Returns true if the two collections are NOT from the set yet share the same interval vector.
	if(sameSet(a,b)){return false;}
	avec=intervalVector(a);
	bvec=intervalVector(b);
	if(avec.join("")==bvec.join("")){return true;}
	return false;
}

function complement(a){
//accepts a pitch collection. Returns the complementary pitch collection.
	a=simplifyCollection(a);
	b="";
	for(x=0;x<=9;x++){
		if(a.search(x)==-1){b+=String(x);}
	}
	if(a.search(/t/i)==-1){b+='t';}
	if(a.search(/e/i)==-1){b+='e';}
	return b;
}

///////////////////////////////////////////////////////////////////////////////////////////////
//binaryCount, unique and sortNumber are utilities to make the subset functions possible.//////////////
function binaryCount(N){
//accepts an integer, N. returns an array of binary numbers of length N. If N=3, returns 000...111
	mask=new Array();
	for(x=0;x<Math.pow(2,N);x++){
		b=x.toString(2);
		while(b.length<N){b='0'+b;}
		mask[x]=b;
	}
	return mask;
}


function sortNumber(a,b)
{
	return pitchClassToInt(a) - pitchClassToInt(b);
}

function unique(a) {
	tmp = new Array(0);
	for(i=0;i<a.length;i++){
		if(!contains(tmp, a[i])){
			tmp.length+=1;
			tmp[tmp.length-1]=a[i];
		}
	}
	return tmp;
}
function contains(a, e) {
	for(j=0;j<a.length;j++)if(a[j]==e)return true;
	return false;
}
///////////////////////////////////////////////////////////////////////////////////////////////


function subsets(a){
//accepts a pitch collection. Returns a two-dimensional array containing all subsets of all lengths. (e.g. arr[3][2] is the third subset of length 3)
//arr[0][0] will be contain null and arr[a.length] will have only one value.
	a=simplifyCollection(a);
	mask=binaryCount(a.length);
	subsetarray=new Array(mask.length);
	sortedarray=new Array(a.length+1);
	for(x=0;x<a.length+1;x++){sortedarray[x]=new Array();}

	for(x=0;x<subsetarray.length;x++){
		subsetarray[x]=''
		for(y=0;y<a.length;y++){
			if(mask[x].charAt(y)=='1'){
				subsetarray[x]+=a.charAt(y);
			}
		}
	}
	subsetarray.sort(sortNumber);
	for(x=0;x<subsetarray.length;x++){
		sortedarray[subsetarray[x].length].push(subsetarray[x]);
	}
	return sortedarray;
}

function uniqueSubsets(a) {
	a=primeForm(a);
	mask=binaryCount(a.length);
	subsetarray=new Array(mask.length);
	sortedarray=new Array(a.length+1);
	for(x=0;x<a.length+1;x++){sortedarray[x]=new Array();}

	for(x=0;x<subsetarray.length;x++){
		subsetarray[x]=''
		for(y=0;y<a.length;y++){
			if(mask[x].charAt(y)=='1'){
				subsetarray[x]+=a.charAt(y);
			}
		}
	}
	for(i=0;i<subsetarray.length;i++){
		subsetarray[i]=primeForm(subsetarray[i]);
	}
	subsetarray.sort(sortNumber);
	subsetarray=unique(subsetarray);
	for(x=0;x<subsetarray.length;x++){
		sortedarray[subsetarray[x].length].push(subsetarray[x]);
	}
	return sortedarray;
}

function literalSubset(a,b){
//accepts two pitch collections. Returns true if the smaller collection is a literal subset of the larger one.
//if the two collections are the same size, this function checks to see if they are identical.
	a=simplifyCollection(a);b=simplifyCollection(b);
	if(a.length==b.length){
		if(a==b){return true;}
		return false;
	}
	if(b.length>a.length){var c=a;a=b;b=c;} //now a is the larger set.
	asubsets=subsets(a);
	for(x=0;x<asubsets[b.length].length;x++){
		if(asubsets[b.length][x]==b){return true;}
	}
	return false;
}

function abstractSubset(a,b){
//accepts two pitch collections. Returns true if the smaller collection is an abstract subset of the larger one.
//if the two collections are the same size, this function checks to see if they are in the same set class.
a=primeForm(a);
b=primeForm(b);
if(a.length==b.length){if (primeForm(a)==primeForm(b)){return true;}else{return false;}}
if(b.length>a.length){c=a;a=b;b=c;} //now a is the larger set.
	asubsets=uniqueSubsets(a);
	for(x=0;x<asubsets[b.length].length;x++){
		if(asubsets[b.length][x]==b){return true;}
	}
	return false;

}
function union(a,b){
//accepts two pitch collections. Returns the pitch collection containing all of the pitches in either collection.
	a=simplifyCollection(a);
	b=simplifyCollection(b);

	pitcharray=new Array();
	for(x=0;x<10;x++){
		if (a.indexOf(String(x))>=0 || b.indexOf(String(x))>=0)
		{
			pitcharray.push(String(x));
		}
	}
	if (a.indexOf('t')>=0 || b.indexOf('T')>=0 || b.indexOf('t')>=0 || b.indexOf('T')>=0)
	{
		pitcharray.push('t');
	}
	if (a.indexOf('e')>=0 || b.indexOf('E')>=0 || b.indexOf('e')>=0 || b.indexOf('E')>=0)
	{
		pitcharray.push('e');
	}
	return pitcharray.join("");

}
function intersection(a,b){
//accepts two pitch collections. Returns the pitch collection containing the pitch classes that the collections share.
	a=simplifyCollection(a);
	b=simplifyCollection(b);

	pitcharray=new Array();
	for(x=0;x<10;x++){
		if (a.indexOf(String(x))>=0 && b.indexOf(String(x))>=0)
		{
			pitcharray.push(String(x));
		}
	}
	if ((a.indexOf('t')>=0 || b.indexOf('T')>=0) && (b.indexOf('t')>=0 || b.indexOf('T')>=0))
	{
		pitcharray.push('t');
	}
	if ((a.indexOf('e')>=0 || b.indexOf('E')>=0) && (b.indexOf('e')>=0 || b.indexOf('E')>=0))
	{
		pitcharray.push('e');
	}
	return pitcharray.join("");

}
function setMultiply(a,b){
//accepts two pitch collections. Returns their product -- builds the intervals in the first set from each note in the second set.
//setMultiply(a,b) != setMultiply(b,a)
//also, the order of the set matters - keep literal.
	a=validateCollection(a); 
	b=validateCollection(b);
	resultarray=new Array();
	for(count=0;count<b.length;count++){
		resultarray[count]=literalTranspose(a,pitchClassToInt(orderedPitchClassInterval(pitchClassToInt(a.charAt(0)),pitchClassToInt(b.charAt(count)))));
	}
	resultarray=resultarray.join("");
	return normalOrder(resultarray);
}
function seriesRotate(a,b){
//accepts an ordered pitch collection and an integer. Rotates the collection 'b' times so that it starts on the (b+1)th note.
	a=validateCollection(a);
	a=a.split("");
	for(var x=0;x<b;x++){
		a.push(a.shift());
	}
	return a.join("");
}