001 /*
002 * File: $HeadURL: https://jvoicexml.svn.sourceforge.net/svnroot/jvoicexml/core/trunk/org.jvoicexml.implementation.mary/src/org/jvoicexml/implementation/mary/MarySynthesizedOutput.java $
003 * Version: $LastChangedRevision: 2694 $
004 * Date: $Date: 2011-06-03 11:28:55 +0200 (Fr, 03 Jun 2011) $
005 * Author: $LastChangedBy: schnelle $
006 *
007 * Copyright (C) 2010-2011 JVoiceXML group - http://jvoicexml.sourceforge.net
008 *
009 * This library is free software; you can redistribute it and/or
010 * modify it under the terms of the GNU Library General Public
011 * License as published by the Free Software Foundation; either
012 * version 2 of the License, or (at your option) any later version.
013 *
014 * This library is distributed in the hope that it will be useful,
015 * but WITHOUT ANY WARRANTY; without even the implied warranty of
016 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
017 * Library General Public License for more details.
018 *
019 * You should have received a copy of the GNU Library General Public
020 * License along with this library; if not, write to the Free Software
021 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
022 *
023 */
024
025 package org.jvoicexml.implementation.mary;
026
027 import java.io.IOException;
028 import java.net.URI;
029 import java.net.URISyntaxException;
030 import java.util.Collection;
031 import java.util.Map;
032
033 import marytts.client.MaryClient;
034
035 import org.apache.log4j.Logger;
036 import org.jvoicexml.ConnectionInformation;
037 import org.jvoicexml.DocumentServer;
038 import org.jvoicexml.SpeakableText;
039 import org.jvoicexml.event.ErrorEvent;
040 import org.jvoicexml.event.error.NoresourceError;
041 import org.jvoicexml.implementation.MarkerReachedEvent;
042 import org.jvoicexml.implementation.ObservableSynthesizedOutput;
043 import org.jvoicexml.implementation.OutputEndedEvent;
044 import org.jvoicexml.implementation.OutputStartedEvent;
045 import org.jvoicexml.implementation.QueueEmptyEvent;
046 import org.jvoicexml.implementation.SynthesizedOutput;
047 import org.jvoicexml.implementation.SynthesizedOutputEvent;
048 import org.jvoicexml.implementation.SynthesizedOutputListener;
049
050 /**
051 * An implementation of the {@link SynthesizedOutput} for the Mary TTS System.
052 * @author Dirk Schnelle-Walka
053 * @author Giannis Assiouras
054 * @version $Revision: 2694 $
055 * @since 0.7.3
056 */
057 public final class MarySynthesizedOutput implements SynthesizedOutput,
058 ObservableSynthesizedOutput, SynthesizedOutputListener {
059 /** Logger for this class. */
060 private static final Logger LOGGER =
061 Logger.getLogger(MarySynthesizedOutput.class);
062
063 /** The system output listener. */
064 private final Collection<SynthesizedOutputListener> listener;
065
066 /** Type of this resources. */
067 private String type;
068
069 /** Object lock for an empty queue. */
070 private final Object emptyLock;
071
072
073 /**
074 * Flag to indicate that TTS output and audio of the current speakable can
075 * be canceled.
076 */
077 private boolean enableBargeIn;
078
079 /** Reference to SynthesisQueue Thread.*/
080 private SynthesisQueue synthesisQueue;
081
082 /**
083 * Reference to the MaryClient object that will be used.
084 * to send the request to Mary server
085 */
086 private MaryClient processor;
087
088 /**
089 * Flag that indicates that synthesisQueue Thread is Currently.
090 * processing a speakable.
091 */
092 private boolean isBusy;
093
094 /**Flag that indicates that speakable queue is empty.*/
095 private boolean speakableQueueEmpty = true;
096
097 /**The HashTable that contains Mary synthesis request parameters.
098 * e.g audioType,voiceName,voiceEffects and their value
099 */
100 private final Map<String, String> maryRequestParameters;
101
102 /**
103 * Constructs a new MarySynthesizedOutput object.
104 */
105 public MarySynthesizedOutput() {
106 listener = new java.util.ArrayList<SynthesizedOutputListener>();
107 emptyLock = new Object();
108 maryRequestParameters = new java.util.HashMap<String, String>();
109 }
110
111
112 /**
113 * {@inheritDoc}
114 */
115 @Override
116 public URI getUriForNextSynthesisizedOutput() throws NoresourceError,
117 URISyntaxException {
118 return null;
119 }
120
121
122 /**
123 * {@inheritDoc}
124 * The queueSpeakable method simply offers a speakable to the queue.
125 * it notifies the synthesisQueue Thread and then it returns
126 * @throws NoresourceError if no MaryClient has been created
127 */
128 @Override
129 public void queueSpeakable(final SpeakableText speakable,
130 final String sessionId, final DocumentServer server)
131 throws NoresourceError {
132 if (processor == null) {
133 throw new NoresourceError("no synthesizer: cannot speak");
134 }
135 synthesisQueue.queueSpeakables(speakable);
136 speakableQueueEmpty = false;
137 }
138
139 /**
140 * {@inheritDoc}
141 */
142 @Override
143 public void waitNonBargeInPlayed() {
144 if (enableBargeIn) {
145 waitQueueEmpty();
146 }
147 }
148
149 /**
150 * {@inheritDoc}
151 */
152 @Override
153 public void waitQueueEmpty() {
154 isBusy();
155 }
156
157 /**
158 * {@inheritDoc}
159 */
160 @Override
161 public void activate() {
162 }
163
164 /**
165 * {@inheritDoc}
166 */
167 @Override
168 public void close() {
169 if (LOGGER.isDebugEnabled()) {
170 LOGGER.debug("closing audio output...");
171 }
172
173 waitQueueEmpty();
174
175 if (LOGGER.isDebugEnabled()) {
176 LOGGER.debug("...audio output closed");
177 }
178 }
179
180 /**
181 * {@inheritDoc}
182 */
183 @Override
184 public String getType() {
185 return type;
186
187 }
188
189
190 @Override
191 public void open() throws NoresourceError {
192 }
193
194
195 /**
196 * {@inheritDoc}
197 */
198 public void passivate() {
199 if (LOGGER.isDebugEnabled()) {
200 LOGGER.debug("passivating output...");
201 }
202 // Clear all lists and reset the flags.
203 listener.clear();
204 if (synthesisQueue != null) {
205 synthesisQueue.clearQueue();
206 synthesisQueue.interrupt();
207 synthesisQueue = null;
208 }
209 if (LOGGER.isDebugEnabled()) {
210 LOGGER.debug("...passivated output");
211 }
212 }
213
214 /**
215 * {@inheritDoc}
216 * It creates the MaryClient and starts the synthesisQueue thread.
217 */
218 @Override
219 public void connect(final ConnectionInformation info)
220 throws IOException {
221 processor = MaryClient.getMaryClient();
222 synthesisQueue = new SynthesisQueue();
223 synthesisQueue.addListener(this);
224 synthesisQueue.setProcessor(processor);
225 synthesisQueue.setRequestParameters(maryRequestParameters);
226 synthesisQueue.start();
227 }
228
229 /**
230 * {@inheritDoc}
231 */
232 @Override
233 public void disconnect(final ConnectionInformation info) {
234 }
235
236 /**
237 * {@inheritDoc}
238 */
239 @Override
240 public void cancelOutput() {
241 synthesisQueue.cancelOutput();
242 }
243
244 /**
245 * {@inheritDoc}
246 * @return <code>true</code>
247 */
248 public boolean supportsBargeIn() {
249 return true;
250 }
251
252 /**
253 * {@inheritDoc}
254 */
255 @Override
256 public void addListener(final SynthesizedOutputListener
257 outputListener) {
258 synchronized (listener) {
259 listener.add(outputListener);
260 }
261 }
262
263 /**
264 * {@inheritDoc}
265 */
266 @Override
267 public void removeListener(final SynthesizedOutputListener
268 outputListener) {
269 synchronized (listener) {
270 listener.remove(outputListener);
271 }
272 }
273
274 /**
275 * Notifies all listeners that output has started.
276 * @param speakable the current speakable.
277 */
278 private void fireOutputStarted(final SpeakableText speakable) {
279 final SynthesizedOutputEvent event =
280 new OutputStartedEvent(this, null, speakable);
281 fireOutputEvent(event);
282 }
283
284 /**
285 * Notifies all listeners that the given marker has been reached.
286 * @param mark the reached marker.
287 */
288 private void fireMarkerReached(final String mark) {
289 final SynthesizedOutputEvent event =
290 new MarkerReachedEvent(this, null, mark);
291 fireOutputEvent(event);
292 }
293
294 /**
295 * Notifies all listeners that output has ended.
296 * @param speakable the current speakable.
297 */
298 private void fireOutputEnded(final SpeakableText speakable) {
299 final SynthesizedOutputEvent event =
300 new OutputEndedEvent(this, null, speakable);
301 fireOutputEvent(event);
302 }
303
304 /**
305 * Notifies all listeners that output queue is empty.
306 */
307 private void fireQueueEmpty() {
308 if (LOGGER.isDebugEnabled()) {
309 LOGGER.debug("Queue empty event fired to Implementation Platform");
310 }
311
312 final SynthesizedOutputEvent event = new QueueEmptyEvent(this, null);
313 fireOutputEvent(event);
314 }
315
316 /**
317 * Notifies all registered listeners about the given event.
318 * @param event the event.
319 * @since 0.6
320 */
321 private void fireOutputEvent(final SynthesizedOutputEvent event) {
322 synchronized (listener) {
323 final Collection<SynthesizedOutputListener> copy =
324 new java.util.ArrayList<SynthesizedOutputListener>();
325 copy.addAll(listener);
326 for (SynthesizedOutputListener current : copy) {
327 current.outputStatusChanged(event);
328 }
329 }
330 }
331
332 /**
333 * Sets the type of this resource.
334 * @param resourceType type of the resource
335 */
336 public void setType(final String resourceType) {
337 type = resourceType;
338 }
339
340
341 /**
342 * Gets the events fired from SynthesisQueue thread and it forwards them.
343 * to ImplementationPlatform
344 * it also sets the appropriate flags
345 * @param event the event.
346 */
347 public void outputStatusChanged(final SynthesizedOutputEvent event) {
348 final int id = event.getEvent();
349 switch (id) {
350 case SynthesizedOutputEvent.OUTPUT_STARTED:
351 final OutputStartedEvent outputStartedEvent =
352 (OutputStartedEvent) event;
353 final SpeakableText startedSpeakable =
354 outputStartedEvent.getSpeakable();
355 if (LOGGER.isDebugEnabled()) {
356 LOGGER.debug("output started " + startedSpeakable);
357 }
358 isBusy = true;
359 fireOutputStarted(startedSpeakable);
360 break;
361 case SynthesizedOutputEvent.OUTPUT_ENDED:
362 final OutputEndedEvent outputEndedEvent =
363 (OutputEndedEvent) event;
364 final SpeakableText endedSpeakable =
365 outputEndedEvent.getSpeakable();
366 if (LOGGER.isDebugEnabled()) {
367 LOGGER.debug("audio playing ended");
368 }
369 isBusy = false;
370 fireOutputEnded(endedSpeakable);
371 break;
372 case SynthesizedOutputEvent.QUEUE_EMPTY:
373 if (LOGGER.isDebugEnabled()) {
374 LOGGER.debug("output queue is empty");
375 }
376 speakableQueueEmpty = true;
377 fireQueueEmpty();
378 synchronized (emptyLock) {
379 emptyLock.notifyAll();
380 }
381 break;
382 /* case SynthesizedOutputEvent.MARKER_REACHED:
383 final MarkerReachedEvent markReachedEvent =
384 (MarkerReachedEvent) event;
385 markname = markReachedEvent.getMark();
386 LOGGER.info("reached mark '" + markname + "'");
387 break;
388 case SynthesizedOutputEvent.OUTPUT_UPDATE:
389 break;*/
390 default:
391 fireOutputEvent(event);
392 break;
393 }
394 }
395
396 /**
397 * {@inheritDoc}
398 */
399 @Override
400 public boolean isBusy() {
401 while (!speakableQueueEmpty || isBusy) {
402 synchronized (emptyLock) {
403 try {
404 emptyLock.wait();
405 } catch (InterruptedException e) {
406 return false;
407 }
408 }
409 }
410 return false;
411 }
412
413 /**
414 * Stops the currently playing Audio.
415 * @throws NoresourceError .
416 * */
417 public void cancelAudioOutput() throws NoresourceError {
418 synthesisQueue.cancelAudioOutput();
419 }
420
421 /**
422 * Sets the audio output.
423 * @param value the new audio type
424 */
425 public void setAudioType(final String value) {
426 if (value == null) {
427 return;
428 }
429 maryRequestParameters.put("audioType", value);
430 }
431
432 /**
433 * Sets the name of the voice to use.
434 * @param name the voice name
435 */
436 public void setVoiceName(final String name) {
437 if (name == null) {
438 return;
439 }
440 maryRequestParameters.put("voiceName", name);
441
442
443 }
444
445 /**
446 * Sets the language.
447 * @param lang the language
448 */
449 public void setLang(final String lang) {
450 if (lang == null) {
451 return;
452 }
453 maryRequestParameters.put("lang", lang);
454 }
455
456
457 /**
458 * {@inheritDoc}
459 */
460 @Override
461 public void outputError(final ErrorEvent error) {
462 synchronized (listener) {
463 final Collection<SynthesizedOutputListener> copy =
464 new java.util.ArrayList<SynthesizedOutputListener>();
465 copy.addAll(listener);
466 for (SynthesizedOutputListener current : copy) {
467 current.outputError(error);
468 }
469 }
470 }
471 }
472