001/**
002 * Copyright 2001 Sun Microsystems, Inc.
003 * 
004 * See the file "license.terms" for information on usage and
005 * redistribution of this file, and for a DISCLAIMER OF ALL 
006 * WARRANTIES.
007 */
008package com.sun.speech.freetts.audio;
009
010import java.io.ByteArrayInputStream;
011import java.io.File;
012import java.io.IOException;
013
014import javax.sound.sampled.AudioFileFormat;
015import javax.sound.sampled.AudioFormat;
016import javax.sound.sampled.AudioInputStream;
017import javax.sound.sampled.AudioSystem;
018
019import com.sun.speech.freetts.util.Utilities;
020
021/**
022 * Streams audio to multiple files as 8-bit samples, one per utterance. 
023 * Currently, FreeTTS always outputs 16-bit samples, and this
024 * MultiFile8BitAudioPlayer will convert them to 8-bit before outputting
025 * them.
026 */
027public class MultiFile8BitAudioPlayer implements AudioPlayer {
028    // 8-bit unsigned little-endian mono audio
029    private AudioFormat currentFormat = new AudioFormat
030    (8000, 8, 1, false, false);
031
032    private int fileCount = 0;
033    private String baseName;
034    private byte[] outputData;
035    private int curIndex = 0;
036    private AudioFileFormat.Type outputType;
037
038
039    /**
040     * Creates a default audio player for an AudioFileFormat of type
041     * WAVE.  Reads the "com.sun.speech.freetts.AudioPlayer.baseName"
042     * property for the base filename to use, and will produce files
043     * of the form <baseName>1.wav.  The default value for the
044     * base name is "freetts".
045     */
046    public MultiFile8BitAudioPlayer() {
047        this(Utilities.getProperty(
048                 "com.sun.speech.freetts.AudioPlayer.baseName", "freetts"),
049             AudioFileFormat.Type.WAVE);
050    }
051    
052    /**
053     * Constructs a MultiFile8BitAudioPlayer 
054     *
055     * @param baseName the base name of the audio file
056     * @param type the type of audio output
057     *
058     */
059    public MultiFile8BitAudioPlayer(String baseName, 
060                                    AudioFileFormat.Type type) {
061        this.baseName = baseName;
062        this.outputType = type;
063    }
064
065
066    /**
067     * Sets the audio format for this player
068     *
069     * @param format the audio format
070     *
071     * @throws UnsupportedOperationException if the line cannot be opened with
072     *     the given format
073     */
074    public synchronized void setAudioFormat(AudioFormat format) {
075    }
076
077
078    /**
079     * Gets the audio format for this player
080     *
081     * @return format the audio format
082     */
083    public AudioFormat getAudioFormat() {
084        return currentFormat;
085    }
086
087
088    /**
089     * Pauses audio output
090     */
091    public void pause() {
092    }
093
094    /**
095     * Resumes audio output
096     */
097    public synchronized void resume() {
098    }
099        
100    /**
101     * Starts the first sample timer
102     */
103    public void startFirstSampleTimer() {
104    }
105
106
107    /**
108     * Cancels currently playing audio
109     */
110    public synchronized void cancel() {
111    }
112
113    /**
114     * Prepares for another batch of output. Larger groups of output
115     * (such as all output associated with a single FreeTTSSpeakable)
116     * should be grouped between a reset/drain pair.
117     */
118    public synchronized void reset() {
119    }
120
121    /**
122     * Closes this audio player
123     */
124    public synchronized void close() {
125    }
126
127    /**
128     * Returns the current volume.
129     *
130     * @return the current volume (between 0 and 1)
131     */
132    public float getVolume() {
133        return 1.0f;
134    }         
135
136    /**
137     * Sets the current volume.
138     *
139     * @param volume  the current volume (between 0 and 1)
140     */
141    public void setVolume(float volume) {
142    }         
143
144
145    /**
146     *  Starts the output of a set of data. Audio data for a single
147     *  utterance should be grouped between begin/end pairs.
148     *
149     * @param size the size of data between now and the end
150     */
151    public void begin(int size) {
152        outputData = new byte[size/2];
153        curIndex = 0;
154    }
155
156    /**
157     * {@inheritDoc}
158     */
159    public boolean end() throws IOException {
160        ByteArrayInputStream bais = new ByteArrayInputStream(outputData);
161        AudioInputStream ais = new AudioInputStream
162            (bais, currentFormat, 
163             outputData.length/currentFormat.getFrameSize());
164        String name = baseName;
165        name = name + fileCount;
166        name = name + "." + outputType.getExtension();
167        File file = new File(name);
168        try {
169            AudioSystem.write(ais, outputType, file);
170            System.out.println("Wrote synthesized speech to " + name);
171        } catch (IllegalArgumentException iae) {
172            throw new IOException("Can't write audio type " + outputType, iae);
173        }
174        fileCount++;
175        return true;
176    }
177
178
179    /**
180     * Waits for all queued audio to be played
181     *
182     * @return true if the audio played to completion, false if
183     *   the audio was stopped
184     */
185    public boolean drain()  {
186        return true;
187    }
188
189    /**
190     * Gets the amount of played since the last mark
191     *
192     * @return the amount of audio in milliseconds
193     */
194    public synchronized long getTime()  {
195        return -1L;
196    }
197
198
199    /**
200     * Resets the audio clock
201     */
202    public synchronized void resetTime() {
203    }
204    
205
206    
207    /**
208     * Writes the given bytes to the audio stream
209     *
210     * @param audioData audio data to write to the device
211     *
212     * @return <code>true</code> of the write completed successfully, 
213     *          <code> false </code>if the write was cancelled.
214     */
215    public boolean write(byte[] audioData) {
216        return write(audioData, 0, audioData.length);
217    }
218
219    
220    /**
221     * Writes the given bytes to the audio stream
222     *
223     * @param bytes audio data to write to the device
224     * @param offset the offset into the buffer
225     * @param size the size into the buffer
226     *
227     * @return <code>true</code> of the write completed successfully, 
228     *          <code> false </code>if the write was cancelled.
229     */
230    public boolean write(byte[] bytes, int offset, int size) {
231        bytes = convert16To8Bits(bytes);
232        size /= 2;
233        System.arraycopy(bytes, offset, outputData, curIndex, size);
234        curIndex += size;
235        return true;
236    }
237
238
239    /**
240     * Converts an array of signed 16-bit audio data to unsigned 8-bit
241     * audio data.
242     *
243     * @param samples16Bit the signed 16-bit audio data to convert
244     *
245     * @return unsigned 8-bit audio data
246     */
247    private static byte[] convert16To8Bits(byte[] samples16Bit) {
248        byte[] samples8Bit = new byte[samples16Bit.length/2];
249        for (int i = 0, j = 0; i < samples16Bit.length; i += 2, j++) {
250            int sample = (0x000000FF & samples16Bit[i]);
251            samples8Bit[j] = (byte) (sample + 128);
252        }
253        return samples8Bit;
254    }
255
256
257    /**
258     * Returns the name of this audioplayer
259     *
260     * @return the name of the audio player
261     */
262    public String toString() {
263        return "MultiFile8BitAudioPlayer";
264    }
265
266    /**
267     * Shows metrics for this audio player
268     */
269    public void showMetrics() {
270    }
271}