2 * Copyright 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 import java.beans.PropertyVetoException;
9 import java.io.BufferedReader;
11 import java.io.FileInputStream;
12 import java.io.FileNotFoundException;
13 import java.io.FileReader;
14 import java.io.InputStream;
15 import java.io.InputStreamReader;
16 import java.io.IOException;
17 import java.net.MalformedURLException;
19 import java.util.Enumeration;
20 import java.util.Locale;
21 import java.util.HashSet;
23 import java.util.Iterator;
25 import javax.speech.AudioException;
26 import javax.speech.Central;
27 import javax.speech.Engine;
28 import javax.speech.EngineException;
29 import javax.speech.EngineList;
30 import javax.speech.synthesis.Synthesizer;
31 import javax.speech.synthesis.SynthesizerModeDesc;
32 import javax.speech.synthesis.SynthesizerProperties;
33 import javax.speech.synthesis.Voice;
35 import javax.swing.DefaultComboBoxModel;
36 import javax.swing.DefaultListModel;
37 import javax.swing.JFrame;
38 import javax.swing.ListModel;
40 import com.sun.speech.engine.synthesis.SynthesizerMonitor;
43 * Implements the text-to-speech data model of the Player application, using
44 * JSAPI. It should work with any JSAPI implementation.
46 public class PlayerModelImpl implements PlayerModel {
48 private Synthesizer synthesizer;
49 private Monitor monitor;
50 private boolean monitorVisible = false;
51 private boolean paused = false;
52 private boolean stopped = false;
53 private boolean playingFile = false;
54 private DefaultListModel playList;
55 private DefaultComboBoxModel synthesizerList;
56 private DefaultComboBoxModel voiceList;
57 private float volume = -1;
58 private static boolean debug = false;
59 private Set loadedSynthesizers;
63 * Constructs a default PlayerModelImpl.
65 public PlayerModelImpl() {
66 playList = new DefaultListModel();
67 synthesizerList = new DefaultComboBoxModel();
68 voiceList = new DefaultComboBoxModel();
69 loadedSynthesizers = new HashSet();
74 * Creates a FreeTTS synthesizer.
76 public void createSynthesizers() {
78 EngineList list = Central.availableSynthesizers(null);
79 Enumeration e = list.elements();
81 while (e.hasMoreElements()) {
82 MySynthesizerModeDesc myModeDesc =
83 new MySynthesizerModeDesc
84 ((SynthesizerModeDesc) e.nextElement(), this);
85 debugPrint(myModeDesc.getEngineName() + " " +
86 myModeDesc.getLocale() + " " +
87 myModeDesc.getModeName() + " " +
88 myModeDesc.getRunning());
89 synthesizerList.addElement(myModeDesc);
92 if (synthesizerList.getSize() > 0) {
95 System.err.println(noSynthesizerMessage());
97 if (synthesizer == null) {
98 System.err.println("PlayerModelImpl: Can't find synthesizer");
101 } catch (Exception e) {
107 * Returns a "no synthesizer" message, and asks
108 * the user to check if the "speech.properties" file is
109 * at <code>user.home</code> or <code>java.home/lib</code>.
111 * @return a no synthesizer message
113 static private String noSynthesizerMessage() {
115 "No synthesizer created. This may be the result of any\n" +
116 "number of problems. It's typically due to a missing\n" +
117 "\"speech.properties\" file that should be at either of\n" +
118 "these locations: \n\n";
119 message += "user.home : " + System.getProperty("user.home") + "\n";
120 message += "java.home/lib: " + System.getProperty("java.home") +
121 File.separator + "lib\n\n" +
122 "Another cause of this problem might be corrupt or missing\n" +
123 "voice jar files in the freetts lib directory. This problem\n" +
124 "also sometimes arises when the freetts.jar file is corrupt\n" +
125 "or missing. Sorry about that. Please check for these\n" +
126 "various conditions and then try again.\n";
131 * Performs TTS on the given Playable.
133 * @param playable the Playable object to play
135 public void play(Playable playable) {
136 if (playable != null) {
137 if (playable.getType() == PlayableType.TEXT) {
138 play(playable.getText());
139 } else if (playable.getType() == PlayableType.JSML) {
140 playJSML(playable.getText());
141 } else if (playable.getType() == PlayableType.TEXT_FILE ||
142 playable.getType() == PlayableType.JSML_FILE) {
143 playFile(playable.getFile(), playable.getType());
144 } else if (playable.getType() == PlayableType.URL) {
146 playURL(new URL(playable.getName()));
147 } catch (MalformedURLException mue) {
148 mue.printStackTrace();
156 * Performs TTS on the object at the given index of the play list.
158 * @param index index of the object in the playlist to play
160 public void play(int index) {
161 if (0 <= index && index < playList.getSize()) {
162 Playable playable = (Playable) playList.getElementAt(index);
163 if (playable != null) {
171 * Performs text-to-speech on the given text.
173 * @param text the text to perform TTS
175 private void play(String text) {
176 synthesizer.speakPlainText(text, null);
181 * Performs text-to-speech on the given JSML text.
183 * @param text the text to perform TTS
185 private void playJSML(String jsmlText) {
187 synthesizer.speak(jsmlText, null);
188 } catch (Exception e) {
195 * Plays the text in the given File.
197 * @param file the File to play
198 * @param type the file type
200 private void playFile(File file, PlayableType type) {
202 FileInputStream fileStream = new FileInputStream(file);
203 playInputStream(fileStream, type);
204 } catch (FileNotFoundException fnfe) {
205 fnfe.printStackTrace();
211 * Plays the text in the given File.
213 * @param file the File to play
214 * @param type the file type
216 private void playInputStream(InputStream inStream, PlayableType type) {
218 if (inStream != null) {
220 BufferedReader reader = new BufferedReader
221 (new InputStreamReader(inStream));
223 if (type == PlayableType.TEXT_FILE) {
224 while (!isStopped() &&
225 (line = reader.readLine()) != null) {
226 if (line.length() > 0) {
230 } else if (type == PlayableType.JSML_FILE) {
231 String fileText = "";
232 while ((line = reader.readLine()) != null) {
235 if (fileText != null && fileText.length() > 0) {
240 } catch (IOException ioe) {
241 ioe.printStackTrace();
249 * Plays the contents of the given URL.
251 * @param url the URL to play
253 private void playURL(URL url) {
255 synthesizer.speak(url, null);
256 } catch (Exception e) {
263 * Returns true if the player is paused.
265 * @return true if the player is paused, false otherwise
267 public synchronized boolean isPaused() {
275 public synchronized void pause() {
282 * Resumes the player.
284 public synchronized void resume() {
287 synthesizer.resume();
288 } catch (AudioException ae) {
289 ae.printStackTrace();
295 * Stops the player if it is playing.
297 public synchronized void stop() {
301 synthesizer.cancelAll();
306 * Cancels the currently playing item.
308 public void cancel() {
309 synthesizer.cancel();
313 * Close this playable
315 public void close() {
316 for (Iterator i = loadedSynthesizers.iterator(); i.hasNext();) {
317 Synthesizer synth = (Synthesizer) i.next();
320 } catch (EngineException ee) {
321 System.out.println("Trouble closing the synthesizer: " + ee);
328 * Returns true if the Player is currently being stopped.
330 * @return true if the Player is currently being stopped; false otherwise
332 private synchronized boolean isStopped() {
338 * Sets whether the Monitor is visible
340 * @param visible true to set the Monitor as visible
342 public void setMonitorVisible(boolean visible) {
343 monitorVisible = visible;
344 if (monitor != null) {
345 monitor.setVisible(monitorVisible);
351 * Tells whether the monitor is visible.
353 * @return true if the monitor is visible, false otherwise
355 public boolean isMonitorVisible() {
356 return monitorVisible;
361 * Returns the monitor of the synthesizer at the given index.
363 * @param index the position of the synthesizer in the synthesizer list
365 * @return the monitor of the specified synthesizer
367 public Monitor getMonitor(int index) {
368 MySynthesizerModeDesc myModeDesc = (MySynthesizerModeDesc)
369 synthesizerList.getElementAt(index);
370 Monitor monitor = null;
372 if (myModeDesc != null) {
373 monitor = myModeDesc.getMonitor();
380 * Returns the monitor of the current synthesizer.
382 * @return the monitor of the current synthesizer
384 public Monitor getMonitor() {
390 * Sets the current monitor.
392 * @param monitor the current monitor
394 public void setMonitor(Monitor monitor) {
395 this.monitor = monitor;
400 * Sets the Synthesizer at the given index to use
402 * @param index index of the synthesizer in the list
404 public void setSynthesizer(int index) {
405 MySynthesizerModeDesc myModeDesc = (MySynthesizerModeDesc)
406 synthesizerList.getElementAt(index);
407 if (myModeDesc != null) {
408 if (isMonitorVisible()) {
409 if (monitor != null) {
410 monitor.setVisible(false);
413 synthesizer = myModeDesc.getSynthesizer();
414 if (synthesizer == null) {
415 synthesizer = myModeDesc.createSynthesizer();
416 if (synthesizer == null) {
417 debugPrint("still null");
419 debugPrint("created");
422 debugPrint("not null");
424 monitor = myModeDesc.getMonitor();
425 if (myModeDesc.isSynthesizerLoaded()) {
426 setVoiceList(myModeDesc);
428 myModeDesc.loadSynthesizer();
431 loadedSynthesizers.add(synthesizer);
432 synthesizerList.setSelectedItem(myModeDesc);
438 * Sets the Voice at the given to use.
440 * @param index the index of the voice
442 public void setVoice(int index) {
444 Voice voice = (Voice) voiceList.getElementAt(index);
446 float oldVolume = getVolume();
447 float oldSpeakingRate = getSpeakingRate();
448 synthesizer.waitEngineState(Synthesizer.QUEUE_EMPTY);
449 synthesizer.getSynthesizerProperties().setVoice(voice);
450 setVolume(oldVolume);
451 setSpeakingRate(oldSpeakingRate);
452 voiceList.setSelectedItem(voice);
454 } catch (PropertyVetoException pve) {
455 pve.printStackTrace();
456 } catch (InterruptedException ie) {
457 ie.printStackTrace();
463 * Returns the volume, in the range of 0 to 10.
465 * @return the volume, or -1 if unknown, or an error occurred
467 public float getVolume() {
469 float adjustedVolume =
470 synthesizer.getSynthesizerProperties().getVolume();
471 if (adjustedVolume < 0.5) {
474 volume = (float) ((adjustedVolume - 0.5) * 20);
476 } catch (Exception e) {
484 * Sets the volume, in the range of 0 to 10.
486 * @param volume the new volume
488 * @return true if new volume is set; false otherwise
490 public boolean setVolume(float volume) {
492 float adjustedVolume = (float) (volume/20 + 0.5);
493 if (synthesizer != null) {
494 synthesizer.getSynthesizerProperties().setVolume
496 this.volume = volume;
499 this.volume = volume;
502 } catch (PropertyVetoException pve) {
504 synthesizer.getSynthesizerProperties().setVolume(this.volume);
505 } catch (PropertyVetoException pe) {
506 pe.printStackTrace();
514 * Returns the speaking rate.
516 * @return the speaking rate, or -1 if unknown or an error occurred
518 public float getSpeakingRate() {
519 if (synthesizer != null) {
520 return synthesizer.getSynthesizerProperties().getSpeakingRate();
528 * Sets the speaking rate in terms of words per minute.
530 * @param wordsPerMin the new speaking rate
532 * @return the speaking rate, or -1 if unknown or an error occurred
534 public boolean setSpeakingRate(float wordsPerMin) {
535 float oldSpeed = getSpeakingRate();
536 SynthesizerProperties properties =
537 synthesizer.getSynthesizerProperties();
539 properties.setSpeakingRate(wordsPerMin);
541 } catch (PropertyVetoException pve) {
543 properties.setSpeakingRate(oldSpeed);
544 } catch (PropertyVetoException pe) {
545 pe.printStackTrace();
553 * Returns the baseline pitch for the current synthesis voice.
555 * @return the baseline pitch for the current synthesis voice
557 public float getPitch() {
558 return synthesizer.getSynthesizerProperties().getPitch();
563 * Sets the baseline pitch for the current synthesis voice.
565 * @param pitch the baseline pitch
567 * @return true if new pitch is set; false otherwise
569 public boolean setPitch(float pitch) {
570 float oldPitch = getPitch();
572 synthesizer.getSynthesizerProperties().setPitch(pitch);
574 } catch (PropertyVetoException pve) {
576 synthesizer.getSynthesizerProperties().setPitch(oldPitch);
577 } catch (PropertyVetoException pe) {
578 pe.printStackTrace();
586 * Returns the pitch range for the current synthesis voice.
588 * @return the pitch range for the current synthesis voice
590 public float getRange() {
591 return synthesizer.getSynthesizerProperties().getPitchRange();
596 * Sets the pitch range for the current synthesis voice.
598 * @param range the pitch range
600 * @return true if new range is set; false otherwise
602 public boolean setRange(float range) {
603 float oldRange = getRange();
605 synthesizer.getSynthesizerProperties().setPitchRange(range);
607 } catch (PropertyVetoException pve) {
609 synthesizer.getSynthesizerProperties().setPitchRange(oldRange);
610 } catch (PropertyVetoException pe) {
611 pe.printStackTrace();
619 * Sets the list of voices using the given Synthesizer mode description.
621 * @param modeDesc the synthesizer mode description
623 public void setVoiceList(SynthesizerModeDesc modeDesc) {
624 Voice[] voices = modeDesc.getVoices();
625 voiceList.removeAllElements();
626 for (int i = 0; i < voices.length; i++) {
627 voiceList.addElement(new MyVoice(voices[i].getName(),
628 voices[i].getGender(),
630 voices[i].getStyle()));
636 * Returns the play list.
638 * @return the play list
640 public ListModel getPlayList() {
646 * Returns the list of voices.
648 * @return the list of voices
650 public ListModel getVoiceList() {
656 * Returns the list synthesizers
658 * @return the synthesizer list
660 public ListModel getSynthesizerList() {
661 return synthesizerList;
666 * Returns the Playable object at the given index of the play list.
668 * @return the Playable object
670 public Object getPlayableAt(int index) {
676 * Adds the given Playable object to the end of the play list.
678 * @param playable the Playable object to add
680 public void addPlayable(Playable playable) {
681 playList.addElement(playable);
686 * Removes the playable at the given position from the list
688 * @param index the index of the Playable to remove
690 public void removePlayableAt(int index) {
691 if (index < playList.getSize()) {
692 playList.removeElementAt(index);
698 * Prints debug statements.
700 * @param statement debug statements
702 public static void debugPrint(String statement) {
704 System.out.println(statement);
711 * A Voice that implements the <code>toString()</code> method so that
712 * it returns the name of the person who owns this Voice.
714 class MyVoice extends Voice {
718 * Constructor provided with voice name, gender, age and style.
720 * @param name the name of the person who owns this Voice
721 * @param gender the gender of the person
722 * @param age the age of the person
723 * @param style the style of the person
725 public MyVoice(String name, int gender, int age, String style) {
726 super(name, gender, age, style);
731 * Returns the name of the person who owns this Voice.
733 * @param String the name of the person
735 public String toString() {
742 * A SynthesizerModeDesc that implements the <code>toString()</code>
743 * method so that it returns the name of the synthesizer.
745 class MySynthesizerModeDesc extends SynthesizerModeDesc {
747 private PlayerModel playerModel = null;
748 private Synthesizer synthesizer = null;
749 private Monitor monitor = null;
750 private boolean synthesizerLoaded = false;
754 * Constructs a MySynthesizerModeDesc with the attributes from
755 * the given SynthesizerModeDesc.
757 * @param modeDesc the SynthesizerModeDesc to get attributes from
759 public MySynthesizerModeDesc(SynthesizerModeDesc modeDesc,
760 PlayerModel playerModel) {
761 super(modeDesc.getEngineName(), modeDesc.getModeName(),
762 modeDesc.getLocale(), modeDesc.getRunning(),
763 modeDesc.getVoices());
764 this.playerModel = playerModel;
769 * Returns true if the synthesizer is already loaded.
771 * @return true if the synthesizer is already loaded
773 public synchronized boolean isSynthesizerLoaded() {
774 if (synthesizer == null) {
777 return ((synthesizer.getEngineState() & Engine.ALLOCATED) != 0);
782 * Returns a Synthesizer that fits the description of this
783 * MySynthesizerModeDesc. If the synthesize was never loaded,
784 * it is loaded in a separate thread.
786 * @return a Synthesizer
788 public synchronized Synthesizer getSynthesizer() {
789 debugPrint("MyModeDesc.getSynthesizer(): " + getEngineName());
795 * Creates the Synthesizer and its Monitor.
797 * @return the created Synthesizer
799 public Synthesizer createSynthesizer() {
801 debugPrint("Creating " + getEngineName() + "...");
802 synthesizer = Central.createSynthesizer(this);
804 if (synthesizer == null) {
805 System.out.println("Central created null synthesizer");
807 synthesizer.allocate();
808 synthesizer.resume();
809 monitor = new Monitor(synthesizer, getEngineName());
810 debugPrint("...created monitor");
812 } catch (Exception e) {
821 * Allocates the synthesizer if it has never been allocated. This
822 * method should be called after method <code>createSynthesizer()</code>.
823 * It spawns a new thread to allocate the synthesizer.
825 public Synthesizer loadSynthesizer() {
827 if (!synthesizerLoaded) {
828 debugPrint("Loading " + getEngineName() + "...");
829 synthesizerLoaded = true;
830 SynthesizerLoader loader = new SynthesizerLoader
834 } catch (Exception e) {
842 * Returns the Monitor of this Synthesizer.
844 * @return the Monitor
846 public synchronized Monitor getMonitor() {
847 if (monitor == null) {
855 * Returns the PlayerModel.
857 * @return the PlayerModel
859 public PlayerModel getPlayerModel() {
865 * Returns the name of the Synthesizer.
867 * @return the name of the Synthesizer
869 public String toString() {
870 return getEngineName();
875 * Prints debug statements.
877 * @param statement debug statements
879 private void debugPrint(String statement) {
880 PlayerModelImpl.debugPrint(statement);
886 * A Thread that loads the Synthesizer.
888 class SynthesizerLoader extends Thread {
889 private Synthesizer synthesizer;
890 private MySynthesizerModeDesc modeDesc;
891 private PlayerModel playerModel;
895 * Constructs a SynthesizerLoaded which loads the given Synthesizer.
897 * @param synthesizer the Synthesizer to load
898 * @param modeDesc the MySynthesizerModeDesc from which we can retrieve
901 public SynthesizerLoader(Synthesizer synthesizer,
902 MySynthesizerModeDesc modeDesc) {
903 this.synthesizer = synthesizer;
904 this.modeDesc = modeDesc;
905 this.playerModel = modeDesc.getPlayerModel();
910 * Implements the <code>run()</code> method of the Thread class.
914 System.out.println("allocating...");
915 synthesizer.allocate();
916 System.out.println("...allocated");
917 synthesizer.resume();
918 System.out.println("...resume");
919 playerModel.setVoiceList(modeDesc);
920 } catch (Exception e) {