upstream version 1.2.2
[debian/freetts] / com / sun / speech / engine / synthesis / BaseSynthesizer.java
1 /**
2  * Copyright 1998-2001 Sun Microsystems, Inc.
3  * 
4  * See the file "license.terms" for information on usage and
5  * redistribution of this file, and for a DISCLAIMER OF ALL 
6  * WARRANTIES.
7  */
8 package com.sun.speech.engine.synthesis;
9
10 import java.io.IOException;
11 import java.net.MalformedURLException;
12 import java.net.URL;
13 import java.util.Collection;
14 import java.util.Enumeration;
15 import java.util.Iterator;
16
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;
28
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;
33
34 /**
35  * Supports the JSAPI 1.0 <code>Synthesizer</code> interface that
36  * performs the core non-engine-specific functions.
37  * 
38  * <p>An actual JSAPI synthesizer implementation needs to extend or
39  * modify this implementation.
40  */
41 abstract public class BaseSynthesizer extends BaseEngine
42     implements Synthesizer, SpeechEventDispatcher {
43
44     /**
45      * Set of speakable listeners belonging to the <code>Synthesizer</code>.
46      * Each item on queue may have an individual listener too.
47      *
48      * @see SpeakableListener
49      */
50     protected Collection speakableListeners;
51
52     /**
53      * The set of voices available in this <code>Synthesizer</code>.
54      * The list can be created in the constructor methods.
55      */
56     protected VoiceList voiceList;
57     
58     /**
59      * Creates a new Synthesizer in the <code>DEALLOCATED</code> state.
60      *
61      * @param mode the operating mode of this <code>Synthesizer</code>
62      */
63     public BaseSynthesizer(SynthesizerModeDesc mode) {
64         super(mode);
65         speakableListeners = new java.util.ArrayList();
66         voiceList = new VoiceList(mode);
67     }
68
69     /**
70      * Speaks JSML text provided as a <code>Speakable</code> object.
71      *
72      * @param jsmlText the JSML text to speak
73      * @param listener the listener to be notified as the
74      *   <code>jsmlText</code> is processed
75      *
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
80      */
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);
86         appendQueue(item);
87     }
88
89     /**
90      * Speaks JSML text provided as a <code>URL</code>.
91      *
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
95      *
96      * @throws EngineStateError 
97      *   if this <code>Synthesizer</code> in the <code>DEALLOCATED</code> or 
98      *   <code>DEALLOCATING_RESOURCES</code> states
99      * @throws IOException
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>
104      */
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);
111         appendQueue(item);
112     }
113
114     /**
115      * Speaks JSML text provided as a <code>String</code>.
116      *
117      * @param jsmlText a <code>String</code> containing JSML.
118      * @param listener the listener to be notified as the
119      *   JSML text is processed
120      *
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
125      */
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);
131         appendQueue(item);
132     }
133
134     /**
135      * Speaks a plain text <code>String</code>.  No JSML parsing is
136      * performed.
137      *
138      * @param text a <code>String</code> containing plain text.
139      * @param listener the listener to be notified as the
140      *   text is processed
141      *
142      * @throws EngineStateError 
143      *   if this <code>Synthesizer</code> in the <code>DEALLOCATED</code> or 
144      *   <code>DEALLOCATING_RESOURCES</code> states
145      */
146     public void speakPlainText(String text, SpeakableListener listener)
147         throws EngineStateError {
148         checkEngineState(DEALLOCATED | DEALLOCATING_RESOURCES);
149         try {
150             BaseSynthesizerQueueItem item = createQueueItem();
151             item.setData(this, text, true, listener);
152             appendQueue(item);
153         } catch (JSMLException e) {
154             throw new RuntimeException("JSMLException should never occur");
155         }
156     }
157
158     /**
159      * Returns a String of the names of all the states implied
160      * in the given bit pattern.
161      *
162      * @param state the bit pattern of states
163      *
164      * @return a String of the names of all the states implied
165      *   in the given bit pattern.
166      */
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();
174     }
175
176     /**
177      * Puts an item on the speaking queue and sends a queue updated
178      * event.
179      *
180      * @param item the item to add to the queue
181      *
182      */
183     abstract protected void appendQueue(BaseSynthesizerQueueItem item);
184
185     /**
186      * Optional method that converts a text string to a phoneme string.
187      *
188      * @param text
189      *   plain text to be converted to phonemes
190      *
191      * @return
192      *   IPA phonemic representation of text or <code>null</code>
193      *
194      * @throws EngineStateError 
195      *   if this <code>Synthesizer</code> in the <code>DEALLOCATED</code> or 
196      *   <code>DEALLOCATING_RESOURCES</code> states
197      */
198     public String phoneme(String text) throws EngineStateError {
199         checkEngineState(DEALLOCATED | DEALLOCATING_RESOURCES);
200         
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.
204         //
205         return null;
206     }
207
208     /**
209      * Returns an enumeration of the queue.
210      *
211      * @return
212      *   an <code>Enumeration</code> of the speech output queue or
213      *   <code>null</code>.
214      *
215      * @throws EngineStateError 
216      *   if this <code>Synthesizer</code> in the <code>DEALLOCATED</code> or 
217      *   <code>DEALLOCATING_RESOURCES</code> states
218      */
219     abstract public Enumeration enumerateQueue() throws EngineStateError;
220
221     /**
222      * Cancels the item at the top of the queue.
223      *
224      * @throws EngineStateError 
225      *   if this <code>Synthesizer</code> in the <code>DEALLOCATED</code> or 
226      *   <code>DEALLOCATING_RESOURCES</code> states
227      */
228     abstract public void cancel() throws EngineStateError;
229
230     /**
231      * Cancels a specific object on the queue.
232      *
233      * @param source
234      *    object to be removed from the speech output queue
235      *
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
241      */
242     abstract public void cancel(Object source)
243         throws IllegalArgumentException, EngineStateError;
244
245     /**
246      * Cancels all items on the output queue.
247      *
248      * @throws EngineStateError 
249      *   if this <code>Synthesizer</code> in the <code>DEALLOCATED</code> or 
250      *   <code>DEALLOCATING_RESOURCES</code> states
251      */
252     abstract public void cancelAll() throws EngineStateError;
253
254     /**
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.
260      *
261      * @return the <code>SynthesizerProperties</code> object for this
262      *   <code>Synthesizer</code>
263      */
264     public SynthesizerProperties getSynthesizerProperties() {
265         checkEngineState(DEALLOCATED | DEALLOCATING_RESOURCES);
266         return (SynthesizerProperties) getEngineProperties();
267     }
268
269     /**
270      * Adds a <code>SpeakableListener</code> to this <code>Synthesizer</code>.
271      *
272      * @param listener the listener to add
273      *
274      * @see #removeSpeakableListener
275      */
276     public void addSpeakableListener(SpeakableListener listener) {
277         if (!speakableListeners.contains(listener)) {
278             speakableListeners.add(listener);
279         }
280     }
281
282     /**
283      * Removes a <code>SpeakableListener</code> from this
284      * <code>Synthesizer</code>.
285      *
286      * @param listener the listener to remove
287      *
288      * @see #addSpeakableListener
289      */
290     public void removeSpeakableListener(SpeakableListener listener) {
291         speakableListeners.remove(listener);
292     }
293
294     /**
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.
300      *
301      * @return a <code>BaseEngineProperties</code> object specific to
302      *   a subclass.
303      */
304     protected BaseEngineProperties createEngineProperties() {
305         SynthesizerModeDesc desc = (SynthesizerModeDesc)engineModeDesc;
306         BaseVoice defaultVoice = (BaseVoice)(desc.getVoices()[0]);
307         
308         float defaultPitch = defaultVoice.defaultPitch;
309         float defaultPitchRange = defaultVoice.defaultPitchRange;
310         float defaultSpeakingRate = defaultVoice.defaultSpeakingRate;
311         float defaultVolume = defaultVoice.defaultVolume;
312         
313         return new BaseSynthesizerProperties(defaultVoice,
314                                              defaultPitch,
315                                              defaultPitchRange,
316                                              defaultSpeakingRate,
317                                              defaultVolume);
318     }
319
320     /**
321      * Factory method that creates a <code>BaseSynthesizerQueueItem</code>.
322      * Override if the synthesizer specializes the
323      * <code>BaseSynthesizerQueueItem</code> class.
324      */
325     protected BaseSynthesizerQueueItem createQueueItem() {
326         return new BaseSynthesizerQueueItem();
327     }
328
329     /**
330      * Returns the list of voices for this <code>Synthesizer</code>.
331      *
332      * @return the list of voices for this <code>Synthesizer</code>.
333      */
334     protected VoiceList getVoiceList() {
335         return voiceList;
336     }    
337
338     /**
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.
343      *
344      * @param topOfQueueChanged <code>true</code> if the top of the
345      *   queue has changed
346      * @param oldState the old state of this <code>Synthesizer</code>
347      * @param newState the new state of this <code>Synthesizer</code>
348      *
349      * @see #fireQueueUpdated
350      * @see #dispatchSpeechEvent
351      * 
352      */
353     public void postQueueUpdated(boolean topOfQueueChanged,
354                                  long oldState, long newState) {
355         SpeechEventUtilities.postSpeechEvent(
356             this,
357             new SynthesizerEvent(this,
358                                  SynthesizerEvent.QUEUE_UPDATED,
359                                  topOfQueueChanged,
360                                  oldState, newState));
361     }
362     
363     /**
364      * Utility function that sends a <code>QUEUE_UPDATED</code>
365      * event to all <code>SynthesizerListeners</code>.  
366      *
367      * @param event the <code>QUEUE_UPDATED</code> event
368      *
369      * @see #postQueueUpdated
370      * @see #dispatchSpeechEvent
371      */
372     public void fireQueueUpdated(SynthesizerEvent event) {
373         if (engineListeners == null) {
374             return;
375         }
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);
382             }
383         }
384     }
385     
386     /**
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.
391      *
392      * @param oldState the old state of this <code>Synthesizer</code>
393      * @param newState the new state of this <code>Synthesizer</code>
394      *
395      * @see #fireQueueEmptied
396      * @see #dispatchSpeechEvent
397      */
398     public void postQueueEmptied(long oldState, long newState) {
399         SpeechEventUtilities.postSpeechEvent(
400             this,
401             new SynthesizerEvent(this,
402                                  SynthesizerEvent.QUEUE_EMPTIED,
403                                  false,
404                                  oldState, newState));
405     }
406     
407     /**
408      * Utility function that sends a <code>QUEUE_EMPTIED</code>
409      * event to all <code>SynthesizerListeners</code>.  
410      *
411      * @param event the <code>QUEUE_EMPTIED</code> event
412      *
413      * @see #postQueueEmptied
414      * @see #dispatchSpeechEvent
415      */
416     public void fireQueueEmptied(SynthesizerEvent event) {
417         if (engineListeners == null) {
418             return;
419         }
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);
426             }
427         }
428     }
429
430     /**
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.
435      *
436      * @param event the <code>SpeechEvent</code> to dispatch
437      *
438      * @see #postQueueUpdated
439      * @see #postQueueEmptied
440      */
441     public void dispatchSpeechEvent(SpeechEvent event) {
442         switch (event.getId()) {
443             case SynthesizerEvent.QUEUE_EMPTIED:
444                 fireQueueEmptied((SynthesizerEvent) event);
445                 break;
446             case SynthesizerEvent.QUEUE_UPDATED:
447                 fireQueueUpdated((SynthesizerEvent) event);
448                 break;
449
450             // Defer to BaseEngine to handle the rest.
451             //
452             default:
453                 super.dispatchSpeechEvent(event);
454                 break;
455         }
456     }
457 }