//=============================================================================================================
// File:		ucbScalePrinter.js
// Purpose: 	Implements a whole scale creation interface.
// Author:		JDH/1999-2002

//=============================================================================================================
// Global variables
//=============================================================================================================

	// The standard monotonic scale
var g_ScaleNotes = [ "A", "A#", "B", "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#" ];

//=============================================================================================================
// Global functions
//=============================================================================================================

// Method:		MidiToNoteValue
// Purpose:		Converts a MIDI value to a note index where 0 = A and 11 = G#.
// Arguments:	midiValue - The MIDI value
//
function MidiToNoteValue( midiValue )
{
	return ( midiValue + 3 ) % 12;
}

// Class:		ScalePrinter
// Purpose:		The scale editing and printing object.
// Arguments:	thisObjectName - The name of this object.  Required because we generate HTML
//					using this object that needs to point back to this object.
//				topMidiNotes - The MIDI values of the notes at the top of the strings (60=middle C).
//				instrumentFretCount - The number of frets on the instrument
//
function ScalePrinter( thisObjectName,
					   topMidiNotes,
					   instrumentFretCount,
					   rootNoteStatic,
					   noteCountStatic,
					   noteListStatic )
{
	// Store this objects name
	this.thisObjectName 		= thisObjectName;
	
	this.rootNoteStatic = rootNoteStatic;
	this.noteCountStatic = noteCountStatic;
	this.noteListStatic = noteListStatic;
	
	// Store the fret and string count
	this.instrumentFretCount 	= instrumentFretCount;
	this.instrumentStringCount 	= topMidiNotes.length;
	
	// Store the top notes
	this.topMidiNotes 			= topMidiNotes;
	
	// Calculate the maximum MIDI note (this assumes that the 
	// last string will have the highest value.)
	this.maxMidiNote 			= this.topMidiNotes [ this.topMidiNotes.length - 1 ] + this.instrumentFretCount;

	// Set initial parameters for the search
	this.scaleSteps 			= [ 1 ];	// The scale steps
	this.maxStretch 			= 2;		// The number of frets to go up before looking for the next string
	this.maxOctaves 			= 2;		// The maximum number of octaves to run to
	this.lastStartString 		= -1;		// The last starting string
	this.lastStartFret 			= -1;		// The last starting fret
	this.clearAll				= "";		// A string that can be evaluated to clear the fretboard
	this.lastAdjusterStart		= -2;		// The last start of the adjuster
	this.lastAdjusterStretch	= -2;		// The last stretch factor shown by the adjuster
	this.clearAdjusterLine		= "";		// A string that can be evaluated to clear the adjuster

	// Set the we have not yet initialized.  We should be initialized from the document.onLoad.
	this.hasInitialized 		= false;

	// This is a constant array that tells you the number of balls located on the indexed fret.	
	this.fretBall = [ 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 2, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 2 ];

	// Get the images for the notes at the top of the fretboard
	this.notesImages = [];
	for( var noteImage = 0; noteImage < 12; noteImage++ )
	{
		this.notesImages[ noteImage ] = new Image( 14, 14 );
		this.notesImages[ noteImage ].src = "images/top_" + noteImage + ".gif";
	}

	// Get the filled in note values on the fretboard	
	this.fretImage = [];
	this.fretImage[ 0 ] = new Image( 14, 17 );
	this.fretImage[ 0 ].src = "images/fret_filled.gif";
	this.fretImage[ 1 ] = new Image( 14, 17 );
	this.fretImage[ 1 ].src = "images/fret_1.gif";
	this.fretImage[ 3 ] = new Image( 14, 17 );
	this.fretImage[ 3 ].src = "images/fret_3.gif";
	this.fretImage[ 5 ] = new Image( 14, 17 );
	this.fretImage[ 5 ].src = "images/fret_5.gif";
	this.fretImage[ 7 ] = new Image( 14, 17 );
	this.fretImage[ 7 ].src = "images/fret_7.gif";
	this.fretImage[ 9 ] = new Image( 14, 17 );
	this.fretImage[ 9 ].src = "images/fret_9.gif";
	
	// Get the filled in note values on the top of the fretboard	
	this.topFretImage = [];
	this.topFretImage[ 0 ] = new Image( 14, 17 );
	this.topFretImage[ 0 ].src = "images/fret_filled_top.gif";
	this.topFretImage[ 1 ] = new Image( 14, 17 );
	this.topFretImage[ 1 ].src = "images/fret_1_top.gif";
	this.topFretImage[ 3 ] = new Image( 14, 17 );
	this.topFretImage[ 3 ].src = "images/fret_3_top.gif";
	this.topFretImage[ 5 ] = new Image( 14, 17 );
	this.topFretImage[ 5 ].src = "images/fret_5_top.gif";
	this.topFretImage[ 7 ] = new Image( 14, 17 );
	this.topFretImage[ 7 ].src = "images/fret_7_top.gif";
	this.topFretImage[ 9 ] = new Image( 14, 17 );
	this.topFretImage[ 9 ].src = "images/fret_9_top.gif";
	
	// Get the plain fretboard and fretboard top images	
	this.fretPlainImage = new Image( 14, 17 );
	this.fretPlainImage.src = "images/fret_plain.gif";
	this.fretPlainTopImage = new Image( 14, 17 );
	this.fretPlainTopImage.src = "images/fret_plain_top.gif";
	
	// Get the generic transparent image	
	this.transparentImage = new Image( 1, 1 );
	this.transparentImage.src = "images/transparent.gif";
	
	// Get the left and left top image	
	this.leftTopImage = new Image( 3, 17 );
	this.leftTopImage.src = "images/left_top.gif";
	this.leftImage = new Image( 3, 17 );
	this.leftImage.src = "images/left.gif";
	
	// Get the right top and right ball images	
	this.rightTopImage = new Image( 7, 17 );
	this.rightTopImage.src = "images/right_top.gif";
	this.rightImage = [];
	this.rightImage[ 0 ] = new Image( 7, 17 );
	this.rightImage[ 0 ].src = "images/right_0.gif";
	this.rightImage[ 1 ] = new Image( 7, 17 );
	this.rightImage[ 1 ].src = "images/right_1.gif";
	this.rightImage[ 2 ] = new Image( 7, 17 );
	this.rightImage[ 2 ].src = "images/right_2.gif";

	// These are scale specific images that are used to build the stretch length
	// adjuster.	
	this.adjustButtonUp = new Image( 17, 17 );
	this.adjustButtonUp.src = "images/scale_adjust_button_up.png";
	this.adjustButtonMiddle = new Image( 17, 17 );
	this.adjustButtonMiddle.src = "images/scale_adjust_button_middle.png";
	this.adjustButtonDown = new Image( 17, 17 );
	this.adjustButtonDown.src = "images/scale_adjust_button_down.png";
	
	this.adjustLineUp = new Image( 17, 17 );
	this.adjustLineUp.src = "images/scale_adjust_line_up.png";
	this.adjustLineMiddle = new Image( 17, 17 );
	this.adjustLineMiddle.src = "images/scale_adjust_line_middle.png";
	this.adjustLineDown = new Image( 17, 17 );
	this.adjustLineDown.src = "images/scale_adjust_line_down.png";
}

