view java/src/uk/ac/qmul/eecs/ccmi/speech/NativeNarrator.java @ 1:e3935c01cde2 tip

moved license of PdPersistenceManager to the beginning of the file
author Fiore Martin <f.martin@qmul.ac.uk>
date Tue, 08 Jul 2014 19:52:03 +0100
parents 78b7fc5391a2
children
line wrap: on
line source
/*  
 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;
	}

}