2014-08-20 20:51:56 +00:00
|
|
|
define(["Tone/core/Tone", "Tone/core/Transport"], function(Tone){
|
|
|
|
|
2014-08-25 22:40:15 +00:00
|
|
|
"use strict";
|
|
|
|
|
2014-08-20 20:51:56 +00:00
|
|
|
/**
|
|
|
|
* @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
|
2014-09-04 02:37:05 +00:00
|
|
|
* @param {Tone.Time} time the time when the note will occur
|
|
|
|
* @param {string|number|Object|Array} value the value of the note
|
2014-08-20 20:51:56 +00:00
|
|
|
*/
|
2014-09-04 02:37:05 +00:00
|
|
|
Tone.Note = function(channel, time, value){
|
2014-08-20 20:51:56 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* the value of the note. This value is returned
|
|
|
|
* when the channel callback is invoked.
|
|
|
|
*
|
2014-08-25 22:40:15 +00:00
|
|
|
* @type {string|number|Object}
|
2014-08-20 20:51:56 +00:00
|
|
|
*/
|
|
|
|
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
|
|
|
|
*/
|
2014-09-04 02:37:05 +00:00
|
|
|
Tone.Note.prototype._trigger = function(time){
|
2014-08-20 20:51:56 +00:00
|
|
|
//invoke the callback
|
2014-09-04 02:37:05 +00:00
|
|
|
channelCallbacks(this._channel, time, this.value);
|
2014-08-20 20:51:56 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* clean up
|
|
|
|
*/
|
|
|
|
Tone.Note.prototype.dispose = function(){
|
|
|
|
Tone.Tranport.clearTimeline(this._timelineID);
|
|
|
|
this.value = null;
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @private
|
|
|
|
* @static
|
|
|
|
* @type {Object}
|
|
|
|
*/
|
|
|
|
var NoteChannels = {};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* invoke all of the callbacks on a specific channel
|
|
|
|
* @private
|
|
|
|
*/
|
2014-09-04 02:37:05 +00:00
|
|
|
function channelCallbacks(channel, time, value){
|
2014-08-25 22:40:15 +00:00
|
|
|
if (NoteChannels.hasOwnProperty(channel)){
|
|
|
|
var callbacks = NoteChannels[channel];
|
2014-08-20 20:51:56 +00:00
|
|
|
for (var i = 0, len = callbacks.length; i < len; i++){
|
2014-09-04 02:37:05 +00:00
|
|
|
var callback = callbacks[i];
|
|
|
|
if (Array.isArray(value)){
|
|
|
|
callback.apply(window, [time].concat(value));
|
|
|
|
} else {
|
|
|
|
callback(time, value);
|
|
|
|
}
|
2014-08-20 20:51:56 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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){
|
2014-08-25 22:40:15 +00:00
|
|
|
if (NoteChannels.hasOwnProperty(channel)){
|
2014-08-20 20:51:56 +00:00
|
|
|
NoteChannels[channel].push(callback);
|
|
|
|
} else {
|
|
|
|
NoteChannels[channel] = [callback];
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* remove a callback from a channel
|
|
|
|
* @static
|
|
|
|
*/
|
|
|
|
Tone.Note.unroute = function(channel, callback){
|
2014-08-25 22:40:15 +00:00
|
|
|
if (NoteChannels.hasOwnProperty(channel)){
|
|
|
|
var channelCallback = NoteChannels[channel];
|
2014-08-20 20:51:56 +00:00
|
|
|
var index = channelCallback.indexOf(callback);
|
|
|
|
if (index !== -1){
|
|
|
|
NoteChannels[channel].splice(index, 1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* parses a score and registers all of the notes
|
|
|
|
*
|
|
|
|
* scores are a JSON object with instruments at the top level
|
|
|
|
* and an array of time, value tuples
|
|
|
|
*
|
|
|
|
* i.e. {
|
|
|
|
* "synth" : [["0", "C3"], ["0:1", "D3"], ["0:2", "E3"], ... ],
|
|
|
|
* "bass" : [["0", "C2"], ["1:0", "A2"], ["2:0", "C2"], ["3:0", "A2"], ... ],
|
|
|
|
* "drums" : [["0", "kick"], ["0:2", "snare"], ["1:0", "kick"], ["1:2", "snare"], ... ],
|
|
|
|
* ...
|
|
|
|
* }
|
|
|
|
* @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){
|
2014-09-04 02:37:05 +00:00
|
|
|
var part = score[inst];
|
2014-09-06 19:37:15 +00:00
|
|
|
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);
|
2014-09-04 02:37:05 +00:00
|
|
|
}
|
2014-09-06 19:37:15 +00:00
|
|
|
} else {
|
|
|
|
throw new TypeError("score parts must be Arrays");
|
2014-08-20 20:51:56 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return notes;
|
|
|
|
};
|
|
|
|
|
2014-09-11 17:00:09 +00:00
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
// 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)
|
|
|
|
* @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;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* convert a note name (i.e. A4, C#5, etc to a frequency)
|
|
|
|
* @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 a midi note number into a note name
|
|
|
|
*
|
|
|
|
* @example
|
|
|
|
* tone.midiToNote(60) => "C4"
|
|
|
|
*
|
|
|
|
* @param {[type]} midiNumber [description]
|
|
|
|
* @return {[type]} [description]
|
|
|
|
*/
|
|
|
|
Tone.prototype.midiToNote = function(midiNumber){
|
|
|
|
var octave = Math.floor(midiNumber / 12) - 2;
|
|
|
|
var note = midiNumber % 12;
|
|
|
|
return noteIndexToNote[note] + octave;
|
|
|
|
};
|
|
|
|
|
2014-08-20 20:51:56 +00:00
|
|
|
return Tone.Note;
|
|
|
|
});
|