PageRenderTime 55ms CodeModel.GetById 28ms RepoModel.GetById 0ms app.codeStats 0ms

/marytts-runtime/src/main/java/marytts/modules/synthesis/MbrolaSynthesizer.java

https://bitbucket.org/marytts/marytts
Java | 284 lines | 212 code | 19 blank | 53 comment | 40 complexity | 3c49588b6cab8f204baee5a40fb5d808 MD5 | raw file
  1. /**
  2. * Copyright 2000-2006 DFKI GmbH.
  3. * All Rights Reserved. Use is subject to license terms.
  4. *
  5. * This file is part of MARY TTS.
  6. *
  7. * MARY TTS is free software: you can redistribute it and/or modify
  8. * it under the terms of the GNU Lesser General Public License as published by
  9. * the Free Software Foundation, version 3 of the License.
  10. *
  11. * This program is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. * GNU Lesser General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU Lesser General Public License
  17. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  18. *
  19. */
  20. package marytts.modules.synthesis;
  21. import java.io.File;
  22. import java.io.IOException;
  23. import java.io.StringReader;
  24. import java.util.ArrayList;
  25. import java.util.Collection;
  26. import java.util.List;
  27. import java.util.Locale;
  28. import java.util.StringTokenizer;
  29. import javax.sound.sampled.AudioFileFormat;
  30. import javax.sound.sampled.AudioFormat;
  31. import javax.sound.sampled.AudioInputStream;
  32. import javax.sound.sampled.AudioSystem;
  33. import marytts.datatypes.MaryData;
  34. import marytts.datatypes.MaryDataType;
  35. import marytts.datatypes.MaryXML;
  36. import marytts.exceptions.SynthesisException;
  37. import marytts.modules.MaryModule;
  38. import marytts.modules.MaryXMLToMbrola;
  39. import marytts.modules.MbrolaCaller;
  40. import marytts.modules.ModuleRegistry;
  41. import marytts.modules.synthesis.Voice.Gender;
  42. import marytts.server.MaryProperties;
  43. import marytts.util.MaryUtils;
  44. import org.apache.log4j.Logger;
  45. import org.w3c.dom.Element;
  46. import org.w3c.dom.NodeList;
  47. /**
  48. * The Mbrola waveform synthesizer wrapper.
  49. */
  50. public class MbrolaSynthesizer implements WaveformSynthesizer {
  51. private MaryXMLToMbrola maryxmlToMbrola;
  52. private MbrolaCaller mbrolaCaller;
  53. private Logger logger;
  54. public MbrolaSynthesizer() {
  55. }
  56. public void startup() throws Exception {
  57. logger = MaryUtils.getLogger(this.toString());
  58. // Try to get instances of our tools from Mary; if we cannot get them,
  59. // instantiate new objects.
  60. try{
  61. maryxmlToMbrola =
  62. (MaryXMLToMbrola) ModuleRegistry.getModule(MaryXMLToMbrola.class);
  63. } catch (NullPointerException npe){
  64. maryxmlToMbrola = null;
  65. }
  66. if (maryxmlToMbrola == null) {
  67. logger.info("Starting my own MaryXMLToMbrola");
  68. maryxmlToMbrola = new MaryXMLToMbrola();
  69. maryxmlToMbrola.startup();
  70. } else if (maryxmlToMbrola.getState() == MaryModule.MODULE_OFFLINE) {
  71. maryxmlToMbrola.startup();
  72. }
  73. String mbrolaCallerProperty;
  74. if (System.getProperty("os.name").startsWith("Windows") && false) { // let's try without this, use cygwin binary instead...
  75. mbrolaCallerProperty = "mbrolasynthesizer.mbrolacaller.class.win32";
  76. } else {
  77. mbrolaCallerProperty = "mbrolasynthesizer.mbrolacaller.class";
  78. }
  79. Class mbrolaClass = MaryProperties.needClass(mbrolaCallerProperty);
  80. Object obj;
  81. try {
  82. obj = ModuleRegistry.getModule(mbrolaClass);
  83. } catch (NullPointerException npe){
  84. obj = null;
  85. }
  86. if (obj == null) {
  87. logger.info("Starting my own MbrolaCaller (" + mbrolaClass.getName() + ")");
  88. obj = mbrolaClass.newInstance();
  89. }
  90. if (!(obj instanceof MbrolaCaller)) {
  91. throw new ClassCastException("Class `" + mbrolaClass.getName() +
  92. "' is not an MbrolaCaller. Check property `" + mbrolaCallerProperty +
  93. "' in configuration files");
  94. }
  95. mbrolaCaller = (MbrolaCaller) obj;
  96. if (mbrolaCaller.getState() == MaryModule.MODULE_OFFLINE) {
  97. mbrolaCaller.startup();
  98. }
  99. // Register Mbrola voices:
  100. String basePath =
  101. System.getProperty("mary.base")
  102. + File.separator
  103. + "lib"
  104. + File.separator
  105. + "voices"
  106. + File.separator;
  107. logger.debug("Register MBROLA voices:");
  108. String voiceNames = MaryProperties.needProperty("mbrola.voices.list");
  109. for (StringTokenizer st = new StringTokenizer(voiceNames); st.hasMoreTokens(); ) {
  110. String voiceName = st.nextToken();
  111. String path = MaryProperties.getFilename("voice."+voiceName+".path", basePath + voiceName + File.separator + voiceName);
  112. if (new File(path).exists()) {
  113. logger.debug("Voice '" + voiceName + "'");
  114. Locale locale = MaryUtils.string2locale(MaryProperties.needProperty("voice."+voiceName+".locale"));
  115. int samplingRate = MaryProperties.getInteger("voice."+voiceName+".samplingrate", 16000);
  116. Gender gender = new Gender(MaryProperties.needProperty("voice."+voiceName+".gender"));
  117. int topStart = MaryProperties.needInteger("voice."+voiceName+".topline.start");
  118. int topEnd = MaryProperties.needInteger("voice."+voiceName+".topline.end");
  119. int baseStart = MaryProperties.needInteger("voice."+voiceName+".baseline.start");
  120. int baseEnd = MaryProperties.needInteger("voice."+voiceName+".baseline.end");
  121. String vqString = MaryProperties.getProperty("voice."+voiceName+".voicequalities", null);
  122. String[] voiceQualities = null;
  123. if (vqString != null) voiceQualities = vqString.split("\\s+");
  124. String missingDiphones = MaryProperties.getFilename("voice."+voiceName+".missingdiphones", null);
  125. Voice v = new MbrolaVoice (path,
  126. new String[] { voiceName },
  127. locale,
  128. mbrolaAudioFormat(samplingRate),
  129. this,
  130. gender,
  131. topStart, topEnd, baseStart, baseEnd,
  132. voiceQualities,
  133. missingDiphones);
  134. Voice.registerVoice(v);
  135. } else { // voice not present
  136. logger.debug("Voice `"+voiceName+"' is not present. Skipping.");
  137. }
  138. }
  139. logger.info("started.");
  140. }
  141. /**
  142. * Perform a power-on self test by processing some example input data.
  143. * @throws Error if the module does not work properly.
  144. */
  145. public synchronized void powerOnSelfTest() throws Error
  146. {
  147. logger.info("Starting power-on self test.");
  148. try {
  149. MaryDataType inType = maryxmlToMbrola.inputType();
  150. Collection voices = Voice.getAvailableVoices(this);
  151. if (voices.isEmpty())
  152. throw new Error("No MBROLA voices present");
  153. Voice v = (Voice) voices.iterator().next();
  154. assert v != null;
  155. MaryData in = new MaryData(inType, v.getLocale());
  156. String example = inType.exampleText(v.getLocale());
  157. if (example != null) {
  158. in.readFrom(new StringReader(example));
  159. in.setDefaultVoice(v);
  160. MaryData mbrola = maryxmlToMbrola.process(in);
  161. mbrola.setAudioFileFormat(new AudioFileFormat(
  162. AudioFileFormat.Type.WAVE, Voice.AF22050, AudioSystem.NOT_SPECIFIED)
  163. );
  164. mbrola.setDefaultVoice(v);
  165. mbrolaCaller.process(mbrola);
  166. } else {
  167. logger.debug("No example text -- no power-on self test!");
  168. }
  169. } catch (Throwable t) {
  170. throw new Error("Module " + toString() + ": Power-on self test failed.", t);
  171. }
  172. logger.info("Power-on self test complete.");
  173. }
  174. public String toString() {
  175. return "MbrolaSynthesizer";
  176. }
  177. /**
  178. * {@inheritDoc}
  179. */
  180. public AudioInputStream synthesize(List<Element> tokensAndBoundaries, Voice voice, String outputParams)
  181. throws SynthesisException {
  182. if (!voice.synthesizer().equals(this)) {
  183. throw new IllegalArgumentException(
  184. "Voice " + voice.getName() + " is not an MBROLA voice.");
  185. }
  186. logger.info("Synthesizing one sentence.");
  187. // 1. Convert into MBROLA .pho format.
  188. List<Element> phonesAndBoundaries = new ArrayList<Element>();
  189. for (Element element : tokensAndBoundaries) {
  190. if (element.getTagName().equals(MaryXML.TOKEN)) {
  191. NodeList nl = element.getElementsByTagName(MaryXML.PHONE);
  192. for (int i = 0; i < nl.getLength(); i++) {
  193. phonesAndBoundaries.add((Element)nl.item(i));
  194. }
  195. } else if (element.getTagName().equals(MaryXML.BOUNDARY)) {
  196. phonesAndBoundaries.add(element);
  197. } else {
  198. throw new IllegalArgumentException(
  199. "Expected only <t> and <boundary> elements, got <"
  200. + element.getTagName()
  201. + ">");
  202. }
  203. }
  204. String pho =
  205. maryxmlToMbrola.convertToMbrola(phonesAndBoundaries, voice);
  206. if (Boolean.getBoolean("democenter.workaround")) {
  207. pho = pho + "_ 300\n";
  208. }
  209. // 2. Call MBROLA synthesizer.
  210. /*Vector audioInputStreams = new Vector();
  211. long totalFrames = 0;
  212. StringTokenizer st = new StringTokenizer(pho, "#");
  213. try {
  214. while (st.hasMoreTokens()) {
  215. AudioInputStream ais =
  216. mbrolaCaller.synthesiseOneSection(st.nextToken(), voice);
  217. audioInputStreams.add(ais);
  218. totalFrames += ais.getFrameLength();
  219. }
  220. } catch (IOException ioe) {
  221. throw new SynthesisException("Cannot synthesise", ioe);
  222. }
  223. assert !audioInputStreams.isEmpty();
  224. return MaryAudioUtils.createSingleAudioInputStream(audioInputStreams);*/
  225. AudioInputStream ais;
  226. try {
  227. ais = mbrolaCaller.synthesiseOneSection(pho, voice);
  228. } catch (IOException ioe) {
  229. throw new SynthesisException("Cannot synthesise", ioe);
  230. }
  231. assert ais != null;
  232. return ais;
  233. }
  234. public static AudioFormat mbrolaAudioFormat(int samplingRate)
  235. {
  236. boolean bigEndian;
  237. if (System.getProperty("os.name").equals("Mac OS X")) {
  238. bigEndian = true; // big-endian
  239. // Special treatment for Mac OS X, because the MBROLA binary
  240. // (which stems from PowerPC times) produces big-endian
  241. // even on an i386 machine
  242. } else if (System.getProperty("os.arch").equals("x86") ||
  243. System.getProperty("os.arch").equals("i386") ||
  244. System.getProperty("os.arch").equals("amd64")) {
  245. bigEndian = false;
  246. } else {
  247. // all others -- e.g., sparc
  248. bigEndian = true;
  249. }
  250. return new AudioFormat(AudioFormat.Encoding.PCM_SIGNED,
  251. samplingRate, // samples per second
  252. 16, // bits per sample
  253. 1, // mono
  254. 2, // nr. of bytes per frame
  255. samplingRate, // nr. of frames per second
  256. bigEndian);
  257. }
  258. public static boolean isMbrolaVoice(Voice voice)
  259. {
  260. if (voice == null) throw new NullPointerException("Received null argument");
  261. WaveformSynthesizer ws = voice.synthesizer();
  262. if (ws == null) throw new NullPointerException("Voice has no waveform synthesizer");
  263. return (ws instanceof MbrolaSynthesizer);
  264. }
  265. }