Tone.js/Tone/core/Note.js

181 lines
5.1 KiB
JavaScript
Raw Normal View History

define(["Tone/core/Tone", "Tone/core/Transport"], function(Tone){
2014-08-25 22:40:15 +00:00
"use strict";
/**
* @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
2015-06-14 00:20:36 +00:00
* @param {Time} time the time when the note will occur
2014-09-04 02:37:05 +00:00
* @param {string|number|Object|Array} value the value of the note
*/
2014-09-04 02:37:05 +00:00
Tone.Note = function(channel, time, value){
/**
* 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}
*/
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){
//invoke the callback
2014-09-04 02:37:05 +00:00
channelCallbacks(this._channel, time, this.value);
};
/**
* clean up
* @returns {Tone.Note} this
*/
Tone.Note.prototype.dispose = function(){
2015-08-04 01:53:13 +00:00
Tone.Transport.clearTimeline(this._timelineID);
this.value = null;
2015-01-06 04:33:05 +00:00
return this;
};
/**
* @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];
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);
}
}
}
}
/**
* 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)){
NoteChannels[channel].push(callback);
} else {
NoteChannels[channel] = [callback];
}
};
/**
2015-02-26 16:26:23 +00:00
* Remove a previously routed callback from a channel.
* @static
2015-02-26 16:26:23 +00:00
* @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){
2014-08-25 22:40:15 +00:00
if (NoteChannels.hasOwnProperty(channel)){
var channelCallback = NoteChannels[channel];
var index = channelCallback.indexOf(callback);
if (index !== -1){
NoteChannels[channel].splice(index, 1);
}
}
};
/**
2014-09-11 17:38:41 +00:00
* Parses a score and registers all of the notes along the timeline.
2015-06-14 01:54:20 +00:00
* <br><br>
2014-09-11 17:38:41 +00:00
* 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.
2015-06-14 01:54:20 +00:00
* <br><br>
2014-12-03 22:20:23 +00:00
* 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
2015-02-26 16:26:23 +00:00
* function registered using `Note.route(channelName, callback)`.
2015-06-14 01:54:20 +00:00
* <br><br>
2015-02-26 16:26:23 +00:00
* To convert MIDI files to score notation, take a look at utils/MidiToScore.js
*
* @example
2015-06-14 01:54:20 +00:00
* //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
2015-06-14 00:56:41 +00:00
* @return {Array} 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-21 19:18:04 +00:00
if (inst === "tempo"){
2015-02-23 05:27:37 +00:00
Tone.Transport.bpm.value = part;
2014-09-21 19:18:04 +00:00
} else if (inst === "timeSignature"){
2015-02-23 05:27:37 +00:00
Tone.Transport.timeSignature = part[0] / (part[1] / 4);
2014-09-21 19:18:04 +00:00
} else if (Array.isArray(part)){
2014-09-06 19:37:15 +00:00
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);
2015-07-18 18:59:18 +00:00
} else if (typeof noteDescription === "object"){
note = new Tone.Note(inst, noteDescription.time, noteDescription);
2014-09-06 19:37:15 +00:00
} 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");
}
}
return notes;
};
return Tone.Note;
});