mirror of
https://github.com/scratchfoundation/scratch-audio.git
synced 2024-12-22 22:12:48 -05:00
extend existing effects from Effect
Add Effect class that manages connecting the effects in a chain from their AudioPlayer to the AudioEngine.
This commit is contained in:
parent
d5b6290d45
commit
61e54b2457
6 changed files with 312 additions and 108 deletions
|
@ -24,7 +24,8 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"audio-context": "1.0.1",
|
"audio-context": "1.0.1",
|
||||||
"minilog": "^3.0.1",
|
"minilog": "^3.0.1",
|
||||||
"startaudiocontext": "1.2.1"
|
"startaudiocontext": "1.2.1",
|
||||||
|
"tap": "^12.0.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"babel-core": "^6.24.1",
|
"babel-core": "^6.24.1",
|
||||||
|
|
|
@ -49,8 +49,8 @@ class AudioEngine {
|
||||||
* will change the volume for all sounds.
|
* will change the volume for all sounds.
|
||||||
* @type {GainNode}
|
* @type {GainNode}
|
||||||
*/
|
*/
|
||||||
this.input = this.audioContext.createGain();
|
this.inputNode = this.audioContext.createGain();
|
||||||
this.input.connect(this.audioContext.destination);
|
this.inputNode.connect(this.audioContext.destination);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* a map of soundIds to audio buffers, holding sounds for all sprites
|
* a map of soundIds to audio buffers, holding sounds for all sprites
|
||||||
|
|
|
@ -14,20 +14,26 @@ class AudioPlayer {
|
||||||
constructor (audioEngine) {
|
constructor (audioEngine) {
|
||||||
this.audioEngine = audioEngine;
|
this.audioEngine = audioEngine;
|
||||||
|
|
||||||
// Create the audio effects
|
this.outputNode = this.audioEngine.audioContext.createGain();
|
||||||
this.pitchEffect = new PitchEffect();
|
|
||||||
this.panEffect = new PanEffect(this.audioEngine);
|
|
||||||
|
|
||||||
// Chain the audio effects together
|
// Create the audio effects
|
||||||
// effectsNode -> panEffect -> audioEngine.input
|
const pitchEffect = new PitchEffect(this.audioEngine, this, null);
|
||||||
this.effectsNode = this.audioEngine.audioContext.createGain();
|
const panEffect = new PanEffect(this.audioEngine, this, pitchEffect);
|
||||||
this.effectsNode.connect(this.panEffect.input);
|
this.effects = {
|
||||||
this.panEffect.connect(this.audioEngine.input);
|
pitch: pitchEffect,
|
||||||
|
pan: panEffect
|
||||||
|
};
|
||||||
|
|
||||||
|
// Chain the effects and player together with the audio engine.
|
||||||
|
// outputNode -> "pitchEffect" -> panEffect -> audioEngine.input
|
||||||
|
panEffect.connect(this.audioEngine.inputNode);
|
||||||
|
pitchEffect.connect(panEffect.inputNode);
|
||||||
|
|
||||||
// reset effects to their default parameters
|
// reset effects to their default parameters
|
||||||
this.clearEffects();
|
this.clearEffects();
|
||||||
|
|
||||||
// sound players that are currently playing, indexed by the sound's soundId
|
// sound players that are currently playing, indexed by the sound's
|
||||||
|
// soundId
|
||||||
this.activeSoundPlayers = {};
|
this.activeSoundPlayers = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -36,7 +42,16 @@ class AudioPlayer {
|
||||||
* @return {AudioNode} the AudioNode for this sprite's input
|
* @return {AudioNode} the AudioNode for this sprite's input
|
||||||
*/
|
*/
|
||||||
getInputNode () {
|
getInputNode () {
|
||||||
return this.effectsNode;
|
return this.outputNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all the sound players owned by this audio player.
|
||||||
|
* @return {object<string, SoundPlayer>} mapping of sound ids to sound
|
||||||
|
* players
|
||||||
|
*/
|
||||||
|
getSoundPlayers () {
|
||||||
|
return this.activeSoundPlayers;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -58,14 +73,17 @@ class AudioPlayer {
|
||||||
// create a new soundplayer to play the sound
|
// create a new soundplayer to play the sound
|
||||||
const player = new SoundPlayer(this.audioEngine.audioContext);
|
const player = new SoundPlayer(this.audioEngine.audioContext);
|
||||||
player.setBuffer(this.audioEngine.audioBuffers[soundId]);
|
player.setBuffer(this.audioEngine.audioBuffers[soundId]);
|
||||||
player.connect(this.effectsNode);
|
player.connect(this.outputNode);
|
||||||
this.pitchEffect.updatePlayer(player);
|
|
||||||
player.start();
|
player.start();
|
||||||
|
|
||||||
// add it to the list of active sound players
|
// add it to the list of active sound players
|
||||||
this.activeSoundPlayers[soundId] = player;
|
this.activeSoundPlayers[soundId] = player;
|
||||||
|
for (const effectName in this.effects) {
|
||||||
|
this.effects[effectName].update();
|
||||||
|
}
|
||||||
|
|
||||||
// remove sounds that are not playing from the active sound players array
|
// remove sounds that are not playing from the active sound players
|
||||||
|
// array
|
||||||
for (const id in this.activeSoundPlayers) {
|
for (const id in this.activeSoundPlayers) {
|
||||||
if (this.activeSoundPlayers.hasOwnProperty(id)) {
|
if (this.activeSoundPlayers.hasOwnProperty(id)) {
|
||||||
if (!this.activeSoundPlayers[id].isPlaying) {
|
if (!this.activeSoundPlayers[id].isPlaying) {
|
||||||
|
@ -93,13 +111,8 @@ class AudioPlayer {
|
||||||
* @param {number} value - the value to set the effect to
|
* @param {number} value - the value to set the effect to
|
||||||
*/
|
*/
|
||||||
setEffect (effect, value) {
|
setEffect (effect, value) {
|
||||||
switch (effect) {
|
if (this.effects.hasOwnProperty(effect)) {
|
||||||
case this.audioEngine.EFFECT_NAMES.pitch:
|
this.effects[effect].set(value);
|
||||||
this.pitchEffect.set(value, this.activeSoundPlayers);
|
|
||||||
break;
|
|
||||||
case this.audioEngine.EFFECT_NAMES.pan:
|
|
||||||
this.panEffect.set(value);
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -107,10 +120,12 @@ class AudioPlayer {
|
||||||
* Clear all audio effects
|
* Clear all audio effects
|
||||||
*/
|
*/
|
||||||
clearEffects () {
|
clearEffects () {
|
||||||
this.panEffect.set(0);
|
for (const effectName in this.effects) {
|
||||||
this.pitchEffect.set(0, this.activeSoundPlayers);
|
this.effects[effectName].clear();
|
||||||
|
}
|
||||||
|
|
||||||
if (this.audioEngine === null) return;
|
if (this.audioEngine === null) return;
|
||||||
this.effectsNode.gain.setTargetAtTime(1.0, 0, this.audioEngine.DECAY_TIME);
|
this.outputNode.gain.setTargetAtTime(1.0, 0, this.audioEngine.DECAY_TIME);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -119,15 +134,22 @@ class AudioPlayer {
|
||||||
*/
|
*/
|
||||||
setVolume (value) {
|
setVolume (value) {
|
||||||
if (this.audioEngine === null) return;
|
if (this.audioEngine === null) return;
|
||||||
this.effectsNode.gain.setTargetAtTime(value / 100, 0, this.audioEngine.DECAY_TIME);
|
this.outputNode.gain.setTargetAtTime(value / 100, 0, this.audioEngine.DECAY_TIME);
|
||||||
|
}
|
||||||
|
|
||||||
|
connect (node) {
|
||||||
|
this.outputNode.connect(node);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clean up and disconnect audio nodes.
|
* Clean up and disconnect audio nodes.
|
||||||
*/
|
*/
|
||||||
dispose () {
|
dispose () {
|
||||||
this.panEffect.dispose();
|
this.effects.pitch.dispose();
|
||||||
this.effectsNode.disconnect();
|
this.effects.pan.dispose();
|
||||||
|
|
||||||
|
this.outputNode.disconnect();
|
||||||
|
this.outputNode = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
137
src/effects/Effect.js
Normal file
137
src/effects/Effect.js
Normal file
|
@ -0,0 +1,137 @@
|
||||||
|
/**
|
||||||
|
* An effect on an AudioPlayer and all its SoundPlayers.
|
||||||
|
*/
|
||||||
|
class 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) {
|
||||||
|
this.audioEngine = audioEngine;
|
||||||
|
this.audioPlayer = audioPlayer;
|
||||||
|
this.lastEffect = lastEffect;
|
||||||
|
|
||||||
|
this.value = this.DEFAULT_VALUE;
|
||||||
|
|
||||||
|
this.initialized = false;
|
||||||
|
|
||||||
|
this.inputNode = null;
|
||||||
|
this.outputNode = null;
|
||||||
|
|
||||||
|
this.targetNode = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default value to set the Effect to when constructed and when clear'ed.
|
||||||
|
* @const {number}
|
||||||
|
*/
|
||||||
|
get DEFAULT_VALUE () {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Does the effect currently affect the player's graph.
|
||||||
|
* The pitch effect is always neutral. Instead of affecting the graph it
|
||||||
|
* affects the player directly.
|
||||||
|
* @return {boolean} is the effect affecting the graph?
|
||||||
|
*/
|
||||||
|
get isNeutral () {
|
||||||
|
return !this.initialized;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the Effect.
|
||||||
|
* Effects start out uninitialized. Then initialize when they are first set
|
||||||
|
* with some value.
|
||||||
|
* @throws {Error} throws when left unimplemented
|
||||||
|
*/
|
||||||
|
initialize () {
|
||||||
|
throw new Error(`${this.constructor.name}.initialize is not implemented.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the effects value.
|
||||||
|
* @private
|
||||||
|
* @param {number} value - new value to set effect to
|
||||||
|
*/
|
||||||
|
_set () {
|
||||||
|
throw new Error(`${this.constructor.name}._set is not implemented.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the effects value.
|
||||||
|
* @param {number} value - new value to set effect to
|
||||||
|
*/
|
||||||
|
set (value) {
|
||||||
|
// Initialize the node on first set.
|
||||||
|
if (!this.initialized) {
|
||||||
|
this.initialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store whether the graph should currently affected by this effect.
|
||||||
|
const isNeutral = this.isNeutral;
|
||||||
|
|
||||||
|
// Call the internal implementation per this Effect.
|
||||||
|
this._set(value);
|
||||||
|
|
||||||
|
// Connect or disconnect from the graph if this now applies or no longer
|
||||||
|
// applies an effect.
|
||||||
|
if (this.isNeutral !== isNeutral && this.targetNode !== null) {
|
||||||
|
this.connect(this.targetNode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the effect for changes in the audioPlayer.
|
||||||
|
*/
|
||||||
|
update () {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear the value back to the default.
|
||||||
|
*/
|
||||||
|
clear () {
|
||||||
|
this.set(this.DEFAULT_VALUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Connnect this effect's output to another audio node
|
||||||
|
* @param {AudioNode} node - the node to connect to
|
||||||
|
*/
|
||||||
|
connect (node) {
|
||||||
|
this.targetNode = node;
|
||||||
|
|
||||||
|
if (node === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.isNeutral) {
|
||||||
|
if (this.lastEffect === null) {
|
||||||
|
this.audioPlayer.connect(node);
|
||||||
|
} else {
|
||||||
|
this.lastEffect.connect(node);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (this.lastEffect === null) {
|
||||||
|
this.audioPlayer.connect(this.inputNode);
|
||||||
|
} else {
|
||||||
|
this.lastEffect.connect(this.inputNode);
|
||||||
|
}
|
||||||
|
this.outputNode.connect(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clean up and disconnect audio nodes.
|
||||||
|
*/
|
||||||
|
dispose () {
|
||||||
|
this.inputNode = null;
|
||||||
|
this.outputNode = null;
|
||||||
|
this.targetNode = null;
|
||||||
|
|
||||||
|
this.initialized = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = Effect;
|
|
@ -1,40 +1,55 @@
|
||||||
|
const Effect = require('./Effect');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A pan effect, which moves the sound to the left or right between the speakers
|
* A pan effect, which moves the sound to the left or right between the speakers
|
||||||
* Effect value of -100 puts the audio entirely on the left channel,
|
* Effect value of -100 puts the audio entirely on the left channel,
|
||||||
* 0 centers it, 100 puts it on the right.
|
* 0 centers it, 100 puts it on the right.
|
||||||
*/
|
*/
|
||||||
class PanEffect {
|
class PanEffect extends Effect {
|
||||||
/**
|
/**
|
||||||
* @param {AudioEngine} audioEngine - the audio engine.
|
* @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
|
||||||
*/
|
*/
|
||||||
constructor (audioEngine) {
|
constructor (audioEngine, audioPlayer, lastEffect) {
|
||||||
this.audioEngine = audioEngine;
|
super(audioEngine, audioPlayer, lastEffect);
|
||||||
this.audioContext = this.audioEngine.audioContext;
|
|
||||||
this.value = 0;
|
|
||||||
|
|
||||||
this.input = this.audioContext.createGain();
|
this.leftGain = null;
|
||||||
this.leftGain = this.audioContext.createGain();
|
this.rightGain = null;
|
||||||
this.rightGain = this.audioContext.createGain();
|
this.channelMerger = null;
|
||||||
this.channelMerger = this.audioContext.createChannelMerger(2);
|
}
|
||||||
|
|
||||||
this.input.connect(this.leftGain);
|
get isNeutral () {
|
||||||
this.input.connect(this.rightGain);
|
return !this.initialized || this.value === 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
initialize () {
|
||||||
|
const audioContext = this.audioEngine.audioContext;
|
||||||
|
|
||||||
|
this.inputNode = audioContext.createGain();
|
||||||
|
this.leftGain = audioContext.createGain();
|
||||||
|
this.rightGain = audioContext.createGain();
|
||||||
|
this.channelMerger = audioContext.createChannelMerger(2);
|
||||||
|
this.outputNode = this.channelMerger;
|
||||||
|
|
||||||
|
this.inputNode.connect(this.leftGain);
|
||||||
|
this.inputNode.connect(this.rightGain);
|
||||||
this.leftGain.connect(this.channelMerger, 0, 0);
|
this.leftGain.connect(this.channelMerger, 0, 0);
|
||||||
this.rightGain.connect(this.channelMerger, 0, 1);
|
this.rightGain.connect(this.channelMerger, 0, 1);
|
||||||
|
|
||||||
this.set(this.value);
|
this.initialized = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the effect value
|
* Set the effect value
|
||||||
* @param {number} val - the new value to set the effect to
|
* @param {number} value - the new value to set the effect to
|
||||||
*/
|
*/
|
||||||
set (val) {
|
_set (value) {
|
||||||
this.value = val;
|
this.value = value;
|
||||||
|
|
||||||
// Map the scratch effect value (-100 to 100) to (0 to 1)
|
// Map the scratch effect value (-100 to 100) to (0 to 1)
|
||||||
const p = (val + 100) / 200;
|
const p = (value + 100) / 200;
|
||||||
|
|
||||||
// Use trig functions for equal-loudness panning
|
// Use trig functions for equal-loudness panning
|
||||||
// See e.g. https://docs.cycling74.com/max7/tutorials/13_panningchapter01
|
// See e.g. https://docs.cycling74.com/max7/tutorials/13_panningchapter01
|
||||||
|
@ -45,22 +60,23 @@ class PanEffect {
|
||||||
this.rightGain.gain.setTargetAtTime(rightVal, 0, this.audioEngine.DECAY_TIME);
|
this.rightGain.gain.setTargetAtTime(rightVal, 0, this.audioEngine.DECAY_TIME);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Connnect this effect's output to another audio node
|
|
||||||
* @param {AudioNode} node - the node to connect to
|
|
||||||
*/
|
|
||||||
connect (node) {
|
|
||||||
this.channelMerger.connect(node);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clean up and disconnect audio nodes.
|
* Clean up and disconnect audio nodes.
|
||||||
*/
|
*/
|
||||||
dispose () {
|
dispose () {
|
||||||
this.input.disconnect();
|
this.inputNode.disconnect();
|
||||||
this.leftGain.disconnect();
|
this.leftGain.disconnect();
|
||||||
this.rightGain.disconnect();
|
this.rightGain.disconnect();
|
||||||
this.channelMerger.disconnect();
|
this.channelMerger.disconnect();
|
||||||
|
|
||||||
|
this.inputNode = null;
|
||||||
|
this.leftGain = null;
|
||||||
|
this.rightGain = null;
|
||||||
|
this.channelMerger = null;
|
||||||
|
this.outputNode = null;
|
||||||
|
this.targetNode = null;
|
||||||
|
|
||||||
|
this.initialized = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,53 +1,78 @@
|
||||||
|
const Effect = require('./Effect');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A pitch change effect, which changes the playback rate of the sound in order
|
* 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
|
* to change its pitch: reducing the playback rate lowers the pitch, increasing
|
||||||
* raises the pitch. The duration of the sound is also changed.
|
* 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
|
* Changing the value of the pitch effect by 10 causes a change in pitch by 1
|
||||||
* (i.e. a musical half-step, such as the difference between C and C#)
|
* 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)
|
* 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).
|
* The value of this effect is not clamped (i.e. it is typically between -120
|
||||||
* We should consider what extreme values to use for clamping it.
|
* 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
|
* Note that this effect functions differently from the other audio effects. It
|
||||||
* on one SoundPlayer or a group of them.
|
* 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 {
|
*/
|
||||||
constructor () {
|
class PitchEffect extends Effect {
|
||||||
this.value = 0; // effect value
|
/**
|
||||||
this.ratio = 1; // the playback rate ratio
|
* @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;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the effect value
|
* Does the effect currently affect the player's graph.
|
||||||
* @param {number} val - the new value to set the effect to
|
* The pitch effect is always neutral. Instead of affecting the graph it
|
||||||
* @param {object} players - a dictionary of SoundPlayer objects to apply the effect to, indexed by md5
|
* affects the player directly.
|
||||||
*/
|
* @returns {boolean} is the effect affecting the graph?
|
||||||
set (val, players) {
|
*/
|
||||||
this.value = val;
|
get isNeutral () {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
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.ratio = this.getRatio(this.value);
|
||||||
this.updatePlayers(players);
|
this.updatePlayers(this.audioPlayer.getSoundPlayers());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Change the effect value
|
* Update the effect for changes in the audioPlayer.
|
||||||
* @param {number} val - the value to change the effect by
|
*/
|
||||||
* @param {object} players - a dictionary of SoundPlayer objects indexed by md5
|
update () {
|
||||||
*/
|
this.updatePlayers(this.audioPlayer.getSoundPlayers());
|
||||||
changeBy (val, players) {
|
|
||||||
this.set(this.value + val, players);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Compute the playback ratio for an effect value.
|
* Compute the playback ratio for an effect value.
|
||||||
* The playback ratio is scaled so that a change of 10 in the 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.
|
* gives a change of 1 semitone in the ratio.
|
||||||
* @param {number} val - an effect value
|
* @param {number} val - an effect value
|
||||||
* @returns {number} a playback ratio
|
* @returns {number} a playback ratio
|
||||||
*/
|
*/
|
||||||
getRatio (val) {
|
getRatio (val) {
|
||||||
const interval = val / 10;
|
const interval = val / 10;
|
||||||
// Convert the musical interval in semitones to a frequency ratio
|
// Convert the musical interval in semitones to a frequency ratio
|
||||||
|
@ -55,23 +80,26 @@ class PitchEffect {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update a sound player's playback rate using the current ratio for the effect
|
* Update a sound player's playback rate using the current ratio for the
|
||||||
* @param {object} player - a SoundPlayer object
|
* effect
|
||||||
*/
|
* @param {object} player - a SoundPlayer object
|
||||||
|
*/
|
||||||
updatePlayer (player) {
|
updatePlayer (player) {
|
||||||
player.setPlaybackRate(this.ratio);
|
player.setPlaybackRate(this.ratio);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update a sound player's playback rate using the current ratio for the effect
|
* Update a sound player's playback rate using the current ratio for the
|
||||||
* @param {object} players - a dictionary of SoundPlayer objects to update, indexed by md5
|
* effect
|
||||||
*/
|
* @param {object} players - a dictionary of SoundPlayer objects to update,
|
||||||
|
* indexed by md5
|
||||||
|
*/
|
||||||
updatePlayers (players) {
|
updatePlayers (players) {
|
||||||
if (!players) return;
|
if (!players) return;
|
||||||
|
|
||||||
for (const md5 in players) {
|
for (const id in players) {
|
||||||
if (players.hasOwnProperty(md5)) {
|
if (players.hasOwnProperty(id)) {
|
||||||
this.updatePlayer(players[md5]);
|
this.updatePlayer(players[id]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue