scratch-audio/src/effects/PitchEffect.js
2018-06-21 16:56:10 -04:00

124 lines
3.7 KiB
JavaScript

const Effect = require('./Effect');
/**
* A pitch change effect, which changes the playback rate of the sound in order
* to change its pitch: reducing the playback rate lowers the pitch, increasing
* the rate raises the pitch. The duration of the sound is also changed.
*
* Changing the value of the pitch effect by 10 causes a change in pitch by 1
* semitone (i.e. a musical half-step, such as the difference between C and C#)
* Changing the pitch effect by 120 changes the pitch by one octave (12
* semitones)
*
* The value of this effect is not clamped (i.e. it is typically between -120
* and 120, but can be set much higher or much lower, with weird and fun
* results). We should consider what extreme values to use for clamping it.
*
* Note that this effect functions differently from the other audio effects. It
* is not part of a chain of audio nodes. Instead, it provides a way to set the
* playback on one SoundPlayer or a group of them.
*/
class PitchEffect extends Effect {
/**
* @param {AudioEngine} audioEngine - audio engine this runs with
* @param {AudioPlayer} audioPlayer - audio player this affects
* @param {Effect} lastEffect - effect in the chain before this one
* @constructor
*/
constructor (audioEngine, audioPlayer, lastEffect) {
super(audioEngine, audioPlayer, lastEffect);
/**
* The playback rate ratio
* @type {Number}
*/
this.ratio = 1;
}
get name () {
return 'pitch';
}
/**
* Should the effect be connected to the audio graph?
* @return {boolean} is the effect affecting the graph?
*/
get _isPatch () {
return false;
}
/**
* Get the input node.
* @return {AudioNode} - audio node that is the input for this effect
*/
getInputNode () {
return this.target.getInputNode();
}
/**
* Initialize the Effect.
* Effects start out uninitialized. Then initialize when they are first set
* with some value.
* @throws {Error} throws when left unimplemented
*/
initialize () {
this.initialized = true;
}
/**
* Set the effect value.
* @param {number} value - the new value to set the effect to
*/
_set (value) {
this.value = value;
this.ratio = this.getRatio(this.value);
this.updatePlayers(this.audioPlayer.getSoundPlayers());
}
/**
* Update the effect for changes in the audioPlayer.
*/
update () {
this.updatePlayers(this.audioPlayer.getSoundPlayers());
}
/**
* Compute the playback ratio for an effect value.
* The playback ratio is scaled so that a change of 10 in the effect value
* gives a change of 1 semitone in the ratio.
* @param {number} val - an effect value
* @returns {number} a playback ratio
*/
getRatio (val) {
const interval = val / 10;
// Convert the musical interval in semitones to a frequency ratio
return Math.pow(2, (interval / 12));
}
/**
* Update a sound player's playback rate using the current ratio for the
* effect
* @param {object} player - a SoundPlayer object
*/
updatePlayer (player) {
player.setPlaybackRate(this.ratio);
}
/**
* Update a sound player's playback rate using the current ratio for the
* effect
* @param {object} players - a dictionary of SoundPlayer objects to update,
* indexed by md5
*/
updatePlayers (players) {
if (!players) return;
for (const id in players) {
if (players.hasOwnProperty(id)) {
this.updatePlayer(players[id]);
}
}
}
}
module.exports = PitchEffect;