// Method:		SetScaleNotes
// Purpose:		Sets the new scale intervales (usually called by a ScaleSelector).
// Arguments:	noteIntervals - The new intervals.
//
ScalePrinter.prototype.SetScaleNotes = function( noteIntervals )
{
	this.scaleSteps = noteIntervals;

	// Rebuild the scale if we have been initialized
	if ( this.hasInitialized )
		this.BuildScale( this.lastStartString, this.lastStartFret );
}

// Method:		Initialize
// Purpose:		Initializes the Scale control.
// Arguments:	None.
//
ScalePrinter.prototype.Initialize = function( )
{
	// Builds a scale at the starting position
	this.BuildScale( 0, 0 );
	
	// Mark that we have been initialized
	this.hasInitialized = true;
}

// Method:		SetMaxOctaves
// Purpose:		Sets the maximum number of octaves.
// Arguments:	maxOctaves - The maximum number of octaves.
//
ScalePrinter.prototype.SetMaxOctaves = function( maxOctaves )
{
	if ( maxOctaves != this.maxOctaves )
	{
		this.maxOctaves = maxOctaves;
		this.BuildScale( this.lastStartString, this.lastStartFret );
	}
}

// Method:		SetMaxOctaves
// Purpose:		Sets the maximum number of octaves.
// Arguments:	maxOctaves - The maximum number of octaves.
//
ScalePrinter.prototype.GetLocationName = function( stringNumber, fretNumber )
{
	var s = "s" + stringNumber + "f" + fretNumber;
	return s;
}

// Method:		GetMidiNote
// Purpose:		Returns the MIDI note for a particular fretboard position.
// Arguments:	stringNumber - The string number.
//				fretNumber - The fret number.
//
ScalePrinter.prototype.GetMidiNote = function( stringNumber, fretNumber )
{
	return this.topMidiNotes[ stringNumber ] + fretNumber;
}

