upstream version 1.2.2
[debian/freetts] / com / sun / speech / engine / synthesis / BaseSynthesizerQueueItem.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.URL;
12 import java.util.Iterator;
13
14 import javax.speech.SpeechEvent;
15 import javax.speech.synthesis.JSMLException;
16 import javax.speech.synthesis.Speakable;
17 import javax.speech.synthesis.SpeakableEvent;
18 import javax.speech.synthesis.SpeakableListener;
19 import javax.speech.synthesis.SynthesizerQueueItem;
20
21 import org.w3c.dom.Document;
22
23 import com.sun.speech.engine.SpeechEventDispatcher;
24 import com.sun.speech.engine.SpeechEventUtilities;
25
26 /**
27  * Extends the JSAPI 1.0 <code>SynthesizerQueueItem</code> with handling
28  * for JSML, generation of engine-specific text, and other features.
29  */
30 public class BaseSynthesizerQueueItem extends SynthesizerQueueItem
31     implements SpeechEventDispatcher {
32     private volatile boolean done = false;
33     private volatile boolean cancelled = false;
34
35     /**
36      * The object containing the DOM of the parsed JSML.
37      */
38     private Document document = null;
39     
40     /**
41      * Global count of queue items used for debug.
42      */
43     protected static int itemNumber = 0;
44
45
46     /**
47      * Count for this item used for debug.
48      */
49     protected int thisItemNumber = 0;
50   
51
52     /**
53      * <code>Synthesizer</code> that has queued this item.
54      */
55     protected BaseSynthesizer synth;
56
57     /**
58      * Class constructor.
59      */
60     public BaseSynthesizerQueueItem() {
61         super(null, null, false, null);
62         thisItemNumber = itemNumber++;
63     }
64
65     /**
66      * Sets queue item data with a <code>Speakable</code> source.
67      *
68      * @param synth the synthesizer
69      * @param source the <code>Speakable</code>
70      * @param listener the <code>SpeakableListener</code> to be
71      *   notified as this object is processed
72      *
73      * @throws JSMLException if the <code>source</code> contains JSML errors
74      */
75     protected void setData(BaseSynthesizer synth,
76                            Speakable source, 
77                            SpeakableListener listener)
78         throws JSMLException {
79         this.synth = synth;
80         this.source = source;
81         this.text = source.getJSMLText();
82         this.plainText = false;
83         this.listener = listener;
84         document = new JSMLParser(this.text, false).getDocument();
85     }
86
87     /**
88      * Sets queue item data with a <code>String</code> source that is
89      * either plain text or JSML.
90      *
91      * @param synth the synthesizer
92      * @param source the text
93      * @param plainText <code>true</code> only if the
94      *   <code>source</code> is plain text
95      * @param listener the <code>SpeakableListener</code> to be
96      *   notified as this object is processed
97      *
98      * @throws JSMLException if the <code>source</code> contains JSML errors
99      */
100     protected void setData(BaseSynthesizer synth,
101                            String source, 
102                            boolean plainText,
103                            SpeakableListener listener)
104         throws JSMLException {
105         this.synth = synth;
106         this.source = source;
107         this.text = source;
108         this.plainText = plainText;
109         this.listener = listener;
110         if (!plainText) {
111             document = new JSMLParser(this.text, false).getDocument();
112         }
113     }
114
115
116     /**
117      * Sets queue item data with a <code>URL</code> source.
118      * 
119      * @param synth the synthesizer
120      * @param source the <code>URL</code> containing JSML text
121      * @param listener the <code>SpeakableListener</code> to be
122      *   notified as this object is processed
123      *
124      * @throws JSMLException if the <code>source</code> contains JSML errors
125      * @throws IOException if there are problems working with the URL.
126      */
127     protected void setData(BaseSynthesizer synth,
128                            URL source, 
129                            SpeakableListener listener)
130         throws JSMLException, IOException {
131         this.synth = synth;
132         this.source = source;
133         this.text = null;
134         this.plainText = false;
135         this.listener = listener;
136         document = new JSMLParser(source, false).getDocument();
137     }
138
139     /**
140      * Gets the DOM document for this object.
141      *
142      * @return the DOM document for this object.
143      */
144     protected Document getDocument() {
145         return document;
146     }
147
148
149     /**
150      * determines if this queue item has been canceled
151      * 
152      * @return <code> true </code> if this item has been canceled; 
153      *   otherwise <code> false </code>
154      */
155     protected synchronized boolean isCancelled() {
156         return cancelled;
157     }
158
159
160     /**
161      * returns true if this queue item has been 
162      * processed.
163      * @return true if it has been processed
164      */
165     public synchronized boolean isCompleted() {
166         return done;
167     }
168
169     /**
170      * wait for this queue item to be completed
171      *
172      * @return true if the item was completed successfully, false if
173      * the item was canceled or an error occurred.
174      */
175     public synchronized boolean waitCompleted() {
176         while  (!isCompleted()) {
177             try {
178                 wait();
179             } catch (InterruptedException ie) {
180                 System.err.println(
181                         "FreeTTSSynthesizerQueueItem.Wait interrupted");
182                 return false;
183             }
184         }
185         return !isCancelled();
186     }
187
188     /**
189      * indicate that this item has been canceled
190      */
191     public synchronized void cancelled() {
192         postSpeakableCancelled();
193         notifyAll();
194     }
195
196     /**
197      * indicate that this item has been completed
198      */
199     public synchronized void completed() {
200         postSpeakableEnded();
201         notifyAll();
202     }
203
204     /**
205      * indicate that this item has been started
206      */
207     public void started() {
208         postSpeakableStarted();
209     }
210     
211     /**
212      * Gets the item number for debug purposes only.  Each queue item
213      * is given a unique ID.
214      *
215      * @return the unique ID for this queue item
216      */
217     public int getItemNumber() {
218         return thisItemNumber;
219     }
220     
221     /**
222      * Utility function that generates a
223      * <code>MARKER_REACHED</code> event and posts it
224      * to the event queue.  Eventually
225      * <code>fireMarkerReached</code> will be called
226      * by <code>dispatchSpeechEvent</code> as a result
227      * of this action.
228      *
229      * @param text the text of the marker
230      * @param markerType the type of marker
231      *
232      * @see SpeakableEvent#getMarkerType
233      * @see #fireMarkerReached
234      * @see #dispatchSpeechEvent
235      */
236     public void postMarkerReached(String text, int markerType) {
237         SpeechEventUtilities.postSpeechEvent(
238             this,
239             new SpeakableEvent(source,
240                                SpeakableEvent.MARKER_REACHED,
241                                text, markerType));
242     }
243
244     /**
245      * Utility function that sends a <code>MARKER_REACHED</code> event
246      * to all speakable listeners.
247      *
248      * @param event the <code>MARKER_REACHED</code> event
249      *
250      * @see #postMarkerReached
251      */
252     public void fireMarkerReached(SpeakableEvent event) {
253         if (listener != null) {
254             listener.markerReached(event);
255         }
256         
257
258         if (synth.speakableListeners != null) {
259             Iterator iterator = synth.speakableListeners.iterator();
260             while (iterator.hasNext()) {
261                 SpeakableListener sl = (SpeakableListener) iterator.next();
262                 sl.markerReached(event);
263             }
264         }
265     }
266
267     /**
268      * Utility function that generates a
269      * <code>SPEAKABLE_CANCELLED</code> event and posts it
270      * to the event queue.  Eventually
271      * <code>fireSpeakableCancelled</code> will be called
272      * by <code>dispatchSpeechEvent</code> as a result
273      * of this action.
274      *
275      * @see #fireSpeakableCancelled
276      * @see #dispatchSpeechEvent
277      */
278     public void postSpeakableCancelled() {
279         boolean shouldPost;
280
281     // The JSAPI docs say that once a canceled event is sent, no
282     // others will be. This makes sure that a canceled will never be
283     // sent twice. This deals with the race that can occur when an
284     // item that is playing is canceled.
285
286         synchronized(this) {
287             shouldPost = !done;
288             done = true;
289             cancelled = true;
290         }
291         if (shouldPost) {
292             SpeechEventUtilities.postSpeechEvent(
293                 this,
294                 new SpeakableEvent(source, SpeakableEvent.SPEAKABLE_CANCELLED));
295         }
296     }
297
298     /**
299      * Utility function that sends a <code>SPEAKABLE_CANCELLED</code> event
300      * to all speakable listeners.
301      *
302      * @param event the <code>SPEAKABLE_CANCELLED</code> event
303      *
304      * @see #postSpeakableCancelled
305      */
306     public void fireSpeakableCancelled(SpeakableEvent event) {
307         if (listener != null) {
308             listener.speakableCancelled(event);
309         }
310         
311
312         if (synth.speakableListeners != null) {
313             Iterator iterator = synth.speakableListeners.iterator();
314             while (iterator.hasNext()) {
315                 SpeakableListener sl = (SpeakableListener) iterator.next();
316                 sl.speakableCancelled(event);
317             }
318         }
319     }
320
321     /**
322      * Utility function that generates a
323      * <code>SPEAKABLE_ENDED</code> event and posts it
324      * to the event queue.  Eventually
325      * <code>fireSpeakableEnded</code> will be called
326      * by <code>dispatchSpeechEvent</code> as a result
327      * of this action.
328      *
329      * @see #fireSpeakableEnded
330      * @see #dispatchSpeechEvent
331      */
332     public void postSpeakableEnded() {
333         boolean shouldPost;
334
335     // The JSAPI docs say that once a canceled event is sent, no
336     // others will be. This makes sure that a canceled will never be
337     // sent twice. This deals with the race that can occur when an
338     // item that is playing is canceled.
339         synchronized(this) {
340             shouldPost = !done;
341             done = true;
342         }
343         if (shouldPost) {
344             SpeechEventUtilities.postSpeechEvent(
345                 this,
346                 new SpeakableEvent(source, SpeakableEvent.SPEAKABLE_ENDED));
347         }
348     }
349
350
351     /**
352      * Utility function that sends a <code>SPEAKABLE_ENDED</code> event
353      * to all speakable listeners.
354      *
355      * @param event the <code>SPEAKABLE_ENDED</code> event
356      *
357      * @see #postSpeakableEnded
358      */
359     public void fireSpeakableEnded(SpeakableEvent event) {
360         if (listener != null) {
361             listener.speakableEnded(event);
362         }
363         
364
365         if (synth.speakableListeners != null) {
366             Iterator iterator = synth.speakableListeners.iterator();
367             while (iterator.hasNext()) {
368                 SpeakableListener sl = (SpeakableListener) iterator.next();
369                 sl.speakableEnded(event);
370             }
371         }
372     }
373
374     /**
375      * Utility function that generates a
376      * <code>SPEAKABLE_PAUSED</code> event and posts it
377      * to the event queue.  Eventually
378      * <code>fireSpeakablePaused</code> will be called
379      * by <code>dispatchSpeechEvent</code> as a result
380      * of this action.
381      *
382      * @see #fireSpeakablePaused
383      * @see #dispatchSpeechEvent
384      */
385     public void postSpeakablePaused() {
386         SpeechEventUtilities.postSpeechEvent(
387             this,
388             new SpeakableEvent(source, SpeakableEvent.SPEAKABLE_PAUSED));
389     }
390
391     /**
392      * Utility function that sends a <code>SPEAKABLE_PAUSED</code> event
393      * to all speakable listeners.
394      *
395      * @param event the <code>SPEAKABLE_PAUSED</code> event
396      *
397      * @see #postSpeakablePaused
398      */
399     public void fireSpeakablePaused(SpeakableEvent event) {
400         if (listener != null) {
401             listener.speakablePaused(event);
402         }
403         
404
405         if (synth.speakableListeners != null) {
406             Iterator iterator = synth.speakableListeners.iterator();
407             while (iterator.hasNext()) {
408                 SpeakableListener sl = (SpeakableListener) iterator.next();
409                 sl.speakablePaused(event);
410             }
411         }
412     }
413
414     /**
415      * Utility function that generates a
416      * <code>SPEAKABLE_RESUMED</code> event and posts it
417      * to the event queue.  Eventually
418      * <code>fireSpeakableResumed</code> will be called
419      * by <code>dispatchSpeechEvent</code> as a result
420      * of this action.
421      *
422      * @see #fireSpeakableResumed
423      * @see #dispatchSpeechEvent
424      */
425     public void postSpeakableResumed() {
426         SpeechEventUtilities.postSpeechEvent(
427             this,
428             new SpeakableEvent(source, SpeakableEvent.SPEAKABLE_RESUMED));
429     }
430
431     /**
432      * Utility function that sends a <code>SPEAKABLE_RESUMED</code> event
433      * to all speakable listeners.
434      *
435      * @param event the <code>SPEAKABLE_RESUMED</code> event
436      *
437      * @see #postSpeakableResumed
438      */
439     public void fireSpeakableResumed(SpeakableEvent event) {
440         if (listener != null) {
441             listener.speakableResumed(event);
442         }
443         
444
445         if (synth.speakableListeners != null) {
446             Iterator iterator = synth.speakableListeners.iterator();
447             while (iterator.hasNext()) {
448                 SpeakableListener sl =
449                     (SpeakableListener) iterator.next();
450                 sl.speakableResumed(event);
451             }
452         }
453     }
454
455     /**
456      * Utility function that generates a
457      * <code>SPEAKABLE_STARTED</code> event and posts it
458      * to the event queue.  Eventually
459      * <code>fireSpeakableStarted</code> will be called
460      * by <code>dispatchSpeechEvent</code> as a result
461      * of this action.
462      *
463      * @see #fireSpeakableStarted
464      * @see #dispatchSpeechEvent
465      */
466     public void postSpeakableStarted() {
467         SpeechEventUtilities.postSpeechEvent(
468             this,
469             new SpeakableEvent(source, SpeakableEvent.SPEAKABLE_STARTED));
470     }
471
472     /**
473      * Utility function that sends a <code>SPEAKABLE_STARTED</code> event
474      * to all speakable listeners.
475      *
476      * @param event the <code>SPEAKABLE_STARTED</code> event
477      *
478      * @see #postSpeakableStarted
479      */
480     public void fireSpeakableStarted(SpeakableEvent event) {
481         if (listener != null) {
482             listener.speakableStarted(event);
483         }
484         
485
486         if (synth.speakableListeners != null) {
487             Iterator iterator = synth.speakableListeners.iterator();
488             while (iterator.hasNext()) {
489                 SpeakableListener sl = (SpeakableListener) iterator.next();
490                 sl.speakableStarted(event);
491             }
492         }
493     }
494
495     /**
496      * Utility function that generates a
497      * <code>TOP_OF_QUEUE</code> event and posts it
498      * to the event queue.  Eventually
499      * <code>fireTopOfQueue</code> will be called
500      * by <code>dispatchSpeechEvent</code> as a result
501      * of this action.
502      *
503      * @see #fireTopOfQueue
504      * @see #dispatchSpeechEvent
505      */
506     public void postTopOfQueue() {
507         SpeechEventUtilities.postSpeechEvent(
508             this,
509             new SpeakableEvent(source, SpeakableEvent.TOP_OF_QUEUE));
510     }
511
512     /**
513      * Utility function that sends a <code>TOP_OF_QUEUE</code> event
514      * to all speakable listeners.
515      *
516      * @param event the <code>TOP_OF_QUEUE</code> event
517      *
518      * @see #postTopOfQueue
519      */
520     public void fireTopOfQueue(SpeakableEvent event) {
521         if (listener != null) {
522             listener.topOfQueue(event);
523         }
524         
525
526         if (synth.speakableListeners != null) {
527             Iterator iterator = synth.speakableListeners.iterator();
528             while (iterator.hasNext()) {
529                 SpeakableListener sl = (SpeakableListener) iterator.next();
530                 sl.topOfQueue(event);
531             }
532         }
533     }
534
535     /**
536      * Utility function that generates a
537      * <code>WORD_STARTED</code> event and posts it
538      * to the event queue.  Eventually
539      * <code>fireWordStarted</code> will be called
540      * by <code>dispatchSpeechEvent</code> as a result
541      * of this action.
542      *
543      * @see #fireWordStarted
544      * @see #dispatchSpeechEvent
545      */
546     public void postWordStarted(String text, int wordStart, int wordEnd) {
547         SpeechEventUtilities.postSpeechEvent(
548             this,
549             new SpeakableEvent(source, SpeakableEvent.WORD_STARTED,
550                                text, wordStart, wordEnd));
551     }
552
553     /**
554      * Utility function that sends a <code>WORD_STARTED</code> event
555      * to all speakable listeners.
556      *
557      * @param event the <code>WORD_STARTED</code> event
558      *
559      * @see #postWordStarted
560      */
561     public void fireWordStarted(SpeakableEvent event) {
562         if (listener != null) {
563             listener.wordStarted(event);
564         }
565         
566
567         if (synth.speakableListeners != null) {
568             Iterator iterator = synth.speakableListeners.iterator();
569             while (iterator.hasNext()) {
570                 SpeakableListener sl = (SpeakableListener) iterator.next();
571                 sl.wordStarted(event);
572             }
573         }
574     }
575
576     /**
577      * Dispatches a <code>SpeechEvent</code>.
578      * The dispatcher should notify all <code>EngineListeners</code>
579      * from this method.  The <code>SpeechEvent</code> was added
580      * via the various post methods of this class.
581      *
582      * @param event the <code>SpeechEvent</code> to dispatch
583      *
584      * @see #postMarkerReached
585      * @see #postSpeakableCancelled
586      * @see #postSpeakableEnded
587      * @see #postSpeakablePaused
588      * @see #postSpeakableResumed
589      * @see #postSpeakableStarted
590      * @see #postTopOfQueue
591      * @see #postWordStarted
592      */
593     public void dispatchSpeechEvent(SpeechEvent event) {
594         switch (event.getId()) {
595             case SpeakableEvent.MARKER_REACHED:
596                 fireMarkerReached((SpeakableEvent) event);
597                 break;
598             case SpeakableEvent.SPEAKABLE_CANCELLED:
599                 fireSpeakableCancelled((SpeakableEvent) event);
600                 break;
601             case SpeakableEvent.SPEAKABLE_ENDED:
602                 fireSpeakableEnded((SpeakableEvent) event);
603                 break;
604             case SpeakableEvent.SPEAKABLE_PAUSED:
605                 fireSpeakablePaused((SpeakableEvent) event);
606                 break;
607             case SpeakableEvent.SPEAKABLE_RESUMED:
608                 fireSpeakableResumed((SpeakableEvent) event);
609                 break;
610             case SpeakableEvent.SPEAKABLE_STARTED:
611                 fireSpeakableStarted((SpeakableEvent) event);
612                 break;
613             case SpeakableEvent.TOP_OF_QUEUE:
614                 fireTopOfQueue((SpeakableEvent) event);
615                 break;
616             case SpeakableEvent.WORD_STARTED:
617                 fireWordStarted((SpeakableEvent) event);
618                 break;
619         }
620     }
621 }
622