toverainc / willow-inference-server

Open source, local, and self-hosted highly optimized language inference server supporting ASR/STT, TTS, and LLM across WebRTC, REST, and WS
Apache License 2.0
351 stars 30 forks source link

Evaluate TTS Engines #60

Open kristiankielhofner opened 1 year ago

kristiankielhofner commented 1 year ago

SpeechT5 is included because it's in Transformers and it's an easy first pick for TTS.

There are several others (in no particular order):

Tortoise

Coqui

Toucan

MMS

Now that WIS has been released I'm very interested in feedback from the community to evaluate different engines, voices, etc so we can select the best default for future versions of WIS.

DePingus commented 1 year ago

Rhasspy released their new engine Piper recently. Their samples sound a lot like Mycroft's Mimic 3 engine.

kristiankielhofner commented 1 year ago

Mycroft's Mimic 3 engine is licensed as AGPL, which has some considerable legal implications. I'd also be hesitant to use anything Mycroft based as their future is uncertain.

You can see from both Mimic and Piper they're using a lot of the same standard components as the frameworks above - VITS, espeak-ng, etc.

nils-se commented 1 year ago

Strong advocate of Coqui. I tried a lot of voices and frameworks and liked it the best in German and English. Here is a little python code for trying it out. I wrote a helper function for saying long texts. The first sentence voice gets generated and played, then the next et cetera. If one would compute all the sentences at once, there would be a huge delay. It works nicely on CPU in sub-realtime, so maybe plays nice with your GPU focused STT and LLM. Great work by the way!

Please mind my non-coding background when trying out my code ;)

Also on second execution the audio files will be reused with a hash. So they won't get generated by say().


from TTS.api import TTS
import subprocess
import time
import os
import hashlib
import pysbd ## for sentence splitting with natural language processing

# Init TTS with the target model name. To list all models just use: TTS.list_models()
#list_voices = TTS.list_models()
#print(list_voices)
#exit()

audiofiles_path = "/tmp/"

## German:
#tts = TTS(model_name="tts_models/de/thorsten/tacotron2-DDC", progress_bar=False, gpu=False)

## English:
tts = TTS(model_name="tts_models/en/ljspeech/tacotron2-DDC", progress_bar=False, gpu=False)

def say(text, blocking=1):

    sentences = pysbd.Segmenter(language="de", clean=False).segment(text)
    print(sentences)

    i_sentence = 0
    for sentence in sentences:
        hash = hashlib.md5()
        hash.update(sentence.encode())
        print("----------------------------------------")
        print("")
        print(sentence)
        print("")
        print("----------------------------------------")
        filepath = hash.hexdigest() + ".wav"
        filepath = os.path.join(audiofiles_path, filepath)
        if not os.path.exists(filepath):

            tts.tts_to_file(sentence, file_path=filepath)
        if i_sentence > 0:
            while play_process.poll() is None: ## test if play-process is still running and wait for it to finish
                time.sleep(0.1)
                print("still waiting")
        if i_sentence == len(sentences) - 1:
            print("last sentence")
            if blocking == 1:
                print("blocking")
                subprocess.call(["aplay", filepath])
            elif blocking == 0:
                print("non blocking")
                subprocess.Popen(["aplay", filepath])
        else:
            print("not last sentence - non blocking")
            print(time.time())
            play_process = subprocess.Popen(["aplay", filepath]) # non blocking, so the next voice generation can start while the voice is played back

        i_sentence = i_sentence + 1

## Benchmark for block-tts vs. sequential-tts:

## German:
# test_text = "Dies ist ein allererster neuer Test für mich. Paris ist die Hauptstadt aller Herzen. Ich bin ein Computer Assistent und ich bin da, um dir zu helfen."

## English:
test_text = "Hello World, this is a test. Just stay put, while I compute all voice sequences. While I am at it, let me tell you something about myself: I am a robotic voice assistant and I wish to serve. Good bye."

t1 = time.time()

tts.tts_to_file(test_text, file_path="/tmp/tts_one_shot.wav")
first_voice = time.time()
subprocess.call(["aplay", "/tmp/tts_one_shot.wav"])

t2 = time.time()
tts_say = t2-t1
tts_first_voice = first_voice - t1

# input("Press Enter to continue...")

t1 = time.time()
print("start")
print(time.time())

say(test_text, blocking = 0)

t2 = time.time()
diff_say = t2-t1

print("say non-blocking: " + str(diff_say))
print("time to first voice: " + str(tts_first_voice))
print("one-shot generation: " + str(tts_say))```
kristiankielhofner commented 1 year ago

That's a vote for coqui!

Yes, we will use caching but my preferred approach is to go about it a little differently.

For WIS itself TTS output to HTTP response should be bytesIO() at a minimum, and this was a little painful last I looked at coqui.

Our plan is to cache TTS output at the network/transport layer for architecture abstraction, scalability, and to do things like enable the use of a CDN for large scale deployments like our hosted community WIS instance. If you're going to use caching the cache hit request shouldn't touch WIS at all.

We have CDN with tiered caching and reserve caching setup already for community hosted WIS and that will provide response times that are ridiculously fast for users around the world with TTS.

nikito commented 1 year ago

Discussed in #78 but also casting my vote here for Coqui 😄

JarbasAl commented 1 year ago

Have you considered OpenVoiceOS plugin manager for this? it provides a lot of TTS options (and several other things such as STT, VAD....) behind a unified api

https://github.com/OpenVoiceOS?q=tts&type=all&language=&sort=

satvikpendem commented 10 months ago

Try Bark, it's MIT licensed.

nikito commented 10 months ago

Think bark was looked at but the performance unfortunately isn't where it needs to be for a good user experience for a voice assistant (TTS generation on an enterprise GPU is at best real time, and on lesser gpus it is less than real time).

kristiankielhofner commented 10 months ago

@nikito is correct in terms of performance.

Bark also has a strange tendency to insert random "ummm" sounds in the audio output as documented in this issue.

satvikpendem commented 10 months ago

Yeah that's true, because it's using a generative pre-transformer model rather than true text to speech. It hallucinates similarly to Stable Diffusion and ChatGPT.

kristiankielhofner commented 10 months ago

We're on a constant hunt for a TTS implementation that provides better quality and more flexibility than SpeechT5 with comparable performance. It's at the top of my list in terms of ongoing WIS improvements but I've yet to find such a thing...

Nortonko commented 7 months ago

Hi. is there something new about TTS? Thanks.

nikito commented 7 months ago

Yes, we have some new engines in process. They aren't in the main branch yet, but you can experiment with them in the feature/split_arch branch.

Nortonko commented 7 months ago

Thanks, i will try it

ther3zz commented 5 months ago

strong vote for coqui, specially with their xtts v2 model since fine tuning is super easy

satvikpendem commented 5 months ago

Just saw this on Hacker News, it's the best I've heard so far:

https://github.com/collabora/WhisperSpeech

Napetc commented 3 months ago

Hello, will it be possible to add other languages? I would like to add it as an additional file. If possible

ccsmart commented 1 month ago

I switched to split_arch branch using coqui and TTS seems much improved. Numbers are spoken out. When building with xts "utils.sh build-xtts" multi language response is supported. The voice is downloaded as part of the install / build. However how do you switch voices with xtts ?