// Method:		GetNoteName
// Purpose:		Returns the name of the note at the location specified.
// Arguments:	stringNumber - The string number.
//				fretNumber - The fret number.
//
ScalePrinter.prototype.GetNoteName = function( stringNumber, fretNumber )
{
	return g_ScaleNotes[ MidiToNoteValue( this.topMidiNotes[ stringNumber ] + fretNumber ) ];
}

// Method:		FindMidiNote
// Purpose:		Finds the fret number of the midi note specified on the string specified.
// Arguments:	stringNumber - The string number.
//				noteNumber - The MIDI note number.
//
ScalePrinter.prototype.FindMidiNote = function( stringNumber, noteNumber )
{
	var fretNumber = noteNumber - this.topMidiNotes[ stringNumber ];
	if ( fretNumber < 0 )
		return -1;
	if ( fretNumber > this.instrumentFretCount )
		return -1;
	return fretNumber;
}

// Method:		BuildScale
// Purpose:		Builds a scale based on the current parameters.
// Arguments:	stringNumber - The starting string number.
//				fretNumber - The starting fret number.
//
ScalePrinter.prototype.BuildScale = function( stringNumber, fretNumber )
{
	// Clear the fretboard
	if ( this.clearAll.length > 0 )
		eval( this.clearAll );
	this.clearAll = "";
	
	// Set the root note
	this.SetLocation( stringNumber, fretNumber, 1 );

	// Reset the count of notes in the scale
	var nCount = 1;

	// Store the starting string and fret
	this.lastStartString = stringNumber;
	this.lastStartFret = fretNumber;

	// Update the stretch adjust line
	this.UpdateAdjusterLine();

	// Set the search locals
	var currentString 	= stringNumber;		// The current string number
	var startedOn 		= fretNumber;		// The fret we started on for this string
	var lastFret 		= fretNumber;		// The fret we are on
	var scaleStep 		= 0;				// The current scale step
	
	// The starting MIDI note value
	var startMidiNote 	= this.GetMidiNote( stringNumber, fretNumber );
	
	// The MIDI note value we are looking for next
	var nextMidiNote 	= this.GetMidiNote( stringNumber, fretNumber ) + this.scaleSteps[ 0 ];
	
	// Continue until we run out of notes on the instrument, or we exceed the octave count
	while( nextMidiNote <= this.maxMidiNote &&
	       ( nextMidiNote - startMidiNote ) < this.maxOctaves * 12 )
	{
		// Check if we can jump to the next string
		if ( lastFret > startedOn + this.maxStretch &&
		 	 currentString + 1 < this.instrumentStringCount )
		{
			// Search the next string
			var nextFret = this.FindMidiNote( currentString + 1, nextMidiNote );
			if ( nextFret >= 0 )
			{
				// We found a note, jump to the next string
				this.SetLocation( currentString + 1, nextFret, 1 );
				currentString = currentString + 1;
				startedOn = nextFret;
				lastFret = nextFret;
			}
			else
			{
				// We didn't find a note, continue on with this string
				nextFret = this.FindMidiNote( currentString, nextMidiNote );
				if ( nextFret < 0 )
					break;
				this.SetLocation( currentString, nextFret, 1 );
				lastFret = nextFret;
			}
		}
		else
		{
			// We weren't ready to change strings yet, so continue on the
			// current string
			var nextFret = this.FindMidiNote( currentString, nextMidiNote );
			if ( nextFret < 0 )
				break;
			this.SetLocation( currentString, nextFret, 1 );
			lastFret = nextFret;
		}
		
		// Go to the next item in the scale
		scaleStep++;
		if ( scaleStep >= this.scaleSteps.length )
			scaleStep = 0;
		
		// Calculate the next MIDI note value
		nextMidiNote += this.scaleSteps[ scaleStep ];
		
		// Store that we have added another note
		nCount++;
	}

	// Update the static text items
	this.UpdateStaticText( nCount );
}

