diff --git a/src/blocks/scratch3_sensing.js b/src/blocks/scratch3_sensing.js index 761df9c0..bb314597 100644 --- a/src/blocks/scratch3_sensing.js +++ b/src/blocks/scratch3_sensing.js @@ -1,4 +1,5 @@ const Cast = require('../util/cast'); +const Timer = require('../util/timer'); class Scratch3SensingBlocks { constructor (runtime) { @@ -14,6 +15,24 @@ class Scratch3SensingBlocks { */ this._answer = ''; + /** + * The timer utility. + * @type {Timer} + */ + this._timer = new Timer(); + + /** + * The stored microphone loudness measurement. + * @type {number} + */ + this._cachedLoudness = -1; + + /** + * The time of the most recent microphone loudness measurement. + * @type {number} + */ + this._cachedLoudnessTimestamp = 0; + /** * The list of queued questions and respective `resolve` callbacks. * @type {!Array} @@ -217,7 +236,17 @@ class Scratch3SensingBlocks { getLoudness () { if (typeof this.runtime.audioEngine === 'undefined') return -1; - return this.runtime.audioEngine.getLoudness(); + if (this.runtime.currentStepTime === null) return -1; + + // Only measure loudness once per step + const timeSinceLoudness = this._timer.time() - this._cachedLoudnessTimestamp; + if (timeSinceLoudness < this.runtime.currentStepTime) { + return this._cachedLoudness; + } + + this._cachedLoudnessTimestamp = this._timer.time(); + this._cachedLoudness = this.runtime.audioEngine.getLoudness(); + return this._cachedLoudness; } getAttributeOf (args) { diff --git a/test/unit/blocks_sensing.js b/test/unit/blocks_sensing.js index 33b48446..a67a8ae0 100644 --- a/test/unit/blocks_sensing.js +++ b/test/unit/blocks_sensing.js @@ -83,3 +83,43 @@ test('set drag mode', t => { t.end(); }); + +test('get loudness with caching', t => { + const rt = new Runtime(); + const sensing = new Sensing(rt); + + // It should report -1 when audio engine is not available. + t.strictEqual(sensing.getLoudness(), -1); + + // Stub the audio engine with its getLoudness function, and set up different + // values to simulate it changing over time. + const firstLoudness = 1; + const secondLoudness = 2; + let simulatedLoudness = firstLoudness; + rt.audioEngine = {getLoudness: () => simulatedLoudness}; + + // It should report -1 when current step time is null. + t.strictEqual(sensing.getLoudness(), -1); + + // Stub the current step time. + rt.currentStepTime = 1000 / 30; + + // The first time it works, it should report the result from the stubbed audio engine. + t.strictEqual(sensing.getLoudness(), firstLoudness); + + // Update the simulated loudness to a new value. + simulatedLoudness = secondLoudness; + + // Simulate time passing by advancing the timer forward a little bit. + // After less than a step, it should still report cached loudness. + let simulatedTime = Date.now() + (rt.currentStepTime / 2); + sensing._timer = {time: () => simulatedTime}; + t.strictEqual(sensing.getLoudness(), firstLoudness); + + // Simulate more than a step passing. It should now request the value + // from the audio engine again. + simulatedTime += rt.currentStepTime; + t.strictEqual(sensing.getLoudness(), secondLoudness); + + t.end(); +});