001/**
002 * Copyright 2002 DFKI GmbH.
003 * All Rights Reserved.  Use is subject to license terms.
004 *
005 * See the file "license.terms" for information on usage and
006 * redistribution of this file, and for a DISCLAIMER OF ALL
007 * WARRANTIES.
008 */
009
010package de.dfki.lt.freetts.mbrola;
011
012import java.io.BufferedInputStream;
013import java.io.IOException;
014import java.io.PrintWriter;
015import java.util.List;
016
017import com.sun.speech.freetts.Item;
018import com.sun.speech.freetts.ProcessException;
019import com.sun.speech.freetts.Relation;
020import com.sun.speech.freetts.Utterance;
021import com.sun.speech.freetts.UtteranceProcessor;
022import com.sun.speech.freetts.util.Utilities;
023
024/**
025 * Calls external MBROLA binary to synthesise the utterance.
026 */
027public class MbrolaCaller implements UtteranceProcessor {
028
029    private String[] cmd;
030    private long closeDelay = 0l;
031
032    /**
033     * Create an Mbrola caller which will call an external MBROLA binary
034     * using the command <code>cmd</code>. The command string is used
035     * as it is, which means that it must contain full path specifications
036     * and the correct file separators.
037     */
038    public MbrolaCaller(String[] cmd) {
039        this.cmd = cmd;
040        closeDelay = Utilities.getLong
041                ("de.dfki.lt.freetts.mbrola.MbrolaCaller.closeDelay",
042                        100L).longValue();
043    }
044
045    /**
046     * Call external MBROLA binary to synthesize the utterance.
047     *
048     * @param  utterance  the utterance to process
049     *
050     * @throws ProcessException if an error occurs while
051     *         processing of the utterance
052     */
053    public void processUtterance(Utterance utterance) throws ProcessException {
054        // Go through Segment relation and print values into Mbrola
055        Relation segmentRelation = utterance.getRelation(Relation.SEGMENT);
056        Item segment = segmentRelation.getHead();
057
058        if (segment == null) {
059            return;
060        }
061
062        // Open Mbrola
063        Process process;
064        try {
065            process = Runtime.getRuntime().exec(cmd);
066        } catch (Exception e) {
067            throw new ProcessException("Cannot start mbrola program: " + cmd,
068                    e);
069        }
070        PrintWriter toMbrola = new PrintWriter(process.getOutputStream());
071        BufferedInputStream fromMbrola =
072            new BufferedInputStream(process.getInputStream());
073
074        while (segment != null) {
075            String name = segment.getFeatures().getString("name");
076            // Individual duration of segment, in milliseconds:
077            int dur = segment.getFeatures().getInt("mbr_dur");
078            // List of time-f0 targets. In each target, the first value
079            // indicates where on the time axis the target is reached,
080            // expressed in percent of segment duration; the second value in
081            // each pair is f0, in Hz.
082            String targets = segment.getFeatures().getString("mbr_targets");
083            String output = (name + " " + dur + " " + targets);
084            // System.out.println(output);
085            toMbrola.println(output);
086            segment = segment.getNext();
087        }
088
089        toMbrola.flush();
090
091        // BUG:
092        // There is a  bug that causes the final 'close' on a stream
093        // going to a sub-process to not be seen by the sub-process on
094        // occasion. This seems to occur mainly when the close occurs
095        // very soon after the creation and writing of data to the
096        // sub-process.  This delay can help work around the problem
097        // If we delay before the close by 
098        // a small amount (100ms), the hang is averted.  This is a WORKAROUND
099        // only and should be removed once the bug in the 'exec' is
100        //  fixed.  We get the delay from the property:
101        //
102        // de.dfki.lt.freetts.mbrola.MbrolaCaller.closeDelay,
103        //
104
105        if (closeDelay > 0l) {
106            try {
107                Thread.sleep(closeDelay);
108            } catch (InterruptedException ie) {
109            }
110        }
111        toMbrola.close();
112      
113        // reading the audio output
114        byte[] buffer = new byte[1024];
115        
116        // In order to avoid resizing a large array, we save the audio data
117        // in the chunks in which we read it.
118
119        List audioData = new java.util.ArrayList();
120        int totalSize = 0;
121        int nrRead = -1; // -1 means end of file
122
123        try {
124            while ((nrRead = fromMbrola.read(buffer)) != -1) {
125                if (nrRead < buffer.length) {
126                    byte[] slice = new byte[nrRead];
127                    System.arraycopy(buffer, 0, slice, 0, nrRead);
128                    audioData.add(slice);
129                } else {
130                    audioData.add(buffer);
131                    buffer = new byte[buffer.length];
132                }
133                totalSize += nrRead;
134            }
135            fromMbrola.close();
136        } catch (IOException e) {
137            throw new ProcessException("Cannot read from mbrola", e);
138        }
139
140        if (totalSize == 0) {
141            throw new ProcessException("No audio data read");
142        }
143
144        utterance.setObject("mbrolaAudio", audioData);
145        utterance.setInt("mbrolaAudioLength", totalSize);
146    }
147
148    public String toString() {
149        return "MbrolaCaller";
150    }
151}