1

I'm currently writing a Java application (chat app) which requires text-to-speech. After looking online for a bit I found out that FreeTTS is a good option, I managed to get it work, and it currently prevents the main UI thread from being blocked as I'm running it in a thread.

The only issue is that FreeTTS only lets one voice speak at once (even though they are in multiple threads as multiple instances). Is there a way I can circumvent this and have multiple users' speak at once? This is my code I have so far.

import lombok.extern.slf4j.Slf4j;

import java.beans.PropertyVetoException;
import java.util.Locale;
import java.util.Random;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.speech.AudioException;
import javax.speech.Central;
import javax.speech.EngineException;
import javax.speech.EngineStateError;
import javax.speech.synthesis.Synthesizer;
import javax.speech.synthesis.SynthesizerModeDesc;
import javax.speech.synthesis.Voice;

@Slf4j
public enum Speech implements SpeechAgent {

    Instance;
    private static Random pool = new Random();

    private static Synthesizer createSynthesizer() {
        try {
            System.setProperty("freetts.voices", "com.sun.speech.freetts.en.us.cmu_us_kal.KevinVoiceDirectory");
            SynthesizerModeDesc desc = new SynthesizerModeDesc(Locale.US);
            Central.registerEngineCentral("com.sun.speech.freetts.jsapi.FreeTTSEngineCentral");
            Synthesizer synthesizer = Central.createSynthesizer(desc);
            synthesizer.allocate();
            synthesizer.resume();

            Voice[] voices = null;

            while (voices == null) {
                voices = ((SynthesizerModeDesc) synthesizer.getEngineModeDesc()).getVoices();
            }
            synthesizer.getSynthesizerProperties().setVoice(voices[pool.nextInt(voices.length)]);
            return synthesizer;

        } catch (Exception e) {
            log.error(String.format("Failed to load synthesizer: %s", e.toString()));
        }

        return null;
    }

    public void speak(String input) {
        new Thread(new Runnable() {
            public void run() {
                Synthesizer synthesizer = createSynthesizer();

                if (synthesizer == null) {
                    return;
                }

                synthesizer.speakPlainText(input.replaceAll("[^a-zA-Z ]+", ""), null);

            }
        }).start();
    }
}

Errors thrown in thread:

Trouble while processing utterance java.lang.IllegalStateException: Syllable relation has already been set
java.lang.IllegalStateException: Syllable relation has already been set
    at com.sun.speech.freetts.Segmenter.processUtterance(Segmenter.java:56)
    at com.sun.speech.freetts.Voice.runProcessor(Voice.java:595)
    at com.sun.speech.freetts.Voice.processUtterance(Voice.java:414)
    at com.sun.speech.freetts.Voice.speak(Voice.java:289)
    at com.sun.speech.freetts.jsapi.FreeTTSSynthesizer$OutputHandler.outputItem(FreeTTSSynthesizer.java:695)
    at com.sun.speech.freetts.jsapi.FreeTTSSynthesizer$OutputHandler.run(FreeTTSSynthesizer.java:622)
Paradoxis
  • 4,471
  • 7
  • 32
  • 66
  • [This thread in their discussion forum](https://sourceforge.net/p/freetts/discussion/137669/thread/6c8b8ae4/) says that this happens because your `Central.createSynthesizer(desc)` will return the same synthesizer to each thread as the specifications (i.e. `desc`) remain the same. They go on talking about how to avoid this by changing synthesizer names, maybe that can help you, too. – Malte Hartwig Jan 03 '18 at 08:42
  • @MalteHartwig just spent about an hour trying it, for whatever reason if I specify a name `Central.createSynthesizer(desc)` just returns NULL at all times – Paradoxis Jan 03 '18 at 09:31
  • Sorry to hear. Did you see that they changed the name in the `synthesizer.properties` file, as opposed to changing it in the `SynthesizerModeDesc` object only (*"if we set "Synthesizer45" in the modeDesc, then a synthesizer with name "Synthesizer45" needs to be present in the synthesizer.properties file"*)? They said they had to create a copy of the prop file for each synthesizer instance they needed (and also came up with a way to make this more generic, but I didn't read too far). Unfortunately, I cannot play around with it myself at the moment. – Malte Hartwig Jan 03 '18 at 09:48
  • Why not openmary? – Nikolay Shmyrev Jan 07 '18 at 22:11
  • @NikolayShmyrev thanks for the suggestion, actually got it working with that after ten minutes :) – Paradoxis Jan 11 '18 at 12:32

0 Answers0