content: /**
   * Copyright 2000-2009 DFKI GmbH.
   * All Rights Reserved.  Use is subject to license terms.
   * <p/>
   * This file is part of MARY TTS.
   * <p/>
   * MARY TTS is free software: you can redistribute it and/or modify
   * it under the terms of the GNU Lesser General Public License as published by
   * the Free Software Foundation, version 3 of the License.
   * <p/>
   * This program is distributed in the hope that it will be useful,
   * but WITHOUT ANY WARRANTY; without even the implied warranty of
   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   * GNU Lesser General Public License for more details.
   * <p/>
   * You should have received a copy of the GNU Lesser General Public License
   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
   */
  package marytts.modules.phonemiser;
  
  import java.io.BufferedReader;
  import java.io.FileInputStream;
  import java.io.IOException;
  import java.io.InputStream;
  import java.io.InputStreamReader;
  import java.util.Properties;
  
  import marytts.cart.CART;
  import marytts.cart.LeafNode.StringAndFloatLeafNode;
  import marytts.cart.io.MaryCARTReader;
  import marytts.exceptions.MaryConfigurationException;
  import marytts.features.FeatureDefinition;
  import marytts.features.FeatureVector;
  
  
  /**
   * This predicts pronunciation from a model trained with LTSTrainer.
   *
   * @author benjaminroth
   */
  public class TrainedLTS {
      public static final String PREDICTED_STRING_FEATURENAME = "predicted-string";
      protected boolean removeTrailingOneFromPhones = true;
      private CART tree;
      private FeatureDefinition featureDefinition;
      private int indexPredictedFeature;
      private int context;
      private AllophoneSet allophoneSet;
      private boolean convertToLowercase;
  
      /**
       * Initializes letter to sound system with a phoneSet, and load the decision
       * tree from the given file.
       *
       * @param aPhonSet                    phoneset used in syllabification
       * @param treeFilename
       * @param removeTrailingOneFromPhones
       * @throws IOException
       */
      public TrainedLTS(AllophoneSet aPhonSet, InputStream treeStream, boolean removeTrailingOneFromPhones)
              throws IOException, MaryConfigurationException {
          this.allophoneSet = aPhonSet;
          this.loadTree(treeStream);
          this.removeTrailingOneFromPhones = removeTrailingOneFromPhones;
      }
  
      /**
       * Initializes letter to sound system with a phoneSet, and load the decision
       * tree from the given file.
       *
       * @param aPhonSet     phoneset used in syllabification
       * @param treeFilename
       * @throws IOException
       */
      public TrainedLTS(AllophoneSet aPhonSet, InputStream treeStream)
              throws IOException, MaryConfigurationException {
          this(aPhonSet, treeStream, true);
      }
  
      public TrainedLTS(AllophoneSet aPhonSet, CART predictionTree) {
          this.allophoneSet = aPhonSet;
          this.tree = predictionTree;
          this.featureDefinition = tree.getFeatureDefinition();
          this.indexPredictedFeature = featureDefinition.getFeatureIndex(PREDICTED_STRING_FEATURENAME);
          Properties props = tree.getProperties();
          if (props == null)
              throw new IllegalArgumentException("Prediction tree does not contain properties");
          convertToLowercase = Boolean.parseBoolean(props.getProperty("lowercase"));
          context = Integer.parseInt(props.getProperty("context"));
      }
  
      public static void main(String[] args) throws IOException, MaryConfigurationException {
  
          if (args.length < 2) {
              System.out.println("Usage:");
              System.out.println("java marytts.modules.phonemiser.TrainedLTS allophones.xml lts-model.lts [removeTrailingOneFromPhones]");
              System.exit(0);
          }
          String allophoneFile = args[0];
          String ltsFile = args[1];
          boolean myRemoveTrailingOneFromPhones = true;
          if (args.length > 2) {
              myRemoveTrailingOneFromPhones = Boolean.getBoolean(args[2]);
          }
  
          TrainedLTS lts = new TrainedLTS(AllophoneSet.getAllophoneSet(allophoneFile), new FileInputStream(ltsFile), myRemoveTrailingOneFromPhones);
  
          BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
          String line;
          while ((line = br.readLine()) != null) {
              line = line.trim();
              String pron = lts.predictPronunciation(line);
              String syl = lts.syllabify(pron);
              String sylStripped = syl.replaceAll("[-' ]+", "");
              System.out.println(sylStripped);
          }
      }
  
      /**
       * Convenience method to load tree from an inputstream
       *
       * @param treeStream
       * @throws IOException
       */
      public void loadTree(InputStream treeStream) throws IOException, MaryConfigurationException {
          MaryCARTReader cartReader = new MaryCARTReader();
          this.tree = cartReader.loadFromStream(treeStream);
          this.featureDefinition = tree.getFeatureDefinition();
          this.indexPredictedFeature = featureDefinition.getFeatureIndex(PREDICTED_STRING_FEATURENAME);
          this.convertToLowercase = false;
          Properties props = tree.getProperties();
          if (props == null)
              throw new IllegalArgumentException("Prediction tree does not contain properties");
          convertToLowercase = Boolean.parseBoolean(props.getProperty("lowercase"));
          context = Integer.parseInt(props.getProperty("context"));
      }
  
      public String predictPronunciation(String graphemes) {
          if (convertToLowercase)
              graphemes = graphemes.toLowerCase(allophoneSet.getLocale());
  
          String returnStr = "";
  
          for (int i = 0; i < graphemes.length(); i++) {
  
              byte[] byteFeatures = new byte[2 * this.context + 1];
  
              for (int fnr = 0; fnr < 2 * this.context + 1; fnr++) {
                  int pos = i - context + fnr;
  
                  String grAtPos = (pos < 0 || pos >= graphemes.length()) ?
                          "null" : graphemes.substring(pos, pos + 1);
  
                  try {
                      byteFeatures[fnr] = this.tree.getFeatureDefinition().getFeatureValueAsByte(fnr, grAtPos);
                      // ... can also try to call explicit:
                      //features[fnr] = this.fd.getFeatureValueAsByte("att"+fnr, cg.substr(pos)
                  } catch (IllegalArgumentException iae) {
                      // Silently ignore unknown characters
                      byteFeatures[fnr] = this.tree.getFeatureDefinition().getFeatureValueAsByte(fnr, "null");
                  }
              }
  
              FeatureVector fv = new FeatureVector(byteFeatures, new short[]{}, new float[]{}, 0);
  
              StringAndFloatLeafNode leaf = (StringAndFloatLeafNode) tree.interpretToNode(fv, 0);
              String prediction = leaf.mostProbableString(featureDefinition, indexPredictedFeature);
              returnStr += prediction.substring(1, prediction.length() - 1);
          }
  
          return returnStr;
  
      }
  
      /**
       * Phone chain is syllabified. After that, no white spaces are
       * included, stress is on syllable of first stress bearing vowal,
       * or assigned rule-based if there is no stress predicted by the tree.
       *
       * @param phones input phone chain, unsyllabified, stress marking attached to vowals
       * @return phone chain, with syllable sepeators "-" and stress symbols "'"
       */
      public String syllabify(String phones) {
  
          Syllabifier sfr = new Syllabifier(this.allophoneSet, this.removeTrailingOneFromPhones);
  
          return sfr.syllabify(phones);
      }
  
  
  }
  
  