/****************************************
* 
* Written by sk89q <http://sk89q.therisenrealm.com>
* Copyright 2008 sk89q. 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, this list of conditions and the following 
*    disclaimer in the documentation and/or other materials provided 
*    with the distribution.
* 
* THIS SOFTWARE IS PROVIDED BY SK89Q "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 SK89Q 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.
*
****************************************/

/****************************************
* 
* EDITOR'S NOTE ::
*
* Edited by aN00Bis <http://www.beliefproductions.org/>
* 
* I have taken the liberty, after personally speaking with the creator
* of this project, to modify it in the ways mentioned below.  I do not
* take any credit for this project.  Only the changes which I have made
* which are commented throughout the code.
*
* The only change not commented in the code is the fact that I switched
* all of the variables/methods inside a namespace (which I called jMusic).
* No functionality of the original variables/methods were removed in doing
* this, however some functionality was added.
*
* I have made these changes after conference with the creator, with the
* following statement given in writing (forgive the grammer, I'm coping
* and pasting from my e-mail:
*
*		+-------------------------------------------------------+
*		| "My code is released under the BSD license, so you're |
* 		| pretty free to as what you can do."                   |
*		|	                                                    |
*		|	~ sk89q                                             |
*		+-------------------------------------------------------+
*
* ---- Changes Made to Original Code ----
*
* 	2008-11-26	- Switched from old form methods to document.getElementById()
* 	2008-11-26	- Added catch for rest notes in play() and consumeLine() methods
* 	2008-11-26	- Added class name switching to play() and stop() methods for buttons
* 	2008-11-26	- ...
*
****************************************/

/***
 *	----------------------------
 *	jMusic Namespace
 *	----------------------------
 */
