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.util.ArrayList;
014import java.util.List;
015
016import com.sun.speech.freetts.lexicon.Lexicon;
017
018/**
019 * Annotates an utterance with <code>Relation.SYLLABLE</code>,
020 * <code>Relation.SYLLABLE_STRUCTURE</code>, and
021 * <code>Relation.SEGMENT</code>.
022 * To determine stress, the <code>isStressed</code> method relies upon
023 * a phone ending in the number "1".  Subclasses should override
024 * <code>isStressed</code> and <code>deStress</code> if stresses are
025 * determined in other ways.
026 *
027 * @see Relation#SEGMENT
028 * @see Relation#SYLLABLE
029 * @see Relation#SYLLABLE_STRUCTURE
030 */
031public class Segmenter implements UtteranceProcessor {
032    private final static String STRESS = "1";
033    private final static String NO_STRESS = "0";
034
035    /**
036     * Annotates an utterance with <code>Relation.SYLLABLE</code>,
037     * <code>Relation.SYLLABLE_STRUCTURE</code>, and
038     * <code>Relation.SEGMENT</code>.
039     *
040     * @param utterance the utterance to process/tokenize
041     *
042     * @see Relation#SEGMENT
043     * @see Relation#SYLLABLE
044     * @see Relation#SYLLABLE_STRUCTURE
045     *
046     * @throws ProcessException if an IOException is thrown during the
047     *   processing of the utterance
048     */
049    public void processUtterance(Utterance utterance) throws ProcessException {
050
051        // preconditions
052        if (utterance.getRelation(Relation.WORD) == null) {
053            throw new IllegalStateException(
054                "Word relation has not been set");
055        } else if (utterance.getRelation(Relation.SYLLABLE) != null) {
056            throw new IllegalStateException(
057                "Syllable relation has already been set");
058        } else if (utterance.getRelation(Relation.SYLLABLE_STRUCTURE)
059                   != null) {
060            throw new IllegalStateException(
061                "SylStructure relation has already been set");
062        } else if (utterance.getRelation(Relation.SEGMENT) != null) {
063            throw new IllegalStateException(
064                "Segment relation has already been set");
065        }
066
067        String stress = NO_STRESS;
068        Relation syl = utterance.createRelation(Relation.SYLLABLE);
069        Relation sylstructure =
070            utterance.createRelation(Relation.SYLLABLE_STRUCTURE);
071        Relation seg = utterance.createRelation(Relation.SEGMENT);
072        Lexicon lex = utterance.getVoice().getLexicon();
073        List syllableList = null;
074
075        for (Item word = utterance.getRelation(Relation.WORD).getHead();
076                        word != null; word = word.getNext()) {
077            Item ssword = sylstructure.appendItem(word);
078            Item sylItem = null;   // item denoting syllable boundaries
079            Item segItem = null;   // item denoting phonelist (segments)
080            Item sssyl = null;     // item denoting syl in word
081
082            String[] phones = null;
083
084            Item token = word.getItemAs("Token");
085            FeatureSet featureSet = null;
086
087            if (token != null) {
088                Item parent = token.getParent();
089                featureSet = parent.getFeatures();
090            }
091            
092            if (featureSet != null && featureSet.isPresent("phones")) {
093                phones = (String[]) featureSet.getObject("phones");
094            } else {
095                phones = lex.getPhones(word.toString(), null);
096            }
097
098            for (int j = 0; j < phones.length; j++) {
099                if (sylItem == null) {
100                    sylItem = syl.appendItem();
101                    sssyl = ssword.addDaughter(sylItem);
102                    stress = NO_STRESS;
103                    syllableList = new ArrayList();
104                }
105                segItem = seg.appendItem();
106                if (isStressed(phones[j])) {
107                    stress = STRESS;
108                    phones[j] = deStress(phones[j]);
109                }
110                segItem.getFeatures().setString("name", phones[j]);
111                sssyl.addDaughter(segItem);
112                syllableList.add(phones[j]);
113                if (lex.isSyllableBoundary(syllableList, phones, j + 1))  { 
114                    sylItem =  null;
115                    if (sssyl != null) {
116                        sssyl.getFeatures().setString("stress", stress);
117                    }
118                }
119            }
120        }
121    }
122
123    /**
124     * Determines if the given phonemene is stressed.
125     * To determine stress, this method relies upon
126     * a phone ending in the number "1".  Subclasses should override this
127     * method if stresses are determined in other ways.
128     *
129     * @param phone the phone to check
130     *
131     * @return true if the phone is stressed, otherwise false
132     */
133    protected boolean isStressed(String phone) {
134        return phone.endsWith("1");
135    }
136
137    /**
138     * Converts stressed phoneme to regular phoneme.  This method
139     * merely removes the last character of the phone.  Subclasses
140     * should override this if another method is to be used.
141     *
142     * @param phone the phone to convert
143     *
144     * @return de-stressed phone
145     */
146    protected String deStress(String phone) {
147        String retPhone = phone;
148        if (isStressed(phone)) {
149            retPhone = phone.substring(0, phone.length() - 1);
150        }
151        return retPhone;
152    }
153
154    /**
155     * Returns the simple name of this class.
156     *
157     * @return the simple name of this class
158     */
159    public String toString() {
160        return "Segmenter";
161    }
162}
163