2 * Copyright 2003 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.freetts.jsapi;
10 import java.beans.PropertyVetoException;
11 import java.util.Enumeration;
12 import java.util.Iterator;
13 import java.util.Vector;
14 import java.util.logging.Logger;
16 import javax.speech.EngineException;
17 import javax.speech.EngineStateError;
18 import javax.speech.synthesis.SynthesizerModeDesc;
20 import com.sun.speech.engine.BaseEngineProperties;
21 import com.sun.speech.engine.synthesis.BaseSynthesizer;
22 import com.sun.speech.engine.synthesis.BaseSynthesizerProperties;
23 import com.sun.speech.engine.synthesis.BaseSynthesizerQueueItem;
24 import com.sun.speech.engine.synthesis.BaseVoice;
25 import com.sun.speech.freetts.OutputQueue;
26 import com.sun.speech.freetts.audio.AudioPlayer;
29 * Provides partial support for a JSAPI 1.0 synthesizer for the
30 * FreeTTS speech synthesis system.
32 public class FreeTTSSynthesizer extends BaseSynthesizer {
33 /** Logger instance. */
34 private static final Logger LOGGER =
35 Logger.getLogger(FreeTTSSynthesizer.class.getName());
38 * Reference to output thread.
40 OutputHandler outputHandler;
43 * The currently active voice for this synthesizer
45 private FreeTTSVoice curVoice;
47 private AudioPlayer audio;
50 * All voice output for this synthesizer goes through
51 * this central utterance queue
53 private OutputQueue outputQueue;
56 * Creates a new Synthesizer in the DEALLOCATED state.
58 * @param desc describes the allowed mode of operations for this
61 public FreeTTSSynthesizer(FreeTTSSynthesizerModeDesc desc) {
63 outputHandler = new OutputHandler();
68 * Starts the output thread. The output thread is responsible for
69 * taking items off of the queue and sending them to the audio
72 * @throws EngineException if an allocation error occurs
74 protected void handleAllocate() throws EngineException {
77 FreeTTSSynthesizerModeDesc desc = (FreeTTSSynthesizerModeDesc)
81 outputQueue = com.sun.speech.freetts.Voice.createOutputThread();
83 if (desc.getVoices().length > 0) {
84 FreeTTSVoice freettsVoice = (FreeTTSVoice) desc.getVoices()[0];
85 ok = setCurrentVoice(freettsVoice);
91 synchronized (engineStateLock) {
92 long newState = ALLOCATED | RESUMED;
93 newState |= (outputHandler.isQueueEmpty()
96 states = setEngineState(CLEAR_ALL_STATE, newState);
98 outputHandler.start();
99 postEngineAllocated(states[0], states[1]);
101 throw new EngineException("Can't allocate FreeTTS synthesizer");
108 * Sets the given voice to be the current voice. If
109 * the voice cannot be loaded, this call has no affect.
111 * @param voice the new voice.
113 private boolean setCurrentVoice(FreeTTSVoice voice)
114 throws EngineException {
116 com.sun.speech.freetts.Voice freettsVoice = voice.getVoice();
120 if (!freettsVoice.isLoaded()) {
121 freettsVoice.setOutputQueue(outputQueue);
122 freettsVoice.allocate();
123 audio = freettsVoice.getAudioPlayer();
125 audio = new com.sun.speech.freetts.audio.JavaClipAudioPlayer();
128 throw new EngineException("Can't get audio player");
130 freettsVoice.setAudioPlayer(audio);
133 if (freettsVoice.isLoaded()) {
136 // notify the world of potential property changes
137 FreeTTSSynthesizerProperties props =
138 (FreeTTSSynthesizerProperties) getSynthesizerProperties();
139 props.checkForPropertyChanges();
145 * Handles a deallocation request. Cancels all pending items,
146 * terminates the output handler, and posts the state changes.
148 * @throws EngineException if a deallocation error occurs
150 protected void handleDeallocate() throws EngineException {
151 long[] states = setEngineState(CLEAR_ALL_STATE, DEALLOCATED);
152 outputHandler.cancelAllItems();
153 outputHandler.terminate();
155 // Close the audio. This should flush out any queued audio data
163 postEngineDeallocated(states[0], states[1]);
167 * Factory method to create a BaseSynthesizerQueueItem.
169 * @return a queue item appropriate for this synthesizer
171 protected BaseSynthesizerQueueItem createQueueItem() {
172 return new FreeTTSSynthesizerQueueItem();
176 * Returns an enumeration of the queue.
178 * @return an enumeration of the contents of the queue. This
179 * enumeration contains FreeTTSSynthesizerQueueItem objects
181 * @throws EngineStateError if the engine was not in the proper
184 public Enumeration enumerateQueue() throws EngineStateError {
185 checkEngineState(DEALLOCATED | DEALLOCATING_RESOURCES);
186 return outputHandler.enumerateQueue();
190 * Places an item on the speaking queue and send the queue update event.
192 * @param item the item to place in the queue
194 protected void appendQueue(BaseSynthesizerQueueItem item) {
195 outputHandler.appendQueue((FreeTTSSynthesizerQueueItem) item);
199 * Cancels the item at the top of the queue.
201 * @throws EngineStateError if the synthesizer is not in the
204 public void cancel() throws EngineStateError {
205 checkEngineState(DEALLOCATED | DEALLOCATING_RESOURCES);
206 outputHandler.cancelItem();
210 * Cancels a specific object on the queue.
212 * @param source the object to cancel
214 * @throws IllegalArgumentException if the source object is not
215 * currently in the queue
216 * @throws EngineStateError the synthesizer is not in the
219 public void cancel(Object source)
220 throws IllegalArgumentException, EngineStateError {
221 checkEngineState(DEALLOCATED | DEALLOCATING_RESOURCES);
222 outputHandler.cancelItem(source);
226 * Cancels all items on the output queue.
228 * @throws EngineStateError
230 public void cancelAll() throws EngineStateError {
231 checkEngineState(DEALLOCATED | DEALLOCATING_RESOURCES);
232 outputHandler.cancelAllItems();
238 protected void handlePause() {
245 protected void handleResume() {
250 * Factory constructor for EngineProperties object.
251 * Gets the default speaking voice from the SynthesizerModeDesc.
252 * Takes the default prosody values (pitch, range, volume, rate)
253 * from the default voice.
254 * Override to set engine-specific defaults.
256 protected BaseEngineProperties createEngineProperties() {
257 SynthesizerModeDesc desc = (SynthesizerModeDesc)engineModeDesc;
258 FreeTTSVoice defaultVoice = (FreeTTSVoice)(desc.getVoices()[0]);
259 return new FreeTTSSynthesizerProperties(defaultVoice,
260 defaultVoice.getPitch(),
261 defaultVoice.getPitchRange(),
262 defaultVoice.getSpeakingRate(),
263 defaultVoice.getVolume());
267 * Manages the FreeTTS synthesizer properties
269 class FreeTTSSynthesizerProperties extends BaseSynthesizerProperties {
274 * @param defaultVoice the voice to use as the default for
276 * @param defaultPitch the default pitch in hertz
277 * @param defaultPitchRange the default range of pitch in
279 * @param defaultSpeakingRate the default speaking rate in
281 * @param defaultVolume the default speaking volume
284 FreeTTSSynthesizerProperties(
285 BaseVoice defaultVoice,
287 float defaultPitchRange,
288 float defaultSpeakingRate,
289 float defaultVolume) {
291 super(defaultVoice, defaultPitch, defaultPitchRange,
292 defaultSpeakingRate, defaultVolume);
296 * Resets the properties to their default values
298 public void reset() {
303 * Checks to see if any properties have changed and if so
304 * fires the proper events
306 void checkForPropertyChanges() {
308 float pitch = getPitch();
309 if (pitch != currentPitch) {
310 super.setPitch(pitch);
313 float pitchRange = getPitchRange();
314 if (pitchRange != currentPitchRange) {
315 super.setPitchRange(pitchRange);
318 float volume = getVolume();
319 if (volume != currentVolume) {
320 super.setVolume(volume);
323 float speakingRate = getSpeakingRate();
324 if (speakingRate != currentSpeakingRate) {
325 super.setSpeakingRate(speakingRate);
328 } catch (PropertyVetoException pve) {
329 // the actual properties in the voices have
330 // already changed to these new values so
331 // we should not expect a PropertyVetoException
336 * Get the baseline pitch for synthesis
338 * @return the current pitch (in hertz)
340 public float getPitch() {
341 com.sun.speech.freetts.Voice voice = curVoice.getVoice();
342 return voice.getPitch();
346 * Sets the voice to a voice that matches the given voice
348 * @param voice the voice that matches it
350 public void setVoice(javax.speech.synthesis.Voice voice) {
351 if (!curVoice.match(voice)) {
352 // chase through the voice list and find the first match
353 // and use that. If no match, just ignore it.
354 FreeTTSSynthesizerModeDesc desc =
355 (FreeTTSSynthesizerModeDesc) getEngineModeDesc();
356 javax.speech.synthesis.Voice voices[] = desc.getVoices();
357 for (int i = 0; i < voices.length; i++) {
358 if (voices[i].match(voice)) {
360 if (setCurrentVoice((FreeTTSVoice) voices[i])) {
362 super.setVoice(voice);
364 } catch (PropertyVetoException pve) {
368 } catch (EngineException ee) {
369 System.err.println("Engine Exception: " +
378 * Set the baseline pitch for the current synthesis voice.
380 * @param hertz sets the current pitch
382 * @throws PropertyVetoException if the synthesizer rejects or
383 * limits the new value
385 public void setPitch(float hertz) throws PropertyVetoException {
386 if (hertz != getPitch()) {
387 com.sun.speech.freetts.Voice voice = curVoice.getVoice();
388 voice.setPitch(hertz);
389 super.setPitch(hertz);
395 * Get the pitch range for synthesis.
397 * @return the current range of pitch in hertz
399 public float getPitchRange() {
400 com.sun.speech.freetts.Voice voice = curVoice.getVoice();
401 return voice.getPitchRange();
405 * Set the pitch range for the current synthesis voice.
407 * @throws PropertyVetoException if the synthesizer rejects or
408 * limits the new value
410 public void setPitchRange(float hertz) throws PropertyVetoException {
411 if (hertz != getPitchRange()) {
412 com.sun.speech.freetts.Voice voice = curVoice.getVoice();
413 voice.setPitchRange(hertz);
414 super.setPitchRange(hertz);
419 * Gets the current target speaking rate.
421 * @return the current speaking rate in words per minute
423 public float getSpeakingRate() {
424 com.sun.speech.freetts.Voice voice = curVoice.getVoice();
425 return voice.getRate();
429 * Set the target speaking rate.
431 * @param wpm sets the target speaking rate in
434 * @throws PropertyVetoException if the synthesizer rejects or
435 * limits the new value
437 public void setSpeakingRate(float wpm) throws PropertyVetoException {
438 if (wpm != getSpeakingRate()) {
439 com.sun.speech.freetts.Voice voice = curVoice.getVoice();
441 super.setSpeakingRate(wpm);
446 * Gets the current volume.
448 * @return the current volume setting (between 0 and 1.0)
450 public float getVolume() {
451 com.sun.speech.freetts.Voice voice = curVoice.getVoice();
452 return voice.getVolume();
458 * @param volume the new volume setting (between 0 and 1)
460 * @throws PropertyVetoException if the synthesizer rejects or
461 * limits the new value
463 public void setVolume(float volume) throws PropertyVetoException {
466 else if (volume < 0.0f)
469 if (volume != getVolume()) {
470 com.sun.speech.freetts.Voice voice = curVoice.getVoice();
471 voice.setVolume(volume);
472 super.setVolume(volume);
479 * The OutputHandler is responsible for taking items off of the
480 * input queue and sending them to the current voice.
482 class OutputHandler extends Thread {
483 protected boolean done = false;
486 * Internal speech output queue that will contain a set of
487 * FreeTTSSynthesizerQueueItems.
489 * @see BaseSynthesizerQueueItem
491 protected Vector queue;
494 * Create a new OutputHandler for the given Synthesizer.
496 public OutputHandler() {
497 queue = new Vector();
501 * shuts down this output handler
503 public synchronized void terminate() {
504 synchronized (queue) {
511 * Returns an enumeration of the queue
513 * @return the enumeration queue
515 public Enumeration enumerateQueue() {
516 synchronized(queue) {
517 return queue.elements();
522 * Determines if the input queue is empty
524 * @return true if the queue is empty; otherwise false
526 public boolean isQueueEmpty() {
527 synchronized(queue) {
528 return queue.size() == 0;
533 * Add an item to be spoken to the output queue. Fires the
534 * appropriate queue events
536 * @param item the item to add to the queue
538 public void appendQueue(FreeTTSSynthesizerQueueItem item) {
539 boolean topOfQueueChanged;
540 synchronized(queue) {
541 topOfQueueChanged = (queue.size() == 0);
542 queue.addElement(item);
545 if (topOfQueueChanged) {
546 long[] states = setEngineState(QUEUE_EMPTY,
548 postQueueUpdated(topOfQueueChanged, states[0], states[1]);
553 * Cancel the current item
555 protected void cancelItem() {
556 FreeTTSSynthesizerQueueItem item = null;
558 synchronized(queue) {
560 if (queue.size() != 0) {
561 item = (FreeTTSSynthesizerQueueItem) queue.remove(0);
563 // item.postSpeakableCancelled();
572 * Cancel all items in the queue
574 protected void cancelAllItems() {
575 FreeTTSSynthesizerQueueItem item = null;
578 synchronized(queue) {
580 copy = (Vector) queue.clone();
584 for (Iterator i = copy.iterator(); i.hasNext(); ) {
585 item = (FreeTTSSynthesizerQueueItem) i.next();
586 // item.postSpeakableCancelled();
593 * Cancel the given item.
595 * @param source the item to cancel.
597 protected void cancelItem(Object source) {
598 FreeTTSSynthesizerQueueItem item = null;
599 synchronized(queue) {
600 int index = queue.indexOf(source);
604 item = (FreeTTSSynthesizerQueueItem) queue.remove(index);
606 // item.postSpeakableCancelled();
615 * Gets the next item from the queue and outputs it
618 FreeTTSSynthesizerQueueItem item;
620 item = getQueueItem();
623 removeQueueItem(item);
629 * Return, but do not remove, the first item on the queue.
631 * @return a queue item
633 protected FreeTTSSynthesizerQueueItem getQueueItem() {
634 FreeTTSSynthesizerQueueItem item = null;
635 synchronized(queue) {
636 while (queue.size() == 0 && !done) {
640 catch (InterruptedException e) {
641 LOGGER.severe("Unexpected interrupt");
642 // Ignore interrupts and we'll loop around
649 item = (FreeTTSSynthesizerQueueItem) queue.elementAt(0);
651 item.postTopOfQueue();
656 * removes the given item, posting the appropriate
657 * events. The item may have already been removed (due to a
660 * @param item the item to remove
662 protected void removeQueueItem(FreeTTSSynthesizerQueueItem item) {
663 boolean queueEmptied = false;
664 synchronized(queue) {
665 boolean found = queue.remove(item);
673 * Should be called iff one or more items have been removed
674 * from the queue. Generates the appropriate state changes and
677 private void queueDrained() {
678 if (queue.size() == 0) {
679 long[] states = setEngineState(QUEUE_NOT_EMPTY, QUEUE_EMPTY);
680 postQueueEmptied(states[0], states[1]);
682 long[] states = setEngineState(QUEUE_NOT_EMPTY,
684 postQueueUpdated(true, states[0], states[1]);
689 * Outputs the given queue item to the current voice
691 * @param item the item to output
693 protected void outputItem(FreeTTSSynthesizerQueueItem item) {
694 com.sun.speech.freetts.Voice voice = curVoice.getVoice();