2 * Copyright 1998-2001 Sun Microsystems, Inc.
4 * See the file "license.terms" for information on usage and
5 * redistribution of this file, and for a DISCLAIMER OF ALL
8 package com.sun.speech.engine.synthesis;
10 import java.io.IOException;
11 import java.net.MalformedURLException;
13 import java.util.Collection;
14 import java.util.Enumeration;
15 import java.util.Iterator;
17 import javax.speech.EngineListener;
18 import javax.speech.EngineStateError;
19 import javax.speech.SpeechEvent;
20 import javax.speech.synthesis.JSMLException;
21 import javax.speech.synthesis.Speakable;
22 import javax.speech.synthesis.SpeakableListener;
23 import javax.speech.synthesis.Synthesizer;
24 import javax.speech.synthesis.SynthesizerEvent;
25 import javax.speech.synthesis.SynthesizerListener;
26 import javax.speech.synthesis.SynthesizerModeDesc;
27 import javax.speech.synthesis.SynthesizerProperties;
29 import com.sun.speech.engine.BaseEngine;
30 import com.sun.speech.engine.BaseEngineProperties;
31 import com.sun.speech.engine.SpeechEventDispatcher;
32 import com.sun.speech.engine.SpeechEventUtilities;
35 * Supports the JSAPI 1.0 <code>Synthesizer</code> interface that
36 * performs the core non-engine-specific functions.
38 * <p>An actual JSAPI synthesizer implementation needs to extend or
39 * modify this implementation.
41 abstract public class BaseSynthesizer extends BaseEngine
42 implements Synthesizer, SpeechEventDispatcher {
45 * Set of speakable listeners belonging to the <code>Synthesizer</code>.
46 * Each item on queue may have an individual listener too.
48 * @see SpeakableListener
50 protected Collection speakableListeners;
53 * The set of voices available in this <code>Synthesizer</code>.
54 * The list can be created in the constructor methods.
56 protected VoiceList voiceList;
59 * Creates a new Synthesizer in the <code>DEALLOCATED</code> state.
61 * @param mode the operating mode of this <code>Synthesizer</code>
63 public BaseSynthesizer(SynthesizerModeDesc mode) {
65 speakableListeners = new java.util.ArrayList();
66 voiceList = new VoiceList(mode);
70 * Speaks JSML text provided as a <code>Speakable</code> object.
72 * @param jsmlText the JSML text to speak
73 * @param listener the listener to be notified as the
74 * <code>jsmlText</code> is processed
76 * @throws JSMLException if the JSML text contains errors
77 * @throws EngineStateError
78 * if this <code>Synthesizer</code> in the <code>DEALLOCATED</code> or
79 * <code>DEALLOCATING_RESOURCES</code> states
81 public void speak(Speakable jsmlText, SpeakableListener listener)
82 throws JSMLException, EngineStateError {
83 checkEngineState(DEALLOCATED | DEALLOCATING_RESOURCES);
84 BaseSynthesizerQueueItem item = createQueueItem();
85 item.setData(this, jsmlText, listener);
90 * Speaks JSML text provided as a <code>URL</code>.
92 * @param jsmlURL the <code>URL</code> containing JSML text
93 * @param listener the listener to be notified as the
94 * JSML text is processed
96 * @throws EngineStateError
97 * if this <code>Synthesizer</code> in the <code>DEALLOCATED</code> or
98 * <code>DEALLOCATING_RESOURCES</code> states
100 * if errors are encountered with the <code>JSMLurl</code>
101 * @throws JSMLException if the JSML text contains errors
102 * @throws MalformedURLException
103 * if errors are encountered with the <code>JSMLurl</code>
105 public void speak(URL jsmlURL, SpeakableListener listener)
106 throws JSMLException, MalformedURLException,
107 IOException, EngineStateError {
108 checkEngineState(DEALLOCATED | DEALLOCATING_RESOURCES);
109 BaseSynthesizerQueueItem item = createQueueItem();
110 item.setData(this, jsmlURL, listener);
115 * Speaks JSML text provided as a <code>String</code>.
117 * @param jsmlText a <code>String</code> containing JSML.
118 * @param listener the listener to be notified as the
119 * JSML text is processed
121 * @throws EngineStateError
122 * if this <code>Synthesizer</code> in the <code>DEALLOCATED</code> or
123 * <code>DEALLOCATING_RESOURCES</code> states
124 * @throws JSMLException if the JSML text contains errors
126 public void speak(String jsmlText, SpeakableListener listener)
127 throws JSMLException, EngineStateError {
128 checkEngineState(DEALLOCATED | DEALLOCATING_RESOURCES);
129 BaseSynthesizerQueueItem item = createQueueItem();
130 item.setData(this, jsmlText, false, listener);
135 * Speaks a plain text <code>String</code>. No JSML parsing is
138 * @param text a <code>String</code> containing plain text.
139 * @param listener the listener to be notified as the
142 * @throws EngineStateError
143 * if this <code>Synthesizer</code> in the <code>DEALLOCATED</code> or
144 * <code>DEALLOCATING_RESOURCES</code> states
146 public void speakPlainText(String text, SpeakableListener listener)
147 throws EngineStateError {
148 checkEngineState(DEALLOCATED | DEALLOCATING_RESOURCES);
150 BaseSynthesizerQueueItem item = createQueueItem();
151 item.setData(this, text, true, listener);
153 } catch (JSMLException e) {
154 throw new RuntimeException("JSMLException should never occur");
159 * Returns a String of the names of all the states implied
160 * in the given bit pattern.
162 * @param state the bit pattern of states
164 * @return a String of the names of all the states implied
165 * in the given bit pattern.
167 protected String stateToString(long state) {
168 StringBuffer buf = new StringBuffer();
169 if ((state & Synthesizer.QUEUE_EMPTY) != 0)
170 buf.append(" QUEUE_EMPTY ");
171 if ((state & Synthesizer.QUEUE_NOT_EMPTY) != 0)
172 buf.append(" QUEUE_NOT_EMPTY ");
173 return super.stateToString(state) + buf.toString();
177 * Puts an item on the speaking queue and sends a queue updated
180 * @param item the item to add to the queue
183 abstract protected void appendQueue(BaseSynthesizerQueueItem item);
186 * Optional method that converts a text string to a phoneme string.
189 * plain text to be converted to phonemes
192 * IPA phonemic representation of text or <code>null</code>
194 * @throws EngineStateError
195 * if this <code>Synthesizer</code> in the <code>DEALLOCATED</code> or
196 * <code>DEALLOCATING_RESOURCES</code> states
198 public String phoneme(String text) throws EngineStateError {
199 checkEngineState(DEALLOCATED | DEALLOCATING_RESOURCES);
201 // BaseSynthesizer does not implement phoneme. The sub-class
202 // should override this method if it supports text to phoneme
203 // conversion. Returning null is legal behavior.
209 * Returns an enumeration of the queue.
212 * an <code>Enumeration</code> of the speech output queue or
215 * @throws EngineStateError
216 * if this <code>Synthesizer</code> in the <code>DEALLOCATED</code> or
217 * <code>DEALLOCATING_RESOURCES</code> states
219 abstract public Enumeration enumerateQueue() throws EngineStateError;
222 * Cancels the item at the top of the queue.
224 * @throws EngineStateError
225 * if this <code>Synthesizer</code> in the <code>DEALLOCATED</code> or
226 * <code>DEALLOCATING_RESOURCES</code> states
228 abstract public void cancel() throws EngineStateError;
231 * Cancels a specific object on the queue.
234 * object to be removed from the speech output queue
236 * @throws IllegalArgumentException
237 * if the source object is not found in the speech output queue.
238 * @throws EngineStateError
239 * if this <code>Synthesizer</code> in the <code>DEALLOCATED</code> or
240 * <code>DEALLOCATING_RESOURCES</code> states
242 abstract public void cancel(Object source)
243 throws IllegalArgumentException, EngineStateError;
246 * Cancels all items on the output queue.
248 * @throws EngineStateError
249 * if this <code>Synthesizer</code> in the <code>DEALLOCATED</code> or
250 * <code>DEALLOCATING_RESOURCES</code> states
252 abstract public void cancelAll() throws EngineStateError;
255 * Returns the <code>SynthesizerProperties</code> object (a JavaBean).
256 * The method returns exactly the same object as the
257 * <code>getEngineProperties</code> method in the <code>Engine</code>
258 * interface. However, with the <code>getSynthesizerProperties</code>
259 * method, an application does not need to cast the return value.
261 * @return the <code>SynthesizerProperties</code> object for this
262 * <code>Synthesizer</code>
264 public SynthesizerProperties getSynthesizerProperties() {
265 checkEngineState(DEALLOCATED | DEALLOCATING_RESOURCES);
266 return (SynthesizerProperties) getEngineProperties();
270 * Adds a <code>SpeakableListener</code> to this <code>Synthesizer</code>.
272 * @param listener the listener to add
274 * @see #removeSpeakableListener
276 public void addSpeakableListener(SpeakableListener listener) {
277 if (!speakableListeners.contains(listener)) {
278 speakableListeners.add(listener);
283 * Removes a <code>SpeakableListener</code> from this
284 * <code>Synthesizer</code>.
286 * @param listener the listener to remove
288 * @see #addSpeakableListener
290 public void removeSpeakableListener(SpeakableListener listener) {
291 speakableListeners.remove(listener);
295 * Factory constructor for <code>EngineProperties</code> object.
296 * Gets the default speaking voice from the
297 * <code>SynthesizerModeDesc</code>.
298 * Takes the default prosody values (pitch, range, volume, rate)
299 * from the default voice. Override to set engine-specific defaults.
301 * @return a <code>BaseEngineProperties</code> object specific to
304 protected BaseEngineProperties createEngineProperties() {
305 SynthesizerModeDesc desc = (SynthesizerModeDesc)engineModeDesc;
306 BaseVoice defaultVoice = (BaseVoice)(desc.getVoices()[0]);
308 float defaultPitch = defaultVoice.defaultPitch;
309 float defaultPitchRange = defaultVoice.defaultPitchRange;
310 float defaultSpeakingRate = defaultVoice.defaultSpeakingRate;
311 float defaultVolume = defaultVoice.defaultVolume;
313 return new BaseSynthesizerProperties(defaultVoice,
321 * Factory method that creates a <code>BaseSynthesizerQueueItem</code>.
322 * Override if the synthesizer specializes the
323 * <code>BaseSynthesizerQueueItem</code> class.
325 protected BaseSynthesizerQueueItem createQueueItem() {
326 return new BaseSynthesizerQueueItem();
330 * Returns the list of voices for this <code>Synthesizer</code>.
332 * @return the list of voices for this <code>Synthesizer</code>.
334 protected VoiceList getVoiceList() {
339 * Utility function that generates <code>QUEUE_UPDATED</code>
340 * event and posts it to the event queue. Eventually
341 * <code>fireQueueUpdated</code> will be called
342 * by <code>dispatchSpeechEvent</code> as a result of this action.
344 * @param topOfQueueChanged <code>true</code> if the top of the
346 * @param oldState the old state of this <code>Synthesizer</code>
347 * @param newState the new state of this <code>Synthesizer</code>
349 * @see #fireQueueUpdated
350 * @see #dispatchSpeechEvent
353 public void postQueueUpdated(boolean topOfQueueChanged,
354 long oldState, long newState) {
355 SpeechEventUtilities.postSpeechEvent(
357 new SynthesizerEvent(this,
358 SynthesizerEvent.QUEUE_UPDATED,
360 oldState, newState));
364 * Utility function that sends a <code>QUEUE_UPDATED</code>
365 * event to all <code>SynthesizerListeners</code>.
367 * @param event the <code>QUEUE_UPDATED</code> event
369 * @see #postQueueUpdated
370 * @see #dispatchSpeechEvent
372 public void fireQueueUpdated(SynthesizerEvent event) {
373 if (engineListeners == null) {
376 Iterator iterator = engineListeners.iterator();
377 while (iterator.hasNext()) {
378 EngineListener el = (EngineListener) iterator.next();
379 if (el instanceof SynthesizerListener) {
380 SynthesizerListener sl = (SynthesizerListener)el;
381 sl.queueUpdated(event);
387 * Utility function that generates <code>QUEUE_EMPTIED</code>
388 * event and posts it to the event queue. Eventually
389 * <code>fireQueueEmptied</code> will be called
390 * by <code>dispatchSpeechEvent</code> as a result of this action.
392 * @param oldState the old state of this <code>Synthesizer</code>
393 * @param newState the new state of this <code>Synthesizer</code>
395 * @see #fireQueueEmptied
396 * @see #dispatchSpeechEvent
398 public void postQueueEmptied(long oldState, long newState) {
399 SpeechEventUtilities.postSpeechEvent(
401 new SynthesizerEvent(this,
402 SynthesizerEvent.QUEUE_EMPTIED,
404 oldState, newState));
408 * Utility function that sends a <code>QUEUE_EMPTIED</code>
409 * event to all <code>SynthesizerListeners</code>.
411 * @param event the <code>QUEUE_EMPTIED</code> event
413 * @see #postQueueEmptied
414 * @see #dispatchSpeechEvent
416 public void fireQueueEmptied(SynthesizerEvent event) {
417 if (engineListeners == null) {
420 Iterator iterator = engineListeners.iterator();
421 while (iterator.hasNext()) {
422 EngineListener el = (EngineListener) iterator.next();
423 if (el instanceof SynthesizerListener) {
424 SynthesizerListener sl = (SynthesizerListener)el;
425 sl.queueEmptied(event);
431 * Dispatches a <code>SpeechEvent</code>.
432 * The dispatcher should notify all <code>SynthesizerListeners</code>
433 * from this method. The <code>SpeechEvent</code> was added
434 * via the various post methods of this class.
436 * @param event the <code>SpeechEvent</code> to dispatch
438 * @see #postQueueUpdated
439 * @see #postQueueEmptied
441 public void dispatchSpeechEvent(SpeechEvent event) {
442 switch (event.getId()) {
443 case SynthesizerEvent.QUEUE_EMPTIED:
444 fireQueueEmptied((SynthesizerEvent) event);
446 case SynthesizerEvent.QUEUE_UPDATED:
447 fireQueueUpdated((SynthesizerEvent) event);
450 // Defer to BaseEngine to handle the rest.
453 super.dispatchSpeechEvent(event);