define(["Tone/core/Tone", "Tone/core/Transport"], function(Tone){

	"use strict";

	/**
	 *  Frequency can be described similar to time, except ultimately the
	 *  values are converted to frequency instead of seconds. A number
	 *  is taken literally as the value in hertz. Additionally any of the 
	 *  {@link Tone.Time} encodings can be used. Note names in the form
	 *  of NOTE OCTAVE (i.e. `C4`) are also accepted and converted to their
	 *  frequency value. 
	 *  
	 *  @typedef {number|string|Tone.Time} Tone.Frequency
	 */

	/**
	 *  @class  A timed note. Creating a note will register a callback 
	 *          which will be invoked on the channel at the time with
	 *          whatever value was specified. 
	 *
	 *  @constructor
	 *  @param {number|string} channel the channel name of the note
	 *  @param {Tone.Time} time the time when the note will occur
	 *  @param {string|number|Object|Array} value the value of the note
	 */
	Tone.Note = function(channel, time, value){

		/**
		 *  the value of the note. This value is returned
		 *  when the channel callback is invoked.
		 *  
		 *  @type {string|number|Object}
		 */
		this.value = value;

		/**
		 *  the channel name or number
		 *  
		 *  @type {string|number}
		 *  @private
		 */
		this._channel = channel;

		/**
		 *  an internal reference to the id of the timeline
		 *  callback which is set. 
		 *  
		 *  @type {number}
		 *  @private
		 */
		this._timelineID = Tone.Transport.setTimeline(this._trigger.bind(this), time);
	};

	/**
	 *  invoked by the timeline
	 *  @private
	 *  @param {number} time the time at which the note should play
	 */
	Tone.Note.prototype._trigger = function(time){
		//invoke the callback
		channelCallbacks(this._channel, time, this.value);
	};

	/**
	 *  clean up
	 *  @returns {Tone.Note} `this`
	 */
	Tone.Note.prototype.dispose = function(){ 
		Tone.Tranport.clearTimeline(this._timelineID);
		this.value = null;
		return this;
	};

	/**
	 *  @private
	 *  @static
	 *  @type {Object}
	 */
	var NoteChannels = {};

	/**
	 *  invoke all of the callbacks on a specific channel
	 *  @private
	 */
	function channelCallbacks(channel, time, value){
		if (NoteChannels.hasOwnProperty(channel)){
			var callbacks = NoteChannels[channel];
			for (var i = 0, len = callbacks.length; i < len; i++){
				var callback = callbacks[i];
				if (Array.isArray(value)){
					callback.apply(window, [time].concat(value));
				} else {
					callback(time, value);
				}
			}
		}
	}

	/**
	 *  listen to a specific channel, get all of the note callbacks
	 *  @static
	 *  @param {string|number} channel the channel to route note events from
	 *  @param {function(*)} callback callback to be invoked when a note will occur
	 *                                        on the specified channel
	 */
	Tone.Note.route = function(channel, callback){
		if (NoteChannels.hasOwnProperty(channel)){
			NoteChannels[channel].push(callback);
		} else {
			NoteChannels[channel] = [callback];
		}
	};

	/**
	 *  Remove a previously routed callback from a channel. 
	 *  @static
	 *  @param {string|number} channel The channel to unroute note events from
	 *  @param {function(*)} callback Callback which was registered to the channel.
	 */
	Tone.Note.unroute = function(channel, callback){
		if (NoteChannels.hasOwnProperty(channel)){
			var channelCallback = NoteChannels[channel];
			var index = channelCallback.indexOf(callback);
			if (index !== -1){
				NoteChannels[channel].splice(index, 1);
			}
		}
	};

	/**
	 *  Parses a score and registers all of the notes along the timeline. 
	 *
	 *  Scores are a JSON object with instruments at the top level
	 *  and an array of time and values. The value of a note can be 0 or more 
	 *  parameters. 
	 *
	 *  The only requirement for the score format is that the time is the first (or only)
	 *  value in the array. All other values are optional and will be passed into the callback
	 *  function registered using `Note.route(channelName, callback)`.
	 *
	 *  To convert MIDI files to score notation, take a look at utils/MidiToScore.js
	 *
	 *  @example
	 *  //an example JSON score which sets up events on channels
	 *  var score = { 
	 *  	"synth"  : [["0", "C3"], ["0:1", "D3"], ["0:2", "E3"], ... ],
	 *  	"bass"  : [["0", "C2"], ["1:0", "A2"], ["2:0", "C2"], ["3:0", "A2"], ... ],
	 *  	"kick"  : ["0", "0:2", "1:0", "1:2", "2:0", ... ],
	 *  	//...
	 *  };
	 *  //parse the score into Notes
	 *  Tone.Note.parseScore(score);
	 *  //route all notes on the "synth" channel
	 *  Tone.Note.route("synth", function(time, note){
	 *  	//trigger synth
	 *  });
	 *  @static
	 *  @param {Object} score
	 *  @return {Array<Tone.Note>} an array of all of the notes that were created
	 */
	Tone.Note.parseScore = function(score){
		var notes = [];
		for (var inst in score){
			var part = score[inst];
			if (inst === "tempo"){
				Tone.Transport.bpm.value = part;
			} else if (inst === "timeSignature"){
				Tone.Transport.timeSignature = part[0] / (part[1] / 4);
			} else if (Array.isArray(part)){
				for (var i = 0; i < part.length; i++){
					var noteDescription = part[i];
					var note;
					if (Array.isArray(noteDescription)){
						var time = noteDescription[0];
						var value = noteDescription.slice(1);
						note = new Tone.Note(inst, time, value);
					} else {
						note = new Tone.Note(inst, noteDescription);
					}
					notes.push(note);
				}
			} else {
				throw new TypeError("score parts must be Arrays");
			}
		}
		return notes;
	};

	///////////////////////////////////////////////////////////////////////////
	//	MUSIC NOTES
	//	
	//	Augments Tone.prototype to include note methods
	///////////////////////////////////////////////////////////////////////////

	var noteToIndex = { "c" : 0, "c#" : 1, "db" : 1, "d" : 2, "d#" : 3, "eb" : 3, 
		"e" : 4, "f" : 5, "f#" : 6, "gb" : 6, "g" : 7, "g#" : 8, "ab" : 8, 
		"a" : 9, "a#" : 10, "bb" : 10, "b" : 11
	};

	var noteIndexToNote = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"];

	var middleC = 261.6255653005986;

	/**
	 *  convert a note name to frequency (i.e. A4 to 440)
	 *  defined in "Tone/core/Note"
	 *  
	 *  @param  {string} note
	 *  @return {number}         
	 */
	Tone.prototype.noteToFrequency = function(note){
		//break apart the note by frequency and octave
		var parts = note.split(/(\d+)/);
		if (parts.length === 3){
			var index = noteToIndex[parts[0].toLowerCase()];
			var octave = parts[1];
			var noteNumber = index + parseInt(octave, 10) * 12;
			return Math.pow(2, (noteNumber - 48) / 12) * middleC;
		} else {
			return 0;
		}
	};

	/**
	 *  test if a string is in note format: i.e. "C4"
	 *  @param  {string|number}  note the note to test
	 *  @return {boolean}      true if it's in the form of a note
	 *  @method isNotation
	 *  @lends Tone.prototype.isNotation
	 */
	Tone.prototype.isNote = ( function(){
		var noteFormat = new RegExp(/[a-g]{1}([b#]{1}|[b#]{0})[0-9]+$/i);
		return function(note){
			if (typeof note === "string"){
				note = note.toLowerCase();
			} 
			return noteFormat.test(note);
		};
	})();

	/**
	 *  a pointer to the previous toFrequency method
	 *  @private
	 *  @function
	 */
	Tone.prototype._overwrittenToFrequency = Tone.prototype.toFrequency;

	/**
	 *  A method which accepts frequencies in the form
	 *  of notes (`"C#4"`), frequencies as strings ("49hz"), frequency numbers,
	 *  or Tone.Time and converts them to their frequency as a number in hertz.
	 *  @param  {Tone.Frequency} note the note name or notation
	 *  @param {number=} 	now 	if passed in, this number will be 
	 *                        		used for all 'now' relative timings
	 *  @return {number}      the frequency as a number
	 */
	Tone.prototype.toFrequency = function(note, now){
		if (this.isNote(note)){
			note = this.noteToFrequency(note);
		} 
		return this._overwrittenToFrequency(note, now);
	};

	/**
	 *  Convert a note name (i.e. A4, C#5, etc to a frequency).
	 *  Defined in "Tone/core/Note"
	 *  @param  {number} freq
	 *  @return {string}         
	 */
	Tone.prototype.frequencyToNote = function(freq){
		var log = Math.log(freq / middleC) / Math.LN2;
		var noteNumber = Math.round(12 * log) + 48;
		var octave = Math.floor(noteNumber/12);
		var noteName = noteIndexToNote[noteNumber % 12];
		return noteName + octave.toString();
	};

	/**
	 *  Convert an interval (in semitones) to a frequency ratio.
	 *
	 *  @param  {number} interval the number of semitones above the base note
	 *  @return {number}          the frequency ratio
	 *  @example
	 *  tone.intervalToFrequencyRatio(0); // returns 1
	 *  tone.intervalToFrequencyRatio(12); // returns 2
	 */
	Tone.prototype.intervalToFrequencyRatio = function(interval){
		return Math.pow(2,(interval/12));
	};

	/**
	 *  Convert a midi note number into a note name/
	 *
	 *  @param  {number} midiNumber the midi note number
	 *  @return {string}            the note's name and octave
	 *  @example
	 *  tone.midiToNote(60); // returns "C3"
	 */
	Tone.prototype.midiToNote = function(midiNumber){
		var octave = Math.floor(midiNumber / 12) - 2;
		var note = midiNumber % 12;
		return noteIndexToNote[note] + octave;
	};

	/**
	 *  convert a note to it's midi value
	 *  defined in "Tone/core/Note"
	 *
	 *  @param  {string} note the note name (i.e. "C3")
	 *  @return {number} the midi value of that note
	 *  @example
	 *  tone.noteToMidi("C3"); // returns 60
	 */
	Tone.prototype.noteToMidi = function(note){
		//break apart the note by frequency and octave
		var parts = note.split(/(\d+)/);
		if (parts.length === 3){
			var index = noteToIndex[parts[0].toLowerCase()];
			var octave = parts[1];
			return index + (parseInt(octave, 10) + 2) * 12;
		} else {
			return 0;
		}
	};

	return Tone.Note;
});