From a150f018b5b2483ff9aac2482c6b95728790782c Mon Sep 17 00:00:00 2001 From: Eric Rosenbaum Date: Mon, 30 Jan 2017 10:59:03 -0500 Subject: [PATCH 01/26] comments --- src/ADPCMSoundLoader.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ADPCMSoundLoader.js b/src/ADPCMSoundLoader.js index bd9735f..70541bb 100644 --- a/src/ADPCMSoundLoader.js +++ b/src/ADPCMSoundLoader.js @@ -56,7 +56,7 @@ ADPCMSoundLoader.prototype.load = function (url) { var samples = this.imaDecompress(this.extractChunk('data', stream), this.adpcmBlockSize); - // this line is the only place Tone is used here, should be possible to remove + // todo: this line is the only place Tone is used here, should be possible to remove var buffer = Tone.context.createBuffer(1, samples.length, this.samplesPerSecond); // todo: optimize this? e.g. replace the divide by storing 1/32768 and multiply? From e1d478244d9419ede84e66f3d4327e0c509fb45c Mon Sep 17 00:00:00 2001 From: Eric Rosenbaum Date: Mon, 30 Jan 2017 11:09:45 -0500 Subject: [PATCH 02/26] audioengine loads sounds indexed by md5 audioplayers store list of their own active sound players indexed by md5 of the sound. sound players are created when the sound is played, deleted when it ends, and removed from the list of active sound players. the pitch effect uses this list of active sound players to set their playback ratios. --- src/effects/PitchEffect.js | 18 ++++--- src/index.js | 96 +++++++++++++++++++++----------------- 2 files changed, 65 insertions(+), 49 deletions(-) diff --git a/src/effects/PitchEffect.js b/src/effects/PitchEffect.js index cb4107b..b92ba1a 100644 --- a/src/effects/PitchEffect.js +++ b/src/effects/PitchEffect.js @@ -8,12 +8,14 @@ var Tone = require('tone'); function PitchEffect () { this.value = 0; + this.ratio = 1; this.tone = new Tone(); } PitchEffect.prototype.set = function (val, players) { this.value = val; + this.ratio = this.getRatio(this.value); this.updatePlayers(players); }; @@ -21,19 +23,23 @@ PitchEffect.prototype.changeBy = function (val, players) { this.set(this.value + val, players); }; -PitchEffect.prototype.getRatio = function () { - return this.tone.intervalToFrequencyRatio(this.value / 10); +PitchEffect.prototype.getRatio = function (val) { + return this.tone.intervalToFrequencyRatio(val / 10); +}; + +PitchEffect.prototype.updatePlayer = function (player) { + player.setPlaybackRate(this.ratio); }; PitchEffect.prototype.updatePlayers = function (players) { if (!players) return; - var ratio = this.getRatio(); - for (var i=0; i { + delete this.activeSoundPlayers[md5]; }); }; @@ -168,9 +178,9 @@ AudioPlayer.prototype.beatsToSec = function (beats) { }; AudioPlayer.prototype.stopAllSounds = function () { - // stop all sound players - for (var i=0; i Date: Mon, 30 Jan 2017 11:13:34 -0500 Subject: [PATCH 03/26] audio engine manages play note and beat timing --- src/index.js | 44 +++++++++++++++++++++----------------------- 1 file changed, 21 insertions(+), 23 deletions(-) diff --git a/src/index.js b/src/index.js index a39bc79..204cd9c 100644 --- a/src/index.js +++ b/src/index.js @@ -38,8 +38,6 @@ function AudioEngine () { // global tempo in bpm (beats per minute) this.currentTempo = 60; - this.minTempo = 10; - this.maxTempo = 1000; // instrument player for play note blocks this.instrumentPlayer = new InstrumentPlayer(this.input); @@ -82,8 +80,27 @@ AudioEngine.prototype.loadSounds = function (sounds) { } } }; + +AudioEngine.prototype.playNoteForBeatsWithInst = function (note, beats, inst) { + var sec = this.beatsToSec(beats); + this.instrumentPlayer.playNoteForSecWithInst(note, sec, inst); + return this.waitForBeats(beats); +}; + +AudioEngine.prototype.beatsToSec = function (beats) { + return (60 / this.currentTempo) * beats; +}; + +AudioEngine.prototype.waitForBeats = function (beats) { + var storedContext = this; + return new Promise(function (resolve) { + setTimeout(function () { + resolve(); + }, storedContext.beatsToSec(beats) * 1000); + }); +}; + AudioEngine.prototype.setTempo = function (value) { - // var newTempo = this._clamp(value, this.minTempo, this.maxTempo); this.currentTempo = value; }; @@ -153,28 +170,9 @@ AudioPlayer.prototype.playSound = function (md5) { }); }; -AudioPlayer.prototype.playNoteForBeats = function (note, beats) { - var sec = this.beatsToSec(beats); - this.audioEngine.instrumentPlayer.playNoteForSecWithInst(note, sec, this.currentInstrument); - return this.waitForBeats(beats); -}; - AudioPlayer.prototype.playDrumForBeats = function (drum, beats) { this.audioEngine.drumPlayer.play(drum, this.effectsNode); - return this.waitForBeats(beats); -}; - -AudioPlayer.prototype.waitForBeats = function (beats) { - var storedContext = this; - return new Promise(function (resolve) { - setTimeout(function () { - resolve(); - }, storedContext.beatsToSec(beats) * 1000); - }); -}; - -AudioPlayer.prototype.beatsToSec = function (beats) { - return (60 / this.audioEngine.currentTempo) * beats; + return this.audioEngine.waitForBeats(beats); }; AudioPlayer.prototype.stopAllSounds = function () { From 7b07d150daa8999fe0a6c07297b478e8ae234e79 Mon Sep 17 00:00:00 2001 From: Eric Rosenbaum Date: Mon, 30 Jan 2017 11:14:07 -0500 Subject: [PATCH 04/26] set instrument moved to target in vm --- src/index.js | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/index.js b/src/index.js index 204cd9c..df0e031 100644 --- a/src/index.js +++ b/src/index.js @@ -247,11 +247,6 @@ AudioPlayer.prototype.clearEffects = function () { this.audioEngine.roboticEffect.set(0); }; -AudioPlayer.prototype.setInstrument = function (instrumentNum) { - this.currentInstrument = instrumentNum; - return this.audioEngine.instrumentPlayer.loadInstrument(this.currentInstrument); -}; - AudioPlayer.prototype.setVolume = function (value) { this.currentVolume = this._clamp(value, 0, 100); this.effectsNode.gain.value = this.currentVolume / 100; From c9029396c6805598fc329cc91d93a060f2f7ace6 Mon Sep 17 00:00:00 2001 From: Eric Rosenbaum Date: Mon, 30 Jan 2017 11:17:18 -0500 Subject: [PATCH 05/26] state of effects, volume and instrument managed by target in vm --- src/index.js | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/index.js b/src/index.js index df0e031..78b144c 100644 --- a/src/index.js +++ b/src/index.js @@ -138,11 +138,6 @@ function AudioPlayer (audioEngine) { // reset effects to their default parameters this.clearEffects(); - this.effectNames = ['PITCH', 'PAN', 'ECHO', 'REVERB', 'FUZZ', 'ROBOT']; - - this.currentVolume = 100; - - this.currentInstrument = 0; // sound players that are currently playing, indexed by the sound's md5 this.activeSoundPlayers = Object.create({}); } From 263f614ef850c9fe6162f0f27b410e85b0fb1e7e Mon Sep 17 00:00:00 2001 From: Eric Rosenbaum Date: Mon, 30 Jan 2017 11:18:28 -0500 Subject: [PATCH 06/26] pitch effect is applied to active sound players array --- src/index.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/index.js b/src/index.js index 78b144c..69d85f8 100644 --- a/src/index.js +++ b/src/index.js @@ -181,7 +181,10 @@ AudioPlayer.prototype.stopAllSounds = function () { // stop drum notes this.audioEngine.drumPlayer.stopAll(); +}; +AudioPlayer.prototype.setPitchEffect = function (value) { + this.pitchEffect.set(value, this.activeSoundPlayers); }; AudioPlayer.prototype.setEffect = function (effect, value) { @@ -233,7 +236,7 @@ AudioPlayer.prototype.changeEffect = function (effect, value) { AudioPlayer.prototype.clearEffects = function () { this.panEffect.set(0); - this.pitchEffect.set(0, this.soundPlayers); + this.pitchEffect.set(0, this.activeSoundPlayers); this.effectsNode.gain.value = 1; this.audioEngine.echoEffect.set(0); From 42f231858ad594da6d4a2354479a9b1ea0e1d84a Mon Sep 17 00:00:00 2001 From: Eric Rosenbaum Date: Mon, 30 Jan 2017 11:24:55 -0500 Subject: [PATCH 07/26] effects are managed by vm target and audio engine --- src/index.js | 38 +++++++------------------------------- 1 file changed, 7 insertions(+), 31 deletions(-) diff --git a/src/index.js b/src/index.js index 69d85f8..ec21178 100644 --- a/src/index.js +++ b/src/index.js @@ -189,51 +189,27 @@ AudioPlayer.prototype.setPitchEffect = function (value) { AudioPlayer.prototype.setEffect = function (effect, value) { switch (effect) { - case 'PITCH': - this.pitchEffect.set(value, this.soundPlayers); + case 'pitch': + this.pitchEffect.set(value, this.activeSoundPlayers); break; - case 'PAN': + case 'pan': this.panEffect.set(value); break; - case 'ECHO': + case 'echo': this.audioEngine.echoEffect.set(value); break; - case 'REVERB': + case 'reverb': this.audioEngine.reverbEffect.set(value); break; - case 'FUZZ' : + case 'fuzz' : this.audioEngine.fuzzEffect.set(value); break; - case 'ROBOT' : + case 'robot' : this.audioEngine.roboticEffect.set(value); break; } }; -AudioPlayer.prototype.changeEffect = function (effect, value) { - switch (effect) { - case 'PITCH': - this.pitchEffect.changeBy(value, this.soundPlayers); - break; - case 'PAN': - this.panEffect.changeBy(value); - break; - case 'ECHO': - this.audioEngine.echoEffect.changeBy(value); - break; - case 'REVERB': - this.audioEngine.reverbEffect.changeBy(value); - break; - case 'FUZZ' : - this.audioEngine.fuzzEffect.changeBy(value); - break; - case 'ROBOT' : - this.audioEngine.roboticEffect.changeBy(value); - break; - - } -}; - AudioPlayer.prototype.clearEffects = function () { this.panEffect.set(0); this.pitchEffect.set(0, this.activeSoundPlayers); From 002a378d9775a172a956f9a4f0f1ec3d2d5fe79c Mon Sep 17 00:00:00 2001 From: Eric Rosenbaum Date: Mon, 30 Jan 2017 11:25:43 -0500 Subject: [PATCH 08/26] soundplayers are explicitly connected to output node when created --- src/SoundPlayer.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/SoundPlayer.js b/src/SoundPlayer.js index 0783efe..4c32491 100644 --- a/src/SoundPlayer.js +++ b/src/SoundPlayer.js @@ -1,13 +1,16 @@ var Tone = require('tone'); var log = require('./log'); -function SoundPlayer (outputNode) { - this.outputNode = outputNode; +function SoundPlayer () { + this.outputNode; this.buffer; // a Tone.Buffer this.bufferSource; this.playbackRate = 1; this.isPlaying = false; } +SoundPlayer.prototype.connect = function (node) { + this.outputNode = node; +}; SoundPlayer.prototype.setBuffer = function (buffer) { this.buffer = buffer; From bee821076488ec735e82a37222d48c390e0ab830 Mon Sep 17 00:00:00 2001 From: Eric Rosenbaum Date: Mon, 30 Jan 2017 11:26:55 -0500 Subject: [PATCH 09/26] use promise on playback finished --- src/SoundPlayer.js | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/src/SoundPlayer.js b/src/SoundPlayer.js index 4c32491..b353966 100644 --- a/src/SoundPlayer.js +++ b/src/SoundPlayer.js @@ -6,7 +6,6 @@ function SoundPlayer () { this.buffer; // a Tone.Buffer this.bufferSource; this.playbackRate = 1; - this.isPlaying = false; } SoundPlayer.prototype.connect = function (node) { this.outputNode = node; @@ -24,7 +23,7 @@ SoundPlayer.prototype.setPlaybackRate = function (playbackRate) { }; SoundPlayer.prototype.stop = function () { - if (this.isPlaying){ + if (this.bufferSource) { this.bufferSource.stop(); } }; @@ -35,20 +34,19 @@ SoundPlayer.prototype.start = function () { return; } - this.stop(); - this.bufferSource = new Tone.BufferSource(this.buffer.get()); this.bufferSource.playbackRate.value = this.playbackRate; this.bufferSource.connect(this.outputNode); this.bufferSource.start(); - this.isPlaying = true; }; -SoundPlayer.prototype.onEnded = function (callback) { - this.bufferSource.onended = function () { - this.isPlaying = false; - callback(); - }; +SoundPlayer.prototype.finished = function () { + var storedContext = this; + return new Promise(function (resolve) { + storedContext.bufferSource.onended = function () { + resolve(); + }; + }); }; module.exports = SoundPlayer; From 0742e8e0b0ef4ef0bf9b5655ea2309646259c157 Mon Sep 17 00:00:00 2001 From: Eric Rosenbaum Date: Mon, 30 Jan 2017 11:27:05 -0500 Subject: [PATCH 10/26] update comments --- src/index.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/index.js b/src/index.js index ec21178..f1c4f94 100644 --- a/src/index.js +++ b/src/index.js @@ -17,7 +17,8 @@ var DrumPlayer = require('./DrumPlayer'); /* Audio Engine The Scratch runtime has a single audio engine that handles global audio properties and effects, -and creates the instrument player and a drum player, used by all play note and play drum blocks +loads all the audio buffers for sounds belonging to sprites, and creates a single instrument player +and a drum player, used by all play note and play drum blocks */ @@ -114,10 +115,9 @@ AudioEngine.prototype.createPlayer = function () { /* Audio Player -Each sprite has an audio player -Clones receive a reference to their parent's audio player -the audio player currently handles sound loading and playback, sprite-specific effects -(pitch and pan) and volume +Each sprite or clone has an audio player +the audio player handles sound playback and the sprite-specific audio effects +pitch and pan, and volume */ From 7f741a509cd883146344b22a4a002c5ecba3fd7b Mon Sep 17 00:00:00 2001 From: Eric Rosenbaum Date: Mon, 30 Jan 2017 18:12:52 -0500 Subject: [PATCH 11/26] sounds stop and restart when played again --- src/SoundPlayer.js | 1 + src/index.js | 13 ++++++++----- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/SoundPlayer.js b/src/SoundPlayer.js index b353966..ae4722b 100644 --- a/src/SoundPlayer.js +++ b/src/SoundPlayer.js @@ -6,6 +6,7 @@ function SoundPlayer () { this.buffer; // a Tone.Buffer this.bufferSource; this.playbackRate = 1; + this.isPlaying = false; } SoundPlayer.prototype.connect = function (node) { this.outputNode = node; diff --git a/src/index.js b/src/index.js index f1c4f94..eca7310 100644 --- a/src/index.js +++ b/src/index.js @@ -144,7 +144,6 @@ function AudioPlayer (audioEngine) { AudioPlayer.prototype.playSound = function (md5) { // if this sprite or clone is already playing this sound, stop it first - // (this is not working, not sure why) if (this.activeSoundPlayers[md5]) { this.activeSoundPlayers[md5].stop(); } @@ -159,10 +158,14 @@ AudioPlayer.prototype.playSound = function (md5) { // add it to the list of active sound players this.activeSoundPlayers[md5] = player; - // when the sound completes, remove it from the list of active sound players - return player.finished().then(() => { - delete this.activeSoundPlayers[md5]; - }); + // remove sounds that are not playing from the active sound players array + for (var id in this.activeSoundPlayers) { + if (!this.activeSoundPlayers[id].isPlaying) { + delete this.activeSoundPlayers[id]; + } + } + + return player.finished(); }; AudioPlayer.prototype.playDrumForBeats = function (drum, beats) { From 088efdeb0ec9b015c8d525d5e9358e1d1353f34d Mon Sep 17 00:00:00 2001 From: Eric Rosenbaum Date: Mon, 30 Jan 2017 18:13:18 -0500 Subject: [PATCH 12/26] sounds track playing state --- src/SoundPlayer.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/SoundPlayer.js b/src/SoundPlayer.js index ae4722b..3afa5b8 100644 --- a/src/SoundPlayer.js +++ b/src/SoundPlayer.js @@ -27,6 +27,7 @@ SoundPlayer.prototype.stop = function () { if (this.bufferSource) { this.bufferSource.stop(); } + this.isPlaying = false; }; SoundPlayer.prototype.start = function () { @@ -39,12 +40,15 @@ SoundPlayer.prototype.start = function () { this.bufferSource.playbackRate.value = this.playbackRate; this.bufferSource.connect(this.outputNode); this.bufferSource.start(); + + this.isPlaying = true; }; SoundPlayer.prototype.finished = function () { var storedContext = this; return new Promise(function (resolve) { storedContext.bufferSource.onended = function () { + this.isPlaying = false; resolve(); }; }); From c564ca5b904120753012df12e0d7610bf9c3b3ac Mon Sep 17 00:00:00 2001 From: Eric Rosenbaum Date: Tue, 31 Jan 2017 18:34:56 -0500 Subject: [PATCH 13/26] remove unused function --- src/index.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/index.js b/src/index.js index eca7310..fe85f09 100644 --- a/src/index.js +++ b/src/index.js @@ -186,10 +186,6 @@ AudioPlayer.prototype.stopAllSounds = function () { this.audioEngine.drumPlayer.stopAll(); }; -AudioPlayer.prototype.setPitchEffect = function (value) { - this.pitchEffect.set(value, this.activeSoundPlayers); -}; - AudioPlayer.prototype.setEffect = function (effect, value) { switch (effect) { case 'pitch': From b70607d659ed2b9be9e78b6bcf03f6dd8a29d0ff Mon Sep 17 00:00:00 2001 From: Eric Rosenbaum Date: Tue, 31 Jan 2017 18:35:06 -0500 Subject: [PATCH 14/26] comments --- src/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/index.js b/src/index.js index fe85f09..c5501b0 100644 --- a/src/index.js +++ b/src/index.js @@ -116,8 +116,8 @@ AudioEngine.prototype.createPlayer = function () { /* Audio Player Each sprite or clone has an audio player -the audio player handles sound playback and the sprite-specific audio effects -pitch and pan, and volume +the audio player handles sound playback, volume, and the sprite-specific audio effects: +pitch and pan */ From 08a81af9e50a640971bd10ed7e63bd898a060e64 Mon Sep 17 00:00:00 2001 From: Eric Rosenbaum Date: Tue, 31 Jan 2017 18:35:37 -0500 Subject: [PATCH 15/26] volume state handled in vm sound blocks --- src/index.js | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/src/index.js b/src/index.js index c5501b0..ab8ceed 100644 --- a/src/index.js +++ b/src/index.js @@ -221,16 +221,7 @@ AudioPlayer.prototype.clearEffects = function () { }; AudioPlayer.prototype.setVolume = function (value) { - this.currentVolume = this._clamp(value, 0, 100); - this.effectsNode.gain.value = this.currentVolume / 100; -}; - -AudioPlayer.prototype.changeVolume = function (value) { - this.setVolume(this.currentVolume + value); -}; - -AudioPlayer.prototype._clamp = function (input, min, max) { - return Math.min(Math.max(input, min), max); + this.effectsNode.gain.value = value / 100; }; module.exports = AudioEngine; From dcdc2c08317e5542414ee38e70e6dc2041e9f2d7 Mon Sep 17 00:00:00 2001 From: Eric Rosenbaum Date: Tue, 31 Jan 2017 18:36:29 -0500 Subject: [PATCH 16/26] check for non-existent sound --- src/index.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/index.js b/src/index.js index ab8ceed..a2f5f3d 100644 --- a/src/index.js +++ b/src/index.js @@ -143,6 +143,11 @@ function AudioPlayer (audioEngine) { } AudioPlayer.prototype.playSound = function (md5) { + // if this sound is not in the audio engine, return + if (!this.audioEngine.audioBuffers[md5]) { + return; + } + // if this sprite or clone is already playing this sound, stop it first if (this.activeSoundPlayers[md5]) { this.activeSoundPlayers[md5].stop(); From 7483cbdb2fa613cfc3f45fdc7a58e11c83d26543 Mon Sep 17 00:00:00 2001 From: Eric Rosenbaum Date: Wed, 1 Feb 2017 18:02:04 -0500 Subject: [PATCH 17/26] JSDoc comments --- src/effects/EchoEffect.js | 39 +++++++++++++++-------- src/effects/FuzzEffect.js | 37 ++++++++++++++-------- src/effects/PanEffect.js | 36 ++++++++++++++++------ src/effects/PitchEffect.js | 60 ++++++++++++++++++++++++++++++------ src/effects/ReverbEffect.js | 36 ++++++++++++++++------ src/effects/RoboticEffect.js | 47 ++++++++++++++++++---------- src/effects/WobbleEffect.js | 41 +++++++++++++++++------- 7 files changed, 215 insertions(+), 81 deletions(-) diff --git a/src/effects/EchoEffect.js b/src/effects/EchoEffect.js index dd78ff0..cc868ea 100644 --- a/src/effects/EchoEffect.js +++ b/src/effects/EchoEffect.js @@ -1,17 +1,18 @@ -/* - -An echo effect - -0 mutes the effect -Values up to 100 set the echo feedback amount, -increasing the time it takes the echo to fade away - -Clamped 0-100 - -*/ - var Tone = require('tone'); +/** +* @fileoverview +* An echo effect (aka 'delay effect' in audio terms) +* Effect value of 0 mutes the effect +* Values up to 100 set the echo feedback amount, +* increasing the time it takes the echo to fade away +* Clamped 0-100 +*/ + +/** +* Initialize and chain the effect +* @constructor +*/ function EchoEffect () { Tone.Effect.call(this); @@ -24,6 +25,10 @@ function EchoEffect () { Tone.extend(EchoEffect, Tone.Effect); +/** +* Set the effect value +* @param {number} val - the new value to set the effect to +*/ EchoEffect.prototype.set = function (val) { this.value = val; @@ -40,10 +45,20 @@ EchoEffect.prototype.set = function (val) { this.delay.feedback.rampTo(feedback, 1/60); }; +/** +* Change the effect value +* @param {number} val - the value to change the effect by +*/ EchoEffect.prototype.changeBy = function (val) { this.set(this.value + val); }; +/** +* Clamp the input to a range +* @param {number} input - the input to clamp +* @param {number} min - the min value to clamp to +* @param {number} max - the max value to clamp to +*/ EchoEffect.prototype.clamp = function (input, min, max) { return Math.min(Math.max(input, min), max); }; diff --git a/src/effects/FuzzEffect.js b/src/effects/FuzzEffect.js index 2fcb369..c92d1e7 100644 --- a/src/effects/FuzzEffect.js +++ b/src/effects/FuzzEffect.js @@ -1,17 +1,17 @@ -/* - -A fuzz effect - -Distortion - -the value controls the wet/dry amount - -Clamped 0-100 - -*/ - var Tone = require('tone'); +/** +* @fileoverview +* An fuzz effect (aka 'distortion effect' in audio terms) +* Effect value controls the wet/dry amount: +* 0 passes through none of the effect, 100 passes through all effect +* Clamped 0-100 +*/ + +/** +* Initialize and chain the effect +* @constructor +*/ function FuzzEffect () { Tone.Effect.call(this); @@ -24,6 +24,10 @@ function FuzzEffect () { Tone.extend(FuzzEffect, Tone.Effect); +/** +* Set the effect value +* @param {number} val - the new value to set the effect to +*/ FuzzEffect.prototype.set = function (val) { this.value = val; @@ -32,10 +36,19 @@ FuzzEffect.prototype.set = function (val) { this.distortion.wet.value = this.value / 100; }; +/** +* Change the effect value +* @param {number} val - the value to change the effect by +*/ FuzzEffect.prototype.changeBy = function (val) { this.set(this.value + val); }; +/** +* @param {number} input - the input to clamp +* @param {number} min - the min value to clamp to +* @param {number} max - the max value to clamp to +*/ FuzzEffect.prototype.clamp = function (input, min, max) { return Math.min(Math.max(input, min), max); }; diff --git a/src/effects/PanEffect.js b/src/effects/PanEffect.js index 430da92..1eaf709 100644 --- a/src/effects/PanEffect.js +++ b/src/effects/PanEffect.js @@ -1,15 +1,17 @@ -/* - -A Pan effect - --100 puts the audio on the left channel, 0 centers it, 100 puts it on the right. - -Clamped -100 to 100 - -*/ - var Tone = require('tone'); +/** +* @fileoverview +* An 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, +* 0 centers it, 100 puts it on the right. +* Clamped -100 to 100 +*/ + +/** +* Initialize and chain the effect +* @constructor +*/ function PanEffect () { Tone.Effect.call(this); @@ -22,6 +24,10 @@ function PanEffect () { Tone.extend(PanEffect, Tone.Effect); +/** +* Set the effect value +* @param {number} val - the new value to set the effect to +*/ PanEffect.prototype.set = function (val) { this.value = val; @@ -30,10 +36,20 @@ PanEffect.prototype.set = function (val) { this.panner.pan.value = this.value / 100; }; +/** +* Change the effect value +* @param {number} val - the value to change the effect by +*/ PanEffect.prototype.changeBy = function (val) { this.set(this.value + val); }; +/** +* Clamp the input to a range +* @param {number} input - the input to clamp +* @param {number} min - the min value to clamp to +* @param {number} max - the max value to clamp to +*/ PanEffect.prototype.clamp = function (input, min, max) { return Math.min(Math.max(input, min), max); }; diff --git a/src/effects/PitchEffect.js b/src/effects/PitchEffect.js index b92ba1a..b48cdd0 100644 --- a/src/effects/PitchEffect.js +++ b/src/effects/PitchEffect.js @@ -1,36 +1,78 @@ -/* - -A Pitch effect - -*/ - var Tone = require('tone'); +/** +* @fileoverview +* An 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. +*/ + +/** +* Initialize the effect +* @constructor +*/ function PitchEffect () { - this.value = 0; - this.ratio = 1; + this.value = 0; // effect value + this.ratio = 1; // the playback rate ratio this.tone = new Tone(); } +/** +* Set the effect value +* @param {number} val - the new value to set the effect to +* @param {object} players - a dictionary of SoundPlayer objects to apply the effect to, indexed by md5 +*/ PitchEffect.prototype.set = function (val, players) { this.value = val; this.ratio = this.getRatio(this.value); this.updatePlayers(players); }; +/** +* Change the effect value +* @param {number} val - the value to change the effect by +* @param {Object} players - a dictionary of SoundPlayer objects indexed by md5 +*/ PitchEffect.prototype.changeBy = function (val, players) { this.set(this.value + val, players); }; +/** +* 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 +*/ PitchEffect.prototype.getRatio = function (val) { return this.tone.intervalToFrequencyRatio(val / 10); }; +/** +* Update a sound player's playback rate using the current ratio for the effect +* @param {Object} player - a SoundPlayer object +*/ PitchEffect.prototype.updatePlayer = function (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 +*/ PitchEffect.prototype.updatePlayers = function (players) { if (!players) return; @@ -39,7 +81,5 @@ PitchEffect.prototype.updatePlayers = function (players) { } }; - - module.exports = PitchEffect; diff --git a/src/effects/ReverbEffect.js b/src/effects/ReverbEffect.js index ec8f598..18c6339 100644 --- a/src/effects/ReverbEffect.js +++ b/src/effects/ReverbEffect.js @@ -1,15 +1,17 @@ -/* - -A Reverb effect - -The value controls the wet/dry amount of the effect - -Clamped 0 to 100 - -*/ - var Tone = require('tone'); +/** +* @fileoverview +* A reverb effect, simulating reverberation in a room +* Effect value controls the wet/dry amount: +* 0 passes through none of the effect, 100 passes through all effect +* Clamped 0 to 100 +*/ + +/** +* Initialize and chain the effect +* @constructor +*/ function ReverbEffect () { Tone.Effect.call(this); @@ -22,6 +24,10 @@ function ReverbEffect () { Tone.extend(ReverbEffect, Tone.Effect); +/** +* Set the effect value +* @param {number} val - the new value to set the effect to +*/ ReverbEffect.prototype.set = function (val) { this.value = val; @@ -30,10 +36,20 @@ ReverbEffect.prototype.set = function (val) { this.reverb.wet.value = this.value / 100; }; +/** +* Change the effect value +* @param {number} val - the value to change the effect by +*/ ReverbEffect.prototype.changeBy = function (val) { this.set(this.value + val); }; +/** +* Clamp the input to a range +* @param {number} input - the input to clamp +* @param {number} min - the min value to clamp to +* @param {number} max - the max value to clamp to +*/ ReverbEffect.prototype.clamp = function (input, min, max) { return Math.min(Math.max(input, min), max); }; diff --git a/src/effects/RoboticEffect.js b/src/effects/RoboticEffect.js index 366db37..4b10825 100644 --- a/src/effects/RoboticEffect.js +++ b/src/effects/RoboticEffect.js @@ -1,20 +1,22 @@ -/* - -A robot-voice effect - -A feedback comb filter with a short delay time creates a low-pitched buzzing -The effect value controls the length of this delay time, changing the pitch - -0 mutes the effect - -Other values changes the pitch of the effect, in units of 10 steps per semitone - -Not clamped - -*/ var Tone = require('tone'); +/** +* @fileoverview +* A "robotic" effect that adds a low-pitched buzzing to the sound, reminiscent of the +* voice of the daleks from Dr. Who. +* In audio terms it is a feedback comb filter with a short delay time. +* The effect value controls the length of this delay time, changing the pitch of the buzz +* A value of 0 mutes the effect. +* Other values change the pitch of the effect, in units of 10 steps per semitone. +* The effect value is not clamped (but probably should be). +* Exterminate. +*/ + +/** +* Initialize and chain the effect +* @constructor +*/ function RoboticEffect () { Tone.Effect.call(this); @@ -28,6 +30,10 @@ function RoboticEffect () { Tone.extend(RoboticEffect, Tone.Effect); +/** +* Set the effect value +* @param {number} val - the new value to set the effect to +*/ RoboticEffect.prototype.set = function (val) { this.value = val; @@ -43,13 +49,22 @@ RoboticEffect.prototype.set = function (val) { this.feedbackCombFilter.delayTime.rampTo(time, 1/60); }; +/** +* Change the effect value +* @param {number} val - the value to change the effect by +*/ RoboticEffect.prototype.changeBy = function (val) { this.set(this.value + val); }; +/** +* Compute the delay time for an effect value. +* Convert the effect value to a musical note (in units of 10 per semitone), +* and return the period (single cycle duration) of the frequency of that note. +* @param {number} val - the effect value +* @returns {number} a delay time in seconds +*/ RoboticEffect.prototype._delayTimeForValue = function (val) { - // convert effect setting range, typically 0-100 but can be outside that, - // to a musical note, and return the period of the frequency of that note var midiNote = ((val - 100) / 10) + 36; var freq = Tone.Frequency(midiNote, 'midi').eval(); return 1 / freq; diff --git a/src/effects/WobbleEffect.js b/src/effects/WobbleEffect.js index 8fb9d04..3c3dd3b 100644 --- a/src/effects/WobbleEffect.js +++ b/src/effects/WobbleEffect.js @@ -1,16 +1,21 @@ -/* - -A wobble effect - -A low frequency oscillator (LFO) controls a gain node -This creates an effect like tremolo - -Clamped 0 to 100 - -*/ - var Tone = require('tone'); +/** +* @fileoverview +* A wobble effect. In audio terms, it sounds like tremolo. +* It is implemented using a low frequency oscillator (LFO) controlling +* a gain node, which causes the loudness of the signal passing through +* to increase and decrease rapidly. +* Effect value controls the wet/dry amount: +* 0 passes through none of the effect, 100 passes through all effect +* Effect value also controls the frequency of the LFO. +* Clamped 0 to 100 +*/ + +/** +* Initialize and chain the effect +* @constructor +*/ function WobbleEffect () { Tone.Effect.call(this); @@ -25,6 +30,10 @@ function WobbleEffect () { Tone.extend(WobbleEffect, Tone.Effect); +/** +* Set the effect value +* @param {number} val - the new value to set the effect to +*/ WobbleEffect.prototype.set = function (val) { this.value = val; @@ -35,10 +44,20 @@ WobbleEffect.prototype.set = function (val) { this.wobbleLFO.frequency.rampTo(this.value / 10, 1/60); }; +/** +* Change the effect value +* @param {number} val - the value to change the effect by +*/ WobbleEffect.prototype.changeBy = function (val) { this.set(this.value + val); }; +/** +* Clamp the input to a range +* @param {number} input - the input to clamp +* @param {number} min - the min value to clamp to +* @param {number} max - the max value to clamp to +*/ WobbleEffect.prototype.clamp = function (input, min, max) { return Math.min(Math.max(input, min), max); }; From a1baa94d3ca169a8dd1e9039a0424e5d540645f9 Mon Sep 17 00:00:00 2001 From: Eric Rosenbaum Date: Thu, 2 Feb 2017 14:49:17 -0500 Subject: [PATCH 18/26] initialization --- src/SoundPlayer.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/SoundPlayer.js b/src/SoundPlayer.js index 3afa5b8..8a7b1ae 100644 --- a/src/SoundPlayer.js +++ b/src/SoundPlayer.js @@ -2,9 +2,9 @@ var Tone = require('tone'); var log = require('./log'); function SoundPlayer () { - this.outputNode; - this.buffer; // a Tone.Buffer - this.bufferSource; + this.outputNode = null; + this.buffer = new Tone.Buffer(); + this.bufferSource = null; this.playbackRate = 1; this.isPlaying = false; } From cdc49def15d2dd2590e5f6f685cfe905bd4d5f1b Mon Sep 17 00:00:00 2001 From: Eric Rosenbaum Date: Thu, 2 Feb 2017 14:51:02 -0500 Subject: [PATCH 19/26] create empty object for use as a map --- src/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/index.js b/src/index.js index a2f5f3d..9e737fe 100644 --- a/src/index.js +++ b/src/index.js @@ -139,7 +139,7 @@ function AudioPlayer (audioEngine) { this.clearEffects(); // sound players that are currently playing, indexed by the sound's md5 - this.activeSoundPlayers = Object.create({}); + this.activeSoundPlayers = Object.create(null); } AudioPlayer.prototype.playSound = function (md5) { From 1a26fecca152f6f3c054d4b82297c56007413160 Mon Sep 17 00:00:00 2001 From: Eric Rosenbaum Date: Thu, 2 Feb 2017 14:53:17 -0500 Subject: [PATCH 20/26] Comment all the things! --- src/ADPCMSoundLoader.js | 47 +++++++++++++----- src/ArrayBufferStream.js | 58 +++++++++++++++++----- src/DrumPlayer.js | 15 ++++++ src/InstrumentPlayer.js | 30 ++++++++++- src/SoundPlayer.js | 29 +++++++++++ src/effects/EchoEffect.js | 5 -- src/effects/FuzzEffect.js | 7 +-- src/effects/PanEffect.js | 7 +-- src/effects/PitchEffect.js | 7 +-- src/effects/ReverbEffect.js | 5 -- src/effects/RoboticEffect.js | 5 -- src/effects/WobbleEffect.js | 5 -- src/index.js | 96 ++++++++++++++++++++++++++++++------ 13 files changed, 239 insertions(+), 77 deletions(-) diff --git a/src/ADPCMSoundLoader.js b/src/ADPCMSoundLoader.js index 70541bb..7af0c1a 100644 --- a/src/ADPCMSoundLoader.js +++ b/src/ADPCMSoundLoader.js @@ -1,19 +1,24 @@ -/* - -ADPCMSoundLoader loads wav files that have been compressed with the ADPCM format - -based on code from Scratch-Flash: -https://github.com/LLK/scratch-flash/blob/master/src/sound/WAVFile.as - -*/ - var ArrayBufferStream = require('./ArrayBufferStream'); var Tone = require('tone'); var log = require('./log'); +/* + * Load wav audio files that have been compressed with the ADPCM format. + * This is necessary because, while web browsers have native decoders for many audio + * formats, ADPCM is a non-standard format used by Scratch since its early days. + * This decoder is based on code from Scratch-Flash: + * https://github.com/LLK/scratch-flash/blob/master/src/sound/WAVFile.as + * @constructor + */ function ADPCMSoundLoader () { } +/** + * Load an ADPCM sound file from a URL, decode it, and return a promise + * with the audio buffer. + * @param {string} url - a url pointing to the ADPCM wav file + * @return {Tone.Buffer} + */ ADPCMSoundLoader.prototype.load = function (url) { return new Promise(function (resolve, reject) { @@ -73,7 +78,10 @@ ADPCMSoundLoader.prototype.load = function (url) { }.bind(this)); }; - +/** + * Data used by the decompression algorithm + * @type {Array} + */ ADPCMSoundLoader.prototype.stepTable = [ 7, 8, 9, 10, 11, 12, 13, 14, 16, 17, 19, 21, 23, 25, 28, 31, 34, 37, 41, 45, 50, 55, 60, 66, 73, 80, 88, 97, 107, 118, 130, 143, 157, 173, 190, 209, 230, @@ -82,10 +90,20 @@ ADPCMSoundLoader.prototype.stepTable = [ 3660, 4026, 4428, 4871, 5358, 5894, 6484, 7132, 7845, 8630, 9493, 10442, 11487, 12635, 13899, 15289, 16818, 18500, 20350, 22385, 24623, 27086, 29794, 32767]; +/** + * Data used by the decompression algorithm + * @type {Array} + */ ADPCMSoundLoader.prototype.indexTable = [ -1, -1, -1, -1, 2, 4, 6, 8, -1, -1, -1, -1, 2, 4, 6, 8]; +/** + * Extract a chunk of audio data from the stream, consisting of a set of audio data bytes + * @param {string} chunkType - the type of chunk to extract. 'data' or 'fmt' (format) + * @param {ArrayBufferStream} stream - an stream containing the audio data + * @return {ArrayBufferStream} a stream containing the desired chunk + */ ADPCMSoundLoader.prototype.extractChunk = function (chunkType, stream) { stream.position = 12; while (stream.position < (stream.getLength() - 8)) { @@ -100,9 +118,14 @@ ADPCMSoundLoader.prototype.extractChunk = function (chunkType, stream) { } }; +/** + * Decompress sample data using the IMA ADPCM algorithm. + * Note: Handles only one channel, 4-bits per sample. + * @param {ArrayBufferStream} compressedData - a stream of compressed audio samples + * @param {number} blockSize - the number of bytes in the stream + * @return {Int16Array} the uncompressed audio samples + */ ADPCMSoundLoader.prototype.imaDecompress = function (compressedData, blockSize) { - // Decompress sample data using the IMA ADPCM algorithm. - // Note: Handles only one channel, 4-bits/sample. var sample, step, code, delta; var index = 0; var lastByte = -1; // -1 indicates that there is no saved lastByte diff --git a/src/ArrayBufferStream.js b/src/ArrayBufferStream.js index df50782..dfc1378 100644 --- a/src/ArrayBufferStream.js +++ b/src/ArrayBufferStream.js @@ -1,39 +1,59 @@ -/* - -ArrayBufferStream wraps the built-in javascript ArrayBuffer, adding the ability to access -data in it like a stream. You can request to read a value from the front of the array, -such as an 8 bit unsigned int, a 16 bit int, etc, and it will keep track of the position -within the byte array, so that successive reads are consecutive. - -*/ +/** + * ArrayBufferStream wraps the built-in javascript ArrayBuffer, adding the ability to access + * data in it like a stream, tracking its position. + * You can request to read a value from the front of the array, and it will keep track of the position + * within the byte array, so that successive reads are consecutive. + * The available types to read include: + * Uint8, Uint8String, Int16, Uint16, Int32, Uint32 + * @param {ArrayBuffer} arrayBuffer - array to use as a stream + * @constructor + */ function ArrayBufferStream (arrayBuffer) { this.arrayBuffer = arrayBuffer; this.position = 0; } -// return a new ArrayBufferStream that is a slice of the existing one +/** + * Return a new ArrayBufferStream that is a slice of the existing one + * @param {number} length - the number of bytes of extract + * @return {ArrayBufferStream} the extracted stream + */ ArrayBufferStream.prototype.extract = function (length) { var slicedArrayBuffer = this.arrayBuffer.slice(this.position, this.position+length); var newStream = new ArrayBufferStream(slicedArrayBuffer); return newStream; }; +/** + * @return {number} the length of the stream in bytes + */ ArrayBufferStream.prototype.getLength = function () { return this.arrayBuffer.byteLength; }; +/** + * @return {number} the number of bytes available after the current position in the stream + */ ArrayBufferStream.prototype.getBytesAvailable = function () { return (this.arrayBuffer.byteLength - this.position); }; +/** + * Read an unsigned 8 bit integer from the stream + * @return {number} + */ ArrayBufferStream.prototype.readUint8 = function () { var val = new Uint8Array(this.arrayBuffer, this.position, 1)[0]; this.position += 1; return val; }; -// convert a sequence of bytes of the given length to a string -// for small length strings only +/** + * Read a sequence of bytes of the given length and convert to a string. + * This is a convenience method for use with short strings. + * @param {number} length - the number of bytes to convert + * @return {String} a String made by concatenating the chars in the input + */ ArrayBufferStream.prototype.readUint8String = function (length) { var arr = new Uint8Array(this.arrayBuffer, this.position, length); this.position += length; @@ -44,24 +64,40 @@ ArrayBufferStream.prototype.readUint8String = function (length) { return str; }; +/** + * Read a 16 bit integer from the stream + * @return {number} + */ ArrayBufferStream.prototype.readInt16 = function () { var val = new Int16Array(this.arrayBuffer, this.position, 1)[0]; this.position += 2; // one 16 bit int is 2 bytes return val; }; +/** + * Read an unsigned 16 bit integer from the stream + * @return {number} + */ ArrayBufferStream.prototype.readUint16 = function () { var val = new Uint16Array(this.arrayBuffer, this.position, 1)[0]; this.position += 2; // one 16 bit int is 2 bytes return val; }; +/** + * Read a 32 bit integer from the stream + * @return {number} + */ ArrayBufferStream.prototype.readInt32 = function () { var val = new Int32Array(this.arrayBuffer, this.position, 1)[0]; this.position += 4; // one 32 bit int is 4 bytes return val; }; +/** + * Read an unsigned 32 bit integer from the stream + * @return {number} + */ ArrayBufferStream.prototype.readUint32 = function () { var val = new Uint32Array(this.arrayBuffer, this.position, 1)[0]; this.position += 4; // one 32 bit int is 4 bytes diff --git a/src/DrumPlayer.js b/src/DrumPlayer.js index 018400b..6df833b 100644 --- a/src/DrumPlayer.js +++ b/src/DrumPlayer.js @@ -1,6 +1,11 @@ var SoundPlayer = require('./SoundPlayer'); var Tone = require('tone'); +/** + * A prototype for the drum sound functionality that can load drum sounds, play, and stop them. + * @param {Tone.Gain} outputNode - a webAudio node that the drum sounds will send their output to + * @constructor + */ function DrumPlayer (outputNode) { this.outputNode = outputNode; @@ -35,11 +40,21 @@ function DrumPlayer (outputNode) { } } +/** + * Play a drum sound. + * The parameter for output node allows sprites or clones to send the drum sound + * to their individual audio effect chains. + * @param {number} drum - the drum number to play (0-indexed) + * @param {Tone.Gain} outputNode - a node to send the output to + */ DrumPlayer.prototype.play = function (drum, outputNode) { this.drumSounds[drum].outputNode = outputNode; this.drumSounds[drum].start(); }; +/** + * Stop all drum sounds. + */ DrumPlayer.prototype.stopAll = function () { for (var i=0; i { @@ -24,6 +44,11 @@ InstrumentPlayer.prototype.playNoteForSecWithInst = function (note, sec, instrum }); }; +/** + * Load an instrument by number + * @param {number} instrumentNum - an instrument number (0-indexed) + * @return {Promise} a Promise that resolves once the instrument audio data has been loaded + */ InstrumentPlayer.prototype.loadInstrument = function (instrumentNum) { if (this.instruments[instrumentNum]) { return Promise.resolve(); @@ -36,6 +61,9 @@ InstrumentPlayer.prototype.loadInstrument = function (instrumentNum) { } }; +/** + * Stop all notes being played on all instruments + */ InstrumentPlayer.prototype.stopAll = function () { for (var i=0; i Date: Thu, 2 Feb 2017 15:10:10 -0500 Subject: [PATCH 21/26] check for own property in for (var _ in _ ) --- src/effects/PitchEffect.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/effects/PitchEffect.js b/src/effects/PitchEffect.js index faed074..d93c35a 100644 --- a/src/effects/PitchEffect.js +++ b/src/effects/PitchEffect.js @@ -72,7 +72,9 @@ PitchEffect.prototype.updatePlayers = function (players) { if (!players) return; for (var md5 in players) { - this.updatePlayer(players[md5]); + if (players.hasOwnProperty(md5)) { + this.updatePlayer(players[md5]); + } } }; From 1c4709e5b9443e711f2634554378c894d2db2952 Mon Sep 17 00:00:00 2001 From: Eric Rosenbaum Date: Thu, 2 Feb 2017 15:25:26 -0500 Subject: [PATCH 22/26] bind to set isPlaying flag on ended --- src/SoundPlayer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/SoundPlayer.js b/src/SoundPlayer.js index a26de1a..2afa3c0 100644 --- a/src/SoundPlayer.js +++ b/src/SoundPlayer.js @@ -79,7 +79,7 @@ SoundPlayer.prototype.finished = function () { storedContext.bufferSource.onended = function () { this.isPlaying = false; resolve(); - }; + }.bind(storedContext); }); }; From de99228cd89feec02009aebb3eeac1f594b43ccf Mon Sep 17 00:00:00 2001 From: Eric Rosenbaum Date: Thu, 2 Feb 2017 15:25:36 -0500 Subject: [PATCH 23/26] check for own property in for (var _ in _ ) --- src/index.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/index.js b/src/index.js index 0bd8af5..4eddc04 100644 --- a/src/index.js +++ b/src/index.js @@ -178,7 +178,7 @@ function AudioPlayer (audioEngine) { this.clearEffects(); // sound players that are currently playing, indexed by the sound's md5 - this.activeSoundPlayers = Object.create(null); + this.activeSoundPlayers = {}; } /** @@ -209,8 +209,10 @@ AudioPlayer.prototype.playSound = function (md5) { // remove sounds that are not playing from the active sound players array for (var id in this.activeSoundPlayers) { - if (!this.activeSoundPlayers[id].isPlaying) { - delete this.activeSoundPlayers[id]; + if (this.activeSoundPlayers.hasOwnProperty(id)) { + if (!this.activeSoundPlayers[id].isPlaying) { + delete this.activeSoundPlayers[id]; + } } } From 7e671791b8561fe3d149b201659d54a3651bc885 Mon Sep 17 00:00:00 2001 From: Eric Rosenbaum Date: Thu, 2 Feb 2017 15:41:16 -0500 Subject: [PATCH 24/26] use an enum for audio effect names --- src/index.js | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/src/index.js b/src/index.js index 4eddc04..eaeb1ca 100644 --- a/src/index.js +++ b/src/index.js @@ -142,6 +142,20 @@ AudioEngine.prototype.changeTempo = function (value) { this.setTempo(this.currentTempo + value); }; +/** + * Names of the audio effects. + * @readonly + * @enum {string} + */ +AudioEngine.prototype.EFFECT_NAMES = { + pitch: 'pitch', + pan: 'pan', + echo: 'echo', + reverb: 'reverb', + fuzz: 'fuzz', + robot: 'robot' +}; + /** * Create an AudioPlayer. Each sprite or clone has an AudioPlayer. * It includes a reference to the AudioEngine so it can use global @@ -254,22 +268,22 @@ AudioPlayer.prototype.stopAllSounds = function () { */ AudioPlayer.prototype.setEffect = function (effect, value) { switch (effect) { - case 'pitch': + case this.audioEngine.EFFECT_NAMES.pitch: this.pitchEffect.set(value, this.activeSoundPlayers); break; - case 'pan': + case this.audioEngine.EFFECT_NAMES.pan: this.panEffect.set(value); break; - case 'echo': + case this.audioEngine.EFFECT_NAMES.echo: this.audioEngine.echoEffect.set(value); break; - case 'reverb': + case this.audioEngine.EFFECT_NAMES.reverb: this.audioEngine.reverbEffect.set(value); break; - case 'fuzz' : + case this.audioEngine.EFFECT_NAMES.fuzz: this.audioEngine.fuzzEffect.set(value); break; - case 'robot' : + case this.audioEngine.EFFECT_NAMES.robot: this.audioEngine.roboticEffect.set(value); break; } From bea203e1225aa440630a66807dddca6fbefe01da Mon Sep 17 00:00:00 2001 From: Eric Rosenbaum Date: Thu, 2 Feb 2017 15:48:26 -0500 Subject: [PATCH 25/26] fix JSDoc comment --- src/ADPCMSoundLoader.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ADPCMSoundLoader.js b/src/ADPCMSoundLoader.js index 7af0c1a..3b27dd9 100644 --- a/src/ADPCMSoundLoader.js +++ b/src/ADPCMSoundLoader.js @@ -2,7 +2,7 @@ var ArrayBufferStream = require('./ArrayBufferStream'); var Tone = require('tone'); var log = require('./log'); -/* +/** * Load wav audio files that have been compressed with the ADPCM format. * This is necessary because, while web browsers have native decoders for many audio * formats, ADPCM is a non-standard format used by Scratch since its early days. From 5e0e4e2e82b0c9e124c969520cd2469ff985a1d8 Mon Sep 17 00:00:00 2001 From: Eric Rosenbaum Date: Thu, 2 Feb 2017 15:52:36 -0500 Subject: [PATCH 26/26] added a comma to a comment --- src/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/index.js b/src/index.js index eaeb1ca..bb6fa7c 100644 --- a/src/index.js +++ b/src/index.js @@ -16,7 +16,7 @@ var DrumPlayer = require('./DrumPlayer'); /** * @fileOverview Scratch Audio is divided into a single AudioEngine, - * that handles global functionality, and AudioPlayers belonging to individual sprites and clones. + * that handles global functionality, and AudioPlayers, belonging to individual sprites and clones. */ /**