/**
* Copyright 1998-2001 Sun Microsystems, Inc.
*
* See the file "license.terms" for information on usage and
* redistribution of this file, and for a DISCLAIMER OF ALL
* WARRANTIES.
*/
package com.sun.speech.engine.synthesis;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Collection;
import java.util.Enumeration;
import java.util.Iterator;
import javax.speech.EngineListener;
import javax.speech.EngineStateError;
import javax.speech.SpeechEvent;
import javax.speech.synthesis.JSMLException;
import javax.speech.synthesis.Speakable;
import javax.speech.synthesis.SpeakableListener;
import javax.speech.synthesis.Synthesizer;
import javax.speech.synthesis.SynthesizerEvent;
import javax.speech.synthesis.SynthesizerListener;
import javax.speech.synthesis.SynthesizerModeDesc;
import javax.speech.synthesis.SynthesizerProperties;
import com.sun.speech.engine.BaseEngine;
import com.sun.speech.engine.BaseEngineProperties;
import com.sun.speech.engine.SpeechEventDispatcher;
import com.sun.speech.engine.SpeechEventUtilities;
/**
* Supports the JSAPI 1.0 <code>Synthesizer</code> interface that
* performs the core non-engine-specific functions.
*
* <p>An actual JSAPI synthesizer implementation needs to extend or
* modify this implementation.
*/
abstract public class BaseSynthesizer extends BaseEngine
implements Synthesizer, SpeechEventDispatcher {
/**
* Set of speakable listeners belonging to the <code>Synthesizer</code>.
* Each item on queue may have an individual listener too.
*
* @see SpeakableListener
*/
protected Collection speakableListeners;
/**
* The set of voices available in this <code>Synthesizer</code>.
* The list can be created in the constructor methods.
*/
protected VoiceList voiceList;
/**
* Creates a new Synthesizer in the <code>DEALLOCATED</code> state.
*
* @param mode the operating mode of this <code>Synthesizer</code>
*/
public BaseSynthesizer(SynthesizerModeDesc mode) {
super(mode);
speakableListeners = new java.util.ArrayList();
voiceList = new VoiceList(mode);
}
/**
* Speaks JSML text provided as a <code>Speakable</code> object.
*
* @param jsmlText the JSML text to speak
* @param listener the listener to be notified as the
* <code>jsmlText</code> is processed
*
* @throws JSMLException if the JSML text contains errors
* @throws EngineStateError
* if this <code>Synthesizer</code> in the <code>DEALLOCATED</code> or
* <code>DEALLOCATING_RESOURCES</code> states
*/
public void speak(Speakable jsmlText, SpeakableListener listener)
throws JSMLException, EngineStateError {
checkEngineState(DEALLOCATED | DEALLOCATING_RESOURCES);
BaseSynthesizerQueueItem item = createQueueItem();
item.setData(this, jsmlText, listener);
appendQueue(item);
}
/**
* Speaks JSML text provided as a <code>URL</code>.
*
* @param jsmlURL the <code>URL</code> containing JSML text
* @param listener the listener to be notified as the
* JSML text is processed
*
* @throws EngineStateError
* if this <code>Synthesizer</code> in the <code>DEALLOCATED</code> or
* <code>DEALLOCATING_RESOURCES</code> states
* @throws IOException
* if errors are encountered with the <code>JSMLurl</code>
* @throws JSMLException if the JSML text contains errors
* @throws MalformedURLException
* if errors are encountered with the <code>JSMLurl</code>
*/
public void speak(URL jsmlURL, SpeakableListener listener)
throws JSMLException, MalformedURLException,
IOException, EngineStateError {
checkEngineState(DEALLOCATED | DEALLOCATING_RESOURCES);
BaseSynthesizerQueueItem item = createQueueItem();
item.setData(this, jsmlURL, listener);
appendQueue(item);
}
/**
* Speaks JSML text provided as a <code>String</code>.
*
* @param jsmlText a <code>String</code> containing JSML.
* @param listener the listener to be notified as the
* JSML text is processed
*
* @throws EngineStateError
* if this <code>Synthesizer</code> in the <code>DEALLOCATED</code> or
* <code>DEALLOCATING_RESOURCES</code> states
* @throws JSMLException if the JSML text contains errors
*/
public void speak(String jsmlText, SpeakableListener listener)
throws JSMLException, EngineStateError {
checkEngineState(DEALLOCATED | DEALLOCATING_RESOURCES);
BaseSynthesizerQueueItem item = createQueueItem();
item.setData(this, jsmlText, false, listener);
appendQueue(item);
}
/**
* Speaks a plain text <code>String</code>. No JSML parsing is
* performed.
*
* @param text a <code>String</code> containing plain text.
* @param listener the listener to be notified as the
* text is processed
*
* @throws EngineStateError
* if this <code>Synthesizer</code> in the <code>DEALLOCATED</code> or
* <code>DEALLOCATING_RESOURCES</code> states
*/
public void speakPlainText(String text, SpeakableListener listener)
throws EngineStateError {
checkEngineState(DEALLOCATED | DEALLOCATING_RESOURCES);
try {
BaseSynthesizerQueueItem item = createQueueItem();
item.setData(this, text, true, listener);
appendQueue(item);
} catch (JSMLException e) {
throw new RuntimeException("JSMLException should never occur");
}
}
/**
* Returns a String of the names of all the states implied
* in the given bit pattern.
*
* @param state the bit pattern of states
*
* @return a String of the names of all the states implied
* in the given bit pattern.
*/
protected String stateToString(long state) {
StringBuffer buf = new StringBuffer();
if ((state & Synthesizer.QUEUE_EMPTY) != 0)
buf.append(" QUEUE_EMPTY ");
if ((state & Synthesizer.QUEUE_NOT_EMPTY) != 0)
buf.append(" QUEUE_NOT_EMPTY ");
return super.stateToString(state) + buf.toString();
}
/**
* Puts an item on the speaking queue and sends a queue updated
* event.
*
* @param item the item to add to the queue
*
*/
abstract protected void appendQueue(BaseSynthesizerQueueItem item);
/**
* Optional method that converts a text string to a phoneme string.
*
* @param text
* plain text to be converted to phonemes
*
* @return
* IPA phonemic representation of text or <code>null</code>
*
* @throws EngineStateError
* if this <code>Synthesizer</code> in the <code>DEALLOCATED</code> or
* <code>DEALLOCATING_RESOURCES</code> states
*/
public String phoneme(String text) throws EngineStateError {
checkEngineState(DEALLOCATED | DEALLOCATING_RESOURCES);
// BaseSynthesizer does not implement phoneme. The sub-class
// should override this method if it supports text to phoneme
// conversion. Returning null is legal behavior.
//
return null;
}
/**
* Returns an enumeration of the queue.
*
* @return
* an <code>Enumeration</code> of the speech output queue or
* <code>null</code>.
*
* @throws EngineStateError
* if this <code>Synthesizer</code> in the <code>DEALLOCATED</code> or
* <code>DEALLOCATING_RESOURCES</code> states
*/
abstract public Enumeration enumerateQueue() throws EngineStateError;
/**
* Cancels the item at the top of the queue.
*
* @throws EngineStateError
* if this <code>Synthesizer</code> in the <code>DEALLOCATED</code> or
* <code>DEALLOCATING_RESOURCES</code> states
*/
abstract public void cancel() throws EngineStateError;
/**
* Cancels a specific object on the queue.
*
* @param source
* object to be removed from the speech output queue
*
* @throws IllegalArgumentException
* if the source object is not found in the speech output queue.
* @throws EngineStateError
* if this <code>Synthesizer</code> in the <code>DEALLOCATED</code> or
* <code>DEALLOCATING_RESOURCES</code> states
*/
abstract public void cancel(Object source)
throws IllegalArgumentException, EngineStateError;
/**
* Cancels all items on the output queue.
*
* @throws EngineStateError
* if this <code>Synthesizer</code> in the <code>DEALLOCATED</code> or
* <code>DEALLOCATING_RESOURCES</code> states
*/
abstract public void cancelAll() throws EngineStateError;
/**
* Returns the <code>SynthesizerProperties</code> object (a JavaBean).
* The method returns exactly the same object as the
* <code>getEngineProperties</code> method in the <code>Engine</code>
* interface. However, with the <code>getSynthesizerProperties</code>
* method, an application does not need to cast the return value.
*
* @return the <code>SynthesizerProperties</code> object for this
* <code>Synthesizer</code>
*/
public SynthesizerProperties getSynthesizerProperties() {
checkEngineState(DEALLOCATED | DEALLOCATING_RESOURCES);
return (SynthesizerProperties) getEngineProperties();
}
/**
* Adds a <code>SpeakableListener</code> to this <code>Synthesizer</code>.
*
* @param listener the listener to add
*
* @see #removeSpeakableListener
*/
public void addSpeakableListener(SpeakableListener listener) {
if (!speakableListeners.contains(listener)) {
speakableListeners.add(listener);
}
}
/**
* Removes a <code>SpeakableListener</code> from this
* <code>Synthesizer</code>.
*
* @param listener the listener to remove
*
* @see #addSpeakableListener
*/
public void removeSpeakableListener(SpeakableListener listener) {
speakableListeners.remove(listener);
}
/**
* Factory constructor for <code>EngineProperties</code> object.
* Gets the default speaking voice from the
* <code>SynthesizerModeDesc</code>.
* Takes the default prosody values (pitch, range, volume, rate)
* from the default voice. Override to set engine-specific defaults.
*
* @return a <code>BaseEngineProperties</code> object specific to
* a subclass.
*/
protected BaseEngineProperties createEngineProperties() {
SynthesizerModeDesc desc = (SynthesizerModeDesc)engineModeDesc;
BaseVoice defaultVoice = (BaseVoice)(desc.getVoices()[0]);
float defaultPitch = defaultVoice.defaultPitch;
float defaultPitchRange = defaultVoice.defaultPitchRange;
float defaultSpeakingRate = defaultVoice.defaultSpeakingRate;
float defaultVolume = defaultVoice.defaultVolume;
return new BaseSynthesizerProperties(defaultVoice,
defaultPitch,
defaultPitchRange,
defaultSpeakingRate,
defaultVolume);
}
/**
* Factory method that creates a <code>BaseSynthesizerQueueItem</code>.
* Override if the synthesizer specializes the
* <code>BaseSynthesizerQueueItem</code> class.
*/
protected BaseSynthesizerQueueItem createQueueItem() {
return new BaseSynthesizerQueueItem();
}
/**
* Returns the list of voices for this <code>Synthesizer</code>.
*
* @return the list of voices for this <code>Synthesizer</code>.
*/
protected VoiceList getVoiceList() {
return voiceList;
}
/**
* Utility function that generates <code>QUEUE_UPDATED</code>
* event and posts it to the event queue. Eventually
* <code>fireQueueUpdated</code> will be called
* by <code>dispatchSpeechEvent</code> as a result of this action.
*
* @param topOfQueueChanged <code>true</code> if the top of the
* queue has changed
* @param oldState the old state of this <code>Synthesizer</code>
* @param newState the new state of this <code>Synthesizer</code>
*
* @see #fireQueueUpdated
* @see #dispatchSpeechEvent
*
*/
public void postQueueUpdated(boolean topOfQueueChanged,
long oldState, long newState) {
SpeechEventUtilities.postSpeechEvent(
this,
new SynthesizerEvent(this,
SynthesizerEvent.QUEUE_UPDATED,
topOfQueueChanged,
oldState, newState));
}
/**
* Utility function that sends a <code>QUEUE_UPDATED</code>
* event to all <code>SynthesizerListeners</code>.
*
* @param event the <code>QUEUE_UPDATED</code> event
*
* @see #postQueueUpdated
* @see #dispatchSpeechEvent
*/
public void fireQueueUpdated(SynthesizerEvent event) {
if (engineListeners == null) {
return;
}
Iterator iterator = engineListeners.iterator();
while (iterator.hasNext()) {
EngineListener el = (EngineListener) iterator.next();
if (el instanceof SynthesizerListener) {
SynthesizerListener sl = (SynthesizerListener)el;
sl.queueUpdated(event);
}
}
}
/**
* Utility function that generates <code>QUEUE_EMPTIED</code>
* event and posts it to the event queue. Eventually
* <code>fireQueueEmptied</code> will be called
* by <code>dispatchSpeechEvent</code> as a result of this action.
*
* @param oldState the old state of this <code>Synthesizer</code>
* @param newState the new state of this <code>Synthesizer</code>
*
* @see #fireQueueEmptied
* @see #dispatchSpeechEvent
*/
public void postQueueEmptied(long oldState, long newState) {
SpeechEventUtilities.postSpeechEvent(
this,
new SynthesizerEvent(this,
SynthesizerEvent.QUEUE_EMPTIED,
false,
oldState, newState));
}
/**
* Utility function that sends a <code>QUEUE_EMPTIED</code>
* event to all <code>SynthesizerListeners</code>.
*
* @param event the <code>QUEUE_EMPTIED</code> event
*
* @see #postQueueEmptied
* @see #dispatchSpeechEvent
*/
public void fireQueueEmptied(SynthesizerEvent event) {
if (engineListeners == null) {
return;
}
Iterator iterator = engineListeners.iterator();
while (iterator.hasNext()) {
EngineListener el = (EngineListener) iterator.next();
if (el instanceof SynthesizerListener) {
SynthesizerListener sl = (SynthesizerListener)el;
sl.queueEmptied(event);
}
}
}
/**
* Dispatches a <code>SpeechEvent</code>.
* The dispatcher should notify all <code>SynthesizerListeners</code>
* from this method. The <code>SpeechEvent</code> was added
* via the various post methods of this class.
*
* @param event the <code>SpeechEvent</code> to dispatch
*
* @see #postQueueUpdated
* @see #postQueueEmptied
*/
public void dispatchSpeechEvent(SpeechEvent event) {
switch (event.getId()) {
case SynthesizerEvent.QUEUE_EMPTIED:
fireQueueEmptied((SynthesizerEvent) event);
break;
case SynthesizerEvent.QUEUE_UPDATED:
fireQueueUpdated((SynthesizerEvent) event);
break;
// Defer to BaseEngine to handle the rest.
//
default:
super.dispatchSpeechEvent(event);
break;
}
}
}