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}