f@0: /*
f@0: CCmI Editor - A Collaborative Cross-Modal Diagram Editing Tool
f@0:
f@0: Copyright (C) 2011 Queen Mary University of London (http://ccmi.eecs.qmul.ac.uk/)
f@0:
f@0: This program is free software: you can redistribute it and/or modify
f@0: it under the terms of the GNU General Public License as published by
f@0: the Free Software Foundation, either version 3 of the License, or
f@0: (at your option) any later version.
f@0:
f@0: This program is distributed in the hope that it will be useful,
f@0: but WITHOUT ANY WARRANTY; without even the implied warranty of
f@0: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
f@0: GNU General Public License for more details.
f@0:
f@0: You should have received a copy of the GNU General Public License
f@0: along with this program. If not, see .
f@0: */
f@0:
f@0: package uk.ac.qmul.eecs.ccmi.speech;
f@0:
f@0: import java.net.URL;
f@0: import java.util.ResourceBundle;
f@0: import java.util.concurrent.LinkedBlockingQueue;
f@0:
f@0: import uk.ac.qmul.eecs.ccmi.utils.ResourceFileWriter;
f@0: import uk.ac.qmul.eecs.ccmi.utils.OsDetector;
f@0: import uk.ac.qmul.eecs.ccmi.utils.PreferencesService;
f@0:
f@0: import com.sun.speech.freetts.Voice;
f@0: import com.sun.speech.freetts.VoiceManager;
f@0: /*
f@0: * Implementation of the Narrator interface using the Windows system text to speech
f@0: * synthesizer.
f@0: */
f@0: class NativeNarrator implements Narrator {
f@0: static {
f@0: nativeLibraryNotFound = true;
f@0: if(OsDetector.isWindows()){
f@0: String res = OsDetector.has64BitJVM() ? "WinNarrator64.dll" : "WinNarrator.dll" ;
f@0: URL url = NativeNarrator.class.getResource(res);
f@0: if(url != null){
f@0: ResourceFileWriter fileWriter = new ResourceFileWriter(url);
f@0: fileWriter.writeOnDisk(
f@0: PreferencesService.getInstance().get("dir.libs", System.getProperty("java.io.tmpdir")),
f@0: OsDetector.has64BitJVM() ? "CCmIWinNarrator64.dll" : "CCmIWinNarrator.dll");
f@0: String path = fileWriter.getFilePath();
f@0: if(path != null)
f@0: try{
f@0: System.load( path );
f@0: nativeLibraryNotFound = false;
f@0: }catch(UnsatisfiedLinkError e){
f@0: e.printStackTrace();
f@0: /* do nothing: nativeLibraryNotFound won't be set to false */
f@0: /* which will trigger a NarratorException */
f@0: }
f@0: }
f@0: }
f@0: }
f@0:
f@0: public NativeNarrator(){
f@0: resources = ResourceBundle.getBundle(Narrator.class.getName());
f@0: VoiceManager voiceManager = VoiceManager.getInstance();
f@0: secondaryVoice = voiceManager.getVoice(VOICE_NAME);
f@0: if(secondaryVoice == null)
f@0: System.out.println("Could not create voice for the second speaker");
f@0: else{
f@0: secondaryVoice.setAudioPlayer(new BeadsAudioPlayer(1.0f,1.0f));
f@0: secondaryVoice.setRate(250f);
f@0: secondaryVoice.allocate();
f@0: }
f@0: }
f@0:
f@0: @Override
f@0: public void init() throws NarratorException {
f@0: if(nativeLibraryNotFound)
f@0: throw new NarratorException();
f@0:
f@0: firstVoiceMuted = false;
f@0: secondVoiceMuted = false;
f@0: queue = new LinkedBlockingQueue();
f@0: executor = new Executor();
f@0: boolean success = _init();
f@0: if(!success)
f@0: throw new NarratorException();
f@0: rate = Integer.parseInt(PreferencesService.getInstance().get("speech_rate", DEFAULT_RATE_VALUE));
f@0: _setRate(rate);
f@0: executor.start();
f@0: }
f@0:
f@0: @Override
f@0: public void setMuted(boolean muted, int voice) {
f@0: if(voice == SECOND_VOICE)
f@0: secondVoiceMuted = muted;
f@0: else
f@0: firstVoiceMuted = muted;
f@0: }
f@0:
f@0: @Override
f@0: public boolean isMuted(int voice){
f@0: if(voice == SECOND_VOICE)
f@0: return secondVoiceMuted;
f@0: else
f@0: return firstVoiceMuted;
f@0: }
f@0:
f@0: @Override
f@0: public void setRate(int rate){
f@0: if(rate < MIN_RATE || rate > MAX_RATE)
f@0: throw new IllegalArgumentException("Rate value must be between 0 and 20");
f@0: _setRate(rate);
f@0: this.rate = rate;
f@0: PreferencesService.getInstance().put("speech_rate", Integer.toString(rate));
f@0: }
f@0:
f@0: @Override
f@0: public int getRate(){
f@0: return rate;
f@0: }
f@0:
f@0: @Override
f@0: public void shutUp() {
f@0: _shutUp();
f@0: }
f@0:
f@0: @Override
f@0: public void speak(String text, int voice) {
f@0: if(firstVoiceMuted || secondVoiceMuted && voice == Narrator.SECOND_VOICE)
f@0: return;
f@0: if(" ".equals(text))
f@0: text = resources.getString("char.space");
f@0: else if("\n".equals(text))
f@0: text = resources.getString("char.new_line");
f@0: else if(text.contains(".ccmi"))
f@0: text = text.replaceAll(".ccmi", " "+resources.getString("ccmi_spell"));
f@0: queue.add(new QueueEntry(text,false,voice));
f@0: }
f@0:
f@0: public void speak(String text){
f@0: speak(text,Narrator.FIRST_VOICE);
f@0: }
f@0:
f@0: @Override
f@0: public void speakWholeText(String text, int voice) {
f@0: if(firstVoiceMuted || secondVoiceMuted && voice == Narrator.SECOND_VOICE)
f@0: return;
f@0: if(" ".equals(text))
f@0: text = resources.getString("char.space");
f@0: else if("\n".equals(text))
f@0: text = resources.getString("char.new_line");
f@0: else if(text.contains(".ccmi"))
f@0: text = text.replaceAll(".ccmi", " "+resources.getString("ccmi_spell"));
f@0: queue.add(new QueueEntry(text,true,voice));
f@0: }
f@0:
f@0: @Override
f@0: public void speakWholeText(String text) {
f@0: speakWholeText(text, Narrator.FIRST_VOICE);
f@0: }
f@0:
f@0: @Override
f@0: public void dispose() {
f@0: executor.mustSayGoodbye = true;
f@0: executor.interrupt();
f@0: }
f@0:
f@0: /* native routines used by the Executor thread */
f@0: private native boolean _init();
f@0:
f@0: private native void _dispose();
f@0:
f@0: private native void _speak(String text);
f@0:
f@0: private native void _speakWholeText(String text);
f@0:
f@0: private native void _setRate(int rate);
f@0:
f@0: private native void _shutUp();
f@0:
f@0: private volatile boolean firstVoiceMuted;
f@0: private volatile boolean secondVoiceMuted;
f@0: private int rate;
f@0: private LinkedBlockingQueue queue;
f@0: private Executor executor;
f@0: private ResourceBundle resources;
f@0: private Voice secondaryVoice;
f@0: private static String DEFAULT_RATE_VALUE = "13";
f@0: private static final String VOICE_NAME = "kevin16";
f@0: private static boolean nativeLibraryNotFound;
f@0:
f@0: private class Executor extends Thread{
f@0: private Executor(){
f@0: super("Narrator Thread");
f@0: mustSayGoodbye = false;
f@0: }
f@0:
f@0: @Override
f@0: public void run(){
f@0: QueueEntry entry;
f@0: while(!mustSayGoodbye){
f@0: try {
f@0: entry = queue.take();
f@0: } catch (InterruptedException e) {
f@0: continue; /* start over the while cycle */
f@0: }
f@0: if(!entry.speakToEnd && queue.peek() != null)
f@0: continue;/* the user submitted another text to be spoken out and this can be overwritten */
f@0: if(entry.speakToEnd && entry.voice == Narrator.FIRST_VOICE){
f@0: _speakWholeText(entry.text);
f@0: }else if(entry.voice == Narrator.FIRST_VOICE){
f@0: _speak(entry.text);
f@0: }else if(secondaryVoice != null){
f@0: secondaryVoice.speak(entry.text);
f@0: }
f@0: }
f@0: if(secondaryVoice != null)
f@0: secondaryVoice.deallocate();
f@0: _dispose();
f@0: }
f@0:
f@0: private volatile boolean mustSayGoodbye;
f@0: }
f@0:
f@0: private static class QueueEntry {
f@0: QueueEntry(String text, boolean speakToEnd, int voice){
f@0: this.text = text;
f@0: this.speakToEnd = speakToEnd;
f@0: this.voice = voice;
f@0: }
f@0: String text;
f@0: boolean speakToEnd;
f@0: int voice;
f@0: }
f@0:
f@0: }