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;
012
013import com.sun.speech.freetts.FeatureSet;
014import com.sun.speech.freetts.Item;
015import com.sun.speech.freetts.relp.LPCResult;
016import com.sun.speech.freetts.UtteranceProcessor;
017import com.sun.speech.freetts.Utterance;
018import com.sun.speech.freetts.Relation;
019import com.sun.speech.freetts.ProcessException;
020import com.sun.speech.freetts.relp.SampleInfo;
021
022/**
023 * Calculates pitchmarks. This is an utterance processor that expects
024 * the utterance to have a target relation. It will create an
025 * LPCResult and add it to the utterance based upon features of the
026 * target relation.
027 *
028 *  @see LPCResult
029 *  @see Relation
030 *  @see SampleInfo
031 */
032public class DiphonePitchmarkGenerator implements UtteranceProcessor {
033
034    /**
035     * Generates the LPCResult for this utterance.
036     *
037     * @param utterance the utterance to process
038     *
039     * @throws ProcessException if an error occurs while processing
040     *     the utterance
041     * @throws IllegalStateException if the given utterance has no
042     *          relation named Relation.TARGET or a feature named
043     *          SampleInfo.UTT_NAME
044     */
045    public void processUtterance(Utterance utterance) throws ProcessException {
046
047        // precondition that must be satisfied
048        Relation targetRelation = utterance.getRelation(Relation.TARGET);
049        if (targetRelation == null) {
050            throw new IllegalStateException
051                ("DiphonePitchmarkGenerator: Target relation does not exist");
052        }
053
054        SampleInfo sampleInfo;
055        sampleInfo = (SampleInfo) utterance.getObject(SampleInfo.UTT_NAME);
056        if (sampleInfo == null) {
057            throw new IllegalStateException
058                ("DiphonePitchmarkGenerator: SampleInfo does not exist");
059        }
060        
061        float pos, f0, m = 0;
062        float lf0 = utterance.getVoice().getPitch();
063        
064        double time = 0;
065        int pitchMarks = 0;  // how many pitch marks
066
067        LPCResult lpcResult;
068        IntLinkedList timesList = new IntLinkedList();
069        
070        // first pass to count how many pitch marks will be required
071        for (Item targetItem = targetRelation.getHead();
072             targetItem != null; targetItem = targetItem.getNext()) {
073            FeatureSet featureSet = targetItem.getFeatures();
074            pos = featureSet.getFloat("pos");
075            f0 = featureSet.getFloat("f0");
076        //System.err.println("Target pos="+pos+", f0="+f0);
077            if (time == pos) {
078            lf0 = f0;
079                continue;
080            }
081            m = (f0-lf0)/pos;
082        //System.err.println("m=("+f0+"-"+lf0+")/"+pos+"="+m);
083            for (; time < pos; pitchMarks++) {
084                time += 1/(lf0 + (time * m));
085        //System.err.println("f("+time+")="+((lf0+(time*m))));
086                // save the time value in a list
087                timesList.add((int) (time * sampleInfo.getSampleRate()));
088            }
089        lf0 = f0;
090        }
091        lpcResult = new LPCResult();
092        // resize the number of frames to the number of pitchmarks
093        lpcResult.resizeFrames(pitchMarks);
094
095        pitchMarks = 0;
096
097        int[] targetTimes = lpcResult.getTimes();
098        
099        // second pass puts the values in
100        timesList.resetIterator();
101        for (; pitchMarks < targetTimes.length; pitchMarks++) {
102            targetTimes[pitchMarks] = timesList.nextInt();
103        }
104        utterance.setObject("target_lpcres", lpcResult);
105    }
106    
107
108    /**
109     * Returns a string representation of this object.
110     *
111     * @return a string representation of this object
112     */
113    public String toString() {
114        return "DiphonePitchmarkGenerator";
115    }
116}
117
118/**
119 * Represents a linked list with each node of the list storing
120 * a primitive int data type. Unlike the java.util.LinkedList, it avoids
121 * the need to wrap the float number in a Float object. This avoids
122 * unnecessary object creation, and is therefore faster and saves memory.
123 * However, it does not implement the java.util.List interface.
124 *
125 * This linked list is used as a replacement for a simple array of
126 * ints. Certain performance critical loops have had performance
127 * issues due to the overhead associated with array index bounds
128 * checking performed by the VM. Using this type of data structure
129 * allowed the checking to be bypassed. Note however that we've seen
130 * great improvement in compiler performance in this area such that we
131 * may be able to revert to using an array without any performance
132 * impact.
133 *
134 * [[[ TODO look at replacing this with a simple int array ]]]
135 */
136class IntLinkedList {
137    private IntListNode head = null;
138    private IntListNode tail = null;
139    private IntListNode iterator = null;
140
141    /**
142     * Constructs an empty IntLinkedList.
143     */
144    public IntLinkedList() {
145        head = null;
146        tail = null;
147        iterator = null;
148    }
149    
150    /**
151     * Adds the given float to the end of the list.
152     *
153     * @param val the float to add
154     */
155    public void add(int val) {
156        IntListNode node = new IntListNode(val);
157        if (head == null) {
158            head = node;
159            tail = node;
160        } else {
161            tail.next = node;
162            tail = node;
163        }
164    }
165    
166    /**
167     * Moves the iterator to point to the front of the list.
168     */
169    public void resetIterator() {
170        iterator = head;
171    }
172    
173    /**
174     * Returns the next float in the list, advances the iterator.
175     * The <code>hasNext()</code> method MUST be called before calling
176     * this method to check if the iterator is point to null,
177     * otherwise NullPointerException will be thrown.
178     *
179     * @return the next value
180     */
181    public int nextInt() {
182        int val = iterator.val;
183        if (iterator != null) {
184            iterator = iterator.next;
185        }
186        return val;
187    }
188    
189    /**
190     * Checks if there are more elements for the iterator.
191     *
192     * @return <code>true</code>  if there are more elements; 
193     *          otherwise <code>false</code>
194     */ 
195    public boolean hasNext() {
196        return (iterator != null);
197    }
198}
199
200/**
201 * Represents a node for the IntList
202 */
203class IntListNode {
204    int val;
205    IntListNode next;
206
207    /**
208     * Creates a node that wraps the given value.
209     *
210     * @param val the value to be contained in the list
211     */
212    public IntListNode(int val) {
213        this.val = val;
214        next = null;
215    }
216}