Mercurial > hg > accesspd
diff java/src/uk/ac/qmul/eecs/ccmi/speech/NativeNarrator.java @ 0:78b7fc5391a2
first import, outcome of NIME 2014 hackaton
author | Fiore Martin <f.martin@qmul.ac.uk> |
---|---|
date | Tue, 08 Jul 2014 16:28:59 +0100 |
parents | |
children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/java/src/uk/ac/qmul/eecs/ccmi/speech/NativeNarrator.java Tue Jul 08 16:28:59 2014 +0100 @@ -0,0 +1,235 @@ +/* + CCmI Editor - A Collaborative Cross-Modal Diagram Editing Tool + + Copyright (C) 2011 Queen Mary University of London (http://ccmi.eecs.qmul.ac.uk/) + + 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 3 of the License, 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 this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +package uk.ac.qmul.eecs.ccmi.speech; + +import java.net.URL; +import java.util.ResourceBundle; +import java.util.concurrent.LinkedBlockingQueue; + +import uk.ac.qmul.eecs.ccmi.utils.ResourceFileWriter; +import uk.ac.qmul.eecs.ccmi.utils.OsDetector; +import uk.ac.qmul.eecs.ccmi.utils.PreferencesService; + +import com.sun.speech.freetts.Voice; +import com.sun.speech.freetts.VoiceManager; +/* + * Implementation of the Narrator interface using the Windows system text to speech + * synthesizer. + */ +class NativeNarrator implements Narrator { + static { + nativeLibraryNotFound = true; + if(OsDetector.isWindows()){ + String res = OsDetector.has64BitJVM() ? "WinNarrator64.dll" : "WinNarrator.dll" ; + URL url = NativeNarrator.class.getResource(res); + if(url != null){ + ResourceFileWriter fileWriter = new ResourceFileWriter(url); + fileWriter.writeOnDisk( + PreferencesService.getInstance().get("dir.libs", System.getProperty("java.io.tmpdir")), + OsDetector.has64BitJVM() ? "CCmIWinNarrator64.dll" : "CCmIWinNarrator.dll"); + String path = fileWriter.getFilePath(); + if(path != null) + try{ + System.load( path ); + nativeLibraryNotFound = false; + }catch(UnsatisfiedLinkError e){ + e.printStackTrace(); + /* do nothing: nativeLibraryNotFound won't be set to false */ + /* which will trigger a NarratorException */ + } + } + } + } + + public NativeNarrator(){ + resources = ResourceBundle.getBundle(Narrator.class.getName()); + VoiceManager voiceManager = VoiceManager.getInstance(); + secondaryVoice = voiceManager.getVoice(VOICE_NAME); + if(secondaryVoice == null) + System.out.println("Could not create voice for the second speaker"); + else{ + secondaryVoice.setAudioPlayer(new BeadsAudioPlayer(1.0f,1.0f)); + secondaryVoice.setRate(250f); + secondaryVoice.allocate(); + } + } + + @Override + public void init() throws NarratorException { + if(nativeLibraryNotFound) + throw new NarratorException(); + + firstVoiceMuted = false; + secondVoiceMuted = false; + queue = new LinkedBlockingQueue<QueueEntry>(); + executor = new Executor(); + boolean success = _init(); + if(!success) + throw new NarratorException(); + rate = Integer.parseInt(PreferencesService.getInstance().get("speech_rate", DEFAULT_RATE_VALUE)); + _setRate(rate); + executor.start(); + } + + @Override + public void setMuted(boolean muted, int voice) { + if(voice == SECOND_VOICE) + secondVoiceMuted = muted; + else + firstVoiceMuted = muted; + } + + @Override + public boolean isMuted(int voice){ + if(voice == SECOND_VOICE) + return secondVoiceMuted; + else + return firstVoiceMuted; + } + + @Override + public void setRate(int rate){ + if(rate < MIN_RATE || rate > MAX_RATE) + throw new IllegalArgumentException("Rate value must be between 0 and 20"); + _setRate(rate); + this.rate = rate; + PreferencesService.getInstance().put("speech_rate", Integer.toString(rate)); + } + + @Override + public int getRate(){ + return rate; + } + + @Override + public void shutUp() { + _shutUp(); + } + + @Override + public void speak(String text, int voice) { + if(firstVoiceMuted || secondVoiceMuted && voice == Narrator.SECOND_VOICE) + return; + if(" ".equals(text)) + text = resources.getString("char.space"); + else if("\n".equals(text)) + text = resources.getString("char.new_line"); + else if(text.contains(".ccmi")) + text = text.replaceAll(".ccmi", " "+resources.getString("ccmi_spell")); + queue.add(new QueueEntry(text,false,voice)); + } + + public void speak(String text){ + speak(text,Narrator.FIRST_VOICE); + } + + @Override + public void speakWholeText(String text, int voice) { + if(firstVoiceMuted || secondVoiceMuted && voice == Narrator.SECOND_VOICE) + return; + if(" ".equals(text)) + text = resources.getString("char.space"); + else if("\n".equals(text)) + text = resources.getString("char.new_line"); + else if(text.contains(".ccmi")) + text = text.replaceAll(".ccmi", " "+resources.getString("ccmi_spell")); + queue.add(new QueueEntry(text,true,voice)); + } + + @Override + public void speakWholeText(String text) { + speakWholeText(text, Narrator.FIRST_VOICE); + } + + @Override + public void dispose() { + executor.mustSayGoodbye = true; + executor.interrupt(); + } + + /* native routines used by the Executor thread */ + private native boolean _init(); + + private native void _dispose(); + + private native void _speak(String text); + + private native void _speakWholeText(String text); + + private native void _setRate(int rate); + + private native void _shutUp(); + + private volatile boolean firstVoiceMuted; + private volatile boolean secondVoiceMuted; + private int rate; + private LinkedBlockingQueue<QueueEntry> queue; + private Executor executor; + private ResourceBundle resources; + private Voice secondaryVoice; + private static String DEFAULT_RATE_VALUE = "13"; + private static final String VOICE_NAME = "kevin16"; + private static boolean nativeLibraryNotFound; + + private class Executor extends Thread{ + private Executor(){ + super("Narrator Thread"); + mustSayGoodbye = false; + } + + @Override + public void run(){ + QueueEntry entry; + while(!mustSayGoodbye){ + try { + entry = queue.take(); + } catch (InterruptedException e) { + continue; /* start over the while cycle */ + } + if(!entry.speakToEnd && queue.peek() != null) + continue;/* the user submitted another text to be spoken out and this can be overwritten */ + if(entry.speakToEnd && entry.voice == Narrator.FIRST_VOICE){ + _speakWholeText(entry.text); + }else if(entry.voice == Narrator.FIRST_VOICE){ + _speak(entry.text); + }else if(secondaryVoice != null){ + secondaryVoice.speak(entry.text); + } + } + if(secondaryVoice != null) + secondaryVoice.deallocate(); + _dispose(); + } + + private volatile boolean mustSayGoodbye; + } + + private static class QueueEntry { + QueueEntry(String text, boolean speakToEnd, int voice){ + this.text = text; + this.speakToEnd = speakToEnd; + this.voice = voice; + } + String text; + boolean speakToEnd; + int voice; + } + +}