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;
+	}
+
+}