1 // Copyright 2014 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.
6 * @fileoverview Base class for Text-to-Speech engines that actually transform
11 goog
.provide('cvox.AbstractTts');
13 goog
.require('cvox.TtsInterface');
14 goog
.require('goog.i18n.MessageFormat');
17 * Creates a new instance.
19 * @implements {cvox.TtsInterface}
21 cvox
.AbstractTts = function() {
22 this.ttsProperties
= new Object();
25 * Default value for TTS properties.
26 * Note that these as well as the subsequent properties might be different
27 * on different host platforms (like Chrome, Android, etc.).
28 * @type {{pitch : number,
33 this.propertyDefault
= {
40 * Min value for TTS properties.
41 * @type {{pitch : number,
53 * Max value for TTS properties.
54 * @type {{pitch : number,
66 * Step value for TTS properties.
67 * @type {{pitch : number,
81 if (cvox
.AbstractTts
.pronunciationDictionaryRegexp_
== undefined) {
82 // Create an expression that matches all words in the pronunciation
83 // dictionary on word boundaries, ignoring case.
85 for (var word
in cvox
.AbstractTts
.PRONUNCIATION_DICTIONARY
) {
88 var expr
= '\\b(' + words
.join('|') + ')\\b';
89 cvox
.AbstractTts
.pronunciationDictionaryRegexp_
= new RegExp(expr
, 'ig');
92 if (cvox
.AbstractTts
.substitutionDictionaryRegexp_
== undefined) {
93 // Create an expression that matches all words in the substitution
96 for (var symbol
in cvox
.AbstractTts
.SUBSTITUTION_DICTIONARY
) {
99 var expr
= '(' + symbols
.join('|') + ')';
100 cvox
.AbstractTts
.substitutionDictionaryRegexp_
= new RegExp(expr
, 'ig');
106 * Default TTS properties for this TTS engine.
110 cvox
.AbstractTts
.prototype.ttsProperties
;
114 cvox
.AbstractTts
.prototype.speak = function(textString
, queueMode
, properties
) {
120 cvox
.AbstractTts
.prototype.isSpeaking = function() {
126 cvox
.AbstractTts
.prototype.stop = function() {
131 cvox
.AbstractTts
.prototype.addCapturingEventListener = function(listener
) { };
135 cvox
.AbstractTts
.prototype.increaseOrDecreaseProperty
=
136 function(propertyName
, increase
) {
137 var min
= this.propertyMin
[propertyName
];
138 var max
= this.propertyMax
[propertyName
];
139 var step
= this.propertyStep
[propertyName
];
140 var current
= this.ttsProperties
[propertyName
];
141 current
= increase
? current
+ step
: current
- step
;
142 this.ttsProperties
[propertyName
] = Math
.max(Math
.min(current
, max
), min
);
147 * Merges the given properties with the default ones. Always returns a
148 * new object, so that you can safely modify the result of mergeProperties
149 * without worrying that you're modifying an object used elsewhere.
150 * @param {Object=} properties The properties to merge with the current ones.
151 * @return {Object} The merged properties.
154 cvox
.AbstractTts
.prototype.mergeProperties = function(properties
) {
155 var mergedProperties
= new Object();
157 if (this.ttsProperties
) {
158 for (p
in this.ttsProperties
) {
159 mergedProperties
[p
] = this.ttsProperties
[p
];
163 var tts
= cvox
.AbstractTts
;
164 if (typeof(properties
[tts
.VOLUME
]) == 'number') {
165 mergedProperties
[tts
.VOLUME
] = properties
[tts
.VOLUME
];
167 if (typeof(properties
[tts
.PITCH
]) == 'number') {
168 mergedProperties
[tts
.PITCH
] = properties
[tts
.PITCH
];
170 if (typeof(properties
[tts
.RATE
]) == 'number') {
171 mergedProperties
[tts
.RATE
] = properties
[tts
.RATE
];
173 if (typeof(properties
[tts
.LANG
]) == 'string') {
174 mergedProperties
[tts
.LANG
] = properties
[tts
.LANG
];
178 var mergeRelativeProperty = function(abs
, rel
) {
179 if (typeof(properties
[rel
]) == 'number' &&
180 typeof(mergedProperties
[abs
]) == 'number') {
181 mergedProperties
[abs
] += properties
[rel
];
182 var min
= context
.propertyMin
[abs
];
183 var max
= context
.propertyMax
[abs
];
184 if (mergedProperties
[abs
] > max
) {
185 mergedProperties
[abs
] = max
;
186 } else if (mergedProperties
[abs
] < min
) {
187 mergedProperties
[abs
] = min
;
192 mergeRelativeProperty(tts
.VOLUME
, tts
.RELATIVE_VOLUME
);
193 mergeRelativeProperty(tts
.PITCH
, tts
.RELATIVE_PITCH
);
194 mergeRelativeProperty(tts
.RATE
, tts
.RELATIVE_RATE
);
197 for (p
in properties
) {
198 if (!mergedProperties
.hasOwnProperty(p
)) {
199 mergedProperties
[p
] = properties
[p
];
203 return mergedProperties
;
208 * Method to preprocess text to be spoken properly by a speech
211 * 1. Replace any single character with a description of that character.
213 * 2. Convert all-caps words to lowercase if they don't look like an
214 * acronym / abbreviation.
216 * @param {string} text A text string to be spoken.
217 * @param {Object= } properties Out parameter populated with how to speak the
219 * @return {string} The text formatted in a way that will sound better by
220 * most speech engines.
223 cvox
.AbstractTts
.prototype.preprocess = function(text
, properties
) {
224 if (text
.length
== 1 && text
>= 'A' && text
<= 'Z') {
225 for (var prop
in cvox
.AbstractTts
.PERSONALITY_CAPITAL
)
226 properties
[prop
] = cvox
.AbstractTts
.PERSONALITY_CAPITAL
[prop
];
229 // Substitute all symbols in the substitution dictionary. This is pretty
230 // efficient because we use a single regexp that matches all symbols
233 cvox
.AbstractTts
.substitutionDictionaryRegexp_
,
235 return ' ' + cvox
.AbstractTts
.SUBSTITUTION_DICTIONARY
[symbol
] + ' ';
238 // Handle single characters that we want to make sure we pronounce.
239 if (text
.length
== 1) {
240 return cvox
.AbstractTts
.CHARACTER_DICTIONARY
[text
] ?
241 (new goog
.i18n
.MessageFormat(cvox
.ChromeVox
.msgs
.getMsg(
242 cvox
.AbstractTts
.CHARACTER_DICTIONARY
[text
])))
243 .format({'COUNT': 1}) :
247 // Substitute all words in the pronunciation dictionary. This is pretty
248 // efficient because we use a single regexp that matches all words
249 // simultaneously, and it calls a function with each match, which we can
250 // use to look up the replacement in our dictionary.
252 cvox
.AbstractTts
.pronunciationDictionaryRegexp_
,
254 return cvox
.AbstractTts
.PRONUNCIATION_DICTIONARY
[word
.toLowerCase()];
257 // Special case for google+, where the punctuation must be pronounced.
258 text
= text
.replace(/google\+/ig, 'google plus');
260 // Expand all repeated characters.
262 cvox
.AbstractTts
.repetitionRegexp_
, cvox
.AbstractTts
.repetitionReplace_
);
264 // If there's no lower case letters, and at least two spaces, skip spacing
266 var skipSpacing
= false;
267 if (!text
.match(/[a-z]+/) && text
.indexOf(' ') != text
.lastIndexOf(' ')) {
271 // Convert all-caps words to lowercase if they don't look like acronyms,
272 // otherwise add a space before all-caps words so that all-caps words in
273 // the middle of camelCase will be separated.
274 text
= text
.replace(/[A-Z]+/g, function(word
) {
275 // If a word contains vowels and is more than 3 letters long, it is
276 // probably a real word and not just an abbreviation. Convert it to lower
277 // case and speak it normally.
278 if ((word
.length
> 3) && word
.match(/([AEIOUY])/g)) {
279 return word
.toLowerCase();
280 } else if (!skipSpacing
) {
281 // Builds spaced-out camelCased/all CAPS words so they sound better when
282 // spoken by TTS engines.
283 return ' ' + word
.split('').join(' ');
293 /** TTS rate property. @type {string} */
294 cvox
.AbstractTts
.RATE
= 'rate';
295 /** TTS pitch property. @type {string} */
296 cvox
.AbstractTts
.PITCH
= 'pitch';
297 /** TTS volume property. @type {string} */
298 cvox
.AbstractTts
.VOLUME
= 'volume';
299 /** TTS language property. @type {string} */
300 cvox
.AbstractTts
.LANG
= 'lang';
302 /** TTS relative rate property. @type {string} */
303 cvox
.AbstractTts
.RELATIVE_RATE
= 'relativeRate';
304 /** TTS relative pitch property. @type {string} */
305 cvox
.AbstractTts
.RELATIVE_PITCH
= 'relativePitch';
306 /** TTS relative volume property. @type {string} */
307 cvox
.AbstractTts
.RELATIVE_VOLUME
= 'relativeVolume';
309 /** TTS color property (for the lens display). @type {string} */
310 cvox
.AbstractTts
.COLOR
= 'color';
311 /** TTS CSS font-weight property (for the lens display). @type {string} */
312 cvox
.AbstractTts
.FONT_WEIGHT
= 'fontWeight';
314 /** TTS punctuation-echo property. @type {string} */
315 cvox
.AbstractTts
.PUNCTUATION_ECHO
= 'punctuationEcho';
317 /** TTS pause property. @type {string} */
318 cvox
.AbstractTts
.PAUSE
= 'pause';
321 * TTS personality for annotations - text spoken by ChromeVox that
322 * elaborates on a user interface element but isn't displayed on-screen.
325 cvox
.AbstractTts
.PERSONALITY_ANNOTATION
= {
326 'relativePitch': -0.25,
327 // TODO:(rshearer) Added this color change for I/O presentation.
329 'punctuationEcho': 'none'
334 * TTS personality for announcements - text spoken by ChromeVox that
335 * isn't tied to any user interface elements.
338 cvox
.AbstractTts
.PERSONALITY_ANNOUNCEMENT
= {
339 'punctuationEcho': 'none'
343 * TTS personality for alerts from the system, such as battery level
347 cvox
.AbstractTts
.PERSONALITY_SYSTEM_ALERT
= {
348 'punctuationEcho': 'none',
349 'doNotInterrupt': true
353 * TTS personality for an aside - text in parentheses.
356 cvox
.AbstractTts
.PERSONALITY_ASIDE
= {
357 'relativePitch': -0.1,
363 * TTS personality for capital letters.
366 cvox
.AbstractTts
.PERSONALITY_CAPITAL
= {
372 * TTS personality for deleted text.
375 cvox
.AbstractTts
.PERSONALITY_DELETED
= {
376 'punctuationEcho': 'none',
377 'relativePitch': -0.6
382 * TTS personality for quoted text.
385 cvox
.AbstractTts
.PERSONALITY_QUOTE
= {
386 'relativePitch': 0.1,
393 * TTS personality for strong or bold text.
396 cvox
.AbstractTts
.PERSONALITY_STRONG
= {
397 'relativePitch': 0.1,
404 * TTS personality for emphasis or italicized text.
407 cvox
.AbstractTts
.PERSONALITY_EMPHASIS
= {
408 'relativeVolume': 0.1,
409 'relativeRate': -0.1,
416 * Flag indicating if the TTS is being debugged.
419 cvox
.AbstractTts
.DEBUG
= true;
423 * Character dictionary. These symbols are replaced with their human readable
424 * equivalents. This replacement only occurs for single character utterances.
425 * @type {Object<string>}
427 cvox
.AbstractTts
.CHARACTER_DICTIONARY
= {
446 ']': 'right_bracket',
457 '?': 'question_mark',
468 * Pronunciation dictionary. Each key must be lowercase, its replacement
469 * should be spelled out the way most TTS engines will pronounce it
470 * correctly. This particular dictionary only handles letters and numbers,
472 * @type {Object<string>}
474 cvox
.AbstractTts
.PRONUNCIATION_DICTIONARY
= {
476 'adsense': 'ad-sense',
477 'adwords': 'ad-words',
478 'angularjs': 'angular j s',
481 'chromevox': 'chrome vox',
484 'doubleclick': 'double-click',
488 'https' : 'H T T P S',
489 'igoogle': 'eye google',
490 'pagerank': 'page-rank',
491 'username': 'user-name',
493 'youtube': 'you tube'
498 * Pronunciation dictionary regexp.
502 cvox
.AbstractTts
.pronunciationDictionaryRegexp_
;
506 * Substitution dictionary. These symbols or patterns are ALWAYS substituted
507 * whenever they occur, so this should be reserved only for unicode characters
508 * and characters that never have any different meaning in context.
510 * For example, do not include '$' here because $2 should be read as
512 * @type {Object<string>}
514 cvox
.AbstractTts
.SUBSTITUTION_DICTIONARY
= {
515 '://': 'colon slash slash',
516 '\u00bc': 'one fourth',
517 '\u00bd': 'one half',
518 '\u2190': 'left arrow',
519 '\u2191': 'up arrow',
520 '\u2192': 'right arrow',
521 '\u2193': 'down arrow',
522 '\u21d0': 'left double arrow',
523 '\u21d1': 'up double arrow',
524 '\u21d2': 'right double arrow',
525 '\u21d3': 'down double arrow',
526 '\u21e6': 'left arrow',
527 '\u21e7': 'up arrow',
528 '\u21e8': 'right arrow',
529 '\u21e9': 'down arrow',
533 '\u25b2': 'up triangle',
534 '\u25b3': 'up triangle',
535 '\u25b4': 'up triangle',
536 '\u25b5': 'up triangle',
537 '\u25b6': 'right triangle',
538 '\u25b7': 'right triangle',
539 '\u25b8': 'right triangle',
540 '\u25b9': 'right triangle',
541 '\u25ba': 'right pointer',
542 '\u25bb': 'right pointer',
543 '\u25bc': 'down triangle',
544 '\u25bd': 'down triangle',
545 '\u25be': 'down triangle',
546 '\u25bf': 'down triangle',
547 '\u25c0': 'left triangle',
548 '\u25c1': 'left triangle',
549 '\u25c2': 'left triangle',
550 '\u25c3': 'left triangle',
551 '\u25c4': 'left pointer',
552 '\u25c5': 'left pointer',
558 * Substitution dictionary regexp.
562 cvox
.AbstractTts
.substitutionDictionaryRegexp_
;
566 * repetition filter regexp.
570 cvox
.AbstractTts
.repetitionRegexp_
=
571 /([-\/\\|!@#$%^&*\(\)=_+\[\]\{\}.?;'":<>])\1{2,}/g;
575 * Constructs a description of a repeated character. Use as a param to
577 * @param {string} match The matching string.
578 * @return {string} The description.
581 cvox
.AbstractTts
.repetitionReplace_ = function(match
) {
582 var count
= match
.length
;
583 return ' ' + (new goog
.i18n
.MessageFormat(cvox
.ChromeVox
.msgs
.getMsg(
584 cvox
.AbstractTts
.CHARACTER_DICTIONARY
[match
[0]])))
585 .format({'COUNT': count
}) + ' ';
592 cvox
.AbstractTts
.prototype.getDefaultProperty = function(property
) {
593 return this.propertyDefault
[property
];