001/**
002 * Copyright 2003 Sun Microsystems, Inc.
003 * 
004 * See the file "license.terms" for information on usage and
005 * redistribution of this file, and for a DISCLAIMER OF ALL 
006 * WARRANTIES.
007 */
008package com.sun.speech.freetts.jsapi;
009
010import java.util.Locale;
011import java.util.Vector;
012
013import javax.speech.EngineCentral;
014import javax.speech.EngineList;
015import javax.speech.EngineModeDesc;
016
017import com.sun.speech.freetts.ValidationException;
018import com.sun.speech.freetts.Voice;
019
020
021/**
022 * Supports the EngineCentral JSAPI 1.0 interface for the
023 * FreeTTSSynthesizer.  To use a FreeTTSSynthesizer, you should place 
024 * a line into the speech.properties file as so:
025 *
026 * <pre>
027 * FreeTTSSynthEngineCentral=com.sun.speech.freetts.jsapi.FreeTTSEngineCentral
028 * </pre>
029 *
030 */
031
032public class FreeTTSEngineCentral implements EngineCentral {
033    private static final String ENGINE_NAME = "FreeTTS Synthesizer";
034
035    /**
036     * Creates a FreeTTSEngineCentral
037     */
038    public FreeTTSEngineCentral() throws Exception {
039        // Note that the JSAPI layer currently is silent
040        // about any exceptions thrown from here, so we are noisy here
041    }
042
043    /**
044     * Returns a list containing references to all matching 
045     * synthesizers.  The mapping of FreeTTS VoiceDirectories and
046     * Voices to JSAPI Synthesizers and Voices is as follows:
047     *
048     * <p><ul>
049     * <li>Each FreeTTS VoiceDirectory specifies the list of FreeTTS
050     * Voices supported by that directory.  Each Voice in that
051     * directory specifies its name (e.g., "kevin" "kevin16" "alan"),
052     * domain (e.g., "general" or "time") and locale (e.g., Locale.US).
053     * <li>For all FreeTTS Voices from all VoiceDirectories discovered
054     * by the VoiceManager, this method will group the Voices
055     * according to those that have both a common locale and domain
056     * (e.g, all "general" domain voices for the US local will be
057     * grouped together).
058     * <li>For each group of voices that shares a common locale and
059     * domain, this method generates a new JSAPI SynthesizerModeDesc
060     * with the following attributes:
061     *   <ul>
062     *   <li>The engine name is of the form: "FreeTTS &lt;locale>
063     *   &lt;domain> synthesizer"  For example, "FreeTTS en_us general
064     *   synthesizer"
065     *   <li>The locale is the locale shared by all the voices (e.g.,
066     *   Locale.US)
067     *   <li>The mode name is the domain shared by all the voices
068     *   (e.g., "general").
069     *   </ul>
070     * <li>The JSAPI Voices for each resulting Synthesizer will have
071     * the name of the FreeTTS Voice (e.g. "kevin" "kevin16").
072     * </ul>
073     *
074     * @param require  an engine mode that describes the desired
075     *                  synthesizer
076     *
077     * @return an engineList containing matching engines, or null if
078     *          no matching engines are found
079     */
080    public EngineList createEngineList(EngineModeDesc require) {
081        EngineList el = new EngineList();
082
083        com.sun.speech.freetts.VoiceManager voiceManager =
084            com.sun.speech.freetts.VoiceManager.getInstance();
085        
086        com.sun.speech.freetts.Voice[] voices = voiceManager.getVoices();
087
088        // We want to get all combinations of domains and locales
089        Vector domainLocaleVector = new Vector();
090        for (int i = 0; i < voices.length; i++) {
091            DomainLocale dl =
092                new DomainLocale(voices[i].getDomain(), voices[i].getLocale());
093            DomainLocale dlentry = (DomainLocale)
094                getItem(domainLocaleVector, dl);
095            if (dlentry == null) {
096                domainLocaleVector.add(dl);
097                dlentry = dl;
098            }
099            dlentry.addVoice(voices[i]);
100        }
101
102        // build list of SynthesizerModeDesc's for each domain/locale
103        // combination
104        for (int i = 0; i < domainLocaleVector.size(); i++) {
105            DomainLocale dl = (DomainLocale) domainLocaleVector.get(i);
106
107            FreeTTSSynthesizerModeDesc desc = new
108                FreeTTSSynthesizerModeDesc("FreeTTS "
109                        + dl.getLocale().toString() + " " + dl.getDomain()
110                        + " synthesizer", dl.getDomain(), dl.getLocale());
111
112            // iterate through the voices in a different order
113            voices = dl.getVoices();
114            for (int j = 0; j < voices.length; j++) {
115                FreeTTSVoice jsapiVoice = new FreeTTSVoice(voices[j], null);
116                desc.addVoice(jsapiVoice);
117            }
118
119            if (require == null || desc.match(require)) {
120                try {
121                    desc.validate();
122                    el.addElement(desc);
123                } catch (ValidationException ve) {
124                    System.err.println(ve.getMessage());
125                }
126            }
127        }
128
129        if (el.size() == 0) {
130            el = null;
131        }
132        return el;
133    }
134
135    /**
136     * Gets an item out of a vector.
137     * Warning: linear search
138     *
139     * @param vector the vector to search
140     * @param o the object to look for using vector.get(i).equals(o)
141     *
142     * @return the item if it exists in the vector, else null
143     */
144    private Object getItem(Vector vector, Object o) {
145        for (int i = 0; i < vector.size(); i++) {
146            if (vector.get(i).equals(o)) {
147                return vector.get(i);
148            }
149        }
150        return null;
151    }
152}
153
154
155/**
156 * Used to be able to generate a list of voices based on unique
157 * combinations of domain/locale pairs.
158 */
159class DomainLocale {
160    private String domain;
161    private Locale locale;
162    private Vector<Voice> voices;
163
164    /**
165     * Constructor
166     *
167     * @param domain the domain to use
168     * @param locale the locale to use
169     */
170    public DomainLocale(String domain, Locale locale) {
171        this.domain = domain;
172        this.locale = locale;
173        this.voices = new Vector<Voice>();
174    }
175
176    /**
177     * See if two DomainLocale objects are equal.
178     * The voices are NOT compared.
179     *
180     * @param o, the object to compare to
181     *
182     * @return true if the domain and locale are both equal, else
183     * false
184     */
185    public boolean equals(Object o) {
186        if (! (o instanceof DomainLocale)) {
187            return false;
188        }
189        return (domain.equals(((DomainLocale) o).getDomain())
190                && locale.equals(((DomainLocale) o).getLocale()));
191    }
192
193    /**
194     * Gets the domain.
195     * @return the domain
196     */
197    public String getDomain() {
198        return domain;
199    }
200
201    /**
202     * Gets the locale.
203     * @return the locale
204     */
205    public Locale getLocale() {
206        return locale;
207    }
208
209    /**
210     * Adds a voice to this instance.
211     *
212     * @param voice the voice to add
213     */
214    public void addVoice(com.sun.speech.freetts.Voice voice) {
215        voices.add(voice);
216    }
217
218    /**
219     * Gets the voices of this instance.
220     *
221     * @return all of the voices that have been added to this
222     * instance.
223     */
224    public com.sun.speech.freetts.Voice[] getVoices() {
225        com.sun.speech.freetts.Voice[] voiceArray =
226            new com.sun.speech.freetts.Voice[voices.size()];
227        return (com.sun.speech.freetts.Voice[]) voices.toArray(voiceArray);
228    }
229}