001/** 002 * Portions Copyright 2001 Sun Microsystems, Inc. 003 * Portions Copyright 1999-2001 Language Technologies Institute, 004 * Carnegie Mellon University. 005 * All Rights Reserved. Use is subject to license terms. 006 * 007 * See the file "license.terms" for information on usage and 008 * redistribution of this file, and for a DISCLAIMER OF ALL 009 * WARRANTIES. 010 */ 011package com.sun.speech.freetts; 012 013import java.io.PrintWriter; 014import java.io.Serializable; 015import java.util.List; 016 017import com.sun.speech.freetts.util.SegmentRelationUtils; 018 019/** 020 * Holds all the data for an utterance to be spoken. 021 * It is incrementally modified by various UtteranceProcessor 022 * implementations. An utterance contains a set of Features (essential 023 * a set of properties) and a set of Relations. A Relation is an 024 * ordered set of Item graphs. The utterance contains a set of 025 * features and implements FeatureSet so that applications can set/get 026 * features directly from the utterance. If a feature query is not 027 * found in the utterance feature set, the query is forwarded to the 028 * FeatureSet of the voice associated with the utterance. 029 */ 030@SuppressWarnings("serial") 031public class Utterance implements FeatureSet, Serializable { 032 033 private Voice voice; 034 private FeatureSet features; 035 private FeatureSet relations; 036 private boolean first; // first in a connected series 037 private boolean last; // last in a connected series 038 private FreeTTSSpeakable speakable; 039 040 /** 041 * Creates a new, empty utterance. 042 * 043 * @param voice the voice associated with the utterance 044 */ 045 public Utterance(Voice voice) { 046 this.voice = voice; 047 features = new FeatureSetImpl(); 048 relations = new FeatureSetImpl(); 049 } 050 051 /** 052 * Creates an utterance with the given set of tokenized text. 053 * 054 * @param voice the voice associated with the utterance 055 * @param tokenList the list of tokens for this utterance 056 */ 057 public Utterance(Voice voice, List<Token> tokenList) { 058 this(voice); 059 setTokenList(tokenList); 060 } 061 062 /** 063 * Sets the speakable item for this utterance. 064 * 065 * @param speakable the speakable item for this utterance 066 */ 067 public void setSpeakable(FreeTTSSpeakable speakable) { 068 this.speakable = speakable; 069 } 070 071 /** 072 * Returns the queueitem associated with this utterance. 073 * 074 * @return the queue item 075 */ 076 public FreeTTSSpeakable getSpeakable() { 077 return speakable; 078 } 079 080 /** 081 * Creates a new relation with the given name and adds it to this 082 * utterance. 083 * 084 * @param name the name of the new relation 085 * 086 * @return the newly created relation 087 */ 088 public Relation createRelation(String name) { 089 Relation relation = new Relation(name, this); 090 relations.setObject(name, relation); 091 return relation; 092 } 093 094 095 /** 096 * Retrieves a relation from this utterance. 097 * 098 * @param name the name of the Relation 099 * 100 * @return the relation or null if the relation is not found 101 */ 102 public Relation getRelation(String name) { 103 return (Relation) relations.getObject(name); 104 } 105 106 /** 107 * Determines if this utterance contains a relation with the given 108 * name. 109 * 110 * @param name the name of the relation of interest. 111 */ 112 public boolean hasRelation(String name) { 113 return relations.isPresent(name); 114 } 115 116 /** 117 * Retrieves the Voice associated with this Utterance. 118 * 119 * @return the voice associated with this utterance. 120 */ 121 public Voice getVoice() { 122 return voice; 123 } 124 125 /** 126 * Dumps this utterance in textual form. 127 * 128 * @param output where to send the formatted output 129 * @param pad the initial padding 130 * @param title the title to print when dumping out the utterance 131 * @param justRelations if true don't print voice features 132 */ 133 public void dump(PrintWriter output, int pad, String title, 134 boolean justRelations) { 135 output.println(" ============ " + title + " ========== "); 136 if (!justRelations) { 137 voice.dump(output, pad + 4, "Voice"); 138 features.dump(output, pad + 4, "Features"); 139 } 140 relations.dump(output, pad + 4, "Relations"); 141 output.flush(); 142 } 143 144 /** 145 * Dumps this utterance in textual form. 146 * 147 * @param output where to send the formatted output 148 * @param pad the initial padding 149 * @param title the title to print when dumping out the utterance 150 */ 151 public void dump(PrintWriter output, int pad, String title) { 152 dump(output, pad, title, false); 153 } 154 155 /** 156 * Dumps this utterance in textual form. 157 * 158 * @param output where to send the formatted output 159 * @param title the title to print when dumping out the utterance 160 */ 161 public void dump(PrintWriter output, String title) { 162 dump(output, 0, title, false); 163 } 164 165 /** 166 * Dumps this utterance in textual form. 167 * 168 * @param title the title to print when dumping out the utterance 169 */ 170 public void dump(String title) { 171 dump(new PrintWriter(System.out), 0, title, false); 172 } 173 174 /** 175 * Dumps the utterance in textual form 176 * @param title the title to print when dumping out the utterance 177 */ 178 public void dumpRelations(String title) { 179 dump(new PrintWriter(System.out), 0, title, true); 180 } 181 182 /** 183 * Determines if the given feature is present. If the feature is 184 * not present in the utterance, the feature set of the voice is 185 * checked. 186 * 187 * @param name the name of the feature of interest 188 * @return true if the named feature is present 189 */ 190 public boolean isPresent(String name) { 191 if (!features.isPresent(name)) { 192 return getVoice().getFeatures().isPresent(name); 193 } else { 194 return true; 195 } 196 } 197 198 /** 199 * Removes the named feature from this set of features. 200 * 201 * @param name the name of the feature of interest 202 */ 203 public void remove(String name) { 204 features.remove(name); 205 } 206 207 /** 208 * Convenience method that returns the named feature as a string. 209 * If the named feature is not present in the utterance, then this 210 * attempts to retrieve it from the voice. 211 * 212 * @param name the name of the feature 213 * 214 * @return the value associated with the name or null if the value 215 * is not found 216 * 217 * @throws ClassCastException if the associated value is not a 218 * String 219 */ 220 public String getString(String name) { 221 if (!features.isPresent(name)) { 222 return getVoice().getFeatures().getString(name); 223 } else { 224 return features.getString(name); 225 } 226 } 227 228 /** 229 * Convenience method that returns the named feature as a int. 230 * If the named feature is not present in the utterance, then this 231 * attempts to retrieve it from the voice. 232 * 233 * @param name the name of the feature 234 * 235 * @return the value associated with the name or null if the value 236 * is not found 237 * 238 * @throws ClassCastException if the associated value is not an 239 * int 240 */ 241 public int getInt(String name) { 242 if (!features.isPresent(name)) { 243 return getVoice().getFeatures().getInt(name); 244 } else { 245 return features.getInt(name); 246 } 247 } 248 249 /** 250 * Convenience method that returns the named feature as a float. 251 * If the named feature is not present in the utterance, then this 252 * attempts to retrieve it from the voice. 253 * 254 * @param name the name of the feature 255 * 256 * @return the value associated with the name or null if the value 257 * is not found 258 * 259 * @throws ClassCastException if the associated value is not a 260 * float 261 */ 262 public float getFloat(String name) { 263 if (!features.isPresent(name)) { 264 return getVoice().getFeatures().getFloat(name); 265 } else { 266 return features.getFloat(name); 267 } 268 } 269 270 /** 271 * Returns the named feature as an object. 272 * If the named feature is not present in the utterance, then this 273 * attempts to retrieve it from the voice. 274 * 275 * @param name the name of the feature 276 * 277 * @return the value associated with the name or null if the value 278 * is not found 279 */ 280 public Object getObject(String name) { 281 if (!features.isPresent(name)) { 282 return getVoice().getFeatures().getObject(name); 283 } else { 284 return features.getObject(name); 285 } 286 } 287 288 /** 289 * Convenience method that sets the named feature as an int. 290 * 291 * @param name the name of the feature 292 * @param value the value of the feature 293 */ 294 public void setInt(String name, int value) { 295 features.setInt(name, value); 296 } 297 298 /** 299 * Convenience method that sets the named feature as a float. 300 * 301 * @param name the name of the feature 302 * @param value the value of the feature 303 */ 304 public void setFloat(String name, float value) { 305 features.setFloat(name, value); 306 } 307 308 /** 309 * Convenience method that sets the named feature as a String. 310 * 311 * @param name the name of the feature 312 * @param value the value of the feature 313 */ 314 public void setString(String name, String value) { 315 features.setString(name, value); 316 } 317 318 /** 319 * Sets the named feature. 320 * 321 * @param name the name of the feature 322 * @param value the value of the feature 323 */ 324 public void setObject(String name, Object value) { 325 features.setObject(name, value); 326 } 327 328 /** 329 * Returns the Item in the given Relation associated with the given time. 330 * 331 * @param relation the name of the relation 332 * @param time the time 333 * 334 * @throws IllegalStateException if the Segment durations 335 * have not been calculated in the Utterance or if the 336 * given relation is not present in the Utterance 337 */ 338 public Item getItem(String relation, float time) { 339 340 Relation segmentRelation = null; 341 342 if ((segmentRelation = getRelation(Relation.SEGMENT)) == null) { 343 throw new IllegalStateException 344 ("Utterance has no Segment relation"); 345 } 346 347 String pathName = null; 348 349 if (relation.equals(Relation.SEGMENT)) { 350 // do nothing 351 } else if (relation.equals(Relation.SYLLABLE)) { 352 pathName = "R:SylStructure.parent.R:Syllable"; 353 } else if (relation.equals(Relation.SYLLABLE_STRUCTURE)) { 354 pathName = "R:SylStructure.parent.parent"; 355 } else if (relation.equals(Relation.WORD)) { 356 pathName = "R:SylStructure.parent.parent.R:Word"; 357 } else if (relation.equals(Relation.TOKEN)) { 358 pathName = "R:SylStructure.parent.parent.R:Token.parent"; 359 } else if (relation.equals(Relation.PHRASE)) { 360 pathName = "R:SylStructure.parent.parent.R:Phrase.parent"; 361 } else { 362 throw new IllegalArgumentException 363 ("Utterance.getItem(): relation cannot be " + relation); 364 } 365 366 PathExtractor path = new PathExtractorImpl(pathName, false); 367 368 // get the Item in the Segment Relation with the given time 369 Item segmentItem = SegmentRelationUtils.getItem 370 (segmentRelation, time); 371 372 if (relation.equals(Relation.SEGMENT)) { 373 return segmentItem; 374 } else if (segmentItem != null) { 375 return path.findItem(segmentItem); 376 } else { 377 return null; 378 } 379 } 380 381 /** 382 * Returns the duration of this Utterance in seconds. It does this 383 * by looking at last Item in the following Relations, in this order: 384 * Segment, Target. If none of these Relations exist, or if these 385 * Relations contain no Items, an IllegalStateException will be thrown. 386 * 387 * @return the duration of this Utterance in seconds 388 */ 389 public float getDuration() { 390 float duration = -1; 391 if ((duration = getLastFloat(Relation.SEGMENT, "end")) == -1) { 392 if ((duration = getLastFloat(Relation.TARGET, "pos")) == -1) { 393 throw new IllegalStateException 394 ("Utterance: Error finding duration"); 395 } 396 } 397 return duration; 398 } 399 400 /** 401 * Returns the float feature of the last Item in the named 402 * Relation. 403 * 404 * @return the float feature of the last Item in the named Relation, 405 * or -1 otherwise 406 */ 407 private float getLastFloat(String relationName, String feature) { 408 float duration = -1; 409 Relation relation; 410 if ((relation = getRelation(relationName)) != null) { 411 Item lastItem = relation.getTail(); 412 if (lastItem != null) { 413 duration = lastItem.getFeatures().getFloat(feature); 414 } 415 } 416 return duration; 417 } 418 419 420 /** 421 * Sets the input text for this utterance 422 * 423 * @param tokenList the set of tokens for this utterance 424 * 425 */ 426 private void setInputText(List<Token> tokenList) { 427 StringBuilder str = new StringBuilder(); 428 for (Token token : tokenList) { 429 str.append(token.toString()); 430 } 431 setString("input_text", str.toString()); 432 } 433 434 435 /** 436 * Sets the token list for this utterance. Note that this could be 437 * optimized by turning the token list directly into the token 438 * relation. 439 * 440 * @param tokenList the tokenList 441 * 442 */ 443 private void setTokenList(List<Token> tokenList) { 444 setInputText(tokenList); 445 446 Relation relation = createRelation(Relation.TOKEN); 447 for (Token token : tokenList) { 448 String tokenWord = token.getWord(); 449 450 if (tokenWord != null && tokenWord.length() > 0) { 451 Item item = relation.appendItem(); 452 453 FeatureSet featureSet = item.getFeatures(); 454 featureSet.setString("name", tokenWord); 455 featureSet.setString("whitespace", token.getWhitespace()); 456 featureSet.setString("prepunctuation", 457 token.getPrepunctuation()); 458 featureSet.setString("punc", token.getPostpunctuation()); 459 featureSet.setString("file_pos", 460 String.valueOf(token.getPosition())); 461 featureSet.setString("line_number", 462 String.valueOf(token.getLineNumber())); 463 464 } 465 } 466 } 467 468 /** 469 * Returns true if this utterance is the first is a series of 470 * utterances. 471 * 472 * @return true if this is the first utterance in a series of 473 * connected utterances. 474 */ 475 public boolean isFirst() { 476 return first; 477 } 478 479 /** 480 * Sets this utterance as the first in a series. 481 * 482 * @param first if true, the item is the first in a series 483 */ 484 public void setFirst(boolean first) { 485 this.first = first; 486 } 487 488 /** 489 * Returns true if this utterance is the last is a series of 490 * utterances. 491 * 492 * @return true if this is the last utterance in a series of 493 * connected utterances. 494 */ 495 public boolean isLast() { 496 return last; 497 } 498 499 /** 500 * Sets this utterance as the last in a series. 501 * 502 * @param last if true, the item is the last in a series 503 */ 504 public void setLast(boolean last) { 505 this.last = last; 506 } 507}