// Method:		UpdateStaticText 
// Purpose:		Updates the static text items on the page.
// Arguments:	nCount - The number of notes built.
//
ScalePrinter.prototype.UpdateStaticText = function( nCount )
{
		// Set the number of notes built static text
	var noteCountText = nCount.toString();
	
	// Set the starting note static text
	var startMidiNote 	= this.GetMidiNote( this.lastStartString, this.lastStartFret );
	var rootNoteText = g_ScaleNotes[ MidiToNoteValue( startMidiNote ) ];
	
	// Set the scale notes static text, which is a list of all of the notes
	// in the current scale
	var sNotes = "";
	for( var scaleItem = 0; scaleItem < this.scaleSteps.length; scaleItem++ )
	{
		sNotes += g_ScaleNotes[ MidiToNoteValue( startMidiNote ) ] + " ";
		startMidiNote += this.scaleSteps[ scaleItem ];
	}
	var staticNoteListText = sNotes;

	this.rootNoteStatic.SetValue( rootNoteText );
	this.noteCountStatic.SetValue( noteCountText );
	this.noteListStatic.SetValue( staticNoteListText );
}

// Method:		onAdjusterClick 
// Purpose:		Responds to the adjuster being clicked.
// Arguments:	fretNumber - The fret number clicked.
//
ScalePrinter.prototype.onAdjusterClick = function( fretNumber )
{
	if ( this.lastStartString == -1 )
		return true;

	// Calculate the new maximum stretch
	this.maxStretch = fretNumber - this.lastStartFret;
	
	// Build the scale
	this.BuildScale( this.lastStartString, this.lastStartFret );
	
	return true;
}

// Method:		onClick 
// Purpose:		Responds to a location on the fretboard being clicked.
// Arguments:	stringNumber - The string number clicked.
//				fretNumber - The fret number clicked.
//
ScalePrinter.prototype.onClick = function( stringNumber, fretNumber )
{
	this.BuildScale( stringNumber, fretNumber );
}

// Method:		SetLocation
// Purpose:		Toggles the display of an item on the fretboard.
// Arguments:	stringNumber - The string number of the item
//				fretNumber - The fret number of the item
//				value - The new value ( 0 = off )
//
ScalePrinter.prototype.SetLocation = function( stringNumber, fretNumber, value )
{
	var sLocation = this.GetLocationName( stringNumber, fretNumber );
	var emptySource = ( fretNumber == 0 ) ? this.fretPlainTopImage.src : this.fretPlainImage.src;
	var newSource = emptySource;
	
	if ( value == 1 )
	{
		newSource = ( fretNumber == 0 ) ? this.topFretImage[ 0 ].src : this.fretImage[ 0 ].src;

		s = "document[\"" + sLocation + "\"].src = \"" + emptySource + "\";";
		this.clearAll += s;
	}

	UCBGetBodyDocument()[ sLocation ].src = newSource;
}

// Method:		GetAdjusterName
// Purpose:		Returns the name of the adjuster segment for the specified fret.
// Arguments:	fretNumber - The fret number to get the name of
//
ScalePrinter.prototype.GetAdjusterName = function( fretNumber )
{
	var s = "adjust" + fretNumber;
	return s;
}

// Method:		DrawAdjusterButton
// Purpose:		Draws the adjuster 'button'.
// Arguments:	fretNumber - The fret number of to draw
//				direction - -1 = down, 0 = middle, 1 = up
//
ScalePrinter.prototype.DrawAdjusterButton = function( fretNumber, direction )
{
	var imageName = this.GetAdjusterName( fretNumber );
	
	if ( direction == -1 )
		UCBGetBodyDocument()[ imageName ].src = this.adjustButtonDown.src;
	else if ( direction == 0 )
		UCBGetBodyDocument()[ imageName ].src = this.adjustButtonMiddle.src;
	else
		UCBGetBodyDocument()[ imageName ].src = this.adjustButtonUp.src;

	this.clearAdjusterLine += "document[\"" + imageName + "\"].src = \"" + this.transparentImage.src + "\";";
}

// Method:		DrawAdjuster
// Purpose:		Draws an adjuster 'line' segment.
// Arguments:	fretNumber - The fret number of to draw
//				direction - -1 = finished down, 0 = middle, 1 = finished up
//
ScalePrinter.prototype.DrawAdjusterLine = function( fretNumber, direction )
{
	var imageName = this.GetAdjusterName( fretNumber );
	
	if ( direction == -1 )
		UCBGetBodyDocument()[ imageName ].src = this.adjustLineDown.src;
	else if ( direction == 0 )
		UCBGetBodyDocument()[ imageName ].src = this.adjustLineMiddle.src;
	else
		UCBGetBodyDocument()[ imageName ].src = this.adjustLineUp.src;

	this.clearAdjusterLine += "document[\"" + imageName + "\"].src = \"" + this.transparentImage.src + "\";";
}

