content: /**
   * Copyright 2000-2006 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.synthesis;
  
  import android.util.Log;
  
  import com.sun.speech.freetts.en.us.CMULexicon;
  import com.sun.speech.freetts.lexicon.Lexicon;
  
  import java.io.BufferedReader;
  import java.io.File;
  import java.io.FileInputStream;
  import java.io.IOException;
  import java.io.InputStream;
  import java.io.InputStreamReader;
  import java.lang.reflect.Constructor;
  import java.util.ArrayList;
  import java.util.Collection;
  import java.util.Collections;
  import java.util.Comparator;
  import java.util.HashMap;
  import java.util.Iterator;
  import java.util.List;
  import java.util.Locale;
  import java.util.Map;
  import java.util.Set;
  import java.util.StringTokenizer;
  import java.util.TreeSet;
  import java.util.Vector;
  
  import lib.sound.sampled.AudioFormat;
  import lib.sound.sampled.AudioInputStream;
  import marytts.cart.DirectedGraph;
  import marytts.cart.io.DirectedGraphReader;
  import marytts.config.MaryConfig;
  import marytts.config.VoiceConfig;
  import marytts.datatypes.MaryData;
  import marytts.datatypes.MaryDataType;
  import marytts.datatypes.MaryXML;
  import marytts.exceptions.MaryConfigurationException;
  import marytts.exceptions.NoSuchPropertyException;
  import marytts.exceptions.SynthesisException;
  import marytts.features.FeatureProcessorManager;
  import marytts.features.FeatureRegistry;
  import marytts.modules.MaryModule;
  import marytts.modules.ModuleRegistry;
  import marytts.modules.acoustic.BoundaryModel;
  import marytts.modules.acoustic.CARTModel;
  import marytts.modules.acoustic.HMMModel;
  import marytts.modules.acoustic.Model;
  import marytts.modules.acoustic.ModelType;
  import marytts.modules.acoustic.SoPModel;
  import marytts.modules.phonemiser.Allophone;
  import marytts.modules.phonemiser.AllophoneSet;
  import marytts.server.Mary;
  import marytts.server.MaryProperties;
  import marytts.unitselection.data.FeatureFileReader;
  import marytts.unitselection.interpolation.InterpolatingSynthesizer;
  import marytts.unitselection.interpolation.InterpolatingVoice;
  import marytts.util.MaryRuntimeUtils;
  import marytts.util.MaryUtils;
  import marytts.vocalizations.VocalizationSynthesizer;
  import mf.org.w3c.dom.Element;
  
  /**
   * A helper class for the synthesis module; each Voice object represents one
   * available voice database.
   *
   * @author Marc Schr&ouml;der
   */
  
  public class Voice {
      /**
       * Gender: male.
       */
      public static final Gender MALE = new Gender("male");
      /**
       * Gender: female.
       */
      public static final Gender FEMALE = new Gender("female");
      /**
       * Audio format: 16kHz,16bit,mono, native byte order
       */
      public static final AudioFormat AF16000 = new AudioFormat
              (AudioFormat.Encoding.PCM_SIGNED,
                      16000, // samples per second
                      16, // bits per sample
                      1, // mono
                      2, // nr. of bytes per frame
                      16000, // nr. of frames per second
                      (System.getProperty("os.arch").equals("x86") ||
                              System.getProperty("os.arch").equals("i386") ||
                              System.getProperty("os.arch").equals("amd64")) ? // byteorder
                              false // little-endian
                              : true); // big-endian
      /**
       * Audio format: 16kHz,16bit,mono, big endian
       */
      public static final AudioFormat AF16000BE = new AudioFormat
              (AudioFormat.Encoding.PCM_SIGNED,
                      16000, // samples per second
                      16, // bits per sample
                      1, // mono
                      2, // nr. of bytes per frame
                      16000, // nr. of frames per second
                      true); // big-endian
      /**
       * Audio format: 22.05kHz,16bit,mono, native byte order
       */
      public static final AudioFormat AF22050 = new AudioFormat
              (AudioFormat.Encoding.PCM_SIGNED,
                      22050, // samples per second
                      16, // bits per sample
                      1, // mono
                      2, // nr. of bytes per frame
                      22050, // nr. of frames per second
                      (System.getProperty("os.arch").equals("x86") ||
                              System.getProperty("os.arch").equals("i386")) ? // byteorder
                              false // little-endian
                              : true); // big-endian
      private static Map<Locale, Voice> defaultVoices = new HashMap<Locale, Voice>();
      /**
       * A local map of already-instantiated Lexicons
       */
      private static Map<String, Lexicon> lexicons = new HashMap<String, Lexicon>();
  
      // protected static Logger logger = MaryUtils.getLogger("Voice");
      protected DirectedGraph durationGraph;
      protected DirectedGraph f0Graph;
      protected FeatureFileReader f0ContourFeatures;
      protected Map<String, Model> acousticModels;
      String preferredModulesClasses;
      private String voiceName;
      private Locale locale;
      private AudioFormat dbAudioFormat = null;
      private WaveformSynthesizer synthesizer;
      private Gender gender;
      private int wantToBeDefault;
      /**
       * List all registered voices. This set will always return the voices in the order of their
       * wantToBeDefault value, highest first.
       */
      private static Set<Voice> allVoices = new TreeSet<Voice>(new Comparator<Voice>() {
          public int compare(Voice v1, Voice v2) {
              // Return negative number if v1 should be listed before v2
              int desireDelta = v2.wantToBeDefault - v1.wantToBeDefault;
              if (desireDelta != 0) return desireDelta;
              // same desire -- sort alphabetically
              return v2.getName().compareTo(v1.getName());
          }
      });
      private AllophoneSet allophoneSet;
      private Vector<MaryModule> preferredModules;
      private Lexicon lexicon;
      private boolean vocalizationSupport;
      private VocalizationSynthesizer vocalizationSynthesizer;
  
      @Deprecated
      public Voice(String name, Locale locale,
                   AudioFormat dbAudioFormat,
                   WaveformSynthesizer synthesizer,
                   Gender gender)
              throws MaryConfigurationException {
          this.voiceName = name;
          this.locale = locale;
          this.dbAudioFormat = dbAudioFormat;
          this.synthesizer = synthesizer;
          this.gender = gender;
  
          try {
              init();
          } catch (Exception n) {
              Log.d(Mary.LOG, "1 Cannot instantiate voice '" + voiceName + "'");
              throw new MaryConfigurationException("Cannot instantiate voice '" + voiceName + "'", n);
          }
      }
  
      public Voice(String name, WaveformSynthesizer synthesizer) throws MaryConfigurationException {
          this.voiceName = name;
          this.synthesizer = synthesizer;
          VoiceConfig config = MaryConfig.getVoiceConfig(voiceName);
          if (config == null) {
              Log.d(Mary.LOG, "Trying to load config for voice '" + voiceName + "' but cannot find it.");
              throw new MaryConfigurationException("Trying to load config for voice '" + voiceName + "' but cannot find it.");
          }
          this.locale = config.getLocale();
          int samplingRate = MaryProperties.getInteger("voice." + voiceName + ".samplingRate", 16000);
          this.dbAudioFormat = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED,
                  samplingRate, // samples per second
                  16, // bits per sample
                  1, // mono
                  2, // nr. of bytes per frame
                  samplingRate, // nr. of frames per second
                  false);
  
          this.gender = new Gender(MaryProperties.needProperty("voice." + voiceName + ".gender"));
  
          try {
              init();
          } catch (Exception n) {
              Log.d(Mary.LOG, "2 Cannot instantiate voice '" + voiceName + "' " + n.getMessage());
              throw new MaryConfigurationException("Cannot instantiate voice '" + voiceName + "'", n);
          }
      }
  
      /**
       * Register the given voice. It will be contained in the list of available voices returned
       * by any subsequent calls to getAvailableVoices(). If the voice has the highest value of
       * <code>wantToBeDefault</code> for its locale it will be registered as the default voice for
       * its locale.
       * This value is set in the config file setting <code>voice.(name).want.to.be.default.voice</code>.
       */
      public static void registerVoice(Voice voice) {
          Log.d(Mary.LOG, "registerVoice=" + voice);
          if (voice == null)
              throw new NullPointerException("Cannot register null voice.");
          if (!allVoices.contains(voice)) {
              Log.i(Mary.LOG, "Registering voice `" + voice.getName() + "': " +
                      voice.gender() + ", locale " + voice.getLocale());
              allVoices.add(voice);
              try {
                  Log.d(Mary.LOG, "FROM registerVoice ... ");
                  FreeTTSVoices.load(voice);
              } catch (NoClassDefFoundError err) {
                  Log.d("ooooooupsss",err.getMessage());
                  // do nothing
              }
          }
          checkIfDefaultVoice(voice);
      }
  
      /**
       * Check if this voice should be registered as default.
       *
       * @param voice
       */
      private static void checkIfDefaultVoice(Voice voice) {
  
          Locale locale = voice.getLocale();
          Voice currentDefault = defaultVoices.get(locale);
          if (currentDefault == null || currentDefault.wantToBeDefault < voice.wantToBeDefault) {
              Log.i(Mary.LOG, "New default voice for locale " + locale + ": " + voice.getName() + " (desire " + voice.wantToBeDefault + ")");
              defaultVoices.put(locale, voice);
          }
      }
  
      /**
       * Get the voice with the given name, or null if there is no voice with that name.
       *
       * @param name
       * @return
       */
      public static Voice getVoice(String name) {
          Log.d(Mary.LOG, "getVoice =" + name);
          for (Iterator<Voice> it = allVoices.iterator(); it.hasNext(); ) {
              Voice v = it.next();
              if (v.hasName(name)) return v;
          }
          // Interpolating voices are created as needed:
          if (InterpolatingVoice.isInterpolatingVoiceName(name)) {
              InterpolatingSynthesizer interpolatingSynthesizer = null;
              for (Iterator<Voice> it = allVoices.iterator(); it.hasNext(); ) {
                  Voice v = it.next();
                  if (v instanceof InterpolatingVoice) {
                      interpolatingSynthesizer = (InterpolatingSynthesizer) v.synthesizer();
                      break;
                  }
              }
              if (interpolatingSynthesizer == null) return null;
              try {
                  Voice v = new InterpolatingVoice(interpolatingSynthesizer, name);
                  registerVoice(v);
                  return v;
              } catch (Exception e) {
                  Log.w(Mary.LOG, "Could not create Interpolating voice:", e);
                  return null;
              }
          }
          return null; // no such voice found
      }
  
      /**
       * Get the list of all available voices. The iterator of the collection returned
       * will return the voices in decreasing order of their "wantToBeDefault" value.
       */
      public static Collection<Voice> getAvailableVoices() {
  
          Log.d(Mary.LOG, "getAvailableVoices = " + allVoices);
          return Collections.unmodifiableSet(allVoices);
      }
  
      /**
       * Get the list of all available voices for a given locale. The iterator of the collection returned
       * will return the voices in decreasing order of their "wantToBeDefault" value.
       *
       * @param locale
       * @return a collection of Voice objects, or an empty collection if no voice is available for the given locale.
       */
      public static Collection<Voice> getAvailableVoices(Locale locale) {
          ArrayList<Voice> list = new ArrayList<Voice>();
          Log.d(Mary.LOG, "getAvailableVoices = " + allVoices);
          for (Voice v : allVoices) {
              if (MaryUtils.subsumes(locale, v.getLocale())) {
                  list.add(v);
              }
          }
          return list;
      }
  
      /**
       * Get the list of all available voices for a given waveform synthesizer. The iterator of the collection returned
       * will return the voices in decreasing order of their "wantToBeDefault" value.
       *
       * @return a collection of Voice objects, or an empty collection if no voice is available for the given waveform synthesizer.
       */
      public static Collection<Voice> getAvailableVoices(WaveformSynthesizer synth) {
          Log.d(Mary.LOG, "getAvailableVoices =" + synth);
          if (synth == null) {
              throw new NullPointerException("Got null WaveformSynthesizer");
          }
          ArrayList<Voice> list = new ArrayList<Voice>();
          for (Voice v : allVoices) {
              if (synth.equals(v.synthesizer())) {
                  list.add(v);
              }
          }
          return list;
      }
  
      /**
       * Get the list of all available voices for a given waveform synthesizer and locale. The iterator of the collection returned
       * will return the voices in decreasing order of their "wantToBeDefault" value.
       *
       * @return a collection of Voice objects, or an empty collection if no voice is available for the given locale.
       */
      public static Collection<Voice> getAvailableVoices(WaveformSynthesizer synth, Locale locale) {
          Log.d(Mary.LOG, "getAvailableVoices =" + synth + " LOCALE =" + locale);
          ArrayList<Voice> list = new ArrayList<Voice>();
          for (Voice v : allVoices) {
              if (v.synthesizer().equals(synth) && MaryUtils.subsumes(locale, v.getLocale())) {
                  list.add(v);
              }
          }
          return list;
      }
  
      public static Voice getVoice(Locale locale, Gender gender) {
          Log.d(Mary.LOG, "getVoice : " + allVoices);
          for (Voice v : allVoices) {
              if (MaryUtils.subsumes(locale, v.getLocale()) && v.gender().equals(gender))
                  return v;
          }
          return null; // no such voice found
      }
  
      public static Voice getVoice(Element voiceElement) {
          if (voiceElement == null ||
                  !voiceElement.getTagName().equals(MaryXML.VOICE)) {
              return null;
          }
  
          Voice v = null;
          // Try to get the voice by name:
          String voiceName = voiceElement.getAttribute("name");
          if (!voiceName.equals("")) {
              v = Voice.getVoice(voiceName);
          }
          // Now if that didn't work, try getting a voice by gender:
          if (v == null) {
              String voiceGender = voiceElement.getAttribute("gender");
              // Try to get the locale for the voice Element.
              // Trust that the locale is encoded in the document root element.
              Locale locale = MaryUtils.string2locale(
                      voiceElement.getOwnerDocument().getDocumentElement()
                              .getAttribute("xml:lang"));
              if (locale == null) {
                  locale = Locale.GERMAN;
              }
              v = Voice.getVoice(locale, new Gender(voiceGender));
          }
          return v;
      }
  
      public static Voice getDefaultVoice(Locale locale) {
          Voice v = defaultVoices.get(locale);
          if (v == null) v = getVoice(locale, FEMALE);
          if (v == null) v = getVoice(locale, MALE);
          if (v == null) Log.w(Mary.LOG, "Could not find default voice for locale " + locale);
          return v;
      }
  
      public static Voice getSuitableVoice(MaryData d) {
          Locale docLocale = d.getLocale();
          if (docLocale == null && d.getType().isXMLType() && d.getDocument() != null
                  && d.getDocument().getDocumentElement().hasAttribute("xml:lang")) {
              docLocale = MaryUtils.string2locale(d.getDocument().getDocumentElement().getAttribute("xml:lang"));
          }
          Voice guessedVoice = null;
          if (docLocale != null) {
              guessedVoice = Voice.getDefaultVoice(docLocale);
          } else {
              // get any voice
              if (allVoices.size() != 0)
                  guessedVoice = allVoices.iterator().next();
          }
          if (guessedVoice != null)
              Log.d(Mary.LOG, "Guessing default voice `" + guessedVoice.getName() + "'");
          else
              Log.d(Mary.LOG, "Couldn't find any voice at all");
  
          return guessedVoice;
      }
  
      /**
       * Look up in the list of already-loaded lexicons whether the requested lexicon
       * is known; otherwise, load it.
       *
       * @param lexiconClass
       * @param lexiconName
       * @return the requested lexicon, or null.
       */
      private static Lexicon getLexicon(String lexiconClass, String lexiconName) {
          if (lexiconClass == null) return null;
          // build the lexicon if not already built
          Lexicon lexicon = null;
          if (lexicons.containsKey(lexiconClass + lexiconName)) {
              return lexicons.get(lexiconClass + lexiconName);
          }
          // need to create a new lexicon instance
          try {
              Log.d(Mary.LOG, "...loading lexicon...");
              if (lexiconName == null) {
                  lexicon = (Lexicon) Class.forName(lexiconClass).newInstance();
              } else { // lexiconName is String argument to constructor
                  Class lexCl = Class.forName(lexiconClass);
                  Constructor lexConstr = lexCl.getConstructor(String.class);
                  // will throw a NoSuchMethodError if constructor does not exist
                  lexicon = (Lexicon) lexConstr.newInstance(lexiconName);
  
                  // Apply our own custom addenda only for cmudict04:
                  if (lexiconName.equals("cmudict04")) {
                      assert lexicon instanceof CMULexicon : "Expected lexicon to be a CMULexicon";
                      String customAddenda = MaryProperties.getFilename("english.lexicon.customAddenda");
                      if (customAddenda != null) {
                          //create lexicon with custom addenda
                          Log.d(Mary.LOG, "...loading custom addenda...");
                          lexicon.load();
                          //open addenda file
                          BufferedReader addendaIn = new BufferedReader(new InputStreamReader(
                                  new FileInputStream(new File(customAddenda)), "UTF-8"));
                          String line;
                          while ((line = addendaIn.readLine()) != null) {
                              if (!line.startsWith("#") && !line.equals("")) {
                                  //add all words in addenda to lexicon
                                  StringTokenizer tok = new StringTokenizer(line);
                                  String word = tok.nextToken();
                                  int numPhones = tok.countTokens();
                                  String[] phones = new String[numPhones];
                                  for (int i = 0; i < phones.length; i++) {
                                      phones[i] = tok.nextToken();
                                  }
                                  lexicon.addAddendum(word, null, phones);
                              }
                          }
                      }
                  }
              }
          } catch (Exception ex) {
              Log.e(Mary.LOG, "Could not load lexicon " + lexiconClass + "('" + lexiconName + "')", ex);
          }
          lexicons.put(lexiconClass + lexiconName, lexicon);
          return lexicon;
      }
  
      /**
       * @throws MaryConfigurationException
       * @throws NoSuchPropertyException
       */
      private void init() throws MaryConfigurationException,
              NoSuchPropertyException, IOException {
          // Read settings from config file:
  
  
          String header = "voice." + getName();
          this.wantToBeDefault = MaryProperties.getInteger(header + ".wants.to.be.default", 0);
  
          // no allophone set for voice, try for locale
          try {
              allophoneSet = MaryRuntimeUtils.needAllophoneSet(MaryProperties.localePrefix(getLocale()) + ".allophoneset");
          } catch (MaryConfigurationException e2) {
              Log.d(Mary.LOG, "No allophone set specified -- neither for voice '" + getName() + "' nor for locale '" + getLocale() + "'");
              throw new MaryConfigurationException("No allophone set specified -- neither for voice '" + getName() + "' nor for locale '" + getLocale() + "'", e2);
          }
  
          preferredModulesClasses = MaryProperties.getProperty(header + ".preferredModules");
  
  
          String lexiconClass = MaryProperties.getProperty(header + ".lexiconClass");
          String lexiconName = MaryProperties.getProperty(header + ".lexicon");
          lexicon = getLexicon(lexiconClass, lexiconName);
          vocalizationSupport = MaryProperties.getBoolean(header + ".vocalizationSupport", false);
          if (vocalizationSupport) {
              vocalizationSynthesizer = new VocalizationSynthesizer(this);
          }
  
          loadOldStyleProsodyModels(header);
          // Log.d(Mary.LOG, "kecdi burdanda .... 1");
          loadAcousticModels(header);
  
          // Log.d(Mary.LOG, "kecdi burdanda .... 2");
          // initialization of FeatureProcessorManager for this voice, if needed:
          initFeatureProcessorManager();
  
      }
  
      @Deprecated
      private void loadOldStyleProsodyModels(String header) throws MaryConfigurationException {
          // see if there are any voice-specific duration and f0 models to load
          durationGraph = null;
          String durationGraphFile = MaryProperties.getFilename(header + ".duration.cart");
          if (durationGraphFile != null) {
              Log.d(Mary.LOG, "...loading duration graph...");
              try {
                  durationGraph = (new DirectedGraphReader()).load(durationGraphFile);
              } catch (IOException e) {
                  Log.d(Mary.LOG, "Cannot load duration graph file '" + durationGraphFile + "'");
                  throw new MaryConfigurationException("Cannot load duration graph file '" + durationGraphFile + "'", e);
              }
          }
  
          f0Graph = null;
          String f0GraphFile = MaryProperties.getFilename(header + ".f0.graph");
          if (f0GraphFile != null) {
              Log.d(Mary.LOG, "...loading f0 contour graph...");
              try {
                  f0Graph = (new DirectedGraphReader()).load(f0GraphFile);
                  // If we have the graph, we need the contour:
                  String f0ContourFile = MaryProperties.needFilename(header + ".f0.contours");
                  f0ContourFeatures = new FeatureFileReader(f0ContourFile);
              } catch (IOException e) {
                  Log.d(Mary.LOG, "Cannot load f0 contour graph file '" + f0GraphFile + "'");
                  throw new MaryConfigurationException("Cannot load f0 contour graph file '" + f0GraphFile + "'", e);
              }
          }
      }
  
      /**
       * Load a flexibly configurable list of acoustic models as specified in the config file.
       *
       * @param header
       * @throws MaryConfigurationException
       * @throws NoSuchPropertyException
       */
      private void loadAcousticModels(String header) throws MaryConfigurationException, NoSuchPropertyException, IOException {
  
          // The feature processor manager that all acoustic models will use to predict their acoustics:
          FeatureProcessorManager symbolicFPM = FeatureRegistry.determineBestFeatureProcessorManager(getLocale());
  
          // Acoustic models:
          String acousticModelsString = MaryProperties.getProperty(header + ".acousticModels");
  
  
          if (acousticModelsString != null) {
              acousticModels = new HashMap<String, Model>();
  
              // add boundary "model" (which could of course be overwritten by appropriate properties in voice config):
              acousticModels.put("boundary", new BoundaryModel(symbolicFPM, voiceName, null, "duration", null, null, null, "boundaries"));
              //Log.d(Mary.LOG, "1 loadAcousticModels   acousticModelsString=" + acousticModelsString);
              StringTokenizer acousticModelStrings = new StringTokenizer(acousticModelsString);
              do {
                  String modelName = acousticModelStrings.nextToken();
                  //  Log.d(Mary.LOG, "2 loadAcousticModels   modelName=" + modelName);
  
                  // get more properties from voice config, depending on the model name:
                  String modelType = MaryProperties.needProperty(header + "." + modelName + ".model");
  
                  // Log.d(Mary.LOG, "3 loadAcousticModels   modelType=" + modelType+ " header="+header+" " );
  
                  InputStream modelDataStream = null;// not used for hmm models
  
  
                  String modelAttributeName = MaryProperties.needProperty(header + "." + modelName + ".attribute");
                  // Log.d(Mary.LOG, "4 loadAcousticModels   modelAttributeName=" + modelAttributeName);
  
                  // the following are null if not defined; this is handled in the Model constructor:
                  String modelAttributeFormat = MaryProperties.getProperty(header + "." + modelName + ".attribute.format");
                  String modelFeatureName = MaryProperties.getProperty(header + "." + modelName + ".feature");
                  String modelPredictFrom = MaryProperties.getProperty(header + "." + modelName + ".predictFrom");
                  String modelApplyTo = MaryProperties.getProperty(header + "." + modelName + ".applyTo");
  
                  // consult the ModelType enum to find appropriate Model subclass...
                  ModelType possibleModelTypes = ModelType.fromString(modelType);
                  // if modelType is not in ModelType.values(), we don't know how to handle it:
                  if (possibleModelTypes == null) {
                      Log.d(Mary.LOG, "Cannot handle unknown model type: " + modelType);
                      throw new MaryConfigurationException("Cannot handle unknown model type: " + modelType);
                  }
  
                  // ...and instantiate it in a switch statement:
                  Model model = null;
                  try {
                      switch (possibleModelTypes) {
                          case CART:
                              model = new CARTModel(symbolicFPM, voiceName, modelDataStream, modelAttributeName, modelAttributeFormat,
                                      modelFeatureName, modelPredictFrom, modelApplyTo);
                              break;
  
                          case SOP:
                              model = new SoPModel(symbolicFPM, voiceName, modelDataStream, modelAttributeName, modelAttributeFormat,
                                      modelFeatureName, modelPredictFrom, modelApplyTo);
                              break;
  
                          case HMM:
                              // if we already have a HMM duration or F0 model, and if this is the other of the two, and if so,
                              // and they use the same dataFile, then let them be the same instance:
                              // if this is the case set the boolean variable predictDurAndF0 to true in HMMModel
                              //   Log.d(Mary.LOG,"loadAcousticModels switch hmm");
                              if (getDurationModel() != null && getDurationModel() instanceof HMMModel && modelName.equalsIgnoreCase("F0")
                                      && voiceName.equals(getDurationModel().getVoiceName())) {
                                  model = getDurationModel();
                                  ((HMMModel) model).setPredictDurAndF0(true);
                              } else if (getF0Model() != null && getF0Model() instanceof HMMModel && modelName.equalsIgnoreCase("duration")
                                      && voiceName.equals(getF0Model().getVoiceName())) {
                                  model = getF0Model();
                                  ((HMMModel) model).setPredictDurAndF0(true);
                              } else {
                                  model = new HMMModel(symbolicFPM, voiceName, modelDataStream, modelAttributeName, modelAttributeFormat,
                                          modelFeatureName, modelPredictFrom, modelApplyTo);
                              }
                              break;
                      }
                  } catch (Throwable t) {
                      Log.d(Mary.LOG, "Cannot instantiate model '" + modelName + "' of type '" + modelType +
                              "' from '" + MaryProperties.getProperty(header + "." + modelName + ".data") + "'");
                      throw new MaryConfigurationException("Cannot instantiate model '" + modelName + "' of type '" + modelType +
                              "' from '" + MaryProperties.getProperty(header + "." + modelName + ".data") + "'", t);
                  }
  
                  // if we got this far, model should not be null:
                  assert model != null;
  
                  // put the model in the Model Map:
                  acousticModels.put(modelName, model);
              } while (acousticModelStrings.hasMoreTokens());
          }
      }
  
      /**
       * Try to determine a feature processor manager. This will look for the voice-specific config setting
       * <code>voice.(voicename).featuremanager</code>. If a feature processor manager
       * is found, it is initialised and entered into the {@link marytts.features.FeatureRegistry}.
       *
       * @throws MaryConfigurationException if the feature processor manager cannot be initialised.
       */
      private void initFeatureProcessorManager() throws MaryConfigurationException {
          FeatureProcessorManager featMgr = null;
  
          // Any feature processor manager settings in the config file?
          String keyVoiceFeatMgr = "voice." + getName() + ".featuremanager";
          String featMgrClass = MaryProperties.getProperty(keyVoiceFeatMgr);
          if (featMgrClass != null) {
              try {
                  featMgr = (FeatureProcessorManager) Class.forName(featMgrClass).newInstance();
              } catch (Exception e) {
                  Log.d(Mary.LOG, "Cannot initialise voice-specific FeatureProcessorManager "
                          + featMgrClass + " from config file");
                  throw new MaryConfigurationException("Cannot initialise voice-specific FeatureProcessorManager "
                          + featMgrClass + " from config file", e);
              }
          } else if (getOtherModels() != null) {
              // Only if there is no feature manager setting in the config file,
              // we consider creating one from the acoustic features;
              // We need to do this only if we have any "other" acoustic models, beyond duration and F0:
  
              FeatureProcessorManager genericFPM = FeatureRegistry.determineBestFeatureProcessorManager(locale);
              // We attempt to create an FPM with same class as genericFPM via the Constructor FPM(Voice):
              Class<? extends FeatureProcessorManager> fpmClass = genericFPM.getClass();
              try {
                  Constructor<? extends FeatureProcessorManager> fpmVoiceConstructor = fpmClass.getConstructor(Voice.class);
                  featMgr = fpmVoiceConstructor.newInstance(this);
              } catch (NoSuchMethodException nsme) {
                  Log.d(Mary.LOG, "Cannot initialise voice-specific FeatureProcessorManager: Class "
                          + fpmClass.getName() + " has no constructor " + fpmClass.getSimpleName() + "(Voice)");
                  throw new MaryConfigurationException("Cannot initialise voice-specific FeatureProcessorManager: Class "
                          + fpmClass.getName() + " has no constructor " + fpmClass.getSimpleName() + "(Voice)");
              } catch (Exception e) {
                  Log.d(Mary.LOG, "Cannot initialise voice-specific FeatureProcessorManager");
                  throw new MaryConfigurationException("Cannot initialise voice-specific FeatureProcessorManager", e);
              }
          }
          // register the FeatureProcessorManager for this Voice:
          if (featMgr != null) {
              FeatureRegistry.setFeatureProcessorManager(this, featMgr);
          }
      }
  
      /**
       * Get the allophone set associated with this voice.
       *
       * @return
       */
      public AllophoneSet getAllophoneSet() {
          return allophoneSet;
      }
  
      /**
       * Get the Allophone set for the given phone symbol.
       *
       * @param phoneSymbol
       * @return an Allophone object if phoneSymbol is a known phone symbol in the voice's AllophoneSet, or null.
       */
      public Allophone getAllophone(String phoneSymbol) {
          return allophoneSet.getAllophone(phoneSymbol);
      }
  
      public synchronized Vector<MaryModule> getPreferredModulesAcceptingType(MaryDataType type) {
          if (preferredModules == null && preferredModulesClasses != null) {
              // need to initialise the list of modules
              preferredModules = new Vector<MaryModule>();
              StringTokenizer st = new StringTokenizer(preferredModulesClasses);
              while (st.hasMoreTokens()) {
                  String moduleInfo = st.nextToken();
                  try {
                      MaryModule mm = null;
                      if (!moduleInfo.contains("(")) { // no constructor info
                          mm = ModuleRegistry.getModule(Class.forName(moduleInfo));
                      }
                      if (mm == null) {
                          // need to create our own:
                          Log.w(Mary.LOG, "Module " + moduleInfo + " is not in the standard list of modules -- will start our own, but will not be able to shut it down at the end.");
                          mm = ModuleRegistry.instantiateModule(moduleInfo);
                          mm.startup();
                      }
                      preferredModules.add(mm);
                  } catch (Exception e) {
                      Log.w(Mary.LOG, "Cannot initialise preferred module " + moduleInfo + " for voice " + getName() + " -- skipping.", e);
                  }
              }
          }
          if (preferredModules != null) {
              Vector<MaryModule> v = new Vector<MaryModule>();
              for (Iterator<MaryModule> it = preferredModules.iterator(); it.hasNext(); ) {
                  MaryModule m = it.next();
                  if (m.inputType().equals(type)) {
                      v.add(m);
                  }
              }
              if (v.size() > 0) return v;
              else return null;
          }
          return null;
      }
  
      public boolean hasName(String aName) {
          return voiceName.equals(aName);
      }
  
      /**
       * Return the name of this voice. If the voice has several possible names,
       * the first one is returned.
       */
      public String getName() {
          return voiceName;
      }
  
      /**
       * Returns the return value of <code>getName()</code>.
       */
      public String toString() {
          return getName();
      }
  
      // Several getters for acoustic models, returning null if undefined:
  
      public Locale getLocale() {
          return locale;
      }
  
      public AudioFormat dbAudioFormat() {
          return dbAudioFormat;
      }
  
      public WaveformSynthesizer synthesizer() {
          return synthesizer;
      }
  
      public Gender gender() {
          return gender;
      }
  
      public boolean hasVocalizationSupport() {
          return vocalizationSupport;
      }
  
      ////////// static stuff //////////
  
      public VocalizationSynthesizer getVocalizationSynthesizer() {
          return vocalizationSynthesizer;
      }
  
      /**
       * Get any styles supported by this voice.
       *
       * @return an array of style names supported by this voice, or null if styles are not supported.
       */
      public String[] getStyles() {
          // TODO: read from config file
          if (voiceName.equals("dfki-pavoque-styles")) {
              return new String[]{"neutral", "poker", "happy", "angry", "sad"};
          }
          return null;
      }
  
      /**
       * Synthesize a list of tokens and boundaries with the waveform synthesizer
       * providing this voice.
       *
       * @param outputParams
       */
      public AudioInputStream synthesize(List<Element> tokensAndBoundaries, String outputParams)
              throws SynthesisException {
          return synthesizer.synthesize(tokensAndBoundaries, this, outputParams);
      }
  
      /**
       * Return the lexicon associated to this voice
       *
       * @return
       */
      public Lexicon getLexicon() {
          return lexicon;
      }
  
      public DirectedGraph getDurationGraph() {
          return durationGraph;
      }
  
      public DirectedGraph getF0Graph() {
          return f0Graph;
      }
  
      public FeatureFileReader getF0ContourFeatures() {
          return f0ContourFeatures;
      }
  
      /**
       * Get the acoustic models defined for this voice.
       *
       * @return a Map mapping model names to models,
       * or null if there are no such models.
       */
      public Map<String, Model> getAcousticModels() {
          return acousticModels;
      }
  
      /**
       * Get the duration model for this voice.
       *
       * @return the model, or null if no such model is defined.
       */
      public Model getDurationModel() {
          if (acousticModels == null) {
              return null;
          }
          return acousticModels.get("duration");
      }
  
      /**
       * Get the F0 model for this voice.
       *
       * @return the model, or null if no such model is defined.
       */
      public Model getF0Model() {
          if (acousticModels == null) {
              return null;
          }
          return acousticModels.get("F0");
      }
  
      /**
       * Get the boundary duration model for this voice.
       *
       * @return the model, or null if no such model is defined.
       */
      public Model getBoundaryModel() {
          if (acousticModels == null) {
              return null;
          }
          return acousticModels.get("boundary");
      }
  
      /**
       * Return any "other" acoustic models that we have.
       * Other models are acoustic models beyond duration, F0 and boundary.
       *
       * @return a Map mapping the model name to the model, or null if no other models exist.
       */
      public Map<String, Model> getOtherModels() {
          if (acousticModels == null) {
              return null;
          }
          Map<String, Model> otherModels = new HashMap<String, Model>();
          for (String modelName : acousticModels.keySet()) {
              // ignore critical Models that have their own getters:
              if (!modelName.equals("duration") && !modelName.equals("F0") && !modelName.equals("boundary")) {
                  otherModels.put(modelName, acousticModels.get(modelName));
              }
          }
          if (otherModels.size() == 0) {
              return null;
          }
          return otherModels;
      }
  
      public static class Gender {
          String name;
  
          public Gender(String name) {
              this.name = name;
          }
  
          public String toString() {
              return name;
          }
  
          public boolean equals(Gender other) {
              return other.toString().equals(name);
          }
      }
  
  }
  