Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(156)

Side by Side Diff: chrome/browser/resources/chromeos/chromevox/cvox2/background/earcon_engine.js

Issue 1273363004: Initial commit of new ChromeVox earcon engine. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Created 5 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright 2015 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 /**
6 * @fileoverview This is the low-level class that generates ChromeVox's
7 * earcons. It's designed to be self-contained and not depend on the
8 * rest of the code.
9 */
10
11 goog.provide('EarconEngine');
12
13 /**
14 * EarconEngine generates ChromeVox's earcons using the web audio API.
15 * @constructor
16 */
17 EarconEngine = function() {
18 // Public control parameters. All of these are meant to be adjustable.
19
20 /** @type {number} The master volume, as an amplification factor. */
21 this.masterVolume = 0.2;
22
23 /** @type {number} The base relative pitch adjustment, in half-steps. */
24 this.masterPitch = -4;
25
26 /** @type {number} The click volume, as an amplification factor. */
27 this.clickVolume = 0.4;
28
29 /** @type {number} The volume of the static sound, as an ampl factor. */
Peter Lundblad 2015/08/11 11:15:28 nit: be consistent ampl vs. amplification
dmazzoni 2015/09/14 18:06:37 Done.
30 this.staticVolume = 0.2;
31
32 /** @type {number} The base delay for repeated sounds, in ms. */
33 this.baseDelay = 45;
Peter Lundblad 2015/08/11 11:15:29 Since many other times are in whole seconds, shoul
dmazzoni 2015/09/14 18:06:37 Done.
34
35 /** @type {number} The master stereo panning, from -1 to 1. */
36 this.masterPan = 0;
37
38 /** @type {number} The master reverb level as an ampl factor. */
39 this.masterReverb = 0.4;
40
41 /** @type {string} The choice of the reverb impulse response to use. */
Peter Lundblad 2015/08/11 11:15:28 Say that this must we one of the items of EarconEn
dmazzoni 2015/09/14 18:06:37 Done.
42 this.reverbSound = 'small_room_2';
43
44 /** @type {number} The base pitch for the 'wrap' sound in half-steps. */
45 this.wrapPitch = 0;
46
47 /** @type {number} The base pitch for the 'alert' sound in half-steps. */
48 this.alertPitch = 0;
49
50 /** @type {string} The choice of base sound for most controls. */
51 this.controlSound = 'control';
52
53 /** @type {number} The delay between sounds in the on/off sweep effect. */
Peter Lundblad 2015/08/11 11:15:29 .. in ms ?
dmazzoni 2015/09/14 18:06:37 Done.
54 this.sweepDelay = 0.045;
55
56 /** @type {number} The delay between echos in the on/off sweep. */
Peter Lundblad 2015/08/11 11:15:29 In ms ?
dmazzoni 2015/09/14 18:06:37 Done.
57 this.sweepEchoDelay = 0.15;
58
59 /** @type {number} The number of echos in the on/off sweep. */
60 this.sweepEchoCount = 3;
61
62 /** @type {number} The pitch offset of the on/off sweep, in half-steps. */
63 this.sweepPitch = -7;
64
65 /** @type {number} The final gain of the progress sound, as an ampl factor. */
66 this.progressFinalGain = 0.05;
67
68 /** @type {number} The multiplicative decay rate of the progress ticks. */
69 this.progressGain_Decay = 0.7;
70
71 // Private variables.
72
73 /** @type {AudioContext} @private */
74 this.context_ = new AudioContext();
75
76 /**
77 * @type {Object<string, AudioBuffer>} A map between the name of an
78 * audio data file and its loaded AudioBuffer.
79 * @private
80 */
81 this.buffers_ = {};
82
83 /**
84 * The source audio nodes for queued tick / tocks for progress.
85 * Kept around so they can be canceled.
86 *
87 * @type {?Array<AudioNode>}
88 * @private
89 */
90 this.progressSources_ = null;
Peter Lundblad 2015/08/11 11:15:28 Do you need the distinction between null and empty
dmazzoni 2015/09/14 18:06:37 Done.
91
92 /** @type {number} The current gain for progress sounds. @private */
93 this.progressGain_ = 1.0;
94
95 /** @type {?number} The current time for progress sounds. @private */
96 this.progressT_ = null;
Peter Lundblad 2015/08/11 11:15:28 Is it very beneficial to use T instead of Time her
dmazzoni 2015/09/14 18:06:37 Done.
97
98 /** @type {?number} The window.setInterval ID for progress sounds.
Peter Lundblad 2015/08/11 11:15:29 nit: start and end comment tokens on their own lin
dmazzoni 2015/09/14 18:06:37 Done.
99 * @private */
100 this.progressIntervalID_ = null;
101
102 // Initialization: load the base sound data files asynchronously.
103 EarconEngine.SOUNDS.forEach((function(sound) {
Peter Lundblad 2015/08/11 11:15:28 Could save some repetitive code by concatenating t
dmazzoni 2015/09/14 18:06:37 Done.
104 var url = EarconEngine.BASE_URL + sound + '.wav';
105 this.loadSound(sound, url);
106 }).bind(this));
107 EarconEngine.REVERBS.forEach((function(reverb) {
108 var url = EarconEngine.BASE_URL + reverb + '.wav';
109 this.loadSound(reverb, url);
110 }).bind(this));
111 };
112
113 /**
114 * @type {Array<string>} The list of sound data files to load.
Peter Lundblad 2015/08/11 11:15:29 @const
dmazzoni 2015/09/14 18:06:37 Done.
115 */
116 EarconEngine.SOUNDS = [
117 'control',
118 'selection',
119 'selection_reverse',
120 'skim',
121 'static'];
122
123 /**
124 * @type {Array<string>} The list of reverb data files to load.
Peter Lundblad 2015/08/11 11:15:28 @const
dmazzoni 2015/09/14 18:06:37 Done.
125 */
126 EarconEngine.REVERBS = [
127 'small_room_2'];
128
129 /**
130 * @type {number} The scale factor for one half-step.
Peter Lundblad 2015/08/11 11:15:28 @const
dmazzoni 2015/09/14 18:06:37 Done.
131 */
132 EarconEngine.HALF_STEP = Math.pow(2.0, 1.0 / 12.0);
133
134 /**
135 * @type {string} The base url for earcon sound resources.
Peter Lundblad 2015/08/11 11:15:28 @cosnt
dmazzoni 2015/09/14 18:06:37 Done.
136 */
137 EarconEngine.BASE_URL = chrome.extension.getURL('cvox2/background/earcons/');
138
139 /**
140 * Fetches a sound asynchronously and loads its data into an AudioBuffer.
141 *
142 * @param {string} sound The name of the sound to load.
Peter Lundblad 2015/08/11 11:15:29 nit: call name?
dmazzoni 2015/09/14 18:06:37 Done.
143 * @param {string} url The url where the sound should be fetched from.
144 */
145 EarconEngine.prototype.loadSound = function(sound, url) {
146 var request = new XMLHttpRequest();
147 request.open('GET', url, true);
148 request.responseType = 'arraybuffer';
149
150 // Decode asynchronously.
151 request.onload = (function() {
152 this.context_.decodeAudioData(
153 /** @type {ArrayBuffer} */ (request.response),
154 (function(buffer) {
155 this.buffers_[sound] = buffer;
156 }).bind(this));
157 }).bind(this);
158 request.send();
159 };
160
161 /**
162 * Return an AudioNode containing the final processing that all
163 * sounds go through: master volume / gain, panning, and reverb.
164 * The chain is hooked up to the destination automatically, so you
165 * just need to connect your source to the return value from this
166 * method.
167 *
168 * @param {{gain: (number | undefined),
169 * pan: (number | undefined),
170 * reverb: (number | undefined)}} properties
171 * An object where you can override the default
172 * gain, pan, and reverb, otherwise these are taken from
173 * masterVolume, masterPan, and masterReverb.
174 * @return {AudioNode} The filters to be applied to all sounds, connected
175 * to the destination node.
176 */
177 EarconEngine.prototype.createCommonFilters = function(properties) {
178 var gain = this.masterVolume;
179 if (properties.gain) {
180 gain *= properties.gain;
181 }
182 var gainNode = this.context_.createGain();
183 gainNode.gain.value = gain;
184 var first = gainNode;
185 var last = gainNode;
186
187 var pan = this.masterPan;
188 if (properties.pan) {
Peter Lundblad 2015/08/11 11:15:29 Compare to undefined here. Else, a caller can't se
dmazzoni 2015/09/14 18:06:37 Good catch.
189 pan = properties.pan;
190 }
191 if (pan != 0) {
192 var panNode = this.context_.createPanner();
193 panNode.setPosition(pan, 0, -1);
194 panNode.setOrientation(0, 0, 1);
195 last.connect(panNode);
196 last = panNode;
197 }
198
199 var reverb = this.masterReverb;
200 if (properties.reverb)
201 reverb = properties.reverb;
Peter Lundblad 2015/08/11 11:15:28 Indentation and please be consistent with one-line
dmazzoni 2015/09/14 18:06:37 Done.
202 if (reverb) {
203 var filter = this.context_.createConvolver();
204 filter.buffer = this.buffers_[this.reverbSound];
205
206 // Dry
207 last.connect(this.context_.destination);
208
209 // Wet
210 var reverbGainNode = this.context_.createGain();
211 reverbGainNode.gain.value = reverb;
212 last.connect(reverbGainNode);
213 reverbGainNode.connect(filter);
214 filter.connect(this.context_.destination);
215 } else {
216 last.connect(this.context_.destination);
217 }
218
219 return first;
220 };
221
222 /**
223 * High-level interface to play a sound from a buffer source by name,
224 * with some simple adjustments like pitch change (in half-steps),
225 * a start time (relative to the current time, in seconds),
226 * gain, panning, and reverb.
227 *
228 * The only required parameter is the name of the sound. The time, pitch,
229 * gain, panning, and reverb are all optional and are passed in an
230 * object of optional properties.
231 *
232 * @param {string} sound The name of the sound to play. It must already
233 * be loaded in a buffer.
234 * @param {{pitch: (number | undefined),
235 * time: (number | undefined),
236 * gain: (number | undefined),
237 * pan: (number | undefined),
238 * reverb: (number | undefined)}=} opt_properties
239 * An object where you can override
240 * the default pitch, gain, pan, and reverb.
Peter Lundblad 2015/08/11 11:15:29 nit: odd line break above. (short line)
dmazzoni 2015/09/14 18:06:37 Done.
241 * @return {AudioBufferSourceNode} The source node, so you can stop it
242 * or set event handlers on it.
243 */
244 EarconEngine.prototype.play = function(sound, opt_properties) {
245 var source = this.context_.createBufferSource();
246 source.buffer = this.buffers_[sound];
247
248 if (!opt_properties) {
249 // This typecast looks silly, but the Closure compiler doesn't support
250 // optional fields in record types very well so this is the shortest hack.
251 opt_properties = /** @type {undefined} */({});
252 }
253
254 var pitch = this.masterPitch;
255 if (opt_properties.pitch) {
256 pitch += opt_properties.pitch;
257 }
258 if (pitch != 0) {
259 source.playbackRate.value = Math.pow(EarconEngine.HALF_STEP, pitch);
260 }
261
262 var destination = this.createCommonFilters(opt_properties);
263 source.connect(destination);
264
265 if (opt_properties.time) {
266 source.start(this.context_.currentTime + opt_properties.time);
267 } else {
268 source.start(this.context_.currentTime);
269 }
270
271 return source;
272 };
273
274 /**
275 * Play the static sound.
276 */
277 EarconEngine.prototype.onStatic = function() {
278 this.play('static', {gain: this.staticVolume});
279 };
280
281 /**
282 * Play the link sound.
283 */
284 EarconEngine.prototype.onLink = function() {
285 this.play('static', {gain: this.clickVolume});
286 this.play(this.controlSound, {pitch: 12});
287 };
288
289 /**
290 * Play the button sound.
291 */
292 EarconEngine.prototype.onButton = function() {
293 this.play('static', {gain: this.clickVolume});
294 this.play(this.controlSound);
295 };
296
297 /**
298 * Play the text field sound.
299 */
300 EarconEngine.prototype.onTextField = function() {
301 this.play('static', {gain: this.clickVolume});
302 this.play('static', {time: this.baseDelay * 0.0015,
303 gain: this.clickVolume * 0.5});
304 this.play(this.controlSound, {pitch: 4});
305 this.play(this.controlSound,
306 {pitch: 4,
307 time: this.baseDelay * 0.0015,
308 gain: 0.5});
309 };
310
311 /**
312 * Play the pop up button sound.
313 */
314 EarconEngine.prototype.onPopUpButton = function() {
315 this.play('static', {gain: this.clickVolume});
316
317 this.play(this.controlSound);
318 this.play(this.controlSound,
319 {time: this.baseDelay * 0.003,
320 gain: 0.2,
321 pitch: 12});
322 this.play(this.controlSound,
323 {time: this.baseDelay * 0.0045,
324 gain: 0.2,
325 pitch: 12});
326 };
327
328 /**
329 * Play the check on sound.
330 */
331 EarconEngine.prototype.onCheckOn = function() {
332 this.play('static', {gain: this.clickVolume});
333 this.play(this.controlSound, {pitch: -5});
334 this.play(this.controlSound, {pitch: 7, time: this.baseDelay * 0.002});
335 };
336
337 /**
338 * Play the check off sound.
339 */
340 EarconEngine.prototype.onCheckOff = function() {
341 this.play('static', {gain: this.clickVolume});
342 this.play(this.controlSound, {pitch: 7});
343 this.play(this.controlSound, {pitch: -5, time: this.baseDelay * 0.002});
344 };
345
346 /**
347 * Play the select control sound.
348 */
349 EarconEngine.prototype.onSelect = function() {
350 this.play('static', {gain: this.clickVolume});
351 this.play(this.controlSound);
352 this.play(this.controlSound, {time: this.baseDelay * 0.001});
353 this.play(this.controlSound, {time: this.baseDelay * 0.002});
354 };
355
356 /**
357 * Play the slider sound.
358 */
359 EarconEngine.prototype.onSlider = function() {
360 this.play('static', {gain: this.clickVolume});
361 this.play(this.controlSound);
362 this.play(this.controlSound,
363 {time: this.baseDelay * 0.001,
364 gain: 0.5,
365 pitch: 2});
366 this.play(this.controlSound,
367 {time: this.baseDelay * 0.002,
368 gain: 0.25,
369 pitch: 4});
370 this.play(this.controlSound,
371 {time: this.baseDelay * 0.003,
372 gain: 0.125,
373 pitch: 6});
374 this.play(this.controlSound,
375 {time: this.baseDelay * 0.004,
376 gain: 0.0625,
377 pitch: 8});
378 };
379
380 /**
381 * Play the skim sound.
382 */
383 EarconEngine.prototype.onSkim = function() {
384 this.play('skim');
385 };
386
387 /**
388 * Play the selection sound.
389 */
390 EarconEngine.prototype.onSelection = function() {
391 this.play('selection');
392 };
393
394 /**
395 * Play the selection reverse sound.
396 */
397 EarconEngine.prototype.onSelectionReverse = function() {
398 this.play('selection_reverse');
399 };
400
401 /**
402 * Generate a synthesized musical note based on a sum of sinusoidals shaped
403 * by an envelope, controlled by a number of properties.
404 *
405 * The sound has a frequency of |freq|, or if |endFreq| is specified, does
406 * an exponential ramp from |freq| to |endFreq|.
407 *
408 * If |overtones| is greater than 1, the sound will be mixed with additional
409 * sinusoidals at multiples of |freq|, each one scaled by |overtoneFactor|.
410 * This creates a rounder tone than a pure sine wave.
411 *
412 * The envelope is shaped by the duration |dur|, the attack time |attack|,
413 * and the decay time |decay|, in seconds.
414 *
415 * As with other functions, |pan| and |reverb| can be used to override
416 * masterPan and masterReverb.
417 *
418 * @param {{gain: number,
419 * freq: number,
420 * endFreq: (number | undefined),
421 * time: (number | undefined),
422 * overtones: (number | undefined),
423 * overtoneFactor: (number | undefined),
424 * dur: (number | undefined),
425 * attack: (number | undefined),
426 * decay: (number | undefined),
427 * pan: (number | undefined),
428 * reverb: (number | undefined)}} properties
429 * An object containing the properties that can be used to
430 * control the sound, as described above.
431 */
432 EarconEngine.prototype.generateSinusoidal = function(properties) {
433 var envelopeNode = this.context_.createGain();
434 envelopeNode.connect(this.context_.destination);
435
436 if (!properties.time) {
Peter Lundblad 2015/08/11 11:15:28 Can you avoid modifying the passed in properties?
dmazzoni 2015/09/14 18:06:37 Done.
437 properties.time = 0;
438 }
439
440 // Generate an oscillator for the frequency corresponding to the specified
441 // frequency, and then additional overtones at multiples of that frequency
442 // scaled by the overtoneFactor. Cue the oscillator to start and stop
443 // based on the start time and specified duration.
444 //
445 // If an end frequency is specified, do an exponential ramp to that end
446 // frequency.
447 var gain = properties.gain;
448 for (var i = 0; i < properties.overtones; i++) {
449 var osc = this.context_.createOscillator();
450 osc.frequency.value = properties.freq * (i + 1);
451
452 if (properties.endFreq) {
453 osc.frequency.setValueAtTime(
454 properties.freq * (i + 1),
455 this.context_.currentTime + properties.time);
456 osc.frequency.exponentialRampToValueAtTime(
457 properties.endFreq * (i + 1),
458 this.context_.currentTime + properties.dur);
459 }
460
461 osc.start(this.context_.currentTime + properties.time);
462 osc.stop(this.context_.currentTime + properties.time + properties.dur);
463
464 var gainNode = this.context_.createGain();
465 gainNode.gain.value = gain;
466 osc.connect(gainNode);
467 gainNode.connect(envelopeNode);
468
469 gain *= properties.overtoneFactor;
470 }
471
472 // Shape the overall sound by an envelope based on the attack and
473 // decay times.
474 envelopeNode.gain.setValueAtTime(
475 0, this.context_.currentTime + properties.time);
476 envelopeNode.gain.linearRampToValueAtTime(
477 1, this.context_.currentTime + properties.time + properties.attack);
478 envelopeNode.gain.setValueAtTime(
479 1, this.context_.currentTime + properties.time +
480 properties.dur - properties.decay);
481 envelopeNode.gain.linearRampToValueAtTime(
482 0, this.context_.currentTime + properties.time + properties.dur);
483
484 // Route everything through the common filters like reverb at the end.
485 var destination = this.createCommonFilters({});
486 envelopeNode.connect(destination);
487 };
488
489 /**
490 * Play a sweep over a bunch of notes in a scale, with an echo,
491 * for the ChromeVox on or off sounds.
492 *
493 * @param {boolean} reverse Whether to play in the reverse direction.
494 */
495 EarconEngine.prototype.onChromeVoxSweep = function(reverse) {
496 var pitches = [-7, -5, 0, 5, 7, 12, 17, 19, 24];
497
498 if (reverse) {
499 pitches.reverse();
500 }
501
502 var attack = 0.015;
503 var dur = pitches.length * this.sweepDelay;
504
505 var destination = this.createCommonFilters({reverb: 2.0});
506 for (var k = 0; k < this.sweepEchoCount; k++) {
507 var envelopeNode = this.context_.createGain();
508 var startTime = this.context_.currentTime + this.sweepEchoDelay * k;
509 var sweepGain = Math.pow(0.3, k);
510 var overtones = 2;
511 var overtoneGain = sweepGain;
512 for (var i = 0; i < overtones; i++) {
513 var osc = this.context_.createOscillator();
514 osc.start(startTime);
515 osc.stop(startTime + dur);
516
517 var gainNode = this.context_.createGain();
518 osc.connect(gainNode);
519 gainNode.connect(envelopeNode);
520
521 for (var j = 0; j < pitches.length; j++) {
522 var freqDecay;
523 if (reverse)
524 freqDecay = Math.pow(0.75, pitches.length - j);
Peter Lundblad 2015/08/11 11:15:29 Indentation.
dmazzoni 2015/09/14 18:06:37 Done.
525 else
526 freqDecay = Math.pow(0.75, j);
527 var gain = overtoneGain * freqDecay;
528 var freq = (i + 1) * 220 *
529 Math.pow(EarconEngine.HALF_STEP, pitches[j] + this.sweepPitch);
530 if (j == 0) {
531 osc.frequency.setValueAtTime(freq, startTime + j * this.sweepDelay);
532 gainNode.gain.setValueAtTime(gain, startTime + j * this.sweepDelay);
Peter Lundblad 2015/08/11 11:15:28 Seems like the second arguments on these two lines
dmazzoni 2015/09/14 18:06:37 Done.
533 } else {
534 osc.frequency.exponentialRampToValueAtTime(
535 freq, startTime + j * this.sweepDelay);
536 gainNode.gain.linearRampToValueAtTime(
537 gain, startTime + j * this.sweepDelay);
538 }
539 osc.frequency.setValueAtTime(
540 freq, startTime + j * this.sweepDelay + this.sweepDelay - attack);
541 }
542
543 overtoneGain *= 0.1 + 0.2 * k;
544 }
545
546 envelopeNode.gain.setValueAtTime(0, startTime);
547 envelopeNode.gain.linearRampToValueAtTime(1, startTime + this.sweepDelay);
548 envelopeNode.gain.setValueAtTime(1, startTime + dur - attack * 2);
549 envelopeNode.gain.linearRampToValueAtTime(0, startTime + dur);
550 envelopeNode.connect(destination);
551 }
552 };
553
554 /**
555 * Play the "ChromeVox On" sound.
556 */
557 EarconEngine.prototype.onChromeVoxOn = function() {
558 this.onChromeVoxSweep(false);
559 };
560
561 /**
562 * Play the "ChromeVox Off" sound.
563 */
564 EarconEngine.prototype.onChromeVoxOff = function() {
565 this.onChromeVoxSweep(true);
566 };
567
568 /**
569 * Play an alert sound.
570 */
571 EarconEngine.prototype.onAlert = function() {
572 var freq1 = 220 * Math.pow(EarconEngine.HALF_STEP, this.alertPitch - 2);
573 var freq2 = 220 * Math.pow(EarconEngine.HALF_STEP, this.alertPitch - 3);
574 this.generateSinusoidal({attack: 0.02,
575 decay: 0.07,
576 dur: 0.15,
577 gain: 0.3,
578 freq: freq1,
579 overtones: 3,
580 overtoneFactor: 0.1});
581 this.generateSinusoidal({attack: 0.02,
582 decay: 0.07,
583 dur: 0.15,
584 gain: 0.3,
585 freq: freq2,
586 overtones: 3,
587 overtoneFactor: 0.1});
588 };
589
590 /**
591 * Play a wrap sound.
592 */
593 EarconEngine.prototype.onWrap = function() {
594 this.play('static', {gain: this.clickVolume * 0.3});
595 var freq1 = 220 * Math.pow(EarconEngine.HALF_STEP, this.wrapPitch - 8);
596 var freq2 = 220 * Math.pow(EarconEngine.HALF_STEP, this.wrapPitch + 8);
597 this.generateSinusoidal({attack: 0.01,
598 decay: 0.1,
599 dur: 0.15,
600 gain: 0.3,
601 freq: freq1,
602 endFreq: freq2,
603 overtones: 1,
604 overtoneFactor: 0.1});
605 };
606
607 /**
608 * Queue up a few tick/tock sounds for a progress bar. This is called
609 * repeatedly by setInterval to keep the sounds going continuously.
610 * @private
611 */
612 EarconEngine.prototype.generateProgressTickTocks_ = function() {
613 while (this.progressT_ < this.context_.currentTime + 3.0) {
614 var t = this.progressT_ - this.context_.currentTime;
615 this.progressSources_.push(
616 [this.progressT_,
617 this.play('static',
618 {gain: 0.5 * this.progressGain_,
619 time: t})]);
620 this.progressSources_.push(
621 [this.progressT_,
622 this.play(this.controlSound,
623 {pitch: 20,
624 time: t,
625 gain: this.progressGain_})]);
626
627 if (this.progressGain_ > this.progressFinalGain) {
628 this.progressGain_ *= this.progressGain_Decay;
629 }
630 t += 0.5;
631
632 this.progressSources_.push(
633 [this.progressT_,
634 this.play('static',
635 {gain: 0.5 * this.progressGain_,
636 time: t})]);
637 this.progressSources_.push(
638 [this.progressT_,
639 this.play(this.controlSound,
640 {pitch: 8,
641 time: t,
642 gain: this.progressGain_})]);
643
644 if (this.progressGain_ > this.progressFinalGain) {
645 this.progressGain_ *= this.progressGain_Decay;
646 }
647
648 this.progressT_ += 1.0;
649 }
650
651 var removeCount = 0;
652 while (removeCount < this.progressSources_.length &&
653 this.progressSources_[removeCount][0] < this.context_.currentTime - 0.2) {
654 removeCount++;
655 }
656 this.progressSources_.splice(0, removeCount);
657 };
658
659 /**
660 * Start playing tick / tock progress sounds continuously until
661 * explicitly canceled.
662 */
663 EarconEngine.prototype.startProgress = function() {
664 this.progressSources_ = [];
665 this.progressGain_ = 0.5;
666 this.progressT_ = this.context_.currentTime;
667 this.generateProgressTickTocks_();
668 this.progressIntervalID_ = window.setInterval(
669 this.generateProgressTickTocks_.bind(this), 1000);
670 };
671
672 /**
673 * Stop playing any tick / tock progress sounds.
674 */
675 EarconEngine.prototype.cancelProgress = function() {
676 if (!this.progressIntervalID_) {
677 return;
678 }
679
680 for (var i = 0; i < this.progressSources_.length; i++) {
681 this.progressSources_[i][1].stop();
682 }
683 this.progressSources_ = [];
684
685 window.clearInterval(this.progressIntervalID_);
686 this.progressIntervalID_ = null;
687 };
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698