// Method:		UpdateAdjusterLine
// Purpose:		Updates the images that represent the adjuster line.
// Arguments:	None
//
ScalePrinter.prototype.UpdateAdjusterLine = function( )
{
	// Make sure we aren't drawing the same thing twice.
	if ( this.lastAdjusterStart == this.lastStartFret &&
	     this.lastAdjusterStretch == this.maxStretch )
		return;

	this.lastAdjusterStart = this.lastStartFret;
	this.lastAdjusterStretch = this.maxStretch;
	
	// Clear the adjuster line
	eval( this.clearAdjusterLine );
	this.clearAdjusterLine = "";
	
	// If there is no scale, then clear the adjuster line and exit
	if ( this.lastStartFret == -1 )
		return;

	// If there is no stretch, then set the ball on the starting fret
	// and exit.
	if ( this.maxStretch == 0 || this.lastStartFret == this.instrumentFretCount )
	{
		this.DrawAdjusterButton( this.lastStartFret, 0 );
		return;
	}

	// If the stretch is less then zero, then set the ball on the 
	// ending fret and draw a line from the starting fret to it.
	if ( this.maxStretch < 0 )
	{
		var baseFret = this.lastStartFret + this.maxStretch;
		if ( baseFret < 0 )
			baseFret = 0;

		this.DrawAdjusterLine( this.lastStartFret, 1 );

		for( var fretNumber = this.lastStartFret - 1;
			 fretNumber > this.lastStartFret + this.maxStretch;
			 fretNumber-- )
			this.DrawAdjusterLine( fretNumber, 0 );
			
		this.DrawAdjusterButton( baseFret, -1 );

		return;
	}

	// Otherwise the stretch is greater then zero, so we set the ball on the 
	// ending fret and draw a line from the starting fret to it.
	var nStretch = this.maxStretch;
	var endFret = this.lastStartFret + nStretch;
	if ( endFret >= this.instrumentFretCount )
	{
		endFret = this.instrumentFretCount;
		nStretch = this.instrumentFretCount - this.lastStartFret;
	}

	this.DrawAdjusterLine( this.lastStartFret, -1 );

	for( var fretNumber = this.lastStartFret + 1;
		 fretNumber < this.lastStartFret + nStretch;
		 fretNumber++ )
		this.DrawAdjusterLine( fretNumber, 0 );
		
	this.DrawAdjusterButton( endFret, 1 );
}

// Method:		DrawFretboard
// Purpose:		'Draws' the fretboard in HTML.
// Arguments:	None
//
ScalePrinter.prototype.DrawFretboard = function( )
{
	var s = "<img src=\"" + this.transparentImage.src + "\" width=17 height=17>";
	for( var stringNum = 0; stringNum < this.topMidiNotes.length; stringNum++ )
	{
		s += "<img src=\"" + this.notesImages[ MidiToNoteValue( this.topMidiNotes[ stringNum ] ) ].src + "\" width=14 height=14>";
	}
	s += "<BR>";

	for( fret = 0; fret <= this.instrumentFretCount; fret++ )
	{
		s += "<a href=\"javascript: void "+ this.thisObjectName + ".onAdjusterClick( " + fret + " );\">";
		s += "<img border=\"0\" ";
		s += " name=\"adjust" + fret + "\"";
		s += " src=\"" + this.transparentImage.src + "\" width=17 height=17></a>";
		
		var leftTopImage = ( fret == 0 ) ? this.leftTopImage.src : this.leftImage.src;
		s += "<a href=\"javascript: void " + this.thisObjectName + ".onAdjusterClick( " + fret + " );\">";
		s += "<img border=\"0\" src=\"" + leftTopImage + "\" width=3 height=17></a>"

		for( stringNum = 0; stringNum < this.instrumentStringCount; stringNum++ )
		{
			var image = ( fret == 0 ) ? this.fretPlainTopImage.src : this.fretPlainImage.src;

			s += "<a href=\"javascript: void "+ this.thisObjectName + ".onClick( " + stringNum + "," + fret + " );\"";
			s += " onmouseover=\"{window.status='Note = " + this.GetNoteName( stringNum, fret ) + "'; return true;}\">";
			s += "<img border=\"0\" src=\"" + image + "\"";
			s += " width=14 height=17";
			s += " name=\"" + this.GetLocationName( stringNum, fret ) + "\"></a>";
		}

		var rightImage = ( fret == 0 ) ? this.rightTopImage.src : this.rightImage[ this.fretBall[ fret ] ].src;
		s += "<img src=\"" + rightImage + "\" width=7 height=17><BR>";
	}

	document.write(s);
}

