001/**
002 * Portions Copyright 2001 Sun Microsystems, Inc.
003 * Portions Copyright 1999-2001 Language Technologies Institute, 
004 * Carnegie Mellon University.
005 * All Rights Reserved.  Use is subject to license terms.
006 * 
007 * See the file "license.terms" for information on usage and
008 * redistribution of this file, and for a DISCLAIMER OF ALL 
009 * WARRANTIES.
010 */
011package com.sun.speech.freetts.diphone;
012import com.sun.speech.freetts.relp.Sample;
013
014import java.nio.ByteBuffer;
015import java.io.IOException;
016import java.io.DataInputStream;
017import java.io.DataOutputStream;
018
019
020/**
021 * Represents two adjacent phones. A diphone is defined by its name,
022 * the set of audio data, and information used to help stitch diphones
023 * together. This class is immutable.
024 */
025public class Diphone {
026    protected final static int MAGIC = 0xFACE0FF;
027    protected final static int ALIAS_MAGIC = 0xBABAF00;
028    protected final static int NAME_LENGTH = 8; 
029    private String name;
030    private int midPoint;
031    private Sample[] samples;
032    private int unitSizePart1;
033    private int unitSizePart2;
034
035    /**
036     * Creates a diphone with the given name, samples and midpoint.
037     *
038     * @param name the name of the diphone
039     * @param samples the set of samples for the diphone
040     * @param midPoint the index of the sample midpoint
041     */
042    public Diphone(String name, Sample[] samples, int midPoint) {
043        this.name = name;
044        this.midPoint = midPoint;
045        this.samples = samples;
046        this.unitSizePart1 = 0;
047        this.unitSizePart2 = 0;
048
049        for (int i = 0; i < midPoint; i++) {
050            unitSizePart1 += samples[i].getResidualSize();
051        }
052        for (int i = midPoint; i < samples.length; i++) {
053            unitSizePart2 += samples[i].getResidualSize();
054        }
055    }
056
057    /**
058     * Constructor to be used only by subclasses who do not use the
059     * variables except for the name
060     * @param name the name of the diphone 
061     */
062    protected Diphone(String name)
063    {
064        this.name = name;
065        this.midPoint = 0;
066        this.samples = null;
067        this.unitSizePart1 = 0;
068        this.unitSizePart2 = 0;
069    }
070
071    /**
072     * Returns the samples associated with this diphone.
073     *
074     * @return the samples associated with this diphone
075     */
076    public Sample[] getSamples() {
077        return samples;
078    }
079
080    /**
081     * Returns a particular sample.
082     *
083     * @param which which sample to return
084     *
085     * @return the desired sample
086     */
087    public Sample getSamples(int which) {
088        return samples[which];
089    }
090
091    /**
092     * Gets the name of the diphone. 
093     *
094     * @return the name of the diphone
095     */
096    public String getName() {
097        return name;
098    }
099    
100
101    /**
102     * Returns the midpoint index. the midpoint index is the sample
103     * that divides the diphone into the first and second parts.
104     *
105     * @return the midpoint index.
106     */
107    public int getMidPoint() {
108        return midPoint;
109    }
110    
111    /**
112     * Returns the midpoint index. the midpoint index is the sample
113     * that divides the diphone into the first and second parts.
114     *
115     * @return the midpoint index.
116     */
117    public int getPbPositionMillis() {
118        return getMidPoint();
119    }
120
121    /**
122     * Returns the sample that is closest to uIndex.
123     *
124     * @param uIndex the desired index
125     * @param unitPart do we want the first have (1) or the second
126     *          half (2)
127     *
128     * @return the sample nearest to the given index in the given
129     *          part
130     */ 
131    public Sample nearestSample(float uIndex, int unitPart) {
132        int i, iSize = 0, nSize;
133        // loop through all the Samples in this Diphone
134        int start = (unitPart == 1) ? 0 : midPoint;
135        int end = (unitPart == 1) ? midPoint : samples.length;
136        
137        for (i = start; i < end; i++) {
138            nSize = iSize + samples[i].getResidualSize();
139
140            if (Math.abs(uIndex - (float) iSize) <
141                Math.abs(uIndex - (float) nSize)) {
142                return samples[i];
143            }
144            iSize = nSize;
145        }
146        return samples[end-1];
147    }
148
149    /**
150     * Returns the total number of residuals in the given part for this
151     * diphone.
152     *
153     * @param unitPart indicates which part is of interest (1 or 2)
154     *
155     * @return the number of residuals in the specified part
156     */
157    public int getUnitSize(int unitPart) {
158        if (unitPart == 1) {
159            return unitSizePart1;
160        } else {
161            return unitSizePart2;
162        }
163    }
164
165    /**
166     * dumps out this Diphone.
167     */
168    public void dump() {
169        System.out.println("Diphone: " + name);
170        System.out.println("    MP : " + midPoint);
171        for (int i = 0; i < samples.length; i++) {
172            samples[i].dump();
173        }
174    }
175
176    /**
177     * Dumps the diphone to the given channel.
178     *
179     * @param bb the ByteBuffer to write to
180     *
181     * @throws IOException if IO error occurs
182     */
183    public void dumpBinary(ByteBuffer bb) throws IOException {
184        char[] nameArray = (name + "        ").toCharArray();
185
186        bb.putInt(MAGIC);
187        for (int i = 0; i < NAME_LENGTH; i++) {
188            bb.putChar(nameArray[i]);
189        }
190        bb.putInt(midPoint);
191        bb.putInt(samples.length);
192
193        for (int i = 0; i < samples.length; i++) {
194            samples[i].dumpBinary(bb);
195        }
196    }
197
198    /**
199     * Dumps the diphone to the given channel.
200     *
201     * @param os the DataOutputStream to write to
202     *
203     * @throws IOException if IO error occurs
204     */
205    public void dumpBinary(DataOutputStream os) throws IOException {
206        char[] nameArray = (name + "        ").toCharArray();
207
208        os.writeInt(MAGIC);
209        for (int i = 0; i < NAME_LENGTH; i++) {
210            os.writeChar(nameArray[i]);
211        }
212        os.writeInt(midPoint);
213        os.writeInt(samples.length);
214
215        for (int i = 0; i < samples.length; i++) {
216            samples[i].dumpBinary(os);
217        }
218    }
219
220    /**
221     * Determines if the two diphones are equivalent. This is for
222     * testing databases. This is not the same as "equals"
223     *
224     * @param other the diphone to compare this one to
225     *
226     * @return <code>true</code> if the diphones match; otherwise
227     *          <code>false</code> 
228     */
229    boolean compare(Diphone other) {
230        if (!name.equals(other.getName())) {
231            return false;
232        }
233
234        if (midPoint != other.getMidPoint()) {
235            return false;
236        }
237
238        if (samples.length != other.getSamples().length) {
239            return false;
240        }
241
242        for (int i = 0; i < samples.length; i++) {
243            if (!samples[i].compare(other.getSamples(i))) {
244                return false;
245            }
246        }
247        return true;
248    }
249
250    /**
251     * Loads a new diphone from  the given buffer.
252     *
253     * @param bb the byte buffer to load the diphone from
254     *
255     * @return the new diphone
256     *
257     * @throws IOException if IO error occurs
258     */
259    public static Diphone loadBinary(ByteBuffer bb) throws IOException {
260        StringBuffer sb = new StringBuffer();
261        int midPoint;
262        int numSamples;
263        Sample[] samples;
264
265        int magic = bb.getInt();
266    if (magic == ALIAS_MAGIC) {
267        for (int i = 0; i < NAME_LENGTH; i++) {
268            char c = bb.getChar();
269            if (!Character.isWhitespace(c)) {
270                sb.append(c);
271            }
272        }
273        String name = sb.toString().trim();
274        sb.setLength(0);
275        for (int i = 0; i < NAME_LENGTH; i++) {
276            char c = bb.getChar();
277            if (!Character.isWhitespace(c)) {
278                sb.append(c);
279            }
280        }
281        String origName = sb.toString().trim();
282        return new AliasDiphone(name, origName);
283    } else if (magic != MAGIC) {
284            throw new Error("Bad magic number in diphone");
285        }
286
287        for (int i = 0; i < NAME_LENGTH; i++) {
288            char c = bb.getChar();
289            if (!Character.isWhitespace(c)) {
290                sb.append(c);
291            }
292        }
293
294        midPoint = bb.getInt();
295        numSamples = bb.getInt();
296
297        samples = new Sample[numSamples];
298        for (int i = 0; i < numSamples; i++) {
299            samples[i] = Sample.loadBinary(bb);
300        }
301        return new Diphone(sb.toString().trim(), samples, midPoint);
302    }
303
304    /**
305     * Loads a new  diphone from  the given DataInputStream.
306     *
307     * @param dis the datainput stream to load the diphone from
308     *
309     * @return the new diphone
310     *
311     * @throws IOException if IO error occurs
312     */
313    public static Diphone loadBinary(DataInputStream dis) throws IOException {
314        StringBuffer sb = new StringBuffer();
315        int midPoint;
316        int numSamples;
317        Sample[] samples;
318
319    int magic = dis.readInt(); 
320    if (magic == ALIAS_MAGIC) {
321        for (int i = 0; i < NAME_LENGTH; i++) {
322            char c = dis.readChar();
323            if (!Character.isWhitespace(c)) {
324                sb.append(c);
325            }
326        }
327        String name = sb.toString().trim();
328        sb.setLength(0);
329        for (int i = 0; i < NAME_LENGTH; i++) {
330            char c = dis.readChar();
331            if (!Character.isWhitespace(c)) {
332                sb.append(c);
333            }
334        }
335        String origName = sb.toString().trim();
336        return new AliasDiphone(name, origName);
337    } else if (magic != MAGIC) {
338            throw new Error("Bad magic number in diphone");
339        }
340
341        for (int i = 0; i < NAME_LENGTH; i++) {
342            char c = dis.readChar();
343            if (!Character.isWhitespace(c)) {
344                sb.append(c);
345            }
346        }
347
348        midPoint = dis.readInt();
349        numSamples = dis.readInt();
350
351        samples = new Sample[numSamples];
352        for (int i = 0; i < numSamples; i++) {
353            samples[i] = Sample.loadBinary(dis);
354        }
355        return new Diphone(sb.toString().trim(), samples, midPoint);
356    }
357}
358