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