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