comparison 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
comparison
equal deleted inserted replaced
-1:000000000000 0:78b7fc5391a2
1 /*
2 CCmI Editor - A Collaborative Cross-Modal Diagram Editing Tool
3
4 Copyright (C) 2011 Queen Mary University of London (http://ccmi.eecs.qmul.ac.uk/)
5
6 This program is free software: you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation, either version 3 of the License, or
9 (at your option) any later version.
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 General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with this program. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20 package uk.ac.qmul.eecs.ccmi.speech;
21
22 import java.net.URL;
23 import java.util.ResourceBundle;
24 import java.util.concurrent.LinkedBlockingQueue;
25
26 import uk.ac.qmul.eecs.ccmi.utils.ResourceFileWriter;
27 import uk.ac.qmul.eecs.ccmi.utils.OsDetector;
28 import uk.ac.qmul.eecs.ccmi.utils.PreferencesService;
29
30 import com.sun.speech.freetts.Voice;
31 import com.sun.speech.freetts.VoiceManager;
32 /*
33 * Implementation of the Narrator interface using the Windows system text to speech
34 * synthesizer.
35 */
36 class NativeNarrator implements Narrator {
37 static {
38 nativeLibraryNotFound = true;
39 if(OsDetector.isWindows()){
40 String res = OsDetector.has64BitJVM() ? "WinNarrator64.dll" : "WinNarrator.dll" ;
41 URL url = NativeNarrator.class.getResource(res);
42 if(url != null){
43 ResourceFileWriter fileWriter = new ResourceFileWriter(url);
44 fileWriter.writeOnDisk(
45 PreferencesService.getInstance().get("dir.libs", System.getProperty("java.io.tmpdir")),
46 OsDetector.has64BitJVM() ? "CCmIWinNarrator64.dll" : "CCmIWinNarrator.dll");
47 String path = fileWriter.getFilePath();
48 if(path != null)
49 try{
50 System.load( path );
51 nativeLibraryNotFound = false;
52 }catch(UnsatisfiedLinkError e){
53 e.printStackTrace();
54 /* do nothing: nativeLibraryNotFound won't be set to false */
55 /* which will trigger a NarratorException */
56 }
57 }
58 }
59 }
60
61 public NativeNarrator(){
62 resources = ResourceBundle.getBundle(Narrator.class.getName());
63 VoiceManager voiceManager = VoiceManager.getInstance();
64 secondaryVoice = voiceManager.getVoice(VOICE_NAME);
65 if(secondaryVoice == null)
66 System.out.println("Could not create voice for the second speaker");
67 else{
68 secondaryVoice.setAudioPlayer(new BeadsAudioPlayer(1.0f,1.0f));
69 secondaryVoice.setRate(250f);
70 secondaryVoice.allocate();
71 }
72 }
73
74 @Override
75 public void init() throws NarratorException {
76 if(nativeLibraryNotFound)
77 throw new NarratorException();
78
79 firstVoiceMuted = false;
80 secondVoiceMuted = false;
81 queue = new LinkedBlockingQueue<QueueEntry>();
82 executor = new Executor();
83 boolean success = _init();
84 if(!success)
85 throw new NarratorException();
86 rate = Integer.parseInt(PreferencesService.getInstance().get("speech_rate", DEFAULT_RATE_VALUE));
87 _setRate(rate);
88 executor.start();
89 }
90
91 @Override
92 public void setMuted(boolean muted, int voice) {
93 if(voice == SECOND_VOICE)
94 secondVoiceMuted = muted;
95 else
96 firstVoiceMuted = muted;
97 }
98
99 @Override
100 public boolean isMuted(int voice){
101 if(voice == SECOND_VOICE)
102 return secondVoiceMuted;
103 else
104 return firstVoiceMuted;
105 }
106
107 @Override
108 public void setRate(int rate){
109 if(rate < MIN_RATE || rate > MAX_RATE)
110 throw new IllegalArgumentException("Rate value must be between 0 and 20");
111 _setRate(rate);
112 this.rate = rate;
113 PreferencesService.getInstance().put("speech_rate", Integer.toString(rate));
114 }
115
116 @Override
117 public int getRate(){
118 return rate;
119 }
120
121 @Override
122 public void shutUp() {
123 _shutUp();
124 }
125
126 @Override
127 public void speak(String text, int voice) {
128 if(firstVoiceMuted || secondVoiceMuted && voice == Narrator.SECOND_VOICE)
129 return;
130 if(" ".equals(text))
131 text = resources.getString("char.space");
132 else if("\n".equals(text))
133 text = resources.getString("char.new_line");
134 else if(text.contains(".ccmi"))
135 text = text.replaceAll(".ccmi", " "+resources.getString("ccmi_spell"));
136 queue.add(new QueueEntry(text,false,voice));
137 }
138
139 public void speak(String text){
140 speak(text,Narrator.FIRST_VOICE);
141 }
142
143 @Override
144 public void speakWholeText(String text, int voice) {
145 if(firstVoiceMuted || secondVoiceMuted && voice == Narrator.SECOND_VOICE)
146 return;
147 if(" ".equals(text))
148 text = resources.getString("char.space");
149 else if("\n".equals(text))
150 text = resources.getString("char.new_line");
151 else if(text.contains(".ccmi"))
152 text = text.replaceAll(".ccmi", " "+resources.getString("ccmi_spell"));
153 queue.add(new QueueEntry(text,true,voice));
154 }
155
156 @Override
157 public void speakWholeText(String text) {
158 speakWholeText(text, Narrator.FIRST_VOICE);
159 }
160
161 @Override
162 public void dispose() {
163 executor.mustSayGoodbye = true;
164 executor.interrupt();
165 }
166
167 /* native routines used by the Executor thread */
168 private native boolean _init();
169
170 private native void _dispose();
171
172 private native void _speak(String text);
173
174 private native void _speakWholeText(String text);
175
176 private native void _setRate(int rate);
177
178 private native void _shutUp();
179
180 private volatile boolean firstVoiceMuted;
181 private volatile boolean secondVoiceMuted;
182 private int rate;
183 private LinkedBlockingQueue<QueueEntry> queue;
184 private Executor executor;
185 private ResourceBundle resources;
186 private Voice secondaryVoice;
187 private static String DEFAULT_RATE_VALUE = "13";
188 private static final String VOICE_NAME = "kevin16";
189 private static boolean nativeLibraryNotFound;
190
191 private class Executor extends Thread{
192 private Executor(){
193 super("Narrator Thread");
194 mustSayGoodbye = false;
195 }
196
197 @Override
198 public void run(){
199 QueueEntry entry;
200 while(!mustSayGoodbye){
201 try {
202 entry = queue.take();
203 } catch (InterruptedException e) {
204 continue; /* start over the while cycle */
205 }
206 if(!entry.speakToEnd && queue.peek() != null)
207 continue;/* the user submitted another text to be spoken out and this can be overwritten */
208 if(entry.speakToEnd && entry.voice == Narrator.FIRST_VOICE){
209 _speakWholeText(entry.text);
210 }else if(entry.voice == Narrator.FIRST_VOICE){
211 _speak(entry.text);
212 }else if(secondaryVoice != null){
213 secondaryVoice.speak(entry.text);
214 }
215 }
216 if(secondaryVoice != null)
217 secondaryVoice.deallocate();
218 _dispose();
219 }
220
221 private volatile boolean mustSayGoodbye;
222 }
223
224 private static class QueueEntry {
225 QueueEntry(String text, boolean speakToEnd, int voice){
226 this.text = text;
227 this.speakToEnd = speakToEnd;
228 this.voice = voice;
229 }
230 String text;
231 boolean speakToEnd;
232 int voice;
233 }
234
235 }