jMusic	 						= {};

	/***
	 *	Global WAV Settings
	 */
	 
		/***
		 *	channels
		 *	----------------------------
		 *	The number of channels to
		 *	use in the WAV file.
		 *	----------------------------
		 */
		jMusic.channels					= 1;
		
		/***
		 *	channels
		 *	----------------------------
		 *	The sample rate of the WAV
		 *	file.
		 *	----------------------------
		 */
		jMusic.sampleRate				= 4024;
		
		/***
		 *	bitsPerSample
		 *	----------------------------
		 *	The bit rate of the WAV
		 *	file.
		 *	----------------------------
		 */
		jMusic.bitsPerSample			= 16;
		
		/***
		 *	volume
		 *	----------------------------
		 *	The (volume) of the WAV
		 *	file.
		 *	----------------------------
		 */
		jMusic.volume					= 32767;
		
	/***
	 *	Global WAV Objects
	 */
	 
		jMusic.players					= [];
		jMusic.playerI					= 0;
		
	/***
	 *	Global Playing Variables
	 */
	 
		jMusic.songLines				= [];
		jMusic.songLineIndex			= 0;
		jMusic.isPlaying				= false;
		
		
		jMusic.noteLengths 				= { 'whole': 2000 , 'half': 1000 , 'quar': 500 , 'eighth': 250 , 'sixth': 125 };
		
	/***
	 *	Global Compression Variables
	 */
	 
		jMusic.keyStr					= "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
		
	/***
	 *	Global Frequency Settings
	 */
	 
	 	jMusic.frequencies				= [ {} , {} ,
											{ 	'C': 65.41,		'C#': 69.30,	'D': 73.42,		'D#': 77.78,	'E': 82.41,		'F': 87.31,		'F#': 92.50,	'G': 98.00,		'G#': 103.8,	'A': 110,		'A#': 116.5,	'B': 123.5	} ,
											{ 	'C': 130.8,		'C#': 138.6,	'D': 146.8,		'D#': 155.6,	'E': 164.8,		'F': 174.6,		'F#': 185,		'G': 196,		'G#': 207.7,	'A': 220,		'A#': 233.1,	'B': 246.9	} ,
											{ 	'C': 261.6,		'C#': 277.2,	'D': 293.7,		'D#': 311.1,	'E': 329.6,		'F': 349.2,		'F#': 370,		'G': 392,		'G#': 415.3,	'A': 440,		'A#': 466.2,	'B': 493.9	} ,
											{ 	'C': 523.3,		'C#': 554.4,	'D': 587.3,		'D#': 622.3,	'E': 659.3,		'F': 698.5,		'F#': 740,		'G': 784,		'G#': 830.6,	'A': 880,		'A#': 932.3,	'B': 987.8	}
										];
	
	/***
	 *	Compression Methods
	 */
	 
		//	dec2hex :: by Jonas John (http://www.jonasjohn.de/snippets/javascript/dec2hex.htm)
		jMusic.dec2hex					= function( n ) {
											n = parseInt(n);
											var c = 'ABCDEF';
											var b = n / 16;
											var r = n % 16;
											b = b - (r / 16); 
											b = ((b >= 0) && (b <= 9)) ? b : c.charAt(b - 10);    
											return ((r >= 0) && (r <= 9)) ? b + '' + r : b + '' + c.charAt(r - 10);
										};
			
		//	encode64 :: by Tyler Akins (http://rumkin.com)
		jMusic.encode64					= function( input ) {
			
											var output = "";
											var chr1, chr2, chr3;
											var enc1, enc2, enc3, enc4;
											var i = 0;
											
											do {
												
												chr1 = input.charCodeAt(i++);
												chr2 = input.charCodeAt(i++);
												chr3 = input.charCodeAt(i++);
										
												enc1 = chr1 >> 2;
												enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
												enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
												enc4 = chr3 & 63;
										
												if (isNaN(chr2)) {
													enc3 = enc4 = 64;
												} else if (isNaN(chr3)) {
													enc4 = 64;
												}
										
												output = output + jMusic.keyStr.charAt(enc1) + jMusic.keyStr.charAt(enc2) + 
														 jMusic.keyStr.charAt(enc3) + jMusic.keyStr.charAt(enc4);
														 
											} while (i < input.length);
										
											return output;
											
										};
		
		//	pack :: (http://sk89q.therisenrealm.com/2008/11/pack-in-javascript/)
		jMusic.pack						= function( fmt ) {
										
											var output = '';
											var argi = 1;
											
											for (var i = 0; i < fmt.length; i++) {
												
												var c = fmt.charAt(i);
												var arg = arguments[argi];
												argi++;
												
												switch (c) {
													case "a":
														output += arg[0] + "\0";
														break;
													case "A":
														output += arg[0] + " ";
														break;
													case "C":
													case "c":
														output += String.fromCharCode(arg);
														break;
													case "n":
														output += String.fromCharCode((arg >> 8) & 255) +
																  String.fromCharCode(arg & 255);
														break;
													case "v":
														output += String.fromCharCode(arg & 255) +
																  String.fromCharCode((arg >> 8) & 255);
														break;
													case "N":
														output += String.fromCharCode((arg >> 24) & 255) + 
																  String.fromCharCode((arg >> 16) & 255) + 
																  String.fromCharCode((arg >> 8) & 255) + 
																  String.fromCharCode(arg & 255);
														break;
													case "V":
														output += String.fromCharCode(arg & 255) + 
																  String.fromCharCode((arg >> 8) & 255) + 
																  String.fromCharCode((arg >> 16) & 255) + 
																  String.fromCharCode((arg >> 24) & 255);
														break;
													case "x":
														argi--;
														output += "\0";
														break;
													default:
														throw new Error("Unknown pack format character '"+c+"'");
												}
											}
											
											return output;
											
										};
				
	/***
	 *	Play Methods
	 */
	 
		jMusic.playFreq					= function( frequency , seconds ) {
			
											var data			= '';
											var samples			= 0;
										
											for (var i = 0; i < jMusic.sampleRate * seconds; i++) {
												for (var c = 0; c < jMusic.channels; c++) {
													var v = jMusic.volume * Math.sin((2 * Math.PI) * (i / jMusic.sampleRate) * frequency);
													data += jMusic.pack("v", v)
													samples++;
												}
											}
										
											// chunk 1
											var chunk1 = "fmt ";
												chunk1 += jMusic.pack("V", 16); // chunk size
												chunk1 += jMusic.pack("v", 1);
												chunk1 += jMusic.pack("v", jMusic.channels);
												chunk1 += jMusic.pack("V", jMusic.sampleRate);
												chunk1 += jMusic.pack("V", jMusic.sampleRate * jMusic.channels * jMusic.bitsPerSample / 8);
												chunk1 += jMusic.pack("v", jMusic.channels * jMusic.bitsPerSample / 8);
												chunk1 += jMusic.pack("v", jMusic.bitsPerSample);
										
											// chunk 2
											var chunk2 = "data";
												chunk2 += jMusic.pack("V", samples * jMusic.channels * jMusic.bitsPerSample / 8); // chunk size
												chunk2 += data
										
											// header
											var header = "RIFF";
												header += jMusic.pack("V", 4 + (8 + chunk1.length) + (8 + chunk2.length));
												header += "WAVE";
										
											var out 	= header + chunk1 + chunk2;
											var dataURI = "data:audio/wav;base64," + escape(jMusic.encode64(out));
										
											// append embed player
											if (jMusic.playerI % 2 == 0) {
												if (jMusic.players[0] && jMusic.players[0].parentNode) {
													jMusic.players[0].parentNode.removeChild(jMusic.players[0]);
												}
												jMusic.players[0] = document.createElement("embed");
												jMusic.players[0].setAttribute("src", dataURI);
												jMusic.players[0].setAttribute("width", 400);
												jMusic.players[0].setAttribute("height", 0);
												jMusic.players[0].setAttribute("autostart", true);
												document.getElementById('players').appendChild(jMusic.players[0]);
											} else {
												if (jMusic.players[1] && jMusic.players[1].parentNode) {
													jMusic.players[1].parentNode.removeChild(jMusic.players[1]);
												}
												jMusic.players[1] = document.createElement("embed");
												jMusic.players[1].setAttribute("src", dataURI);
												jMusic.players[1].setAttribute("width", 400);
												jMusic.players[1].setAttribute("height", 0);
												jMusic.players[1].setAttribute("autostart", true);
												document.getElementById('players').appendChild(jMusic.players[1]);
											}
											jMusic.playerI++;
										};
									
		jMusic.play						= function() {
			
											jMusic.channels 		= parseInt( document.getElementById('channels').value );								// aN00Bis - Switched to using document.getElementById()
											jMusic.sampleRate 		= parseInt( document.getElementById('sampleRate').value );								// aN00Bis - Switched to using document.getElementById()
											jMusic.bitsPerSample	= parseInt( document.getElementById('bitDepth').value );								// aN00Bis - Switched to using document.getElementById()
											jMusic.volume 			= parseInt( document.getElementById('volume').value );									// aN00Bis - Switched to using document.getElementById()
											
											jMusic.songLines 		= [];
											jMusic.songLineIndex 	= 0;
											
											var lines 				= document.getElementById('song').value.split("\n");									// aN00Bis - Switched to using document.getElementById()
											var errors 				= []; // parsing errors
										
											for (var i in lines) {
												
												var line = lines[i].replace(/^\s+|\s+$/g, '');
												
												if (line == "") { continue; }
												
												if (line.charAt(0) == '#') {
													
													// comment
													
												} else if (line.charAt(0) == '@') {
													
													// instruction
													
													var instruction = line.substr(1).split(/[\t ]+/);
													
													if (instruction[0] == "tempo") {
														var ms = 60. / parseInt(instruction[1]) * 1000;
														jMusic.noteLengths['whole'] 	= ms * 4;
														jMusic.noteLengths['half'] 	= ms * 2;
														jMusic.noteLengths['quar'] 	= ms;
														jMusic.noteLengths['eighth'] 	= ms / 2;
														jMusic.noteLengths['sixth'] 	= ms / 2 / 2;
													}
													
													if(instruction[0] == "rest") {																			// aN00Bis - Added way to deal with rest notes
														jMusic.songLines[jMusic.songLines.length] = [null, noteLength];
													}
													
												} else {
													
													// note
													
													var m = line.match(/^(\-?[0-9]+)[\t ]+([A-Ga-g]#?)[\t ]+(.+)$/, line);
													
													if (m) { 
													
														try {
															
															var octave 	= parseInt(m[1]);
															var note 	= m[2];
															var freq 	= jMusic.frequencies[octave][note];
															
														} catch (e) {
															errors[errors.length] = 'Bad note specification at line #' + ( i + 1 ) + ': ' + line;
															continue;
														}
														
														try {
															
															var noteLengthTexts	= m[3].split('+');
															var noteLength 		= 0;
															
															for (var t in noteLengthTexts) {
																
																var noteLengthText = noteLengthTexts[t].replace( /^\s+|\s+$/g , '' );
																
																if (noteLengthText.charAt(noteLengthText.length-1) == ".") {
																	var l = noteLengthText.substr( 0 , noteLengthText.length - 1 );
																} else {
																	var l = noteLengthText;
																}
																
																if (jMusic.noteLengths[l]) {
																	noteLength += jMusic.noteLengths[l]/1000;
																} else {
																	throw new Error( 'Unknown ' + l + ' note length specification' );
																}
																
															}
															
														} catch (e) {
															errors[errors.length] = 'Bad note length specification at line #' + (i + 1) + ': ' + line;
															continue;
														}
														
														jMusic.songLines[jMusic.songLines.length] = [freq, noteLength];
														
													} else {
														errors[errors.length] = 'Parse error at line #' + (i + 1) + ': ' + line;
													}
													
												}
												
											}
											
											if (errors.length == 0) {
												
												jMusic.isPlaying = true;
												jMusic.consumeLine();
												
												document.getElementById('stopButton').disabled 	= false;													// aN00Bis - Switched to using document.getElementById()
												document.getElementById('playButton').disabled 	= true;														// aN00Bis - Switched to using document.getElementById()
												document.getElementById('playButton').className = 'btn invert';												// aN00Bis - Added className switcher to button
												
											} else {
												
												alert( "There are some errors with your input:\n - " + errors.join("\n - ") );
												
											}
											
										};

		jMusic.stop						= function() {
											jMusic.isPlaying = false;
										//	jMusician.unhighlightNotes();																					// aN00Bis - Added highlighting to notes
											document.getElementById('stopButton').disabled = true;															// aN00Bis - Switched to using document.getElementById()
											document.getElementById('playButton').disabled = false;															// aN00Bis - Switched to using document.getElementById()
											document.getElementById('playButton').className = 'btn';														// aN00Bis - Added className switcher to button
										};
										
		jMusic.consumeLine				= function() {
											
											if( !jMusic.isPlaying || jMusic.songLineIndex >= jMusic.songLines.length ) {
												jMusic.stop();
												return;
											}
											
											var songLine = jMusic.songLines[jMusic.songLineIndex];
											
										//	jMusician.highlightNote( jMusic.songLineIndex );																// aN00Bis - Added highlighting to notes
											
											if( songLine[0] != null )
												jMusic.playFreq( songLine[0] , songLine[1] );
												
											jMusic.songLineIndex++;
											
											setTimeout( function() { jMusic.consumeLine(); } , songLine[1] * 1000 );
											
										};

