/*	staff.js
	copyleft Tancrède Bastié 2006-2008

requires:
	dom.js
	js_lib.js

index:
	Staff(clef, key, numberOfNotes, staffTitle)
		addClefAndKeySelect(clefId, keyId, onchange)
		addStaff(id)
		currentNote()
		currentInterval()
		displayHeight(index, height)
		fillStaff(maxInterval, [noteMask])
		heightToNote(height)
		heightsToInterval(rootHeight, otherHeight)
		randomHeight([noteMask])
		resetCursor()
		setCursor(index, color)
		setCursorColor(color)
		setHeight(index, height)
		stepCursor()
*/

function Staff(clef, key, numberOfNotes, staffTitle, cursorSize) {
/*	prototype of staff
	prototype de portée
*/

	if (cursorSize == null) cursorSize = 1;

	clefPositions = new Array(8, 12);
	utPositions = new Array(4, 2);
	noteSteps = new Array(0, 2, 4, 5, 7, 9, 11)

	keys = new Array;
	keys.push(new Array(-1, -1, -1, -1, -1, -1, -1));
	keys.push(new Array(-1, -1, -1, -1, 0, -1, -1));
	keys.push(new Array(0, -1, -1, 0, -1, -1, -1));
	keys.push(new Array(0, -1, -1, 0, 0, -1, -1));
	keys.push(new Array(0, 0, -1, 0, 0, -1, -1));
	keys.push(new Array(0, 0, -1, 0, 0, 0, -1));
	keys.push(new Array(0, 0, 0, 0, 0, 0, -1));
	keys.push(new Array(0, 0, 0, 0, 0, 0, 0));
	keys.push(new Array(0, 0, 0, 1, 0, 0, 0));
	keys.push(new Array(1, 0, 0, 1, 0, 0, 0));
	keys.push(new Array(1, 0, 0, 1, 1, 0, 0));
	keys.push(new Array(1, 1, 0, 1, 1, 0, 0));
	keys.push(new Array(1, 1, 0, 1, 1, 1, 0));
	keys.push(new Array(1, 1, 1, 1, 1, 1, 0));
	keys.push(new Array(1, 1, 1, 1, 1, 1, 1));

	var clefSelect;
	var keySelect;

	this.addClefAndKeySelect = function(clefId, keyId, onchange) {
	/*	creates the selectors used to specify the clef and key signature
		créé les sélecteurs utilisés pour spécifier la clef et la tonalité
	*/
		clefSelect = select();
		clefSelect.appendChild(option("sol", 0));
		clefSelect.appendChild(option("fa", 1));

		keySelect = select();
		keySelect.appendChild(option("do #", 14));
		keySelect.appendChild(option("fa #", 13));
		keySelect.appendChild(option("si", 12));
		keySelect.appendChild(option("mi", 11));
		keySelect.appendChild(option("la", 10));
		keySelect.appendChild(option("ré", 9));
		keySelect.appendChild(option("sol", 8));
		keySelect.appendChild(option("do", 7));
		keySelect.appendChild(option("fa", 6));
		keySelect.appendChild(option("si b", 5));
		keySelect.appendChild(option("mi b", 4));
		keySelect.appendChild(option("la b", 3));
		keySelect.appendChild(option("ré b", 2));
		keySelect.appendChild(option("sol b", 1));
		keySelect.appendChild(option("do b", 0));

		clefSelect.onchange = function() {
			clefDisplay.src = "clef_" + clefSelect.value + ".gif";
			keyDisplay.src = "key_" + clefSelect.value + "_" + keySelect.value + ".gif";
			onchange();
		}
		keySelect.onchange = function() {
			clefDisplay.src = "clef_" + clefSelect.value + ".gif";
			keyDisplay.src = "key_" + clefSelect.value + "_" + keySelect.value + ".gif";
		}

		addTo(clefId, textNode("Clef : "), clefSelect);
		clefSelect.value = clef;
		addTo(keyId, textNode("Tonalité : "), keySelect, textNode(" (majeur)"));
		keySelect.value = key;
	}

	var previousRandomHeight = 0;

	this.randomHeight = function(noteMask) {
	/*	pick a random height on the staff, optional mask is relative to clef
		tire au sort une hauteur aléatoire sur la portée, la masque optionel est relatif à la clef
	*/
		var height = Math.randomInteger(21);
		if (noteMask == null) while (height == previousRandomHeight) height = Math.randomInteger(21);
		else while ((height == previousRandomHeight) || (!noteMask[(height - clefPositions[clefSelect.value] + 7) % 7])) height = Math.randomInteger(21);
		previousRandomHeight = height;
		return height;
	}

	var noteImages = new Array;
	var notes = new Array

	this.addStaff = function(id) {
	/*	creates staff display
		créé l'affichage de la portée
	*/
		var container = document.getElementById(id);
		clefDisplay = img("clef_" + clef + ".gif", "Clef", "Clef");
		clefDisplay.style.position = "absolute";
		clefDisplay.style.left = "0px";
		clefDisplay.style.top = "0px";
		keyDisplay = img("key_" + clef + "_" + key + ".gif", "Tonalité", "Tonalité");
		keyDisplay.style.position = "absolute";
		keyDisplay.style.left = "24px";
		keyDisplay.style.top = "0px";
		appendTo(container, clefDisplay, keyDisplay);
		left = 78;
		for (var i = 0; i < numberOfNotes; i++) {
			if ((i % 16 == 0) && (i > 0)) {
				var bar = img("bar.gif", "Barre", staffTitle);
				bar.style.position = "absolute";
				bar.style.left = left + "px";
				bar.style.top = "0px";
				appendTo(container, bar);
				left += 3;
			}
			var noteImage = img("note_0.gif", "Portée", staffTitle);
			noteImage.style.position = "absolute";
			noteImage.style.left = left + "px";
			noteImage.style.top = "0px";
			noteImages.push(noteImage);
			notes.push(0);
			appendTo(container, noteImage);
			left += 12;
		}

		this.resetCursor();

		return container;
	}

	this.setHeight = function(index, height) {
	/*	set and display a note on the staff
		règle et affiche une note sur la portée
	*/
		notes[index] = height;
		this.displayHeight(index, height);
	}

	this.fillStaff = function(maxInterval, noteMask) {
	/*	fill staff with random notes according to maximum interval and note mask
		rempli la portée de notes aléatoires selon l'intervalle maximum et le masque de note
	*/
		var current;
		var previous = this.randomHeight(noteMask);
		var i = 0;
		while (i < numberOfNotes) {
			var j = 0;
			while (j < 40) {
				current = this.randomHeight(noteMask);
				var interval = current - previous;
				if ((interval != 0) && (Math.abs(interval) <= maxInterval)) break;
				j += 1;
			}
			this.setHeight(i, current);
			previous = current;
			i += 1;
		}
	}

	/*	Current position on staff
		Position courante sur la portée
	*/
	var cursor = 0;

	var cursorColors = new Array("transparent", "#F32", "#FE6", "#0F0")

	/*	Set cursor colored state
		Change l'état coloré du curseur
	*/
	this.setCursorColor = function(color) {
		for (var i = 0; i < cursorSize; i++) noteImages[cursor + i].style.backgroundColor = cursorColors[color];
	}

	this.setCursor = function(index, color) {
	/*	Set cursor position and color
		Règle la position et la couleur du curseur
	*/
		this.setCursorColor(0);
		cursor = index;
		if (cursor + cursorSize > numberOfNotes) cursor = 0;
		this.setCursorColor(color);
		return cursor;
	}

	this.resetCursor = function() {
	/*	Reset cursor to initial position
		Replace le curseur à la position initiale
	*/
		for (var i = 0; i < numberOfNotes; i++) noteImages[i].style.backgroundColor = cursorColors[0];
		this.setCursor(0, 2)
	}

	/*	Move cursor one step
		Avance le curseur d'un cran
	*/
	this.stepCursor = function() {
		return this.setCursor(cursor + 1, 2);
	}

	/*	Return note at cursor
		Rends la note courante
	*/
	this.currentNote = function() {
		return heightToNote(notes[cursor]);
	}

	/*	Return interval at cursor
		Rends l'intervalle courant
	*/
	this.currentInterval = function() {
		return heightsToInterval(notes[cursor], notes[cursor + 1]);
	}

	function heightToNote(height) {
	/*	returns a note according to height on staff, clef and key
		rend une note d'après une hauteur sur la portée, la clef et la tonalité
	*/
		var note = (height - utPositions[clefSelect.value] + 7) % 7;
		return (keys[keySelect.value][note] + noteSteps[note] + 12) % 12;
	}

	function heightsToInterval(rootHeight, otherHeight) {
	/*	returns an interval according to two height on staff, clef and key
		rend un intervalle d'après deux hauteurs sur la portée, la clef et la tonalité
	*/
		var root = heightToNote(rootHeight);
		var other = heightToNote(otherHeight);
		if (rootHeight > otherHeight) return (root + 12 - other) % 12;
		else return (other + 12 - root) % 12;
	}

	this.displayHeight = function(index, height) {
	/*	display note of given height at index
		affiche la note de hauteur donnée à l'index
	*/
		var src = "note_" + height + ".gif";
		noteImages[index].src = src
	}
}
