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: }