upstream version 1.2.2
[debian/freetts] / com / sun / speech / freetts / Utterance.java
1 /**
2  * Portions Copyright 2001 Sun Microsystems, Inc.
3  * Portions Copyright 1999-2001 Language Technologies Institute, 
4  * Carnegie Mellon University.
5  * All Rights Reserved.  Use is subject to license terms.
6  * 
7  * See the file "license.terms" for information on usage and
8  * redistribution of this file, and for a DISCLAIMER OF ALL 
9  * WARRANTIES.
10  */
11 package com.sun.speech.freetts;
12
13 import java.io.PrintWriter;
14 import java.io.Serializable;
15 import java.util.Iterator;
16 import java.util.List;
17
18 import com.sun.speech.freetts.util.SegmentRelationUtils;
19
20 /**
21  * Holds all the data for an utterance to be spoken.
22  * It is incrementally modified by various UtteranceProcessor
23  * implementations.  An utterance contains a set of Features (essential
24  * a set of properties) and a set of Relations. A Relation is an
25  * ordered set of Item graphs. The utterance contains a set of
26  * features and implements FeatureSet so that applications can set/get
27  * features directly from the utterance. If a feature query is not
28  * found in the utterance feature set, the query is forwarded to the
29  * FeatureSet of the voice associated with the utterance.
30  */
31 public class Utterance implements FeatureSet, Serializable {
32
33     private Voice voice;
34     private FeatureSetImpl features;
35     private FeatureSetImpl relations;
36     private boolean first;      // first in a connected series
37     private boolean last;       // last in a connected series
38     private FreeTTSSpeakable speakable;
39
40     /**
41      * Creates a new, empty utterance.
42      *
43      * @param voice the voice associated with the utterance
44      */
45     public Utterance(Voice voice) {
46         this.voice = voice;
47         features = new FeatureSetImpl();
48         relations = new FeatureSetImpl();
49     }
50
51     /**
52      * Creates an utterance with the given set of tokenized text.
53      *
54      * @param voice the voice associated with the utterance
55      * @param tokenList the list of tokens for this utterance
56      */
57     public Utterance(Voice voice, List tokenList) {
58         this(voice);
59         setTokenList(tokenList);
60     }
61
62     /**
63      * Sets the speakable item for this utterance.
64      *
65      * @param speakable the speakable item for this utterance
66      */
67     public void setSpeakable(FreeTTSSpeakable speakable) {
68         this.speakable = speakable;
69     }
70
71     /**
72      * Returns the queueitem associated with this utterance.
73      *
74      * @return the queue item
75      */
76     public FreeTTSSpeakable getSpeakable() {
77         return speakable;
78     }
79
80     /**
81      * Creates a new relation with the given name and adds it to this
82      * utterance.
83      *
84      * @param name the name of the new relation
85      *
86      * @return the newly created relation
87      */
88     public Relation createRelation(String name) {
89         Relation relation = new Relation(name, this);
90         relations.setObject(name, relation);
91         return relation;
92     }
93
94
95     /**
96      * Retrieves a relation from this utterance.
97      *
98      * @param name the name of the Relation
99      *
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 tokenList) {
427         StringBuffer sb = new StringBuffer();
428         for (Iterator i = tokenList.iterator(); i.hasNext(); ) {
429             sb.append(i.next().toString());
430         }
431         setString("input_text", sb.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 tokenList) {
444         setInputText(tokenList);
445
446         Relation relation = createRelation(Relation.TOKEN);
447         for (Iterator i = tokenList.iterator(); i.hasNext(); ) {
448             Token token = (Token) i.next();
449             String tokenWord = token.getWord();
450             
451             if (tokenWord != null && tokenWord.length() > 0) {
452                 Item item = relation.appendItem();
453                 
454                 FeatureSet featureSet = item.getFeatures();
455                 featureSet.setString("name", tokenWord);
456                 featureSet.setString("whitespace", token.getWhitespace());
457                 featureSet.setString("prepunctuation", 
458                         token.getPrepunctuation());
459                 featureSet.setString("punc", token.getPostpunctuation());
460                 featureSet.setString("file_pos", 
461                         String.valueOf(token.getPosition()));
462                 featureSet.setString("line_number", 
463                         String.valueOf(token.getLineNumber()));
464                 
465             }
466         }
467     }
468
469     /**
470      * Returns true if this utterance is the first is a series of
471      * utterances.
472      *
473      * @return true if this is the first utterance in a series of
474      *     connected utterances.
475      */
476     public boolean isFirst() {
477         return first;
478     }
479
480     /**
481      * Sets this utterance as the first in a series.
482      *
483      * @param first if true, the item is the first in a series
484      */
485     public void setFirst(boolean first) {
486         this.first = first;
487     }
488
489     /**
490      * Returns true if this utterance is the last is a series of
491      * utterances.
492      *
493      * @return true if this is the last utterance in a series of
494      *     connected utterances.
495      */
496     public boolean isLast() {
497         return last;
498     }
499
500     /**
501      * Sets this utterance as the last in a series.
502      *
503      * @param last if true, the item is the last in a series
504      */
505     public void setLast(boolean last) {
506         this.last = last;
507     }
508 }