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.en.us;
012
013import java.util.HashSet;
014import java.util.Set;
015import java.util.regex.Pattern;
016
017import com.sun.speech.freetts.FeatureProcessor;
018import com.sun.speech.freetts.Item;
019import com.sun.speech.freetts.PartOfSpeech;
020import com.sun.speech.freetts.PathExtractor;
021import com.sun.speech.freetts.PathExtractorImpl;
022import com.sun.speech.freetts.ProcessException;
023import com.sun.speech.freetts.Relation;
024import com.sun.speech.freetts.Voice;
025
026
027
028/**
029 * Provides the set of feature processors that are used by this
030 * language as part of the CART processing.
031 */
032public class FeatureProcessors {
033
034    private final static PathExtractor FIRST_SYLLABLE_PATH = 
035        new PathExtractorImpl(
036      "R:SylStructure.parent.R:Phrase.parent.daughter.R:SylStructure.daughter",
037       false);
038
039    private final static PathExtractor LAST_SYLLABLE_PATH = 
040        new PathExtractorImpl(
041      "R:SylStructure.parent.R:Phrase.parent.daughtern.R:SylStructure.daughter",
042      false);
043
044    private final static PathExtractor LAST_LAST_SYLLABLE_PATH = 
045        new PathExtractorImpl(
046  "R:SylStructure.parent.R:Phrase.parent.daughtern.R:SylStructure.daughtern",
047      false);
048
049    private final static PathExtractor SUB_PHRASE_PATH = 
050        new PathExtractorImpl("R:SylStructure.parent.R:Phrase.parent.p", false);
051
052    private final static Pattern DOUBLE_PATTERN 
053        = Pattern.compile(USEnglish.RX_DOUBLE);
054
055    private final static Pattern DIGITS_PATTERN  
056        = Pattern.compile(USEnglish.RX_DIGITS);
057
058    private static Set months;
059    private static Set days;
060
061    // the set of month names
062    static {
063        months = new HashSet();
064        months.add("jan");
065        months.add("january");
066        months.add("feb");
067        months.add("february");
068        months.add("mar");
069        months.add("march");
070        months.add("apr");
071        months.add("april");
072        months.add("may");
073        months.add("jun");
074        months.add("june");
075        months.add("jul");
076        months.add("july");
077        months.add("aug");
078        months.add("august");
079        months.add("sep");
080        months.add("september");
081        months.add("oct");
082        months.add("october");
083        months.add("nov");
084        months.add("november");
085        months.add("dec");
086        months.add("december");
087    }
088
089    // the set of week neames
090    static {
091        days = new HashSet();
092        days.add("sun");
093        days.add("sunday");
094        days.add("mon");
095        days.add("monday");
096        days.add("tue");
097        days.add("tuesday");
098        days.add("wed");
099        days.add("wednesday");
100        days.add("thu");
101        days.add("thursday");
102        days.add("fri");
103        days.add("friday");
104        days.add("sat");
105        days.add("saturday");
106    }
107
108    // no instances
109    private FeatureProcessors() {}
110
111    /**
112     * Returns a guess of the part-of-speech.
113     *
114     * This is a feature processor. A feature processor takes an item,
115     * performs some sort of processing on the item and returns an object.
116     */
117    public static class Gpos implements FeatureProcessor {
118        PartOfSpeech pos;
119        /**
120         * Creates a GPOS with the given part-of-speech table
121         *
122         * @param pos part of speech determiner
123         */
124        public Gpos(PartOfSpeech pos) {
125            this.pos = pos;
126        }
127
128        /**
129         * Performs some processing on the given item.
130         *
131         * @param  item  the item to process
132         *
133         * @return a guess at the part-of-speech for the item
134         *
135         * @throws ProcessException if an exception occurred during the
136         * processing
137         */
138        public String process(Item item) throws ProcessException {
139            return pos.getPartOfSpeech(item.toString());
140        }
141    }
142
143
144    /**
145     * Returns as an Integer the number of syllables in the given
146     * word.  This is a feature processor. A feature processor takes an item,
147     * performs some sort of processing on the item and returns an object.
148     */
149    public static class WordNumSyls implements FeatureProcessor {
150
151        /**
152         * Performs some processing on the given item.
153         *
154         * @param  item  the item to process
155         *
156         * @return the number of syllables in the given word
157         *
158         * @throws ProcessException if an exception occurred during the
159         * processing
160         */
161        public String process(Item item) throws ProcessException {
162            int count = 0;
163            Item daughter = item.getItemAs(
164                Relation.SYLLABLE_STRUCTURE).getDaughter();
165            while (daughter != null) {
166                count++;
167                daughter = daughter.getNext();
168            }
169            return Integer.toString(rail(count));
170        }
171    }
172
173    /**
174     * Counts the number of accented syllables since the last major break.
175     * This is a feature processor. A feature processor takes an item,
176     * performs some sort of processing on the item and returns an object.
177     */
178    public static class AccentedSylIn implements FeatureProcessor {
179
180        /**
181         * Performs some processing on the given item.
182         *
183         * @param  item  the item to process
184         *
185         * @return the number of accented syllables since the last
186         *    major break
187         *
188         * @throws ProcessException if an exception occurred during the
189         * processing
190         */
191        public String process(Item item) throws ProcessException {
192            int count = 0;
193            Item ss = item.getItemAs(Relation.SYLLABLE);
194            Item firstSyllable  = FIRST_SYLLABLE_PATH.findItem(item);
195
196            for (Item p = ss; p != null; p = p.getPrevious() )  {
197                if (isAccented(p)) {
198                    count++;
199                }
200                if (p.equalsShared(firstSyllable)) {
201                    break;
202                }
203            }
204            return Integer.toString(rail(count));
205        }
206    }
207
208
209    /**
210     * Counts the number of stressed syllables since the last major break.
211     * This is a feature processor. A feature processor takes an item,
212     * performs some sort of processing on the item and returns an object.
213     */
214    public static class StressedSylIn implements FeatureProcessor {
215
216        /**
217         * Performs some processing on the given item.
218         *
219         * @param  item  the item to process
220         *
221         * @return the number of stresses syllables since the last
222         * major break
223         *
224         * @throws ProcessException if an exception occurred during the
225         * processing
226         */
227        public String process(Item item) throws ProcessException {
228            int count = 0;
229            Item ss = item.getItemAs(Relation.SYLLABLE);
230            Item firstSyllable  = FIRST_SYLLABLE_PATH.findItem(item);
231
232            // this should include the first syllable, but
233            // flite 1.1 and festival don't.
234
235            for (Item p = ss.getPrevious(); 
236                    p != null && !p.equalsShared(firstSyllable);
237                    p = p.getPrevious() )  {
238                if ("1".equals(p.getFeatures().getString("stress"))) {
239                    count++;
240                }
241            }
242            return Integer.toString(rail(count));
243        }
244    }
245
246    /**
247     * Counts the number of stressed syllables until the next major break.
248     * This is a feature processor. A feature processor takes an item,
249     * performs some sort of processing on the item and returns an object.
250     */
251    public static class StressedSylOut implements FeatureProcessor {
252
253        /**
254         * Performs some processing on the given item.
255         *
256         * @param  item  the item to process
257         *
258         * @return the number of stressed syllables until the next major break
259         *
260         * @throws ProcessException if an exception occurred during the
261         * processing
262         */
263        public String process(Item item) throws ProcessException {
264            int count = 0;
265            Item ss = item.getItemAs(Relation.SYLLABLE);
266            Item lastSyllable  = LAST_SYLLABLE_PATH.findItem(item);
267
268            for (Item p = ss.getNext(); p != null; p = p.getNext() )  {
269                if ("1".equals(p.getFeatures().getString("stress"))) {
270                    count++;
271                }
272                if (p.equalsShared(lastSyllable)) {
273                    break;
274                }
275            }
276            return Integer.toString(rail(count));
277        }
278    }
279
280    /**
281     * Returns the length of the string. (generally this is a digit
282     * string)
283     * This is a feature processor. A feature processor takes an item,
284     * performs some sort of processing on the item and returns an object.
285     */
286    public static class NumDigits implements FeatureProcessor {
287
288        /**
289         * Performs some processing on the given item.
290         *
291         * @param  item  the item to process
292         *
293         * @return the length of the string
294         *
295         * @throws ProcessException if an exception occurred during the
296         * processing
297         */
298        public String process(Item item) throws ProcessException {
299            String name = item.getFeatures().getString("name");
300            return Integer.toString(rail(name.length()));
301        }
302    }
303
304    /**
305     * Returns true ("1") if the given item is a number between 0 and
306     * 32 exclusive, otherwise, returns "0".
307     * string)
308     * This is a feature processor. A feature processor takes an item,
309     * performs some sort of processing on the item and returns an object.
310     */
311    public static class MonthRange implements FeatureProcessor {
312
313        /**
314         * Performs some processing on the given item.
315         *
316         * @param  item  the item to process
317         *
318         * @return returns "1" if the given item is a number between 0
319         * and 32 (exclusive) otherwise returns "0"
320         *
321         * @throws ProcessException if an exception occurred during the
322         * processing
323         */
324        public String process(Item item) throws ProcessException {
325            int v = Integer.parseInt(item.getFeatures().getString("name"));
326            if ((v > 0) && (v < 32)) {
327                return "1";
328            } else {
329                return "0";
330            }
331        }
332    }
333
334    /**
335     * Attempts to guess the part of speech.
336     * This is a feature processor. A feature processor takes an item,
337     * performs some sort of processing on the item and returns an object.
338     */
339    public static class TokenPosGuess implements FeatureProcessor {
340        /**
341         * Performs some processing on the given item.
342         *
343         * @param  item  the item to process
344         *
345         * @return  a guess at the part of speech
346         *
347         * @throws ProcessException if an exception occurred during the
348         * processing
349         */
350        public String process(Item item) throws ProcessException {
351            String name = item.getFeatures().getString("name");
352            String dc = name.toLowerCase();
353            if (DIGITS_PATTERN.matcher(dc).matches()) {
354                return "numeric";
355            } else if (DOUBLE_PATTERN.matcher(dc).matches()) {
356                return "number";
357            } else if (months.contains(dc)) {
358                return "month";
359            } else if (days.contains(dc)) {
360                return "day";
361            } else if (dc.equals("a")) {
362                return "a";
363            } else if (dc.equals("flight")) {
364                return "flight";
365            } else if (dc.equals("to")) {
366                return "to";
367            } else {
368                return "_other_";
369            }
370        }
371    }
372
373    /**
374     * Checks to see if the given syllable is accented.
375     * This is a feature processor. A feature processor takes an item,
376     * performs some sort of processing on the item and returns an object.
377     */
378    public static class Accented implements FeatureProcessor {
379
380        /**
381         * Performs some processing on the given item.
382         * @param  item  the item to process
383         *
384         * @return "1" if the syllable is accented; otherwise "0"
385         *
386         * @throws ProcessException if an exception occurred during the
387         * processing
388         */
389        public String process(Item item) throws ProcessException {
390            if (isAccented(item)) {
391                return "1";
392            } else {
393                return "0";
394            }
395        }
396    }
397
398    /**
399     * Find the last accented syllable
400     * This is a feature processor. A feature processor takes an item,
401     * performs some sort of processing on the item and returns an object.
402     */
403    public static class LastAccent implements FeatureProcessor {
404
405        /**
406         * Performs some processing on the given item.
407         *
408         * @param  item  the item to process
409         * 
410         * @return the count of the last accented syllable
411         *
412         * @throws ProcessException if an exception occurred during the
413         * processing
414         */
415        public String process(Item item) throws ProcessException {
416            int count = 0;
417
418            for (Item p = item.getItemAs(Relation.SYLLABLE); 
419                      p != null; p = p.getPrevious(), count++)  {
420                if (isAccented(p)) {
421                    break;
422                }
423            }
424            return Integer.toString(rail(count));
425        }
426    }
427
428    /**
429     * Finds the position of the phoneme in the syllable
430     * This is a feature processor. A feature processor takes an item,
431     * performs some sort of processing on the item and returns an object.
432     */
433    public static class PosInSyl implements FeatureProcessor {
434
435        /**
436         * Performs some processing on the given item.
437         *
438         * @param  item  the item to process
439         *
440         * @return the position of the phoneme in the syllable
441         *
442         * @throws ProcessException if an exception occurred during the
443         * processing
444         */
445        public String process(Item item) throws ProcessException {
446            int count = -1;
447
448            for (Item p = item.getItemAs(Relation.SYLLABLE_STRUCTURE); 
449                      p != null; p = p.getPrevious() )  {
450                count++;
451            }
452            return Integer.toString(rail(count));
453        }
454    }
455
456    /**
457     * Classifies the the syllable as single, initial, mid or final.
458     * This is a feature processor. A feature processor takes an item,
459     * performs some sort of processing on the item and returns an object.
460     */
461    public static class PositionType implements FeatureProcessor {
462
463        /**
464         * Performs some processing on the given item.
465         *
466         * @param  item  the item to process
467         *
468         * @return classifies the syllable as "single", "final",
469         * "initial" or "mid"
470         *
471         * @throws ProcessException if an exception occurred during the
472         * processing
473         */
474        public String process(Item item) throws ProcessException {
475            String type;
476
477            Item s = item.getItemAs(Relation.SYLLABLE_STRUCTURE); 
478            if (s == null) {
479                type = "single";
480            } else if (s.getNext() == null) {
481                if (s.getPrevious() == null) {
482                    type = "single";
483                } else {
484                    type = "final";
485                }
486            } else if (s.getPrevious() == null) {
487                type = "initial";
488            } else {
489                type = "mid";
490            }
491            return type;
492        }
493    }
494
495
496    /**
497     * Counts the number of stressed syllables since the last major break.
498     * This is a feature processor. A feature processor takes an item,
499     * performs some sort of processing on the item and returns an object.
500     */
501    public static class SylIn implements FeatureProcessor {
502
503        /**
504         * Performs some processing on the given item.
505         *
506         * @param  item  the item to process
507         * 
508         * @return the number of stressed syllables since the last
509         * major break
510         *
511         * @throws ProcessException if an exception occurred during the
512         * processing
513         */
514        public String process(Item item) throws ProcessException {
515            int count = 0;
516            Item ss = item.getItemAs(Relation.SYLLABLE);
517            Item firstSyllable  = FIRST_SYLLABLE_PATH.findItem(item);
518
519            for (Item p = ss; p != null; p = p.getPrevious(), count++ )  {
520                if (p.equalsShared(firstSyllable)) {
521                    break;
522                }
523            }
524            return Integer.toString(rail(count));
525        }
526    }
527
528    /**
529     * Counts the number of stressed syllables since the last major break.
530     * This is a feature processor. A feature processor takes an item,
531     * performs some sort of processing on the item and returns an object.
532     */
533    public static class SylOut implements FeatureProcessor {
534
535        /**
536         * Performs some processing on the given item.
537         *
538         * @param  item  the item to process
539         *
540         * @return the number of stressed syllables since the last
541         * major break
542         *
543         * @throws ProcessException if an exception occurred during the
544         * processing
545         */
546        public String process(Item item) throws ProcessException {
547            int count = 0;
548            Item ss = item.getItemAs(Relation.SYLLABLE);
549            Item firstSyllable  = LAST_LAST_SYLLABLE_PATH.findItem(item);
550
551            for (Item p = ss; p != null; p = p.getNext() )  {
552                if (p.equalsShared(firstSyllable)) {
553                    break;
554                }
555                count++;
556            }
557            return Integer.toString(rail(count));
558        }
559    }
560
561
562    /**
563     * Determines the break level after this syllable
564     * This is a feature processor. A feature processor takes an item,
565     * performs some sort of processing on the item and returns an object.
566     */
567    public static class SylBreak implements FeatureProcessor {
568
569        /**
570         * Performs some processing on the given item.
571         *
572         * @param  syl  the item to process
573         *
574         * @return the break level after this syllable
575         *
576         * @throws ProcessException if an exception occurred during the
577         * processing
578         */
579        public String process(Item syl) throws ProcessException {
580            Item ss = syl.getItemAs(Relation.SYLLABLE_STRUCTURE);
581            if (ss == null) {
582                return "1";
583            } else if (ss.getNext() != null) {
584                return "0";
585            } else if (ss.getParent() == null) {
586                return "1";
587            } else {
588                return wordBreak(ss.getParent());
589            }
590        }
591    }
592
593
594
595    /**
596     * Determines the word break.
597     * This is a feature processor. A feature processor takes an item,
598     * performs some sort of processing on the item and returns an object.
599     */
600    public static class WordBreak implements FeatureProcessor {
601
602        /**
603         * Performs some processing on the given item.
604         *
605         * @param  word  the item to process
606         *
607         * @return the break level for this word
608         *
609         * @throws ProcessException if an exception occurred during the
610         * processing
611         */
612        public String process(Item word) throws ProcessException {
613            return wordBreak(word);
614        }
615    }
616
617    /**
618     * Determines the word punctuation.
619     * This is a feature processor. A feature processor takes an item,
620     * performs some sort of processing on the item and returns an object.
621     */
622    public static class WordPunc implements FeatureProcessor {
623
624        /**
625         * Performs some processing on the given item.
626         *
627         * @param  word  the item to process
628         *
629         * @return the punctuation for this word
630         *
631         * @throws ProcessException if an exception occurred during the
632         * processing
633         */
634        public String process(Item word) throws ProcessException {
635            return wordPunc(word);
636        }
637    }
638
639    /**
640     * Return consonant cplace 
641     *   l-labial a-alveolar p-palatal b-labio_dental d-dental v-velar
642     *
643     * This is a feature processor. A feature processor takes an item,
644     * performs some sort of processing on the item and returns an object.
645     */
646    public static class PH_CPlace implements FeatureProcessor {
647
648        /**
649         * Performs some processing on the given item.
650         *
651         * @param  item  the item to process
652         *
653         * @return consonant cplace
654         *
655         * @throws ProcessException if an exception occurred during the
656         * processing
657         */
658        public String process(Item item) throws ProcessException {
659            return getPhoneFeature(item, "cplace");
660        }
661    }
662
663    /**
664     * Return consonant type 
665     *   s-stop f-fricative a-affricative n-nasal * l-liquid
666     *
667     * This is a feature processor. A feature processor takes an item,
668     * performs some sort of processing on the item and returns an object.
669     */
670    public static class PH_CType implements FeatureProcessor {
671
672        /**
673         * Performs some processing on the given item.
674         *
675         * @param  item  the item to process
676         *
677         * @return consonant type
678         *
679         * @throws ProcessException if an exception occurred during the
680         * processing
681         */
682        public String process(Item item) throws ProcessException {
683            return getPhoneFeature(item, "ctype");
684        }
685    }
686
687    /**
688     * Return consonant voicing 
689     *   +=on -=off
690     *
691     * This is a feature processor. A feature processor takes an item,
692     * performs some sort of processing on the item and returns an object.
693     */
694    public static class PH_CVox implements FeatureProcessor {
695
696        /**
697         * Performs some processing on the given item.
698         *
699         * @param  item  the item to process
700         * 
701         * @return consonant voicing
702         *
703         * @throws ProcessException if an exception occurred during the
704         * processing
705         */
706        public String process(Item item) throws ProcessException {
707            return getPhoneFeature(item, "cvox");
708        }
709    }
710
711    /**
712     * Return vowel or consonant
713     *   +=on -=off
714     *
715     * This is a feature processor. A feature processor takes an item,
716     * performs some sort of processing on the item and returns an object.
717     */
718    public static class PH_VC implements FeatureProcessor {
719
720        /**
721         * Performs some processing on the given item.
722         *
723         * @param  item  the item to process
724         *
725         * @return vowel or consonant
726         *
727         * @throws ProcessException if an exception occurred during the
728         * processing
729         */
730        public String process(Item item) throws ProcessException {
731            return getPhoneFeature(item, "vc");
732        }
733    }
734
735    /**
736     * Return vowel frontness
737     *  1-front  2-mid 3-back
738     *
739     * This is a feature processor. A feature processor takes an item,
740     * performs some sort of processing on the item and returns an object.
741     */
742    public static class PH_VFront implements FeatureProcessor {
743
744        /**
745         * Performs some processing on the given item.
746         *
747         * @param  item  the item to process
748         *
749         * @return vowel frontness
750         *
751         * @throws ProcessException if an exception occurred during the
752         * processing
753         */
754        public String process(Item item) throws ProcessException {
755            return getPhoneFeature(item, "vfront");
756        }
757    }
758
759    /**
760     * Return vowel height
761     *   1-high 2-mid 3-low
762     *
763     * This is a feature processor. A feature processor takes an item,
764     * performs some sort of processing on the item and returns an object.
765     */
766    public static class PH_VHeight implements FeatureProcessor {
767
768        /**
769         * Performs some processing on the given item.
770         *
771         * @param  item  the item to process
772         * 
773         * @return vowel height
774         *
775         * @throws ProcessException if an exception occurred during the
776         * processing
777         */
778        public String process(Item item) throws ProcessException {
779            return getPhoneFeature(item, "vheight");
780        }
781    }
782
783
784    /**
785     * Return vowel length
786     *   s-short l-long d-dipthong a-schwa
787     *
788     * This is a feature processor. A feature processor takes an item,
789     * performs some sort of processing on the item and returns an object.
790     */
791    public static class PH_VLength implements FeatureProcessor {
792
793        /**
794         * Performs some processing on the given item.
795         *
796         * @param  item  the item to process
797         *
798         * @return vowel length
799         *
800         * @throws ProcessException if an exception occurred during the
801         * processing
802         */
803        public String process(Item item) throws ProcessException {
804            return getPhoneFeature(item, "vlng");
805        }
806    }
807
808
809
810
811    /**
812     * Return vowel rnd (lip rounding)
813     *   lip rounding  +=on -=off
814     *
815     * This is a feature processor. A feature processor takes an item,
816     * performs some sort of processing on the item and returns an object.
817     */
818    public static class PH_VRnd implements FeatureProcessor {
819
820        /**
821         * Performs some processing on the given item.
822         *
823         * @param  item  the item to process
824         *
825         * @return volwel rnd
826         *
827         * @throws ProcessException if an exception occurred during the
828         * processing
829         */
830        public String process(Item item) throws ProcessException {
831            return getPhoneFeature(item, "vrnd");
832        }
833    }
834
835    /**
836     * Determines the onset size of this syllable
837     * This is a feature processor. A feature processor takes an item,
838     * performs some sort of processing on the item and returns an object.
839     */
840    public static class SylOnsetSize implements FeatureProcessor {
841
842        /**
843         * Performs some processing on the given item.
844         *
845         * @param  syl  the item to process
846         *
847         * @return onset size of this syllable
848         *
849         * @throws ProcessException if an exception occurred during the
850         * processing
851         */
852        public String process(Item syl) throws ProcessException {
853            int count = 0;
854            Item daughter = syl.getItemAs(
855                Relation.SYLLABLE_STRUCTURE).getDaughter();
856            while (daughter != null) {
857                if ("+".equals(getPhoneFeature(daughter, "vc"))) {
858                    break;
859                }
860                count++;
861                daughter = daughter.getNext();
862            }
863            return Integer.toString(rail(count));
864        }
865    }
866
867
868    /**
869     * Determines the coda size
870     * This is a feature processor. A feature processor takes an item,
871     * performs some sort of processing on the item and returns an object.
872     */
873    public static class SylCodaSize implements FeatureProcessor {
874
875        /**
876         * Performs some processing on the given item.
877         *
878         * @param  syl  the item to process
879         *
880         * @return coda size
881         *
882         * @throws ProcessException if an exception occurred during the
883         * processing
884         */
885        public String process(Item syl) throws ProcessException {
886            int count = 0;
887            Item daughter = syl.getItemAs(
888                Relation.SYLLABLE_STRUCTURE).getLastDaughter();
889
890            while (daughter != null) {
891                if ("+".equals(getPhoneFeature(daughter, "vc"))) {
892                    break;
893                }
894
895                daughter = daughter.getPrevious();
896                count++;
897            }
898            return Integer.toString(rail(count));
899        }
900    }
901
902
903    /**
904     * Checks for fricative
905     * This is a feature processor. A feature processor takes an item,
906     * performs some sort of processing on the item and returns an object.
907     */
908    public static class SegCodaFric implements FeatureProcessor {
909
910        /**
911         * Performs some processing on the given item.
912         *
913         * @param  seg  the item to process
914         * 
915         * @return "1" if fricative; else "0"
916         *
917         * @throws ProcessException if an exception occurred during the
918         * processing
919         */
920        public String process(Item seg) throws ProcessException {
921            return segCodaCtype(seg, "f");
922        }
923    }
924
925    /**
926     * Checks for fricative
927     * This is a feature processor. A feature processor takes an item,
928     * performs some sort of processing on the item and returns an object.
929     */
930    public static class SegOnsetFric implements FeatureProcessor {
931
932        /**
933         * Performs some processing on the given item.
934         *
935         * @param  seg  the item to process
936         * 
937         * @return "1" if fricative; else "0"
938         *
939         * @throws ProcessException if an exception occurred during the
940         * processing
941         */
942        public String process(Item seg) throws ProcessException {
943            return segOnsetCtype(seg, "f");
944        }
945    }
946
947
948
949    /**
950     * Checks for coda stop
951     * This is a feature processor. A feature processor takes an item,
952     * performs some sort of processing on the item and returns an object.
953     */
954    public static class SegCodaStop implements FeatureProcessor {
955
956        /**
957         * Performs some processing on the given item.
958         *
959         * @param  seg  the item to process
960         *
961         * @return if coda stop "1"; otherwise "0"
962         *
963         * @throws ProcessException if an exception occurred during the
964         * processing
965         */
966        public String process(Item seg) throws ProcessException {
967            return segCodaCtype(seg, "s");
968        }
969    }
970
971    /**
972     * Checks for onset stop
973     * This is a feature processor. A feature processor takes an item,
974     * performs some sort of processing on the item and returns an object.
975     */
976    public static class SegOnsetStop implements FeatureProcessor {
977
978        /**
979         * Performs some processing on the given item.
980         *
981         * @param  seg  the item to process
982         *
983         * @return if Onset Stop "1"; otherwise "0"
984         *
985         * @throws ProcessException if an exception occurred during the
986         * processing
987         */
988        public String process(Item seg) throws ProcessException {
989            return segOnsetCtype(seg, "s");
990        }
991    }
992
993    /**
994     * Checks for coda nasal
995     * This is a feature processor. A feature processor takes an item,
996     * performs some sort of processing on the item and returns an object.
997     */
998    public static class SegCodaNasal implements FeatureProcessor {
999
1000        /**
1001         * Performs some processing on the given item.
1002         *
1003         * @param  seg  the item to process
1004         *
1005         * @return if coda stop "1"; otherwise "0"
1006         *
1007         * @throws ProcessException if an exception occurred during the
1008         * processing
1009         */
1010        public String process(Item seg) throws ProcessException {
1011            return segCodaCtype(seg, "n");
1012        }
1013    }
1014
1015    /**
1016     * Checks for onset nasal
1017     * This is a feature processor. A feature processor takes an item,
1018     * performs some sort of processing on the item and returns an object.
1019     */
1020    public static class SegOnsetNasal implements FeatureProcessor {
1021
1022        /**
1023         * Performs some processing on the given item.
1024         *
1025         * @param  seg  the item to process
1026         *
1027         * @return if Onset Stop "1"; otherwise "0"
1028         *
1029         * @throws ProcessException if an exception occurred during the
1030         * processing
1031         */
1032        public String process(Item seg) throws ProcessException {
1033            return segOnsetCtype(seg, "n");
1034        }
1035    }
1036
1037    /**
1038     * Checks for coda glide
1039     * This is a feature processor. A feature processor takes an item,
1040     * performs some sort of processing on the item and returns an object.
1041     */
1042    public static class SegCodaGlide implements FeatureProcessor {
1043
1044        /**
1045         * Performs some processing on the given item.
1046         *
1047         * @param  seg  the item to process
1048         *
1049         * @return if coda stop "1"; otherwise "0"
1050         *
1051         * @throws ProcessException if an exception occurred during the
1052         * processing
1053         */
1054        public String process(Item seg) throws ProcessException {
1055            if (segCodaCtype(seg, "r").equals("0")) {
1056                return segCodaCtype(seg, "l");
1057            }
1058            return "1";
1059        }
1060    }
1061
1062    /**
1063     * Checks for onset glide
1064     * This is a feature processor. A feature processor takes an item,
1065     * performs some sort of processing on the item and returns an object.
1066     */
1067    public static class SegOnsetGlide implements FeatureProcessor {
1068
1069        /**
1070         * Performs some processing on the given item.
1071         *
1072         * @param  seg  the item to process
1073         *
1074         * @return if coda stop "1"; otherwise "0"
1075         *
1076         * @throws ProcessException if an exception occurred during the
1077         * processing
1078         */
1079        public String process(Item seg) throws ProcessException {
1080            if (segOnsetCtype(seg, "r").equals("0")) {
1081                return segOnsetCtype(seg, "l");
1082            }
1083            return "1";
1084        }
1085    }
1086
1087
1088    /**
1089     * Checks for onset coda 
1090     * This is a feature processor. A feature processor takes an item,
1091     * performs some sort of processing on the item and returns an object.
1092     */
1093    public static class SegOnsetCoda implements FeatureProcessor {
1094
1095        /**
1096         * Performs some processing on the given item.
1097         *
1098         * @param  seg  the item to process
1099         *
1100         * @return if onset coda "1"; otherwise "0"
1101         *
1102         * @throws ProcessException if an exception occurred during the
1103         * processing
1104         */
1105        public String process(Item seg) throws ProcessException {
1106            Item s = seg.getItemAs(Relation.SYLLABLE_STRUCTURE);
1107            if (s == null) {
1108                return "coda";
1109            }
1110
1111            s = s.getNext();
1112            while (s != null) {
1113                if ("+".equals(getPhoneFeature(s, "vc"))) {
1114                    return "onset";
1115                }
1116
1117                s = s.getNext();
1118            }
1119            
1120            return "coda";
1121        }
1122    }
1123
1124    /**
1125     * Counts the number of phrases before this one.
1126     * This is a feature processor. A feature processor takes an item,
1127     * performs some sort of processing on the item and returns an object.
1128     */
1129    public static class SubPhrases implements FeatureProcessor {
1130
1131        /**
1132         * Performs some processing on the given item.
1133         *
1134         * @param  item  the item to process
1135         *
1136         * @return the number of phrases before this one
1137         *
1138         * @throws ProcessException if an exception occurred during the
1139         * processing
1140         */
1141        public String process(Item item) throws ProcessException {
1142            int count = 0;
1143            Item inPhrase  = SUB_PHRASE_PATH.findItem(item);
1144
1145            for (Item p = inPhrase; p != null; p = p.getPrevious() )  {
1146                count++;
1147            }
1148            return Integer.toString(rail(count));
1149        }
1150    }
1151
1152    /**
1153     * Returns the duration of the given segment
1154     * This is a feature processor. A feature processor takes an item,
1155     * performs some sort of processing on the item and returns an object.
1156     */
1157    public static class SegmentDuration implements FeatureProcessor {
1158
1159        /**
1160         * Performs some processing on the given item.
1161         *
1162         * @param  seg  the item to process
1163         *
1164         * @return the duration of the segment as a string.
1165         *
1166         * @throws ProcessException if an exception occurred during the
1167         * processing
1168         */
1169        public String process(Item seg) throws ProcessException {
1170            if (seg == null) {
1171                return "0";
1172            } else if (seg.getPrevious() == null) {
1173                return seg.getFeatures().getObject("end").toString();
1174            } else {
1175                return Float.toString(
1176                        seg.getFeatures().getFloat("end") -
1177                        seg.getPrevious().getFeatures().getFloat("end")
1178                   );
1179            }
1180        }
1181    }
1182
1183    /**
1184     * Gets the phoneset feature with the given name
1185     *
1186     * @param item item the phoneme of interest
1187     * @param featureName the feature of interest
1188     *
1189     * @return the phone feature for the item
1190     *
1191     */
1192
1193    public static String getPhoneFeature(Item item, String featureName) {
1194        Voice voice = item.getUtterance().getVoice();
1195        String feature = voice.getPhoneFeature(item.toString(), featureName);
1196        return feature;
1197    }
1198
1199    /**
1200     * Classifies the type of word break
1201     *
1202     * @param  item  the item to process
1203     *
1204     * @return  "4" for a big break, "3" for  a break; otherwise "1"
1205     *
1206     * @throws ProcessException if an exception occurred during the
1207     * processing
1208     */
1209    public static String wordBreak(Item item) throws ProcessException {
1210        Item ww = item.getItemAs(Relation.PHRASE);
1211        if (ww == null || ww.getNext() != null) {
1212            return "1";
1213        } else {
1214            String pname = ww.getParent().toString();
1215            if (pname.equals("BB")) {
1216                return "4";
1217            } else if (pname.equals("B")) {
1218                return "3";
1219            } else {
1220                return "1";
1221            }
1222        }
1223    }
1224
1225    /**
1226     * Gets the punctuation associated with the word
1227     *
1228     * @param  item  the word to process
1229     *
1230     * @return  the punctuation associated with the word
1231     *
1232     * @throws ProcessException if an exception occurred during the
1233     * processing
1234     */
1235    public static String wordPunc(Item item) throws ProcessException {
1236        Item ww = item.getItemAs(Relation.TOKEN);
1237        if (ww != null && ww.getNext() != null) {
1238            return "";
1239        } else {
1240            if (ww != null && ww.getParent() != null) {
1241                return ww.getParent().getFeatures().getString("punc");
1242            } else {
1243                return "";
1244            }
1245        }
1246    }
1247
1248    /**
1249     * Tests the coda ctype of the given segment.
1250     *
1251     * @param seg the segment to test
1252     * @param ctype the ctype to check for
1253     * 
1254     * @return "1" on match "0" on no match
1255     */
1256    private static String segCodaCtype(Item seg, String ctype) {
1257        Item daughter 
1258            = seg.getItemAs(
1259                Relation.SYLLABLE_STRUCTURE).getParent().getLastDaughter();
1260
1261        while (daughter != null) {
1262            if ("+".equals(getPhoneFeature(daughter, "vc"))) {
1263                return "0";
1264            }
1265            if (ctype.equals(getPhoneFeature(daughter, "ctype"))) {
1266                return "1";
1267            }
1268
1269            daughter = daughter.getPrevious();
1270        }
1271        return "0";
1272    }
1273
1274    /**
1275     * Tests the onset ctype of the given segment.
1276     *
1277     * @param  seg  the segment to test to process
1278     * @param ctype the ctype to check for
1279     *
1280     * @return if Onset Stop "1"; otherwise "0"
1281     *
1282     */
1283    private static  String segOnsetCtype(Item seg, String ctype) {
1284        Item daughter = seg.getItemAs(
1285            Relation.SYLLABLE_STRUCTURE).getParent().getDaughter();
1286
1287        while (daughter != null) {
1288            if ("+".equals(getPhoneFeature(daughter, "vc"))) {
1289                return "0";
1290            }
1291            if (ctype.equals(getPhoneFeature(daughter, "ctype"))) {
1292                return "1";
1293            }
1294
1295            daughter = daughter.getNext();
1296        }
1297        return "0";
1298    }
1299
1300    /**
1301     * Determines if the given item is accented
1302     *
1303     * @param item the item of interest
1304     *
1305     * @return <code>true</code> if the item is accented, otherwise
1306     * <code>false</code>
1307     */
1308    private static boolean isAccented(Item item) {
1309        return (item.getFeatures().isPresent("accent") ||
1310            item.getFeatures().isPresent("endtone"));
1311    }
1312
1313    /**
1314     * Rails an int. flite never returns an int more than 19 from
1315     * a feature processor, we duplicate that behavior
1316     * here so that our tests will match.
1317     *
1318     * @param val the value to rail
1319     *
1320     * @return val clipped to be betweein 0 and 19
1321     */
1322    private static int rail(int val) {
1323        return val > 19 ? 19 : val;
1324    }
1325}