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