2 * Portions Copyright 2001 Sun Microsystems, Inc.
3 * Portions Copyright 1999-2001 Language Technologies Institute,
4 * Carnegie Mellon University.
5 * All Rights Reserved. Use is subject to license terms.
7 * See the file "license.terms" for information on usage and
8 * redistribution of this file, and for a DISCLAIMER OF ALL
11 package com.sun.speech.freetts;
13 import java.io.BufferedReader;
14 import java.io.IOException;
15 import java.io.InputStream;
16 import java.io.InputStreamReader;
17 import java.io.PrintWriter;
18 import java.io.Reader;
20 import java.util.ArrayList;
21 import java.util.Collections;
22 import java.util.HashMap;
23 import java.util.Iterator;
24 import java.util.List;
25 import java.util.Locale;
27 import java.util.logging.Level;
28 import java.util.logging.Logger;
30 import org.w3c.dom.Document;
31 import org.w3c.dom.Node;
32 import org.w3c.dom.Text;
34 import com.sun.speech.freetts.audio.AudioPlayer;
35 import com.sun.speech.freetts.lexicon.Lexicon;
36 import com.sun.speech.freetts.relp.LPCResult;
37 import com.sun.speech.freetts.util.BulkTimer;
38 import com.sun.speech.freetts.util.Utilities;
42 * Performs text-to-speech using a series of
43 * <code>UtteranceProcessors</code>. It is the main conduit to the FreeTTS
44 * speech synthesizer. It can perform TTS on ASCII text,
45 * a JSML document, an <code>InputStream</code>, or a
46 * <code>FreeTTSSpeakable</code>, by invoking the method <code>speak</code>.
48 * <p>Before a Voice can perform TTS, it must have a
49 * <code>Lexicon</code>, from which it gets the vocabulary, and
50 * an <code>AudioPlayer</code>, to which it sends the synthesized output.
52 * <p><b>Example</b> (using the <code>CMUDiphoneVoice</code>,
53 * <code>CMULexicon</code> and <code>JavaClipAudioPlayer</code>):
56 * Voice voice = new CMUDiphoneVoice();
59 * voice.setLexicon(new CMULexicon());
61 * // sets the AudioPlayer
62 * voice.setAudioPlayer(new JavaClipAudioPlayer());
68 * voice.speak("I can talk forever without getting tired!");
72 * <p>A user can override the AudioPlayer to use by defining the
73 * "com.sun.speech.freetts.voice.defaultAudioPlayer" system property.
74 * The value of this property must be the name of a class that
75 * implements the AudioPlayer interface, and which also has a no-arg
81 public abstract class Voice implements UtteranceProcessor, Dumpable {
82 /** Logger instance. */
83 private static final Logger LOGGER =
84 Logger.getLogger(Voice.class.getName());
87 * Constant that describes the name of the unit database used by
90 public final static String DATABASE_NAME = "databaseName";
92 private List utteranceProcessors;
93 private Map featureProcessors;
94 private FeatureSetImpl features;
95 private boolean metrics = false;
96 private boolean detailedMetrics = false;
97 private boolean dumpUtterance = false;
98 private boolean dumpRelations = false;
99 private String runTitle = "unnamed run";
100 private Lexicon lexicon = null;
101 private AudioPlayer defaultAudioPlayer = null;
102 private AudioPlayer audioPlayer = null;
103 private UtteranceProcessor audioOutput;
104 private OutputQueue outputQueue = null;
105 private String waveDumpFile = null;
106 private BulkTimer runTimer = new BulkTimer();
107 private BulkTimer threadTimer = new BulkTimer();
108 private boolean externalOutputQueue = false;
109 private boolean externalAudioPlayer = false;
112 private float nominalRate = 150; // nominal speaking rate for this voice
113 private float pitch = 100; // pitch baseline (hertz)
114 private float range = 10; // pitch range (hertz)
115 private float pitchShift = 1; // F0 Shift
116 private float volume = 0.8f; // the volume (range 0 to 1)
117 private float durationStretch = 1f; // the duration stretch
119 private boolean loaded = false;
121 private String name = "default_name";
122 private Age age = Age.DONT_CARE;
123 private Gender gender = Gender.DONT_CARE;
124 private String description = "default description";
125 private Locale locale = Locale.getDefault();
126 private String domain = "general";
127 private String style = "standard";
128 private String organization = "unknown";
131 * Prefix for System property names.
133 public final static String PROP_PREFIX = "com.sun.speech.freetts.voice.";
136 * Feature name for the silence phone string.
138 public final static String FEATURE_SILENCE = "silence";
141 * Feature name for the join type string.
143 public final static String FEATURE_JOIN_TYPE = "join_type";
146 * Feature name for the default AudioPlayer class to use.
148 public final static String DEFAULT_AUDIO_PLAYER =
149 PROP_PREFIX + "defaultAudioPlayer";
153 * The default class to use for the DEFAULT_AUDIO_PLAYER.
155 public final static String DEFAULT_AUDIO_PLAYER_DEFAULT =
156 "com.sun.speech.freetts.audio.JavaStreamingAudioPlayer";
160 * Creates a new Voice. Utterances are sent to an
161 * output queue to be rendered as audio. Utterances are placed
162 * on the queue by an output thread. This
163 * queue is usually created via a call to 'createOutputThread,'
164 * which creates a thread that waits on the queue and sends the
165 * output to the audio player associated with this voice. If
166 * the queue is null, the output is rendered in the calling
169 * @see #createOutputThread
172 /* Make the utteranceProcessors a synchronized list to avoid
173 * some threading issues.
175 utteranceProcessors = Collections.synchronizedList(new ArrayList());
176 features = new FeatureSetImpl();
177 featureProcessors = new HashMap();
180 nominalRate = Float.parseFloat(
181 Utilities.getProperty(PROP_PREFIX + "speakingRate","150"));
182 pitch = Float.parseFloat(
183 Utilities.getProperty(PROP_PREFIX + "pitch","100"));
184 range = Float.parseFloat(
185 Utilities.getProperty(PROP_PREFIX + "range","10"));
186 volume = Float.parseFloat(
187 Utilities.getProperty(PROP_PREFIX + "volume","1.0"));
188 } catch (SecurityException se) {
189 // can't get properties, just use defaults
193 defaultAudioPlayer = null;
197 * Creates a new Voice like above, except that it also
198 * stores the properties of the voice.
199 * @param name the name of the voice
200 * @param gender the gender of the voice
201 * @param age the age of the voice
202 * @param description a human-readable string providing a
203 * description that can be displayed for the users.
204 * @param locale the locale of the voice
205 * @param domain the domain of this voice. For example,
206 * @param organization the organization which created the voice
207 * "general", "time", or
208 * "weather".
212 public Voice(String name, Gender gender, Age age,
213 String description, Locale locale, String domain,
214 String organization) {
219 setDescription(description);
222 setOrganization(organization);
227 * Speaks the given text.
229 * @param text the text to speak
231 * @return <code>true</code> if the given text is spoken properly;
232 * otherwise <code>false</code>
234 public boolean speak(String text) {
235 return speak(new FreeTTSSpeakableImpl(text));
240 * Speaks the given document.
242 * @param doc the JSML document to speak
244 * @return <code>true</code> if the given document is spoken properly;
245 * otherwise <code>false</code>
247 public boolean speak(Document doc) {
248 return speak(new FreeTTSSpeakableImpl(doc));
253 * Speaks the input stream.
255 * @param inputStream the inputStream to speak
257 * @return <code>true</code> if the given input stream is spoken properly;
258 * otherwise <code>false</code>
260 public boolean speak(InputStream inputStream) {
261 return speak(new FreeTTSSpeakableImpl(inputStream));
266 * Speak the given queue item. This is a synchronous method that
267 * does not return until the speakable is completely
268 * spoken or has been cancelled.
270 * @param speakable the item to speak
272 * @return <code>true</code> if the utterance was spoken properly,
273 * <code>false</code> otherwise
275 public boolean speak(FreeTTSSpeakable speakable) {
276 if (LOGGER.isLoggable(Level.FINE)) {
277 LOGGER.fine("speak(FreeTTSSpeakable) called");
280 boolean posted = false;
282 getAudioPlayer().startFirstSampleTimer();
284 for (Iterator i = tokenize(speakable);
285 !speakable.isCompleted() && i.hasNext() ; ) {
287 Utterance utterance = (Utterance) i.next();
288 if (utterance != null) {
289 processUtterance(utterance);
292 } catch (ProcessException pe) {
297 runTimer.start("WaitAudio");
298 ok = speakable.waitCompleted();
299 runTimer.stop("WaitAudio");
301 if (LOGGER.isLoggable(Level.FINE)) {
302 LOGGER.fine("speak(FreeTTSSpeakable) completed");
309 * @deprecated As of FreeTTS 1.2, replaced by {@link #allocate}.
316 * Allocate this Voice. It loads the lexicon and the
317 * audio output handler, and creates an audio output thread by
318 * invoking <code>createOutputThread()</code>, if
319 * one is not already created. It then calls the <code>loader()</code>
320 * method to load Voice-specific data, which include utterance processors.
322 public void allocate() {
326 BulkTimer.LOAD.start();
329 if (!lexicon.isLoaded()) {
332 } catch (IOException ioe) {
333 LOGGER.severe("Can't load voice " + ioe);
334 throw new Error(ioe);
339 audioOutput = getAudioOutput();
340 } catch (IOException ioe) {
341 LOGGER.severe("Can't load audio output handler for voice " + ioe);
342 throw new Error(ioe);
344 if (outputQueue == null) {
345 outputQueue = createOutputThread();
349 } catch (IOException ioe) {
350 LOGGER.severe("Can't load voice " + ioe);
351 throw new Error(ioe);
353 BulkTimer.LOAD.stop();
355 BulkTimer.LOAD.show("loading " + toString() + " for " +
363 * Returns true if this voice is loaded.
365 * @return <code>true</code> if the voice is loaded;
366 * otherwise <code>false</code>
368 public boolean isLoaded() {
373 * Sets the loaded state
375 * @param loaded the new loaded state
376 * otherwise <code>false</code>
378 protected void setLoaded(boolean loaded) {
379 this.loaded = loaded;
383 * Processes the given Utterance by passing it to each
384 * UtteranceProcessor managed by this Voice. The
385 * UtteranceProcessors are called in the order they were added to
388 * @param u the Utterance to process
390 * @throws ProcessException if an exception occurred while performing
391 * operations on the Utterance
393 public void processUtterance(Utterance u) throws ProcessException {
394 UtteranceProcessor[] processors;
396 if (utteranceProcessors == null) {
400 throw new ProcessException("Utterance is null.");
403 runTimer.start("processing");
404 processors = new UtteranceProcessor[utteranceProcessors.size()];
405 processors = (UtteranceProcessor[])
406 utteranceProcessors.toArray(processors);
408 if (LOGGER.isLoggable(Level.FINE)) {
409 LOGGER.fine("Processing Utterance: " + u.getString("input_text"));
412 for (int i = 0; i < processors.length &&
413 !u.getSpeakable().isCompleted(); i++) {
414 runProcessor(processors[i], u, runTimer);
416 if (!u.getSpeakable().isCompleted()) {
417 if (outputQueue == null) {
418 if (LOGGER.isLoggable(Level.FINE)) {
419 LOGGER.fine("To AudioOutput");
421 outputUtterance(u, runTimer);
423 runTimer.start("..post");
425 runTimer.stop("..post");
428 } catch (ProcessException pe) {
429 System.err.println("Processing Utterance: " + pe);
430 } catch (Exception e) {
431 System.err.println("Trouble while processing utterance " + e);
433 u.getSpeakable().cancelled();
436 if (LOGGER.isLoggable(Level.FINE)) {
437 LOGGER.fine("Done Processing Utterance: "
438 + u.getString("input_text"));
440 runTimer.stop("processing");
446 u.dumpRelations("Utterance");
454 * Dumps the wave for the given utterance.
456 * @param utterance the utterance of interest
458 private void dumpASCII(Utterance utterance) {
459 if (waveDumpFile != null) {
460 LPCResult lpcResult =
461 (LPCResult) utterance.getObject("target_lpcres");
463 if (waveDumpFile.equals("-")) {
464 lpcResult.dumpASCII();
466 lpcResult.dumpASCII(waveDumpFile);
468 } catch (IOException ioe) {
469 LOGGER.severe("Can't dump file to " + waveDumpFile + " " + ioe);
470 throw new Error(ioe);
477 * Creates an output thread that will asynchronously
478 * output utterances that are generated by this voice (and other
481 * @return the queue where utterances should be placed.
483 public static OutputQueue createOutputThread() {
484 final OutputQueue queue = new OutputQueue();
485 Thread t = new Thread() {
487 Utterance utterance = null;
489 utterance = queue.pend();
490 if (utterance != null) {
491 Voice voice = utterance.getVoice();
492 if (LOGGER.isLoggable(Level.FINE)) {
494 + utterance.getString("input_text"));
496 voice.outputUtterance(utterance, voice.threadTimer);
498 } while (utterance != null);
508 * Sends the given utterance to the audio output processor
509 * associated with this voice. If the queue item associated with
510 * this utterance is completed, then this set of utterances has
511 * been cancelled or otherwise aborted and the utterance should
514 * @param utterance the utterance to be output
515 * @param timer the timer for gathering performance metrics
517 * @return true if the utterance was output properly; otherwise
520 private boolean outputUtterance(Utterance utterance, BulkTimer timer) {
522 FreeTTSSpeakable speakable = utterance.getSpeakable();
524 if (!speakable.isCompleted()) {
525 if (utterance.isFirst()) {
526 getAudioPlayer().reset();
528 if (LOGGER.isLoggable(Level.FINE)) {
529 LOGGER.fine(" --- started ---");
533 // log(" utt: " + utterance.getString("input_text"));
535 if (!speakable.isCompleted()) {
536 runProcessor(audioOutput, utterance, timer);
540 } catch (ProcessException pe) {
543 if (ok && utterance.isLast()) {
544 getAudioPlayer().drain();
545 speakable.completed();
546 if (LOGGER.isLoggable(Level.FINE)) {
547 LOGGER.fine(" --- completed ---");
550 // getAudioPlayer().drain();
551 speakable.cancelled();
552 if (LOGGER.isLoggable(Level.FINE)) {
553 LOGGER.fine(" --- cancelled ---");
556 if (LOGGER.isLoggable(Level.FINE)) {
557 LOGGER.fine(" --- not last: " + speakable.getText()
561 if (LOGGER.isLoggable(Level.FINE)) {
562 LOGGER.fine("Calling speakable.completed() on "
563 + speakable.getText());
567 if (LOGGER.isLoggable(Level.FINE)) {
568 LOGGER.fine("STRANGE: speakable already completed: "
569 + speakable.getText());
577 * Runs the given utterance processor.
579 * @param processor the processor to run. If the processor
580 * is null, it is ignored
581 * @param utterance the utterance to process
583 * @throws ProcessException if an exceptin occurs while processing
586 private void runProcessor(UtteranceProcessor processor,
587 Utterance utterance, BulkTimer timer)
588 throws ProcessException {
589 if (processor != null) {
590 String processorName = ".." + processor.toString();
591 if (LOGGER.isLoggable(Level.FINE)) {
592 LOGGER.fine(" Running " + processorName);
594 timer.start(processorName);
595 processor.processUtterance(utterance);
596 timer.stop(processorName);
602 * Returns the tokenizer associated with this voice.
604 * @return the tokenizer
606 public abstract Tokenizer getTokenizer();
610 * Return the list of UtteranceProcessor instances. Applications
611 * should use this to obtain and modify the contents of the
612 * UtteranceProcessor list.
614 * @return a List containing UtteranceProcessor instances
616 public List getUtteranceProcessors() {
617 return utteranceProcessors;
622 * Returns the feature set associated with this voice.
624 * @return the feature set.
626 public FeatureSet getFeatures() {
632 * Starts a batch of utterances. Utterances are sometimes
633 * batched in groups for timing purposes.
637 public void startBatch() {
638 runTimer.setVerbose(detailedMetrics);
644 * Ends a batch of utterances.
648 public void endBatch() {
652 runTimer.show(getRunTitle() + " run");
653 threadTimer.show(getRunTitle() + " thread");
654 getAudioPlayer().showMetrics();
655 long totalMemory = Runtime.getRuntime().totalMemory();
658 + (totalMemory - Runtime.getRuntime().freeMemory()) / 1024
659 + "k of " + totalMemory / 1024 + "k");
664 * Sets the output queue for this voice. If no output queue is set
665 * for the voice when the voice is loaded, a queue and thread will
666 * be created when the voice is loaded. If the outputQueue is set
667 * by an external entity by calling setOutputQueue, the caller is
668 * responsible for shutting down the output thread. That is, if
669 * you call 'setOutputQueue' then you are responsible for shutting
670 * down the output thread on your own. This is necessary since the
671 * output queue may be shared by a number of voices.
673 * <p>Utterances are placed on the
674 * queue to be output by an output thread. This queue is
675 * usually created via a call to 'createOutputThread' which
676 * creates a thread that waits on the queue and sends the
677 * output to the audio player associated with this voice. If
678 * the queue is null, the output is rendered in the calling
681 * @param queue the output queue
683 public void setOutputQueue(OutputQueue queue) {
684 externalOutputQueue = true;
689 * Returns the output queue associated with this voice.
691 * @return the output queue associated with this voice
693 public OutputQueue getOutputQueue() {
698 * Loads voice specific data. Subclasses of voice should
699 * implement this to perform class specific loading.
701 protected abstract void loader() throws IOException;
704 * tokenizes the given the queue item.
706 * @return an iterator that will yield a series of utterances
708 private Iterator tokenize(FreeTTSSpeakable speakable) {
709 return new FreeTTSSpeakableTokenizer(speakable).iterator();
713 * Converts the document to a string (a placeholder for more
714 * sophisticated logic to be done).
716 * @param dom the jsml document
718 * @return the document as a string.
720 private String documentToString(Document dom) {
721 StringBuffer buf = new StringBuffer();
723 return buf.toString();
727 * Appends the text for this node to the given StringBuffer.
729 * @param n the node to traverse in depth-first order
730 * @param buf the buffer to append text to
732 private void linearize(Node n, StringBuffer buf) {
733 StringBuffer endText = processNode(n, buf);
734 for (Node child = n.getFirstChild();
736 child = child.getNextSibling()) {
737 linearize(child, buf);
740 if (endText != null) {
746 * Adds text for just this node and returns any text that might
747 * be needed to undo the effects of this node after it is
750 * @param n the node to traverse in depth-first order
751 * @param buf the buffer to append text to
753 * @return a <code>String</code> containing text to undo the
754 * effects of the node
756 protected StringBuffer processNode(Node n, StringBuffer buf) {
757 StringBuffer endText = null;
759 int type = n.getNodeType();
761 case Node.ATTRIBUTE_NODE:
764 case Node.DOCUMENT_NODE:
767 case Node.ELEMENT_NODE:
768 // endText = processElement((Element) n, buf);
772 buf.append(((Text) n).getData());
775 // Pass processing instructions (e.g., <?blah?>
776 // right on to the synthesizer. These types of things
777 // probably should not be used. Instead the 'engine'
778 // element is probably the best thing to do.
780 case Node.PROCESSING_INSTRUCTION_NODE:
783 // The document type had better be JSML.
785 case Node.DOCUMENT_TYPE_NODE:
788 // I think NOTATION nodes are only DTD's.
790 case Node.NOTATION_NODE:
793 // Should not get COMMENTS because the JSMLParser
796 case Node.COMMENT_NODE:
799 // Should not get CDATA because the JSMLParser is
802 case Node.CDATA_SECTION_NODE:
805 // Should not get ENTITY related notes because
806 // entities are expanded by the JSMLParser
808 case Node.ENTITY_NODE:
809 case Node.ENTITY_REFERENCE_NODE:
812 // Should not get DOCUMENT_FRAGMENT nodes because I
813 // [[[WDW]]] think they are only created via the API's
814 // and cannot be defined via content.
816 case Node.DOCUMENT_FRAGMENT_NODE:
827 * Dumps the voice in textual form.
829 * @param output where to send the formatted output
830 * @param pad the initial padding
831 * @param title the title to print when dumping out
833 public void dump(PrintWriter output, int pad, String title) {
834 Utilities.dump(output, pad, title);
835 features.dump(output, pad + 4, title + " Features");
836 dumpProcessors(output, pad + 4, title + " Processors");
841 * Dumps the voice processors.
843 * @param output where to send the formatted output
844 * @param pad the initial padding
845 * @param title the title to print when dumping out
847 public void dumpProcessors(PrintWriter output, int pad, String title) {
848 UtteranceProcessor[] processors;
849 if (utteranceProcessors == null) {
853 processors = new UtteranceProcessor[utteranceProcessors.size()];
854 processors = (UtteranceProcessor[])
855 utteranceProcessors.toArray(processors);
857 Utilities.dump(output, pad, title);
858 for (int i = 0; i < processors.length; i++) {
859 Utilities.dump(output, pad + 4, processors[i].toString());
865 * Returns a language/voice specific Feature Processor.
867 * @param name the name of the processor
869 * @return the processor associated with the name or null if none
872 public FeatureProcessor getFeatureProcessor(String name) {
873 return (FeatureProcessor) featureProcessors.get(name);
877 * Adds a language/voice specific Feature Processor to the set of
878 * FeatureProcessors supported by this voice.
880 * @param name the name of the processor
881 * @param fp the processor
883 public void addFeatureProcessor(String name, FeatureProcessor fp) {
884 featureProcessors.put(name, fp);
888 * Gets the state of the metrics mode.
890 * @return true if metrics mode is on
892 public boolean isMetrics() {
897 * Sets the metrics mode.
899 * @param metrics true if metrics mode should be on
901 public void setMetrics(boolean metrics) {
902 this.metrics = metrics;
903 if (LOGGER.isLoggable(Level.FINE)) {
904 LOGGER.fine("Metrics mode is " + metrics);
909 * Gets the state of the detailedMetrics mode.
911 * @return true if detailedMetrics mode is on
913 public boolean isDetailedMetrics() {
914 return detailedMetrics;
918 * Sets the state of the detailedMetrics mode.
920 * @param detailedMetrics true if detailedMetrics mode should be on
922 public void setDetailedMetrics(boolean detailedMetrics) {
923 this.detailedMetrics = detailedMetrics;
924 if (LOGGER.isLoggable(Level.FINE)) {
925 LOGGER.fine("DetailedMetrics mode is " + detailedMetrics);
930 * Gets the state of the dumpUtterance mode.
932 * @return true if dumpUtterance mode is on
934 public boolean isDumpUtterance() {
935 return dumpUtterance;
939 * Sets the state of the dumpUtterance mode.
941 * @param dumpUtterance true if dumpUtterance mode should be on
943 public void setDumpUtterance(boolean dumpUtterance) {
944 this.dumpUtterance = dumpUtterance;
945 if (LOGGER.isLoggable(Level.FINE)) {
946 LOGGER.fine("DumpUtterance mode is " + dumpUtterance);
951 * Gets the state of the dumpRelations mode.
953 * @return true if dumpRelations mode is on
955 public boolean isDumpRelations() {
956 return dumpRelations;
960 * Sets the state of the dumpRelations mode.
962 * @param dumpRelations true if dumpRelations mode should be on
964 public void setDumpRelations(boolean dumpRelations) {
965 this.dumpRelations = dumpRelations;
966 if (LOGGER.isLoggable(Level.FINE)) {
967 LOGGER.fine("DumpRelations mode is " + dumpRelations);
972 * Sets the title for this run.
974 * @param runTitle the title for the run
976 public void setRunTitle(String runTitle) {
977 this.runTitle = runTitle;
981 * Gets the title for this run.
983 * @return the title for the run
985 public String getRunTitle() {
990 * Given a phoneme and a feature name, returns the feature.
992 * @param phone the phoneme of interest
993 * @param featureName the name of the feature of interest
995 * @return the feature with the given name
997 public String getPhoneFeature(String phone, String featureName) {
1002 * Shuts down the voice processing.
1004 public void deallocate() {
1007 if (!externalAudioPlayer) {
1008 if (audioPlayer != null) {
1009 audioPlayer.close();
1014 if (!externalOutputQueue) {
1015 outputQueue.close();
1020 * Sets the baseline pitch.
1022 * @param hertz the baseline pitch in hertz
1024 public void setPitch(float hertz) {
1029 * Retreives the baseline pitch.
1031 * @return the baseline pitch in hertz
1033 public float getPitch() {
1038 * Sets the pitch range.
1040 * @param range the range in hertz
1042 public void setPitchRange(float range) {
1047 * Gets the pitch range.
1049 * @return the range in hertz
1051 public float getPitchRange() {
1056 * Sets the pitch shift
1058 * @param shift the pitch shift (1.0 is no shift)
1060 public void setPitchShift(float shift) {
1061 this.pitchShift = shift;
1065 * Gets the pitch shift.
1067 * @return the pitch shift
1069 public float getPitchShift() {
1074 * Sets the duration stretch
1076 * @param stretch the duration stretch (1.0 is no stretch)
1078 public void setDurationStretch(float stretch) {
1079 this.durationStretch = stretch;
1083 * Gets the duration Stretch
1085 * @return the duration stretch
1087 public float getDurationStretch() {
1088 return durationStretch;
1092 * Sets the rate of speech.
1094 * @param wpm words per minute
1096 public void setRate(float wpm) {
1097 if (wpm > 0 && wpm < 1000) {
1098 setDurationStretch(nominalRate / wpm);
1103 * Gets the rate of speech.
1105 * @return words per minute
1107 public float getRate() {
1108 return durationStretch * nominalRate;
1115 * @param vol the volume (0 to 1.0)
1117 public void setVolume(float vol) {
1124 * @return the volume (0 to 1.0)
1126 public float getVolume() {
1131 * Gets the lexicon for this voice.
1133 * @return the lexicon (or null if there is no lexicon)
1135 public Lexicon getLexicon() {
1140 * Sets the lexicon to be used by this voice.
1142 * @param lexicon the lexicon to use
1144 public void setLexicon(Lexicon lexicon) {
1145 this.lexicon = lexicon;
1150 * Sets the dumpfile for this voice.
1152 * @param waveDumpFile the dumpfile
1154 public void setWaveDumpFile(String waveDumpFile) {
1155 this.waveDumpFile = waveDumpFile;
1159 * Gets the dumpfile for this voice.
1161 * @return the dumpfile
1163 public String getWaveDumpFile() {
1164 return waveDumpFile;
1168 * Sets the audio player associated with this voice. The caller is
1169 * responsible for closing this player.
1171 * @param player the audio player
1173 public void setAudioPlayer(AudioPlayer player) {
1174 audioPlayer = player;
1175 externalAudioPlayer = true;
1179 * Gets the default audio player for this voice. The return
1180 * value will be non-null only if the DEFAULT_AUDIO_PLAYER
1181 * system property has been set to the name of an AudioPlayer
1182 * class, and that class is able to be instantiated via a
1183 * no arg constructor. getAudioPlayer will automatically set
1184 * the audio player for this voice to the default audio player
1185 * if the audio player has not yet been set.
1187 * @see #DEFAULT_AUDIO_PLAYER
1188 * @see #getAudioPlayer
1189 * @return the default AudioPlayer
1191 public AudioPlayer getDefaultAudioPlayer() throws InstantiationException {
1192 if (defaultAudioPlayer != null) {
1193 return defaultAudioPlayer;
1196 String className = Utilities.getProperty(
1197 DEFAULT_AUDIO_PLAYER, DEFAULT_AUDIO_PLAYER_DEFAULT);
1200 Class cls = Class.forName(className);
1201 defaultAudioPlayer = (AudioPlayer) cls.newInstance();
1202 return defaultAudioPlayer;
1203 } catch (ClassNotFoundException e) {
1204 throw new InstantiationException("Can't find class " + className);
1205 } catch (IllegalAccessException e) {
1206 throw new InstantiationException("Can't find class " + className);
1207 } catch (ClassCastException e) {
1208 throw new InstantiationException(className + " cannot be cast "
1209 + "to AudioPlayer");
1214 * Gets the audio player associated with this voice. If the
1215 * audio player has not yet been set, the value will default
1216 * to the return value of getDefaultAudioPlayer.
1218 * @see #getDefaultAudioPlayer
1219 * @return the audio player
1221 public AudioPlayer getAudioPlayer() {
1222 if (audioPlayer == null) {
1224 audioPlayer = getDefaultAudioPlayer();
1225 } catch (InstantiationException e) {
1226 e.printStackTrace();
1233 * Get a resource for this voice.
1234 * By default, the voice is searched for in the package
1235 * to which the voice class belongs. Subclasses are free to
1236 * override this behaviour.
1238 protected URL getResource(String resource) {
1239 return this.getClass().getResource(resource);
1243 * Set the name of this voice.
1244 * [[[TODO: any standard format to the name?]]]
1246 * @param name the name to assign this voice
1248 protected void setName(String name) {
1254 * Get the name of this voice.
1258 public String getName() {
1263 * Returns the name of this Voice.
1265 * @return the name of this Voice
1267 public String toString() {
1272 * Set the gender of this voice.
1274 * @param gender the gender to assign
1276 protected void setGender(Gender gender) {
1277 this.gender = gender;
1281 * Get the gender of this voice.
1283 * @return the gender of this voice
1285 public Gender getGender() {
1290 * Set the age of this voice.
1292 * @param age the age to assign
1294 protected void setAge(Age age) {
1299 * Get the age of this voice.
1301 * @return the age of this voice
1303 public Age getAge() {
1308 * Set the description of this voice.
1310 * @param description the human readable description to assign
1312 protected void setDescription(String description) {
1313 this.description = description;
1317 * Get the description of this voice.
1319 * @return the human readable description of this voice
1321 public String getDescription() {
1326 * Set the locale of this voice.
1328 * @param locale the locale of this voice.
1330 protected void setLocale(Locale locale) {
1331 this.locale = locale;
1335 * Get the locale of this voice.
1337 * @return the locale of this voice.
1339 public Locale getLocale() {
1344 * Set the domain of this voice.
1346 * @param domain the domain of this voice. For example,
1347 * "general", "time", or
1348 * "weather".
1350 protected void setDomain(String domain) {
1351 this.domain = domain;
1355 * Get the domain of this voice.
1357 * @return the domain of this voice. For example,
1358 * "general", "time", or
1359 * "weather".
1361 public String getDomain() {
1366 * Sets the voice style. This parameter is designed for human
1367 * interpretation. Values might include "business", "casual",
1368 * "robotic", "breathy"
1370 * @param style the stile of this voice.
1372 public void setStyle(String style) {
1377 * Gets the voice style. This parameter is designed for human
1378 * interpretation. Values might include "business", "casual",
1379 * "robotic", "breathy".
1381 public String getStyle() {
1386 * Sets the organization which created this voice. For example
1389 * @param organization the name of the organization
1391 protected void setOrganization(String organization) {
1392 this.organization = organization;
1396 * Gets the organization which created this voice. For example
1399 * @return the name of the organization
1401 public String getOrganization() {
1402 return organization;
1406 * Returns the AudioOutput processor to be used by this voice.
1407 * Derived voices typically override this to customize behaviors.
1409 * @return the audio output processor
1411 * @throws IOException if an IO error occurs while getting
1414 protected abstract UtteranceProcessor getAudioOutput() throws IOException ;
1417 * Tokenizes a FreeTTSSpeakable
1419 private class FreeTTSSpeakableTokenizer {
1420 FreeTTSSpeakable speakable;
1421 Tokenizer tok = getTokenizer();
1426 * @param speakable the queue item to be pretokenized
1428 public FreeTTSSpeakableTokenizer(FreeTTSSpeakable speakable) {
1429 this.speakable = speakable;
1430 if (speakable.isPlainText()) {
1431 tok.setInputText(speakable.getText());
1432 } else if (speakable.isStream()) {
1433 Reader reader = new BufferedReader(
1434 new InputStreamReader(speakable.getInputStream()));
1435 tok.setInputReader(reader);
1436 } else if (speakable.isDocument()) {
1437 tok.setInputText(documentToString(speakable.getDocument()));
1442 * Returns an iterator for this text item.
1444 public Iterator iterator() {
1445 return new Iterator() {
1446 boolean first = true;
1447 Token savedToken = null;
1450 * Determines if there are more utterances
1452 * @return true if there are more tokens
1454 public boolean hasNext() {
1455 return savedToken != null || tok.hasMoreTokens();
1459 * Returns the next utterance.
1461 * @return the next utterance (as an object) or
1462 * null if there is are no utterances left
1464 public Object next() {
1465 ArrayList tokenList = new ArrayList();
1466 Utterance utterance = null;
1468 if (savedToken != null) {
1469 tokenList.add(savedToken);
1473 while (tok.hasMoreTokens()) {
1474 Token token = tok.getNextToken();
1475 if ((token.getWord().length() == 0) ||
1476 (tokenList.size() > 500) ||
1481 tokenList.add(token);
1483 utterance = new Utterance(Voice.this, tokenList);
1484 utterance.setSpeakable(speakable);
1485 utterance.setFirst(first);
1488 (!tok.hasMoreTokens() &&
1489 (savedToken == null ||
1490 savedToken.getWord().length() == 0));
1491 utterance.setLast(isLast);
1495 public void remove() {
1496 throw new UnsupportedOperationException("remove");