/*	guitare_neck.js
	copyleft Tancrède Bastié 2004-2008

requires:
	dom.js
	js_lib.js

index:
	GuitarNeck(notBass, notLeftHanded, neckTitle)
		addFretRange(id, onchange)
		addGuitarNeck(id)
		addTuners(id, onchange)
		clearLayer(layer)
		clearLayers()
		displayCTone(string)
		displayCMap()
		displayNote(layer, string, octave, note, src)
		displayPosition(layer, string, fret, src)
		positionToInterval(rootString, rootFret, otherString, otherFret)
		positionToNote(string, fret)
		randomFret()
		randomOffsetFret(rootFret, [maxOffset])
		randomOffsetString (rootString, [maxOffset])
		randomString([from], [to])
*/

function GuitarNeck(notBass, notLeftHanded, neckTitle) {
/*	prototype of guitar neck
	prototype de manche de guitare
*/

	if (notBass) {
		var numberOfStrings = 6;
		var neckSrc = "guitar_neck_";
		var neckAlt = "manche de guitare";
	}
	else {
		var numberOfStrings = 4;
		var neckSrc = "bass_neck_";
		var neckAlt = "manche de basse";
	}
	if (notLeftHanded) neckSrc += "right.jpg"; else neckSrc += "left.jpg";

	var fromFretRange;
	var toFretRange;

	this.addFretRange = function(id, onchange) {
	/*	creates selectors used to specify the studied range of fret
		créé les sélecteurs utilisés pour spécifier la plage des cases étudiées
	*/
		function rangeSelector(value) {
			var selector = select();
			for (var i = 0; i < 25; i++) selector.appendChild(option(i, i));
			/*	Unfortunately Opera cannot do this immediately (see hack later in the file) :
				Malheureusement Opera ne peut pas faire ceci immédiatement (voir l'astuce plus loin) :
			*/
			selector.value = value;
			return selector;
		}

		fromFretRange = rangeSelector(0);
		toFretRange = rangeSelector(24);
		fromFretRange.onchange = function() { if (1*fromFretRange.value > 1*toFretRange.value) toFretRange.value = fromFretRange.value; onchange(); }
		toFretRange.onchange = function() { if (1*fromFretRange.value > 1*toFretRange.value) fromFretRange.value = toFretRange.value; onchange(); }

		/*	Opera hack (I want this fucking value set and I'm gonna have it set) :
			Astuce pour Opera (je veux fixer cette valeur et je la fixerai, nom d'une pipe) :
		*/
		fromFretRange.id = "fromFretRange";
		toFretRange.id = "toFretRange";
		setTimeout("document.getElementById('fromFretRange').value = 0;", 10);
		setTimeout("document.getElementById('toFretRange').value = 24;", 10);
		setTimeout("rangeSelectorsReady = true;", 10);

		return addTo(id, textNode("de la case "), fromFretRange, textNode(" à la case "), toFretRange);
	}

	this.randomFret = function() {
	/*	pick a random fret inside the studied range
		tire au sort une case dans la plage étudiée
	*/
		return Math.randomInteger(fromFretRange.value, toFretRange.value);
	}

	this.randomOffsetFret = function(rootFret, maxOffset) {
	/*	pick a random fret relative to another fret inside the studied range
		tire au sort une case par rapport à une autre dans la plage étudiée
	*/
		if (maxOffset == null) maxOffset = 4;
		var newFret = rootFret + Math.randomInteger(-maxOffset, maxOffset);
		if (newFret < fromFretRange.value) return fromFretRange.value;
		if (newFret > toFretRange.value) return toFretRange.value;
		return newFret;
	}

	var stringCheckboxes = new Array;
	var stringTuners = new Array;
	var stringTunings = new Array(4, 9, 2, 7, 11, 4);

	this.addTuners = function(id, onchange) {
	/*	creates checkboxes used to specify the studied strings and tuning selectors
		créé les cases à cocher utilisées pour spécifier les cordes étudiées et les menus déroulants d'accordage
	*/
		var noteNames = new Array("C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B");

		function tuningSelector(value) {
			var selector = select();
			for (var i = 0; i < noteNames.length; i++) selector.appendChild(option(noteNames[i], i));
			/*	Unfortunately Opera cannot do this immediately (see hack later in the file) :
				Malheureusement Opera ne peut pas faire ceci immédiatement (voir l'astuce plus loin) :
			*/
			selector.value = value;
			return selector;
		}

		var container = document.getElementById(id);

		for (var i = 0; i < numberOfStrings; i++) {
			stringCheckboxes[i] = checkbox(true);
			stringCheckboxes[i].onchange = onchange;

			stringTuners[i] = tuningSelector(stringTunings[i]);
			stringTuners[i].tunerIndex = i;
			stringTuners[i].onchange = function() { stringTunings[this.tunerIndex] = this.value; onchange(); }

			/*	Opera hack (I want this fucking value set and I'm gonna have it set) :
				Astuce pour Opera (je veux fixer cette valeur et je la fixerai, nom d'une pipe) :
			*/
			stringTuners[i].id = "stringTuner" + i;
			setTimeout("document.getElementById('stringTuner" + i + "').value = " + stringTunings[i] + ";", 10);

			var inlineSpan = span(stringCheckboxes[i], stringTuners[i]);
			inlineSpan.className = "tuner";
			appendTo(container, inlineSpan);
		}

		return container;
	}

	this.randomString = function(from, to) {
	/*	pick a random studied string
		tire au sort une corde étudiée
	*/
		if (to == null)
			if (from == null) {
				from = 0;
				to = numberOfStrings;
			}
			else {
				to = from;
				from = 0;
			}
		var i = Math.randomInteger(from, to);
		for (var j = 0; j < numberOfStrings; j++) {
			if (stringCheckboxes[i].checked) break;
			i += 1;
			if (i >= numberOfStrings) i = 0;
		}
		return i;
	}

	this.randomOffsetString = function(rootString, maxOffset) {
	/*	pick a random studied string, relative to another string
		tire au sort une corde étudiée, par rapport à une autre
	*/
		if (maxOffset == null) maxOffset = numberOfStrings;
		for (var i = 0; i < 6; i++) {
			var newString = this.randomString(rootString, numberOfStrings);
			if (newString - rootString <= maxOffset) return newString;
		}
		return newString;
	}

	/*	strings is the array of fingering images
		strings est le tableau des images de doigtés
	*/
	var strings = new Array;
	for (var i = 0; i < numberOfStrings; i++) strings.push(new Array);

	this.addGuitarNeck = function(id) {
	/*	creates neck display
		créé l'affichage du manche
	*/
		var container = document.getElementById(id);
		var neck = img(neckSrc, neckAlt, neckTitle);
		neck.style.position = "absolute";
		neck.style.left = "0px";
		neck.style.top = "0px";
		appendTo(container, neck);

		if (notBass) {
			var x = new Array(0, 65, 133, 198, 261, 321, 380, 434, 485, 532, 577, 618, 656, 691, 724, 755, 785, 811, 837, 861, 883, 903, 922, 940, 958);
			var xa = new Array(1, 0.995, 0.987, 0.980, 0.974, 0.968);
			var xb = new Array(13, 15, 18, 20, 22, 24);
			var ya = new Array(0.002, 0, -0.003, -0.005, -0.008, -0.011);
			var yb = new Array(71, 58, 45, 32, 20, 8);
			if (notLeftHanded) var offset = 0; else var offset = 984;
		}
		else {
			var x = new Array(0, 61, 124, 183, 241, 299, 354, 406, 455, 503, 549, 592, 632, 670, 706, 740, 772, 801, 830, 857, 881, 904, 926, 946, 965);
			var xa = new Array(1, 0.994, 0.987, 0.979);
			var xb = new Array(11, 14, 17, 20);
			var ya = new Array(0.013, 0.004, -0.006, -0.013);
			var yb = new Array(47, 35, 23, 11);
			if (notLeftHanded) var offset = 0; else var offset = 990;
		}
		if (notLeftHanded) var sign = 1; else var sign = -1;
		for (var i = 0; i < numberOfStrings; i++) for (var j = 0; j < 25; j++) {
			var finger = img("empty.gif", neckAlt, neckTitle);
			finger.style.position = "absolute";
			finger.style.left = Math.round(sign * (xa[i] * x[j] + xb[i]) + offset) + "px";
			finger.style.top = Math.round(ya[i] * x[j] + yb[i]) + "px";
			appendTo(container, finger);
			strings[i].push(finger);
		}

		return container;
	}

	/*	there are two layers of fingerings
		il y a deux calques de doigtés
	*/
	var layers = new Array;
	for (var i = 0; i < 2; i++) {
		layers.push(new Array);
		for (var j = 0; j < numberOfStrings; j++) {
			layers[i].push(new Array);
			for (var k = 0; k < 25; k++) layers[i][j].push(null);
		}
	}

	this.positionToNote = function(string, fret) {
	/*	return a note according to position and current tuning
		rend une note d'après une position et l'accordage courant
	*/
		return (1*fret + 1*stringTunings[string] + 12) % 12;
	}

	this.positionToInterval = function(rootString, rootFret, otherString, otherFret) {
	/*	return an interval according to two positions and current tuning
		rend un intervalle d'après deux positions et l'accordage courant
	*/
		var root = this.positionToNote(rootString, rootFret);
		var other = this.positionToNote(otherString, otherFret);
		if (rootString < otherString) return ((1*other + 12) - root) % 12;
		else if (rootString > otherString) return ((1*root + 12) - other) % 12;
			else if (rootFret < otherFret) return ((1*other + 12) - root) % 12;
				else return ((1*root + 12) - other) % 12;
	}

	this.displayPosition = function(layer, string, fret, src) {
	/*	display src at given string and fret
		affiche src sur la corde et à la frette données
	*/
		if (layer == 0) if (layers[1][string][fret] != null) return;
		layers[layer][string][fret] = src;
		if (src == null) src = "empty.gif";
		strings[string][fret].src = src;
	}

	this.displayNote = function(layer, string, octave, note, src) {
	/*	display src at given string octave and note
		affiche src sur la corde, à l'octave et à la note données
	*/
		this.displayPosition(layer, string, 12 * octave + (note - stringTunings[string]  + 12) % 12, src);
	}

	this.displayCTone = function(string) {
	/*	display notes of C major scale on bottom layer
		affiche les notes d'ut sur le calque inférieur
	*/
		var noteSrc = new Array("c.gif", false, "d.gif", false, "e.gif", "f.gif", false, "g.gif", false, "a.gif", false, "b.gif");
		for (var i = 0; i < noteSrc.length; i++)
			if (noteSrc[i]) {
				var fret = (i - stringTunings[string]  + 12) % 12;
				this.displayPosition(0, string, fret, noteSrc[i]);
				this.displayPosition(0, string, 1*fret + 12, noteSrc[i]);
				if (fret == 0) this.displayPosition(0, string, 24, noteSrc[i]);
			}
	}

	this.displayCMap = function() {
	/*	display notes of C major scale over whole neck
		affiche les notes d'ut sur tout le manche
	*/
		var allUnchecked = true;
		for (var i = 0; i < numberOfStrings; i++) allUnchecked = allUnchecked & (!stringCheckboxes[i].checked);
		for (var i = 0; i < numberOfStrings; i++) if (allUnchecked | stringCheckboxes[i].checked) this.displayCTone(i);
	}

	this.clearLayer = function(layer) {
		for (var i = 0; i < numberOfStrings; i++) for (j = 0; j < 25; j++)
			if (layers[layer][i][j] != null) this.displayPosition(layer, i, j);
	}

	this.clearLayers = function() {
		this.clearLayer(0);
		this.clearLayer(1);
	}
}
