| Index: chrome/browser/resources/chromeos/chromevox/cvox2/background/earcon_engine.js
|
| diff --git a/chrome/browser/resources/chromeos/chromevox/cvox2/background/earcon_engine.js b/chrome/browser/resources/chromeos/chromevox/cvox2/background/earcon_engine.js
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..fc428c548276ea988edbc27fce3e79b4e934fc0f
|
| --- /dev/null
|
| +++ b/chrome/browser/resources/chromeos/chromevox/cvox2/background/earcon_engine.js
|
| @@ -0,0 +1,711 @@
|
| +// Copyright 2015 The Chromium Authors. All rights reserved.
|
| +// Use of this source code is governed by a BSD-style license that can be
|
| +// found in the LICENSE file.
|
| +
|
| +/**
|
| + * @fileoverview This is the low-level class that generates ChromeVox's
|
| + * earcons. It's designed to be self-contained and not depend on the
|
| + * rest of the code.
|
| + */
|
| +
|
| +goog.provide('EarconEngine');
|
| +
|
| +/**
|
| + * EarconEngine generates ChromeVox's earcons using the web audio API.
|
| + * @constructor
|
| + */
|
| +EarconEngine = function() {
|
| + // Public control parameters. All of these are meant to be adjustable.
|
| +
|
| + /** @type {number} The master volume, as an amplification factor. */
|
| + this.masterVolume = 0.2;
|
| +
|
| + /** @type {number} The base relative pitch adjustment, in half-steps. */
|
| + this.masterPitch = -4;
|
| +
|
| + /** @type {number} The click volume, as an amplification factor. */
|
| + this.clickVolume = 0.4;
|
| +
|
| + /**
|
| + * @type {number} The volume of the static sound, as an
|
| + * amplification factor.
|
| + */
|
| + this.staticVolume = 0.2;
|
| +
|
| + /** @type {number} The base delay for repeated sounds, in seconds. */
|
| + this.baseDelay = 0.045;
|
| +
|
| + /** @type {number} The master stereo panning, from -1 to 1. */
|
| + this.masterPan = 0;
|
| +
|
| + /** @type {number} The master reverb level as an amplification factor. */
|
| + this.masterReverb = 0.4;
|
| +
|
| + /**
|
| + * @type {string} The choice of the reverb impulse response to use.
|
| + * Must be one of the strings from EarconEngine.REVERBS.
|
| + */
|
| + this.reverbSound = 'small_room_2';
|
| +
|
| + /** @type {number} The base pitch for the 'wrap' sound in half-steps. */
|
| + this.wrapPitch = 0;
|
| +
|
| + /** @type {number} The base pitch for the 'alert' sound in half-steps. */
|
| + this.alertPitch = 0;
|
| +
|
| + /** @type {string} The choice of base sound for most controls. */
|
| + this.controlSound = 'control';
|
| +
|
| + /**
|
| + * @type {number} The delay between sounds in the on/off sweep effect,
|
| + * in seconds.
|
| + */
|
| + this.sweepDelay = 0.045;
|
| +
|
| + /**
|
| + * @type {number} The delay between echos in the on/off sweep, in seconds.
|
| + */
|
| + this.sweepEchoDelay = 0.15;
|
| +
|
| + /** @type {number} The number of echos in the on/off sweep. */
|
| + this.sweepEchoCount = 3;
|
| +
|
| + /** @type {number} The pitch offset of the on/off sweep, in half-steps. */
|
| + this.sweepPitch = -7;
|
| +
|
| + /**
|
| + * @type {number} The final gain of the progress sound, as an
|
| + * amplification factor.
|
| + */
|
| + this.progressFinalGain = 0.05;
|
| +
|
| + /** @type {number} The multiplicative decay rate of the progress ticks. */
|
| + this.progressGain_Decay = 0.7;
|
| +
|
| + // Private variables.
|
| +
|
| + /** @type {AudioContext} @private The audio context. */
|
| + this.context_ = new AudioContext();
|
| +
|
| + /** @type {?ConvolverNode} @private The reverb node, lazily initialized. */
|
| + this.reverbConvolver_ = null;
|
| +
|
| + /**
|
| + * @type {Object<string, AudioBuffer>} A map between the name of an
|
| + * audio data file and its loaded AudioBuffer.
|
| + * @private
|
| + */
|
| + this.buffers_ = {};
|
| +
|
| + /**
|
| + * The source audio nodes for queued tick / tocks for progress.
|
| + * Kept around so they can be canceled.
|
| + *
|
| + * @type {Array<AudioNode>}
|
| + * @private
|
| + */
|
| + this.progressSources_ = [];
|
| +
|
| + /** @type {number} The current gain for progress sounds. @private */
|
| + this.progressGain_ = 1.0;
|
| +
|
| + /** @type {?number} The current time for progress sounds. @private */
|
| + this.progressTime_ = this.context_.currentTime;
|
| +
|
| + /**
|
| + * @type {?number} The window.setInterval ID for progress sounds.
|
| + * @private
|
| + */
|
| + this.progressIntervalID_ = null;
|
| +
|
| + // Initialization: load the base sound data files asynchronously.
|
| + var allSoundFilesToLoad = EarconEngine.SOUNDS.concat(EarconEngine.REVERBS);
|
| + allSoundFilesToLoad.forEach((function(sound) {
|
| + var url = EarconEngine.BASE_URL + sound + '.wav';
|
| + this.loadSound(sound, url);
|
| + }).bind(this));
|
| +};
|
| +
|
| +/**
|
| + * @type {Array<string>} The list of sound data files to load.
|
| + * @const
|
| + */
|
| +EarconEngine.SOUNDS = [
|
| + 'control',
|
| + 'selection',
|
| + 'selection_reverse',
|
| + 'skim',
|
| + 'static'];
|
| +
|
| +/**
|
| + * @type {Array<string>} The list of reverb data files to load.
|
| + * @const
|
| + */
|
| +EarconEngine.REVERBS = [
|
| + 'small_room_2'];
|
| +
|
| +/**
|
| + * @type {number} The scale factor for one half-step.
|
| + * @const
|
| + */
|
| +EarconEngine.HALF_STEP = Math.pow(2.0, 1.0 / 12.0);
|
| +
|
| +/**
|
| + * @type {string} The base url for earcon sound resources.
|
| + * @const
|
| + */
|
| +EarconEngine.BASE_URL = chrome.extension.getURL('cvox2/background/earcons/');
|
| +
|
| +/**
|
| + * Fetches a sound asynchronously and loads its data into an AudioBuffer.
|
| + *
|
| + * @param {string} name The name of the sound to load.
|
| + * @param {string} url The url where the sound should be fetched from.
|
| + */
|
| +EarconEngine.prototype.loadSound = function(name, url) {
|
| + var request = new XMLHttpRequest();
|
| + request.open('GET', url, true);
|
| + request.responseType = 'arraybuffer';
|
| +
|
| + // Decode asynchronously.
|
| + request.onload = (function() {
|
| + this.context_.decodeAudioData(
|
| + /** @type {ArrayBuffer} */ (request.response),
|
| + (function(buffer) {
|
| + this.buffers_[name] = buffer;
|
| + }).bind(this));
|
| + }).bind(this);
|
| + request.send();
|
| +};
|
| +
|
| +/**
|
| + * Return an AudioNode containing the final processing that all
|
| + * sounds go through: master volume / gain, panning, and reverb.
|
| + * The chain is hooked up to the destination automatically, so you
|
| + * just need to connect your source to the return value from this
|
| + * method.
|
| + *
|
| + * @param {{gain: (number | undefined),
|
| + * pan: (number | undefined),
|
| + * reverb: (number | undefined)}} properties
|
| + * An object where you can override the default
|
| + * gain, pan, and reverb, otherwise these are taken from
|
| + * masterVolume, masterPan, and masterReverb.
|
| + * @return {AudioNode} The filters to be applied to all sounds, connected
|
| + * to the destination node.
|
| + */
|
| +EarconEngine.prototype.createCommonFilters = function(properties) {
|
| + var gain = this.masterVolume;
|
| + if (properties.gain) {
|
| + gain *= properties.gain;
|
| + }
|
| + var gainNode = this.context_.createGain();
|
| + gainNode.gain.value = gain;
|
| + var first = gainNode;
|
| + var last = gainNode;
|
| +
|
| + var pan = this.masterPan;
|
| + if (properties.pan !== undefined) {
|
| + pan = properties.pan;
|
| + }
|
| + if (pan != 0) {
|
| + var panNode = this.context_.createPanner();
|
| + panNode.setPosition(pan, 0, -1);
|
| + panNode.setOrientation(0, 0, 1);
|
| + last.connect(panNode);
|
| + last = panNode;
|
| + }
|
| +
|
| + var reverb = this.masterReverb;
|
| + if (properties.reverb !== undefined) {
|
| + reverb = properties.reverb;
|
| + }
|
| + if (reverb) {
|
| + if (!this.reverbConvolver_) {
|
| + this.reverbConvolver_ = this.context_.createConvolver();
|
| + this.reverbConvolver_.buffer = this.buffers_[this.reverbSound];
|
| + this.reverbConvolver_.connect(this.context_.destination);
|
| + }
|
| +
|
| + // Dry
|
| + last.connect(this.context_.destination);
|
| +
|
| + // Wet
|
| + var reverbGainNode = this.context_.createGain();
|
| + reverbGainNode.gain.value = reverb;
|
| + last.connect(reverbGainNode);
|
| + reverbGainNode.connect(this.reverbConvolver_);
|
| + } else {
|
| + last.connect(this.context_.destination);
|
| + }
|
| +
|
| + return first;
|
| +};
|
| +
|
| +/**
|
| + * High-level interface to play a sound from a buffer source by name,
|
| + * with some simple adjustments like pitch change (in half-steps),
|
| + * a start time (relative to the current time, in seconds),
|
| + * gain, panning, and reverb.
|
| + *
|
| + * The only required parameter is the name of the sound. The time, pitch,
|
| + * gain, panning, and reverb are all optional and are passed in an
|
| + * object of optional properties.
|
| + *
|
| + * @param {string} sound The name of the sound to play. It must already
|
| + * be loaded in a buffer.
|
| + * @param {{pitch: (number | undefined),
|
| + * time: (number | undefined),
|
| + * gain: (number | undefined),
|
| + * pan: (number | undefined),
|
| + * reverb: (number | undefined)}=} opt_properties
|
| + * An object where you can override the default pitch, gain, pan,
|
| + * and reverb.
|
| + * @return {AudioBufferSourceNode} The source node, so you can stop it
|
| + * or set event handlers on it.
|
| + */
|
| +EarconEngine.prototype.play = function(sound, opt_properties) {
|
| + var source = this.context_.createBufferSource();
|
| + source.buffer = this.buffers_[sound];
|
| +
|
| + if (!opt_properties) {
|
| + // This typecast looks silly, but the Closure compiler doesn't support
|
| + // optional fields in record types very well so this is the shortest hack.
|
| + opt_properties = /** @type {undefined} */({});
|
| + }
|
| +
|
| + var pitch = this.masterPitch;
|
| + if (opt_properties.pitch) {
|
| + pitch += opt_properties.pitch;
|
| + }
|
| + if (pitch != 0) {
|
| + source.playbackRate.value = Math.pow(EarconEngine.HALF_STEP, pitch);
|
| + }
|
| +
|
| + var destination = this.createCommonFilters(opt_properties);
|
| + source.connect(destination);
|
| +
|
| + if (opt_properties.time) {
|
| + source.start(this.context_.currentTime + opt_properties.time);
|
| + } else {
|
| + source.start(this.context_.currentTime);
|
| + }
|
| +
|
| + return source;
|
| +};
|
| +
|
| +/**
|
| + * Play the static sound.
|
| + */
|
| +EarconEngine.prototype.onStatic = function() {
|
| + this.play('static', {gain: this.staticVolume});
|
| +};
|
| +
|
| +/**
|
| + * Play the link sound.
|
| + */
|
| +EarconEngine.prototype.onLink = function() {
|
| + this.play('static', {gain: this.clickVolume});
|
| + this.play(this.controlSound, {pitch: 12});
|
| +};
|
| +
|
| +/**
|
| + * Play the button sound.
|
| + */
|
| +EarconEngine.prototype.onButton = function() {
|
| + this.play('static', {gain: this.clickVolume});
|
| + this.play(this.controlSound);
|
| +};
|
| +
|
| +/**
|
| + * Play the text field sound.
|
| + */
|
| +EarconEngine.prototype.onTextField = function() {
|
| + this.play('static', {gain: this.clickVolume});
|
| + this.play('static', {time: this.baseDelay * 1.5,
|
| + gain: this.clickVolume * 0.5});
|
| + this.play(this.controlSound, {pitch: 4});
|
| + this.play(this.controlSound,
|
| + {pitch: 4,
|
| + time: this.baseDelay * 1.5,
|
| + gain: 0.5});
|
| +};
|
| +
|
| +/**
|
| + * Play the pop up button sound.
|
| + */
|
| +EarconEngine.prototype.onPopUpButton = function() {
|
| + this.play('static', {gain: this.clickVolume});
|
| +
|
| + this.play(this.controlSound);
|
| + this.play(this.controlSound,
|
| + {time: this.baseDelay * 3,
|
| + gain: 0.2,
|
| + pitch: 12});
|
| + this.play(this.controlSound,
|
| + {time: this.baseDelay * 4.5,
|
| + gain: 0.2,
|
| + pitch: 12});
|
| +};
|
| +
|
| +/**
|
| + * Play the check on sound.
|
| + */
|
| +EarconEngine.prototype.onCheckOn = function() {
|
| + this.play('static', {gain: this.clickVolume});
|
| + this.play(this.controlSound, {pitch: -5});
|
| + this.play(this.controlSound, {pitch: 7, time: this.baseDelay * 2});
|
| +};
|
| +
|
| +/**
|
| + * Play the check off sound.
|
| + */
|
| +EarconEngine.prototype.onCheckOff = function() {
|
| + this.play('static', {gain: this.clickVolume});
|
| + this.play(this.controlSound, {pitch: 7});
|
| + this.play(this.controlSound, {pitch: -5, time: this.baseDelay * 2});
|
| +};
|
| +
|
| +/**
|
| + * Play the select control sound.
|
| + */
|
| +EarconEngine.prototype.onSelect = function() {
|
| + this.play('static', {gain: this.clickVolume});
|
| + this.play(this.controlSound);
|
| + this.play(this.controlSound, {time: this.baseDelay});
|
| + this.play(this.controlSound, {time: this.baseDelay * 2});
|
| +};
|
| +
|
| +/**
|
| + * Play the slider sound.
|
| + */
|
| +EarconEngine.prototype.onSlider = function() {
|
| + this.play('static', {gain: this.clickVolume});
|
| + this.play(this.controlSound);
|
| + this.play(this.controlSound,
|
| + {time: this.baseDelay,
|
| + gain: 0.5,
|
| + pitch: 2});
|
| + this.play(this.controlSound,
|
| + {time: this.baseDelay * 2,
|
| + gain: 0.25,
|
| + pitch: 4});
|
| + this.play(this.controlSound,
|
| + {time: this.baseDelay * 3,
|
| + gain: 0.125,
|
| + pitch: 6});
|
| + this.play(this.controlSound,
|
| + {time: this.baseDelay * 4,
|
| + gain: 0.0625,
|
| + pitch: 8});
|
| +};
|
| +
|
| +/**
|
| + * Play the skim sound.
|
| + */
|
| +EarconEngine.prototype.onSkim = function() {
|
| + this.play('skim');
|
| +};
|
| +
|
| +/**
|
| + * Play the selection sound.
|
| + */
|
| +EarconEngine.prototype.onSelection = function() {
|
| + this.play('selection');
|
| +};
|
| +
|
| +/**
|
| + * Play the selection reverse sound.
|
| + */
|
| +EarconEngine.prototype.onSelectionReverse = function() {
|
| + this.play('selection_reverse');
|
| +};
|
| +
|
| +/**
|
| + * Generate a synthesized musical note based on a sum of sinusoidals shaped
|
| + * by an envelope, controlled by a number of properties.
|
| + *
|
| + * The sound has a frequency of |freq|, or if |endFreq| is specified, does
|
| + * an exponential ramp from |freq| to |endFreq|.
|
| + *
|
| + * If |overtones| is greater than 1, the sound will be mixed with additional
|
| + * sinusoidals at multiples of |freq|, each one scaled by |overtoneFactor|.
|
| + * This creates a rounder tone than a pure sine wave.
|
| + *
|
| + * The envelope is shaped by the duration |dur|, the attack time |attack|,
|
| + * and the decay time |decay|, in seconds.
|
| + *
|
| + * As with other functions, |pan| and |reverb| can be used to override
|
| + * masterPan and masterReverb.
|
| + *
|
| + * @param {{gain: number,
|
| + * freq: number,
|
| + * endFreq: (number | undefined),
|
| + * time: (number | undefined),
|
| + * overtones: (number | undefined),
|
| + * overtoneFactor: (number | undefined),
|
| + * dur: (number | undefined),
|
| + * attack: (number | undefined),
|
| + * decay: (number | undefined),
|
| + * pan: (number | undefined),
|
| + * reverb: (number | undefined)}} properties
|
| + * An object containing the properties that can be used to
|
| + * control the sound, as described above.
|
| + */
|
| +EarconEngine.prototype.generateSinusoidal = function(properties) {
|
| + var envelopeNode = this.context_.createGain();
|
| + envelopeNode.connect(this.context_.destination);
|
| +
|
| + var time = properties.time;
|
| + if (time === undefined) {
|
| + time = 0;
|
| + }
|
| +
|
| + // Generate an oscillator for the frequency corresponding to the specified
|
| + // frequency, and then additional overtones at multiples of that frequency
|
| + // scaled by the overtoneFactor. Cue the oscillator to start and stop
|
| + // based on the start time and specified duration.
|
| + //
|
| + // If an end frequency is specified, do an exponential ramp to that end
|
| + // frequency.
|
| + var gain = properties.gain;
|
| + for (var i = 0; i < properties.overtones; i++) {
|
| + var osc = this.context_.createOscillator();
|
| + osc.frequency.value = properties.freq * (i + 1);
|
| +
|
| + if (properties.endFreq) {
|
| + osc.frequency.setValueAtTime(
|
| + properties.freq * (i + 1),
|
| + this.context_.currentTime + time);
|
| + osc.frequency.exponentialRampToValueAtTime(
|
| + properties.endFreq * (i + 1),
|
| + this.context_.currentTime + properties.dur);
|
| + }
|
| +
|
| + osc.start(this.context_.currentTime + time);
|
| + osc.stop(this.context_.currentTime + time + properties.dur);
|
| +
|
| + var gainNode = this.context_.createGain();
|
| + gainNode.gain.value = gain;
|
| + osc.connect(gainNode);
|
| + gainNode.connect(envelopeNode);
|
| +
|
| + gain *= properties.overtoneFactor;
|
| + }
|
| +
|
| + // Shape the overall sound by an envelope based on the attack and
|
| + // decay times.
|
| + envelopeNode.gain.setValueAtTime(0, this.context_.currentTime + time);
|
| + envelopeNode.gain.linearRampToValueAtTime(
|
| + 1, this.context_.currentTime + time + properties.attack);
|
| + envelopeNode.gain.setValueAtTime(
|
| + 1, this.context_.currentTime + time +
|
| + properties.dur - properties.decay);
|
| + envelopeNode.gain.linearRampToValueAtTime(
|
| + 0, this.context_.currentTime + time + properties.dur);
|
| +
|
| + // Route everything through the common filters like reverb at the end.
|
| + var destination = this.createCommonFilters({});
|
| + envelopeNode.connect(destination);
|
| +};
|
| +
|
| +/**
|
| + * Play a sweep over a bunch of notes in a scale, with an echo,
|
| + * for the ChromeVox on or off sounds.
|
| + *
|
| + * @param {boolean} reverse Whether to play in the reverse direction.
|
| + */
|
| +EarconEngine.prototype.onChromeVoxSweep = function(reverse) {
|
| + var pitches = [-7, -5, 0, 5, 7, 12, 17, 19, 24];
|
| +
|
| + if (reverse) {
|
| + pitches.reverse();
|
| + }
|
| +
|
| + var attack = 0.015;
|
| + var dur = pitches.length * this.sweepDelay;
|
| +
|
| + var destination = this.createCommonFilters({reverb: 2.0});
|
| + for (var k = 0; k < this.sweepEchoCount; k++) {
|
| + var envelopeNode = this.context_.createGain();
|
| + var startTime = this.context_.currentTime + this.sweepEchoDelay * k;
|
| + var sweepGain = Math.pow(0.3, k);
|
| + var overtones = 2;
|
| + var overtoneGain = sweepGain;
|
| + for (var i = 0; i < overtones; i++) {
|
| + var osc = this.context_.createOscillator();
|
| + osc.start(startTime);
|
| + osc.stop(startTime + dur);
|
| +
|
| + var gainNode = this.context_.createGain();
|
| + osc.connect(gainNode);
|
| + gainNode.connect(envelopeNode);
|
| +
|
| + for (var j = 0; j < pitches.length; j++) {
|
| + var freqDecay;
|
| + if (reverse) {
|
| + freqDecay = Math.pow(0.75, pitches.length - j);
|
| + } else {
|
| + freqDecay = Math.pow(0.75, j);
|
| + }
|
| + var gain = overtoneGain * freqDecay;
|
| + var freq = (i + 1) * 220 *
|
| + Math.pow(EarconEngine.HALF_STEP, pitches[j] + this.sweepPitch);
|
| + if (j == 0) {
|
| + osc.frequency.setValueAtTime(freq, startTime);
|
| + gainNode.gain.setValueAtTime(gain, startTime);
|
| + } else {
|
| + osc.frequency.exponentialRampToValueAtTime(
|
| + freq, startTime + j * this.sweepDelay);
|
| + gainNode.gain.linearRampToValueAtTime(
|
| + gain, startTime + j * this.sweepDelay);
|
| + }
|
| + osc.frequency.setValueAtTime(
|
| + freq, startTime + j * this.sweepDelay + this.sweepDelay - attack);
|
| + }
|
| +
|
| + overtoneGain *= 0.1 + 0.2 * k;
|
| + }
|
| +
|
| + envelopeNode.gain.setValueAtTime(0, startTime);
|
| + envelopeNode.gain.linearRampToValueAtTime(1, startTime + this.sweepDelay);
|
| + envelopeNode.gain.setValueAtTime(1, startTime + dur - attack * 2);
|
| + envelopeNode.gain.linearRampToValueAtTime(0, startTime + dur);
|
| + envelopeNode.connect(destination);
|
| + }
|
| +};
|
| +
|
| +/**
|
| + * Play the "ChromeVox On" sound.
|
| + */
|
| +EarconEngine.prototype.onChromeVoxOn = function() {
|
| + this.onChromeVoxSweep(false);
|
| +};
|
| +
|
| +/**
|
| + * Play the "ChromeVox Off" sound.
|
| + */
|
| +EarconEngine.prototype.onChromeVoxOff = function() {
|
| + this.onChromeVoxSweep(true);
|
| +};
|
| +
|
| +/**
|
| + * Play an alert sound.
|
| + */
|
| +EarconEngine.prototype.onAlert = function() {
|
| + var freq1 = 220 * Math.pow(EarconEngine.HALF_STEP, this.alertPitch - 2);
|
| + var freq2 = 220 * Math.pow(EarconEngine.HALF_STEP, this.alertPitch - 3);
|
| + this.generateSinusoidal({attack: 0.02,
|
| + decay: 0.07,
|
| + dur: 0.15,
|
| + gain: 0.3,
|
| + freq: freq1,
|
| + overtones: 3,
|
| + overtoneFactor: 0.1});
|
| + this.generateSinusoidal({attack: 0.02,
|
| + decay: 0.07,
|
| + dur: 0.15,
|
| + gain: 0.3,
|
| + freq: freq2,
|
| + overtones: 3,
|
| + overtoneFactor: 0.1});
|
| +};
|
| +
|
| +/**
|
| + * Play a wrap sound.
|
| + */
|
| +EarconEngine.prototype.onWrap = function() {
|
| + this.play('static', {gain: this.clickVolume * 0.3});
|
| + var freq1 = 220 * Math.pow(EarconEngine.HALF_STEP, this.wrapPitch - 8);
|
| + var freq2 = 220 * Math.pow(EarconEngine.HALF_STEP, this.wrapPitch + 8);
|
| + this.generateSinusoidal({attack: 0.01,
|
| + decay: 0.1,
|
| + dur: 0.15,
|
| + gain: 0.3,
|
| + freq: freq1,
|
| + endFreq: freq2,
|
| + overtones: 1,
|
| + overtoneFactor: 0.1});
|
| +};
|
| +
|
| +/**
|
| + * Queue up a few tick/tock sounds for a progress bar. This is called
|
| + * repeatedly by setInterval to keep the sounds going continuously.
|
| + * @private
|
| + */
|
| +EarconEngine.prototype.generateProgressTickTocks_ = function() {
|
| + while (this.progressTime_ < this.context_.currentTime + 3.0) {
|
| + var t = this.progressTime_ - this.context_.currentTime;
|
| + this.progressSources_.push(
|
| + [this.progressTime_,
|
| + this.play('static',
|
| + {gain: 0.5 * this.progressGain_,
|
| + time: t})]);
|
| + this.progressSources_.push(
|
| + [this.progressTime_,
|
| + this.play(this.controlSound,
|
| + {pitch: 20,
|
| + time: t,
|
| + gain: this.progressGain_})]);
|
| +
|
| + if (this.progressGain_ > this.progressFinalGain) {
|
| + this.progressGain_ *= this.progressGain_Decay;
|
| + }
|
| + t += 0.5;
|
| +
|
| + this.progressSources_.push(
|
| + [this.progressTime_,
|
| + this.play('static',
|
| + {gain: 0.5 * this.progressGain_,
|
| + time: t})]);
|
| + this.progressSources_.push(
|
| + [this.progressTime_,
|
| + this.play(this.controlSound,
|
| + {pitch: 8,
|
| + time: t,
|
| + gain: this.progressGain_})]);
|
| +
|
| + if (this.progressGain_ > this.progressFinalGain) {
|
| + this.progressGain_ *= this.progressGain_Decay;
|
| + }
|
| +
|
| + this.progressTime_ += 1.0;
|
| + }
|
| +
|
| + var removeCount = 0;
|
| + while (removeCount < this.progressSources_.length &&
|
| + this.progressSources_[removeCount][0] < this.context_.currentTime - 0.2) {
|
| + removeCount++;
|
| + }
|
| + this.progressSources_.splice(0, removeCount);
|
| +};
|
| +
|
| +/**
|
| + * Start playing tick / tock progress sounds continuously until
|
| + * explicitly canceled.
|
| + */
|
| +EarconEngine.prototype.startProgress = function() {
|
| + this.progressSources_ = [];
|
| + this.progressGain_ = 0.5;
|
| + this.progressTime_ = this.context_.currentTime;
|
| + this.generateProgressTickTocks_();
|
| + this.progressIntervalID_ = window.setInterval(
|
| + this.generateProgressTickTocks_.bind(this), 1000);
|
| +};
|
| +
|
| +/**
|
| + * Stop playing any tick / tock progress sounds.
|
| + */
|
| +EarconEngine.prototype.cancelProgress = function() {
|
| + if (!this.progressIntervalID_) {
|
| + return;
|
| + }
|
| +
|
| + for (var i = 0; i < this.progressSources_.length; i++) {
|
| + this.progressSources_[i][1].stop();
|
| + }
|
| + this.progressSources_ = [];
|
| +
|
| + window.clearInterval(this.progressIntervalID_);
|
| + this.progressIntervalID_ = null;
|
| +};
|
|
|