Tone.js/Tone/instrument/PolySynth.js

297 lines
9.1 KiB
JavaScript
Raw Normal View History

define(["../core/Tone", "../instrument/Synth", "../source/Source"], function(Tone){
2014-08-25 17:26:26 +00:00
"use strict";
2014-08-25 17:26:26 +00:00
/**
2015-06-14 04:32:17 +00:00
* @class Tone.PolySynth handles voice creation and allocation for any
2017-10-21 23:02:46 +00:00
* instruments passed in as the second paramter. PolySynth is
* not a synthesizer by itself, it merely manages voices of
* one of the other types of synths, allowing any of the
* monophonic synthesizers to be polyphonic.
2014-08-25 17:26:26 +00:00
*
* @constructor
2014-09-20 23:24:04 +00:00
* @extends {Tone.Instrument}
2015-06-20 22:03:49 +00:00
* @param {number|Object} [polyphony=4] The number of voices to create
* @param {function} [voice=Tone.Synth] The constructor of the voices
2017-10-21 23:02:46 +00:00
* uses Tone.Synth by default.
2015-02-28 04:24:51 +00:00
* @example
* //a polysynth composed of 6 Voices of Synth
* var synth = new Tone.PolySynth(6, Tone.Synth).toMaster();
2015-06-14 04:32:17 +00:00
* //set the attributes using the set interface
* synth.set("detune", -1200);
2015-06-16 02:36:20 +00:00
* //play a chord
* synth.triggerAttackRelease(["C4", "E4", "A4"], "4n");
2014-08-25 17:26:26 +00:00
*/
Tone.PolySynth = function(){
var options = Tone.defaults(arguments, ["polyphony", "voice"], Tone.PolySynth);
Tone.Instrument.call(this, options);
2017-04-30 19:03:49 +00:00
options = Tone.defaultArg(options, Tone.Instrument.defaults);
2014-08-25 17:26:26 +00:00
2016-03-03 18:01:11 +00:00
//max polyphony
options.polyphony = Math.min(Tone.PolySynth.MAX_POLYPHONY, options.polyphony);
2014-08-25 17:26:26 +00:00
/**
* the array of voices
* @type {Array}
*/
2014-12-08 16:03:20 +00:00
this.voices = new Array(options.polyphony);
2018-06-21 03:02:44 +00:00
this.assert(options.polyphony > 0, "polyphony must be greater than 0");
2014-08-25 17:26:26 +00:00
/**
* The detune in cents
* @type {Cents}
* @signal
*/
this.detune = new Tone.Signal(options.detune, Tone.Type.Cents);
this._readOnly("detune");
2014-08-25 17:26:26 +00:00
//create the voices
for (var i = 0; i < options.polyphony; i++){
2015-02-20 06:00:32 +00:00
var v = new options.voice(arguments[2], arguments[3]);
if (!(v instanceof Tone.Monophonic)){
throw new Error("Synth constructor must be instance of Tone.Monophonic");
}
2014-12-08 16:03:20 +00:00
this.voices[i] = v;
2018-06-21 03:02:44 +00:00
v.index = i;
2014-08-25 17:26:26 +00:00
v.connect(this.output);
if (v.hasOwnProperty("detune")){
this.detune.connect(v.detune);
}
2014-08-25 17:26:26 +00:00
}
};
2014-09-20 23:24:04 +00:00
Tone.extend(Tone.PolySynth, Tone.Instrument);
2014-08-25 17:26:26 +00:00
/**
* the defaults
* @const
* @static
* @type {Object}
*/
Tone.PolySynth.defaults = {
"polyphony" : 4,
"volume" : 0,
"detune" : 0,
"voice" : Tone.Synth
2014-08-25 17:26:26 +00:00
};
/**
* Get the closest available voice, that is the
* one that is either the closest to the note,
* or has the lowest envelope value.
* @param {Time} time return the voice that has the lowest energy at this time.
* @param {Note} note if there is a voice with this note, that should be returned
* @return {Tone.Monophonic} A synth voice.
* @private
*/
Tone.PolySynth.prototype._getClosestVoice = function(time, note){
//play the note which has the same frequency, if that exists
2018-06-13 04:20:23 +00:00
var sameNote = this.voices.find(function(voice){
//break if it's within a small epsion of the voice's frequency
if (Math.abs(voice.frequency.getValueAtTime(time) - Tone.Frequency(note)) < 1e-4 &&
//and that note is currently active
voice.getLevelAtTime(time) > 1e-5){
return voice;
}
});
if (sameNote){
return sameNote;
}
//find available notes
var availableVoices = this.voices.filter(function(voice){
//check that it's not ascending in energy (in attack phase)
var levelNow = voice.getLevelAtTime(time);
var nextLevel = voice.getLevelAtTime(time + this.sampleTime);
//and it's near silent
return levelNow >= nextLevel && voice.getLevelAtTime(time) < 1e-5;
}.bind(this));
2018-06-21 03:02:44 +00:00
if (availableVoices.length){
//return the first one
return availableVoices[0];
}
//otherwise take the one with the lowest energy
var closestVoice = this.voices[0];
2018-06-21 03:02:44 +00:00
this.voices.forEach(function(voice){
if (voice.getLevelAtTime(time) < closestVoice.getLevelAtTime(time)){
closestVoice = voice;
}
});
return closestVoice;
};
2015-02-20 06:00:32 +00:00
/**
2015-06-20 22:03:49 +00:00
* Trigger the attack portion of the note
* @param {Frequency|Array} notes The notes to play. Accepts a single
* Frequency or an array of frequencies.
* @param {Time} [time=now] The start time of the note.
* @param {number} [velocity=1] The velocity of the note.
* @returns {Tone.PolySynth} this
2015-06-20 22:03:49 +00:00
* @example
* //trigger a chord immediately with a velocity of 0.2
* poly.triggerAttack(["Ab3", "C4", "F5"], undefined, 0.2);
2014-08-25 17:26:26 +00:00
*/
2015-06-20 22:03:49 +00:00
Tone.PolySynth.prototype.triggerAttack = function(notes, time, velocity){
2015-06-26 05:21:59 +00:00
if (!Array.isArray(notes)){
notes = [notes];
}
time = this.toSeconds(time);
notes.forEach(function(note){
var voice = this._getClosestVoice(time, note);
voice.triggerAttack(note, time, velocity);
2018-06-21 03:02:44 +00:00
this.log("triggerAttack", voice.index, note);
}.bind(this));
return this;
};
/**
* Trigger the release of the note. Unlike monophonic instruments,
* a note (or array of notes) needs to be passed in as the first argument.
* @param {Frequency|Array} notes The notes to play. Accepts a single
* Frequency or an array of frequencies.
* @param {Time} [time=now] When the release will be triggered.
* @returns {Tone.PolySynth} this
* @example
* poly.triggerRelease(["Ab3", "C4", "F5"], "+2n");
*/
Tone.PolySynth.prototype.triggerRelease = function(notes, time){
if (!Array.isArray(notes)){
notes = [notes];
2014-08-25 17:26:26 +00:00
}
time = this.toSeconds(time);
notes.forEach(function(note){
var voice = this._getClosestVoice(time, note);
2018-06-21 03:02:44 +00:00
this.log("triggerRelease", voice.index, note);
voice.triggerRelease(time);
}.bind(this));
2015-02-02 18:30:36 +00:00
return this;
2014-09-04 02:35:27 +00:00
};
/**
2015-06-20 22:03:49 +00:00
* Trigger the attack and release after the specified duration
2017-10-21 23:02:46 +00:00
*
2015-06-20 22:03:49 +00:00
* @param {Frequency|Array} notes The notes to play. Accepts a single
* Frequency or an array of frequencies.
2015-06-14 00:20:36 +00:00
* @param {Time} duration the duration of the note
* @param {Time} [time=now] if no time is given, defaults to now
2014-12-02 06:42:08 +00:00
* @param {number} [velocity=1] the velocity of the attack (0-1)
* @returns {Tone.PolySynth} this
2015-06-20 22:03:49 +00:00
* @example
2017-10-21 23:02:46 +00:00
* //trigger a chord for a duration of a half note
2015-06-20 22:03:49 +00:00
* poly.triggerAttackRelease(["Eb3", "G4", "C5"], "2n");
* @example
* //can pass in an array of durations as well
* poly.triggerAttackRelease(["Eb3", "G4", "C5"], ["2n", "4n", "4n"]);
2014-09-04 02:35:27 +00:00
*/
2015-06-20 22:03:49 +00:00
Tone.PolySynth.prototype.triggerAttackRelease = function(notes, duration, time, velocity){
2014-09-04 02:35:27 +00:00
time = this.toSeconds(time);
2015-06-26 05:21:59 +00:00
this.triggerAttack(notes, time, velocity);
if (Tone.isArray(duration) && Tone.isArray(notes)){
for (var i = 0; i < notes.length; i++){
var d = duration[Math.min(i, duration.length - 1)];
2017-10-21 23:02:46 +00:00
this.triggerRelease(notes[i], time + this.toSeconds(d));
}
} else {
this.triggerRelease(notes, time + this.toSeconds(duration));
}
2015-02-02 18:30:36 +00:00
return this;
2014-08-25 17:26:26 +00:00
};
/**
* Sync the instrument to the Transport. All subsequent calls of
* [triggerAttack](#triggerattack) and [triggerRelease](#triggerrelease)
* will be scheduled along the transport.
* @example
* synth.sync()
* //schedule 3 notes when the transport first starts
* synth.triggerAttackRelease('8n', 0)
* synth.triggerAttackRelease('8n', '8n')
* synth.triggerAttackRelease('8n', '4n')
* //start the transport to hear the notes
* Transport.start()
* @returns {Tone.Instrument} this
*/
Tone.PolySynth.prototype.sync = function(){
this._syncMethod("triggerAttack", 1);
this._syncMethod("triggerRelease", 1);
return this;
};
2014-08-25 17:26:26 +00:00
/**
2017-10-21 23:02:46 +00:00
* Set a member/attribute of the voices.
2015-04-24 23:34:26 +00:00
* @param {Object|string} params
* @param {number=} value
2015-06-14 00:20:36 +00:00
* @param {Time=} rampTime
* @returns {Tone.PolySynth} this
2015-06-20 22:03:49 +00:00
* @example
* poly.set({
* "filter" : {
* "type" : "highpass"
* },
* "envelope" : {
* "attack" : 0.25
* }
* });
2014-08-25 17:26:26 +00:00
*/
2015-04-24 23:34:26 +00:00
Tone.PolySynth.prototype.set = function(params, value, rampTime){
2014-12-08 16:03:20 +00:00
for (var i = 0; i < this.voices.length; i++){
2015-04-24 23:34:26 +00:00
this.voices[i].set(params, value, rampTime);
2014-08-25 17:26:26 +00:00
}
2015-02-02 18:30:36 +00:00
return this;
2014-08-25 17:26:26 +00:00
};
2015-02-20 06:00:32 +00:00
/**
2015-06-20 22:03:49 +00:00
* Get the synth's attributes. Given no arguments get
* will return all available object properties and their corresponding
* values. Pass in a single attribute to retrieve or an array
* of attributes. The attribute strings can also include a "."
* to access deeper properties.
2017-10-21 23:02:46 +00:00
* @param {Array=} params the parameters to get, otherwise will return
2015-02-20 06:00:32 +00:00
* all available.
*/
Tone.PolySynth.prototype.get = function(params){
return this.voices[0].get(params);
};
/**
* Trigger the release portion of all the currently active voices.
* @param {Time} [time=now] When the notes should be released.
* @return {Tone.PolySynth} this
*/
Tone.PolySynth.prototype.releaseAll = function(time){
time = this.toSeconds(time);
this.voices.forEach(function(voice){
voice.triggerRelease(time);
});
return this;
};
2014-08-25 17:26:26 +00:00
/**
2015-06-20 22:03:49 +00:00
* Clean up.
* @returns {Tone.PolySynth} this
2014-08-25 17:26:26 +00:00
*/
Tone.PolySynth.prototype.dispose = function(){
2014-09-20 23:24:04 +00:00
Tone.Instrument.prototype.dispose.call(this);
this.voices.forEach(function(voice){
voice.dispose();
});
this._writable("detune");
this.detune.dispose();
this.detune = null;
2014-12-08 16:03:20 +00:00
this.voices = null;
2015-02-02 18:30:36 +00:00
return this;
2014-08-25 17:26:26 +00:00
};
2016-03-03 18:01:11 +00:00
/**
2017-10-21 23:02:46 +00:00
* The maximum number of notes that can be allocated
* to a polysynth.
2016-03-03 18:01:11 +00:00
* @type {Number}
* @static
*/
Tone.PolySynth.MAX_POLYPHONY = 20;
2014-08-25 17:26:26 +00:00
return Tone.PolySynth;
2017-10-21 23:02:46 +00:00
});