content: /**
   * Copyright 2010 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;
  
  import java.io.BufferedReader;
  import java.io.FileReader;
  import java.io.StringReader;
  import java.util.Locale;
  import java.util.Scanner;
  
  import marytts.datatypes.MaryData;
  import marytts.datatypes.MaryDataType;
  import marytts.datatypes.MaryXML;
  import marytts.features.FeatureDefinition;
  import marytts.features.FeatureProcessorManager;
  import marytts.features.FeatureRegistry;
  import marytts.features.TargetFeatureComputer;
  import marytts.machinelearning.SoP;
  import marytts.modules.phonemiser.AllophoneSet;
  import marytts.modules.synthesis.Voice;
  import marytts.server.MaryProperties;
  import marytts.unitselection.select.Target;
  import marytts.unitselection.select.UnitSelector;
  import marytts.util.MaryRuntimeUtils;
  import marytts.util.MaryUtils;
  import marytts.util.dom.DomUtils;
  import mf.org.w3c.dom.Document;
  import mf.org.w3c.dom.Element;
  import mf.org.w3c.dom.traversal.NodeIterator;
  import mf.org.w3c.dom.traversal.TreeWalker;
  
  public class SoPDurationModeller extends InternalModule {
  
      protected TargetFeatureComputer featureComputer;
      private String sopFileName;
      private SoP vowelSop;
      private SoP consonantSop;
      private SoP pauseSop;
      private boolean logDuration = true;
      private FeatureProcessorManager featureProcessorManager;
      private AllophoneSet allophoneSet;
      private FeatureDefinition voiceFeatDef;
  
      /**
       * Constructor which can be directly called from init info in the config file.
       * This constructor will use the registered feature processor manager for the given locale.
       *
       * @param locale      a locale string, e.g. "en"
       * @param sopFileName
       * @throws Exception
       */
      public SoPDurationModeller(String locale, String sopFile)
              throws Exception {
          this(MaryUtils.string2locale(locale), sopFile,
                  FeatureRegistry.getFeatureProcessorManager(MaryUtils.string2locale(locale)));
      }
  
      /**
       * Constructor which can be directly called from init info in the config file.
       * Different languages can call this code with different settings.
       *
       * @param locale            a locale string, e.g. "en"
       * @param propertyPrefix    the prefix to be used when looking up entries in the config files, e.g. "english.duration"
       * @param featprocClassInfo a package name for an instance of FeatureProcessorManager, e.g. "marytts.language.en.FeatureProcessorManager"
       * @throws Exception
       */
      public SoPDurationModeller(String locale, String sopFile, String featprocClassInfo)
              throws Exception {
          this(MaryUtils.string2locale(locale), sopFile,
                  (FeatureProcessorManager) MaryRuntimeUtils.instantiateObject(featprocClassInfo));
      }
  
      /**
       * Constructor to be called with instantiated objects.
       *
       * @param locale
       * @param propertyPrefix the prefix to be used when looking up entries in the config files, e.g. "english.duration"
       * @praam featureProcessorManager the manager to use when looking up feature processors.
       */
      protected SoPDurationModeller(Locale locale,
                                    String sopFile, FeatureProcessorManager featureProcessorManager) {
          super("SoPDurationModeller",
                  MaryDataType.ALLOPHONES,
                  MaryDataType.DURATIONS, locale);
          this.sopFileName = sopFile;
          this.featureProcessorManager = featureProcessorManager;
  
      }
  
      @Override
      public void startup() throws Exception {
          super.startup();
  
          // Read dur.sop file to load linear equations
          // The first section contains the feature definition, after one empty line,
          // the first line corresponds to vowels, next line to consonants and next line to pause
          String sopFile = MaryProperties.getFilename(sopFileName);
          //System.out.println("sopFileName: " + sopFile);
          String nextLine;
          String strContext = "";
          Scanner s = null;
          try {
              s = new Scanner(new BufferedReader(new FileReader(sopFile)));
  
              // The first part contains the feature definition
              while (s.hasNext()) {
                  nextLine = s.nextLine();
                  if (nextLine.trim().equals("")) break;
                  else
                      strContext += nextLine + "\n";
              }
              // the featureDefinition is the same for vowel, consonant and Pause
              voiceFeatDef = new FeatureDefinition(new BufferedReader(new StringReader(strContext)), false);
  
              // vowel line
              if (s.hasNext()) {
                  nextLine = s.nextLine();
                  //System.out.println("line vowel = " + nextLine);
                  vowelSop = new SoP(nextLine, voiceFeatDef);
                  //vowelSop.printCoefficients();
              }
              // consonant line
              if (s.hasNext()) {
                  nextLine = s.nextLine();
                  //System.out.println("line consonants = " + nextLine);
                  consonantSop = new SoP(nextLine, voiceFeatDef);
                  //consonantSop.printCoefficients();
              }
              // pause line
              if (s.hasNext()) {
                  nextLine = s.nextLine();
                  //System.out.println("line pause = " + nextLine);
                  pauseSop = new SoP(nextLine, voiceFeatDef);
                  //pauseSop.printCoefficients();
              }
          } finally {
              if (s != null)
                  s.close();
          }
  
          // get a feature computer
          featureComputer = FeatureRegistry.getTargetFeatureComputer(featureProcessorManager, voiceFeatDef.getFeatureNames());
  
      }
  
      @Override
      public MaryData process(MaryData d)
              throws Exception {
          Document doc = d.getDocument();
          NodeIterator sentenceIt = DomUtils.createNodeIterator(doc, MaryXML.SENTENCE);
          Element sentence = null;
          while ((sentence = (Element) sentenceIt.nextNode()) != null) {
              // Make sure we have the correct voice:
              Element voice = (Element) DomUtils.getAncestor(sentence, MaryXML.VOICE);
              Voice maryVoice = Voice.getVoice(voice);
  
              if (maryVoice == null) {
                  maryVoice = d.getDefaultVoice();
              }
              if (maryVoice == null) {
                  // Determine Locale in order to use default voice
                  Locale locale = MaryUtils.string2locale(doc.getDocumentElement().getAttribute("xml:lang"));
                  maryVoice = Voice.getDefaultVoice(locale);
              }
  
              allophoneSet = maryVoice.getAllophoneSet();
              TargetFeatureComputer currentFeatureComputer = featureComputer;
  
              // cumulative duration from beginning of sentence, in seconds:
              float end = 0;
              float durInSeconds;
              TreeWalker tw = DomUtils.createTreeWalker(sentence, MaryXML.PHONE, MaryXML.BOUNDARY);
              Element segmentOrBoundary;
              Element previous = null;
              while ((segmentOrBoundary = (Element) tw.nextNode()) != null) {
                  String phone = UnitSelector.getPhoneSymbol(segmentOrBoundary);
  
                  Target t = new Target(phone, segmentOrBoundary);
                  t.setFeatureVector(currentFeatureComputer.computeFeatureVector(t));
  
                  if (segmentOrBoundary.getTagName().equals(MaryXML.BOUNDARY)) { // a pause
                      System.out.print("Pause PHONE: " + phone);
                      durInSeconds = (float) pauseSop.solve(t, voiceFeatDef, logDuration, false);
                      if (durInSeconds < 0.0) {
                          System.out.println("\nWARNING: duration < 0.0");
                          durInSeconds = (float) pauseSop.solve(t, voiceFeatDef, logDuration, true);
                      }
                  } else {
                      if (allophoneSet.getAllophone(phone).isVowel()) {
                          // calculate duration with vowelSop
                          System.out.print("Vowel PHONE: " + phone);
                          durInSeconds = (float) vowelSop.solve(t, voiceFeatDef, logDuration, false);
                          if (durInSeconds < 0.0) {
                              System.out.println("\nWARNING: duration < 0.0");
                              durInSeconds = (float) vowelSop.solve(t, voiceFeatDef, logDuration, true);
                          }
                      } else {
                          // calculate duration with consonantSop
                          System.out.print("Cons. PHONE: " + phone);
                          durInSeconds = (float) consonantSop.solve(t, voiceFeatDef, logDuration, false);
                          if (durInSeconds < 0.0) {
                              System.out.println("\nWARNING: duration < 0.0");
                              durInSeconds = (float) consonantSop.solve(t, voiceFeatDef, logDuration, true);
                          }
                      }
                  }
                  // TODO: where do we check that the solution is log(duration) or duration???
                  System.out.format(" = %.3f\n", durInSeconds);
                  // TODO: this problem is not solved, it seems it has to do with punctuation (?)
                  if (durInSeconds < 0) {
                      throw new Exception("Error generating SoP Duration: durInSeconds < 0.0 ");
                  }
                  end += durInSeconds;
                  int durInMillis = (int) (1000 * durInSeconds);
                  if (segmentOrBoundary.getTagName().equals(MaryXML.BOUNDARY)) {
                      segmentOrBoundary.setAttribute("duration", String.valueOf(durInMillis));
                  } else { // phone
                      segmentOrBoundary.setAttribute("d", String.valueOf(durInMillis));
                      segmentOrBoundary.setAttribute("end", String.valueOf(end));
                  }
                  previous = segmentOrBoundary;
              }
          }
          MaryData output = new MaryData(outputType(), d.getLocale());
          output.setDocument(doc);
          return output;
  
      }
  
  
  }
  