/**
*
* Copyright (c) 2009-2016 Freedomotic team http://freedomotic.com
*
* This file is part of Freedomotic
*
* This Program is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by the Free Software
* Foundation; either version 2, or (at your option) any later version.
*
* 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 General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License along with
* Freedomotic; see the file COPYING. If not, see
* <http://www.gnu.org/licenses/>.
*/
/**
* @author Mauro Cicolella
*/
package com.freedomotic.plugins.marytts;
import com.freedomotic.api.EventTemplate;
import com.freedomotic.api.Protocol;
import com.freedomotic.exceptions.PluginStartupException;
import com.freedomotic.exceptions.UnableToExecuteException;
import com.freedomotic.reactions.Command;
import com.freedomotic.settings.Info;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Method;
import java.net.URI;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Locale;
import java.util.Set;
import java.util.logging.Level;
import javax.sound.sampled.AudioInputStream;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import marytts.LocalMaryInterface;
import marytts.MaryInterface;
import marytts.exceptions.MaryConfigurationException;
import marytts.exceptions.SynthesisException;
import marytts.modules.synthesis.Voice;
import marytts.util.data.audio.AudioPlayer;
public class MaryTTS extends Protocol {
private static final Logger LOG = LoggerFactory.getLogger(MaryTTS.class.getName());
private MaryInterface marytts;
private Voice defaultVoice;
private Voice voice;
private AudioInputStream audio;
private final String VOICE_JAR_FILE = configuration.getProperty("voice-jar-file");
private final String VOICE_NAME = configuration.getProperty("voice");
private MaryTTSGui GUI;
public MaryTTS() {
super("MaryTTS", "/marytts/marytts-manifest.xml");
}
@Override
protected void onStart() throws PluginStartupException {
GUI = new MaryTTSGui(this);
try {
//add voices folder to classpath
addPath(Info.PATHS.PATH_DEVICES_FOLDER + System.getProperty("file.separator") + "marytts" + System.getProperty("file.separator") + "lib" + System.getProperty("file.separator") + VOICE_JAR_FILE);
System.setProperty("mary.base", Info.PATHS.PATH_DEVICES_FOLDER + System.getProperty("file.separator") + "marytts");
marytts = new LocalMaryInterface();
// print available voices and languages
LOG.info("Available voices: ''{}''", marytts.getAvailableVoices());
LOG.info("Available languages: ''{}''", marytts.getAvailableLocales());
// set default voice
defaultVoice = Voice.getVoice("cmu-slt-hsmm");
} catch (MaryConfigurationException ex) {
throw new PluginStartupException("Plugin can't start for a configuration problem.", ex);
} catch (Exception ex) {
throw new PluginStartupException("Plugin can't start. Impossible to modify classpath to add voice file " + VOICE_JAR_FILE + " for " + ex.getLocalizedMessage());
}
}
@Override
protected void onShowGui() {
bindGuiToPlugin(GUI);
}
public void say(String message) {
if (!message.isEmpty()) {
try {
new MaryTTS.Speaker(message).start();
} catch (Exception ex) {
LOG.error("Error while speaking message '" + message + "'", ex);
}
} else {
LOG.warn("Impossible to speak. Message is empty");
}
}
@Override
protected void onCommand(Command c) throws IOException, UnableToExecuteException {
String message = c.getProperty("say");
say(message);
}
@Override
protected boolean canExecute(Command c) {
throw new UnsupportedOperationException("Not supported yet.");
}
@Override
protected void onRun() {
//throw new UnsupportedOperationException("Not supported yet.");
}
@Override
protected void onEvent(EventTemplate event) {
//throw new UnsupportedOperationException("Not supported yet.");
}
private class Speaker extends Thread {
String message = "";
private Speaker(String message) {
this.message = message;
}
@Override
public synchronized void run() {
if (!isVoiceAvailable(VOICE_NAME)) {
LOG.info("Voice ''{}'' not found. Using default voice ", VOICE_NAME);
voice = defaultVoice;
} else {
voice = Voice.getVoice(VOICE_NAME);
LOG.info("Voice set to ''{}''", voice.getName());
}
try {
marytts.setVoice(voice.getName());
audio = marytts.generateAudio(message);
AudioPlayer player = new AudioPlayer(audio);
player.start();
player.join();
} catch (SynthesisException ex) {
LOG.error("Error during audio generation", ex);
} catch (InterruptedException ex) {
LOG.error("Error during speaking", ex);
} finally {
try {
if (audio != null) {
audio.close();
}
} catch (IOException ex) {
//unrecoverable, do nothing
}
}
}
}
//need to do add path to Classpath with reflection since the URLClassLoader.addURL(URL url) method is protected:
private void addPath(String s) throws Exception {
File f = new File(s);
URI u = f.toURI();
URLClassLoader urlClassLoader = (URLClassLoader) ClassLoader.getSystemClassLoader();
Class<URLClassLoader> urlClass = URLClassLoader.class;
Method method = urlClass.getDeclaredMethod("addURL", new Class[]{URL.class});
method.setAccessible(true);
method.invoke(urlClassLoader, new Object[]{u.toURL()});
}
private boolean isVoiceAvailable(String voice) {
if (voice.isEmpty()) {
return false;
} else {
Set<String> availableVoices = marytts.getAvailableVoices();
return availableVoices.contains(voice);
}
}
}