Initial commit
Browse files- .gitattributes +12 -0
- README.md +15 -8
- app.py +718 -0
- assets/audio/arabic_female.wav +3 -0
- assets/audio/chinese_female.wav +3 -0
- assets/audio/english_female.wav +3 -0
- assets/audio/english_male.wav +3 -0
- assets/audio/french_female.wav +3 -0
- assets/audio/german_male.wav +3 -0
- assets/audio/hindi_male.wav +3 -0
- assets/audio/japanese_male.wav +3 -0
- assets/audio/portuguese_male.wav +3 -0
- assets/audio/russian_female.wav +3 -0
- assets/audio/spanish_male.wav +3 -0
- assets/audio/swedish_female.wav +3 -0
- assets/examples.json +52 -0
- configs/app.json +23 -0
- configs/generator.json +58 -0
- configs/speaking_rate.json +51 -0
- packages.txt +1 -0
- requirements.txt +1 -0
.gitattributes
CHANGED
|
@@ -33,3 +33,15 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
| 36 |
+
assets/audio/arabic_female.wav filter=lfs diff=lfs merge=lfs -text
|
| 37 |
+
assets/audio/chinese_female.wav filter=lfs diff=lfs merge=lfs -text
|
| 38 |
+
assets/audio/english_female.wav filter=lfs diff=lfs merge=lfs -text
|
| 39 |
+
assets/audio/english_male.wav filter=lfs diff=lfs merge=lfs -text
|
| 40 |
+
assets/audio/french_female.wav filter=lfs diff=lfs merge=lfs -text
|
| 41 |
+
assets/audio/german_male.wav filter=lfs diff=lfs merge=lfs -text
|
| 42 |
+
assets/audio/hindi_male.wav filter=lfs diff=lfs merge=lfs -text
|
| 43 |
+
assets/audio/japanese_male.wav filter=lfs diff=lfs merge=lfs -text
|
| 44 |
+
assets/audio/portuguese_male.wav filter=lfs diff=lfs merge=lfs -text
|
| 45 |
+
assets/audio/russian_female.wav filter=lfs diff=lfs merge=lfs -text
|
| 46 |
+
assets/audio/spanish_male.wav filter=lfs diff=lfs merge=lfs -text
|
| 47 |
+
assets/audio/swedish_female.wav filter=lfs diff=lfs merge=lfs -text
|
README.md
CHANGED
|
@@ -1,13 +1,20 @@
|
|
| 1 |
---
|
| 2 |
title: Demo
|
| 3 |
-
emoji:
|
| 4 |
-
colorFrom:
|
| 5 |
-
colorTo:
|
| 6 |
sdk: gradio
|
| 7 |
-
|
| 8 |
-
|
| 9 |
app_file: app.py
|
| 10 |
-
pinned:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
---
|
| 12 |
-
|
| 13 |
-
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
|
|
|
|
| 1 |
---
|
| 2 |
title: Demo
|
| 3 |
+
emoji: ⚡
|
| 4 |
+
colorFrom: blue
|
| 5 |
+
colorTo: purple
|
| 6 |
sdk: gradio
|
| 7 |
+
python_version: 3.10
|
| 8 |
+
sdk_version: 4.44.1
|
| 9 |
app_file: app.py
|
| 10 |
+
pinned: true
|
| 11 |
+
license: cc-by-4.0
|
| 12 |
+
short_description: Zero-shot TTS with Controllable Speaking-rate
|
| 13 |
+
models:
|
| 14 |
+
- voxtream2/model
|
| 15 |
+
preload_from_hub:
|
| 16 |
+
- voxtream2/model model.safetensors,config.json,phoneme_to_token.json
|
| 17 |
+
- kyutai/moshiko-pytorch-bf16 tokenizer-e351c8d8-checkpoint125.safetensors
|
| 18 |
+
- sarulab-speech/sidon-v0.1 decoder_cuda.pt
|
| 19 |
+
- sarulab-speech/sidon-v0.1 feature_extractor_cuda.pt
|
| 20 |
---
|
|
|
|
|
|
app.py
ADDED
|
@@ -0,0 +1,718 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import argparse
|
| 2 |
+
import json
|
| 3 |
+
import os
|
| 4 |
+
import uuid
|
| 5 |
+
from pathlib import Path
|
| 6 |
+
|
| 7 |
+
# Disable PyTorch dynamo/inductor globally for HuggingFace ZeroGPU.
|
| 8 |
+
os.environ["TORCHDYNAMO_DISABLE"] = "1"
|
| 9 |
+
os.environ["TORCHINDUCTOR_DISABLE"] = "1"
|
| 10 |
+
|
| 11 |
+
import torch._dynamo as dynamo
|
| 12 |
+
|
| 13 |
+
dynamo.config.suppress_errors = True
|
| 14 |
+
|
| 15 |
+
import gradio as gr
|
| 16 |
+
import numpy as np
|
| 17 |
+
import soundfile as sf
|
| 18 |
+
import spaces
|
| 19 |
+
import torch
|
| 20 |
+
|
| 21 |
+
from voxtream.config import SpeechGeneratorConfig
|
| 22 |
+
from voxtream.generator import SpeechGenerator
|
| 23 |
+
from voxtream.utils.app import (
|
| 24 |
+
CUSTOM_CSS,
|
| 25 |
+
AppConfig,
|
| 26 |
+
GenerationControl,
|
| 27 |
+
SharedGenerationState,
|
| 28 |
+
SpeakingRateState,
|
| 29 |
+
VisualizationState,
|
| 30 |
+
build_low_latency_audio_head,
|
| 31 |
+
clear_outputs,
|
| 32 |
+
empty_rate_plot,
|
| 33 |
+
float32_to_int16,
|
| 34 |
+
load_app_config,
|
| 35 |
+
render_audio_stream,
|
| 36 |
+
render_text_progress,
|
| 37 |
+
)
|
| 38 |
+
from voxtream.utils.generator import DTYPE_MAP, existing_file, text_generator
|
| 39 |
+
from voxtream.utils.generator.helpers import autocast_ctx
|
| 40 |
+
from voxtream.utils.generator.text import build_text_progress_metadata
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
def generation_button_updates(running: bool, paused: bool = False):
|
| 44 |
+
if not running:
|
| 45 |
+
return (
|
| 46 |
+
gr.update(visible=False),
|
| 47 |
+
gr.update(visible=False),
|
| 48 |
+
gr.update(visible=False),
|
| 49 |
+
)
|
| 50 |
+
if paused:
|
| 51 |
+
return (
|
| 52 |
+
gr.update(visible=False),
|
| 53 |
+
gr.update(visible=True, interactive=True),
|
| 54 |
+
gr.update(visible=True, interactive=True),
|
| 55 |
+
)
|
| 56 |
+
return (
|
| 57 |
+
gr.update(visible=True, interactive=True),
|
| 58 |
+
gr.update(visible=False),
|
| 59 |
+
gr.update(visible=False),
|
| 60 |
+
)
|
| 61 |
+
|
| 62 |
+
|
| 63 |
+
def ensure_generator_on_cuda(speech_generator: SpeechGenerator) -> None:
|
| 64 |
+
if not torch.cuda.is_available():
|
| 65 |
+
return
|
| 66 |
+
if next(speech_generator.model.parameters()).device.type != "cpu":
|
| 67 |
+
return
|
| 68 |
+
|
| 69 |
+
dtype = DTYPE_MAP["cuda"]
|
| 70 |
+
speech_generator.model.to("cuda", dtype=dtype)
|
| 71 |
+
speech_generator.mimi.to("cuda", dtype=dtype)
|
| 72 |
+
speech_generator.ctx.mimi_prompt.to("cuda", dtype=dtype)
|
| 73 |
+
speech_generator.ctx.spk_enc.to("cuda", dtype=dtype)
|
| 74 |
+
speech_generator.ctx.device = "cuda"
|
| 75 |
+
speech_generator.ctx.dtype = dtype
|
| 76 |
+
speech_generator._autocast_ctx = autocast_ctx(device="cuda", dtype=dtype)
|
| 77 |
+
|
| 78 |
+
if speech_generator._mimi_streaming_started:
|
| 79 |
+
speech_generator._mimi_stream_ctx.__exit__(None, None, None)
|
| 80 |
+
speech_generator._mimi_stream_ctx = None
|
| 81 |
+
speech_generator._mimi_streaming_started = False
|
| 82 |
+
|
| 83 |
+
|
| 84 |
+
def demo_app(
|
| 85 |
+
config: SpeechGeneratorConfig,
|
| 86 |
+
app_config: AppConfig,
|
| 87 |
+
demo_examples,
|
| 88 |
+
synthesize_fn,
|
| 89 |
+
speaking_rate_state: SpeakingRateState,
|
| 90 |
+
generation_control: GenerationControl,
|
| 91 |
+
shared_generation_state: SharedGenerationState,
|
| 92 |
+
):
|
| 93 |
+
with gr.Blocks(
|
| 94 |
+
css=CUSTOM_CSS,
|
| 95 |
+
head=build_low_latency_audio_head(app_config),
|
| 96 |
+
title="VoXtream2",
|
| 97 |
+
) as demo:
|
| 98 |
+
gr.Markdown("# VoXtream2 TTS demo")
|
| 99 |
+
gr.Markdown(
|
| 100 |
+
"⚠️ The initial latency can be high due to deployment on ZeroGPU. "
|
| 101 |
+
"For faster inference, please try local deployment."
|
| 102 |
+
)
|
| 103 |
+
|
| 104 |
+
with gr.Row(equal_height=True, elem_id="cols"):
|
| 105 |
+
with gr.Column(scale=1, elem_id="left-col"):
|
| 106 |
+
prompt_audio = gr.Audio(
|
| 107 |
+
sources=["microphone", "upload"],
|
| 108 |
+
type="filepath",
|
| 109 |
+
label=(
|
| 110 |
+
"Prompt audio (3-10 sec of target voice. "
|
| 111 |
+
f"Max {config.max_prompt_sec} sec)"
|
| 112 |
+
),
|
| 113 |
+
)
|
| 114 |
+
with gr.Accordion("Advanced options", open=False):
|
| 115 |
+
enable_speaking_rate = gr.Checkbox(
|
| 116 |
+
label="Use speaking rate control", value=True
|
| 117 |
+
)
|
| 118 |
+
prompt_enhancement = gr.Checkbox(
|
| 119 |
+
label="Prompt enhancement", value=False
|
| 120 |
+
)
|
| 121 |
+
prompt_enhancement_msg = gr.Markdown(
|
| 122 |
+
"⚠️ First 3-5 runs may have higher latency due to model "
|
| 123 |
+
"loading and warmup.",
|
| 124 |
+
visible=False,
|
| 125 |
+
)
|
| 126 |
+
voice_activity_detection = gr.Checkbox(
|
| 127 |
+
label="Voice activity detection", value=False
|
| 128 |
+
)
|
| 129 |
+
streaming_input = gr.Checkbox(label="Streaming input", value=False)
|
| 130 |
+
|
| 131 |
+
with gr.Column(scale=1, elem_id="right-col"):
|
| 132 |
+
target_text = gr.Textbox(
|
| 133 |
+
lines=4,
|
| 134 |
+
max_length=config.max_phone_tokens,
|
| 135 |
+
label=(
|
| 136 |
+
"Target text (Required, "
|
| 137 |
+
f"max {config.max_phone_tokens} chars)"
|
| 138 |
+
),
|
| 139 |
+
placeholder="What you want the model to say",
|
| 140 |
+
)
|
| 141 |
+
output_audio = gr.Audio(
|
| 142 |
+
label="Synthesized audio",
|
| 143 |
+
interactive=False,
|
| 144 |
+
streaming=False,
|
| 145 |
+
autoplay=False,
|
| 146 |
+
show_download_button=True,
|
| 147 |
+
show_share_button=False,
|
| 148 |
+
visible=False,
|
| 149 |
+
)
|
| 150 |
+
stream_audio = gr.HTML(
|
| 151 |
+
render_audio_stream(app_config), elem_id="audio-stream-container"
|
| 152 |
+
)
|
| 153 |
+
|
| 154 |
+
with gr.Row():
|
| 155 |
+
clear_btn = gr.Button("Clear", elem_id="clear", variant="secondary")
|
| 156 |
+
submit_btn = gr.Button(
|
| 157 |
+
"Submit", elem_id="submit", variant="primary", interactive=False
|
| 158 |
+
)
|
| 159 |
+
pause_btn = gr.Button(
|
| 160 |
+
"Pause", elem_id="pause", variant="secondary", visible=False
|
| 161 |
+
)
|
| 162 |
+
resume_btn = gr.Button(
|
| 163 |
+
"Resume", elem_id="resume", variant="primary", visible=False
|
| 164 |
+
)
|
| 165 |
+
stop_btn = gr.Button("Stop", elem_id="stop", variant="stop", visible=False)
|
| 166 |
+
|
| 167 |
+
validation_msg = gr.Markdown("", visible=False)
|
| 168 |
+
speaking_rate_control = gr.Slider(
|
| 169 |
+
minimum=app_config.speaking_rate_min,
|
| 170 |
+
maximum=app_config.speaking_rate_max,
|
| 171 |
+
step=app_config.speaking_rate_step,
|
| 172 |
+
value=app_config.speaking_rate_default,
|
| 173 |
+
label="Speaking rate (SPS). Change the speed of speech synthesis in real-time. ",
|
| 174 |
+
)
|
| 175 |
+
rate_plot = gr.HTML(empty_rate_plot(app_config), elem_id="rate-plot-container")
|
| 176 |
+
text_progress = gr.HTML(
|
| 177 |
+
render_text_progress(app_config, None), elem_id="text-progress-container"
|
| 178 |
+
)
|
| 179 |
+
generation_session = gr.State("")
|
| 180 |
+
|
| 181 |
+
def validate_inputs(audio, ttext):
|
| 182 |
+
if not audio:
|
| 183 |
+
return gr.update(
|
| 184 |
+
visible=True, value="⚠️ Please provide a prompt audio."
|
| 185 |
+
), gr.update(interactive=False)
|
| 186 |
+
if not ttext or not ttext.strip():
|
| 187 |
+
return gr.update(
|
| 188 |
+
visible=True, value="⚠️ Please provide target text."
|
| 189 |
+
), gr.update(interactive=False)
|
| 190 |
+
return gr.update(visible=False, value=""), gr.update(interactive=True)
|
| 191 |
+
|
| 192 |
+
enable_speaking_rate.change(
|
| 193 |
+
fn=lambda enabled: gr.update(interactive=enabled),
|
| 194 |
+
inputs=enable_speaking_rate,
|
| 195 |
+
outputs=speaking_rate_control,
|
| 196 |
+
)
|
| 197 |
+
prompt_enhancement.change(
|
| 198 |
+
fn=lambda enabled: gr.update(visible=enabled),
|
| 199 |
+
inputs=prompt_enhancement,
|
| 200 |
+
outputs=prompt_enhancement_msg,
|
| 201 |
+
)
|
| 202 |
+
|
| 203 |
+
def update_speaking_rate(value, session_id):
|
| 204 |
+
speaking_rate_state.update(value)
|
| 205 |
+
shared_generation_state.update_speaking_rate(session_id, value)
|
| 206 |
+
|
| 207 |
+
speaking_rate_control.input(
|
| 208 |
+
fn=update_speaking_rate,
|
| 209 |
+
inputs=[speaking_rate_control, generation_session],
|
| 210 |
+
queue=False,
|
| 211 |
+
show_progress="hidden",
|
| 212 |
+
)
|
| 213 |
+
speaking_rate_control.release(
|
| 214 |
+
fn=update_speaking_rate,
|
| 215 |
+
inputs=[speaking_rate_control, generation_session],
|
| 216 |
+
queue=False,
|
| 217 |
+
show_progress="hidden",
|
| 218 |
+
)
|
| 219 |
+
|
| 220 |
+
for inp in [prompt_audio, target_text]:
|
| 221 |
+
inp.change(
|
| 222 |
+
fn=validate_inputs,
|
| 223 |
+
inputs=[prompt_audio, target_text],
|
| 224 |
+
outputs=[validation_msg, submit_btn],
|
| 225 |
+
)
|
| 226 |
+
|
| 227 |
+
def prepare_generation(speaking_rate, enable_rate):
|
| 228 |
+
session_id = shared_generation_state.create(speaking_rate)
|
| 229 |
+
generation_control.start()
|
| 230 |
+
speaking_rate_state.start(speaking_rate)
|
| 231 |
+
return (
|
| 232 |
+
gr.update(value=None, visible=False),
|
| 233 |
+
gr.update(interactive=False),
|
| 234 |
+
empty_rate_plot(app_config, show_target=enable_rate),
|
| 235 |
+
render_text_progress(app_config, None),
|
| 236 |
+
render_audio_stream(app_config, session_id=session_id),
|
| 237 |
+
*generation_button_updates(running=True),
|
| 238 |
+
session_id,
|
| 239 |
+
)
|
| 240 |
+
|
| 241 |
+
submit_btn.click(
|
| 242 |
+
fn=prepare_generation,
|
| 243 |
+
inputs=[speaking_rate_control, enable_speaking_rate],
|
| 244 |
+
outputs=[
|
| 245 |
+
output_audio,
|
| 246 |
+
enable_speaking_rate,
|
| 247 |
+
rate_plot,
|
| 248 |
+
text_progress,
|
| 249 |
+
stream_audio,
|
| 250 |
+
pause_btn,
|
| 251 |
+
resume_btn,
|
| 252 |
+
stop_btn,
|
| 253 |
+
generation_session,
|
| 254 |
+
],
|
| 255 |
+
show_progress="hidden",
|
| 256 |
+
).then(
|
| 257 |
+
fn=synthesize_fn,
|
| 258 |
+
inputs=[
|
| 259 |
+
prompt_audio,
|
| 260 |
+
target_text,
|
| 261 |
+
prompt_enhancement,
|
| 262 |
+
voice_activity_detection,
|
| 263 |
+
streaming_input,
|
| 264 |
+
speaking_rate_control,
|
| 265 |
+
enable_speaking_rate,
|
| 266 |
+
generation_session,
|
| 267 |
+
],
|
| 268 |
+
outputs=[
|
| 269 |
+
output_audio,
|
| 270 |
+
enable_speaking_rate,
|
| 271 |
+
rate_plot,
|
| 272 |
+
text_progress,
|
| 273 |
+
stream_audio,
|
| 274 |
+
pause_btn,
|
| 275 |
+
resume_btn,
|
| 276 |
+
stop_btn,
|
| 277 |
+
generation_session,
|
| 278 |
+
],
|
| 279 |
+
)
|
| 280 |
+
|
| 281 |
+
def pause_generation(session_id):
|
| 282 |
+
generation_control.pause()
|
| 283 |
+
shared_generation_state.pause(session_id)
|
| 284 |
+
return generation_button_updates(running=True, paused=True)
|
| 285 |
+
|
| 286 |
+
def resume_generation(session_id):
|
| 287 |
+
generation_control.resume()
|
| 288 |
+
shared_generation_state.resume(session_id)
|
| 289 |
+
return generation_button_updates(running=True)
|
| 290 |
+
|
| 291 |
+
def stop_generation(session_id):
|
| 292 |
+
generation_control.stop()
|
| 293 |
+
speaking_rate_state.stop()
|
| 294 |
+
shared_generation_state.stop(session_id)
|
| 295 |
+
return generation_button_updates(running=False)
|
| 296 |
+
|
| 297 |
+
pause_btn.click(
|
| 298 |
+
fn=pause_generation,
|
| 299 |
+
inputs=generation_session,
|
| 300 |
+
outputs=[pause_btn, resume_btn, stop_btn],
|
| 301 |
+
js=(
|
| 302 |
+
"() => { if (window.voxtreamLowLatencyAudio) { "
|
| 303 |
+
"window.voxtreamLowLatencyAudio.pause(); } return []; }"
|
| 304 |
+
),
|
| 305 |
+
queue=False,
|
| 306 |
+
)
|
| 307 |
+
resume_btn.click(
|
| 308 |
+
fn=resume_generation,
|
| 309 |
+
inputs=generation_session,
|
| 310 |
+
outputs=[pause_btn, resume_btn, stop_btn],
|
| 311 |
+
js=(
|
| 312 |
+
"() => { if (window.voxtreamLowLatencyAudio) { "
|
| 313 |
+
"window.voxtreamLowLatencyAudio.resume(); } return []; }"
|
| 314 |
+
),
|
| 315 |
+
queue=False,
|
| 316 |
+
)
|
| 317 |
+
stop_btn.click(
|
| 318 |
+
fn=stop_generation,
|
| 319 |
+
inputs=generation_session,
|
| 320 |
+
outputs=[pause_btn, resume_btn, stop_btn],
|
| 321 |
+
js=(
|
| 322 |
+
"() => { if (window.voxtreamLowLatencyAudio) { "
|
| 323 |
+
"window.voxtreamLowLatencyAudio.stop(); } return []; }"
|
| 324 |
+
),
|
| 325 |
+
queue=False,
|
| 326 |
+
)
|
| 327 |
+
|
| 328 |
+
def clear_generation(session_id):
|
| 329 |
+
generation_control.stop()
|
| 330 |
+
speaking_rate_state.stop()
|
| 331 |
+
shared_generation_state.stop(session_id)
|
| 332 |
+
return (
|
| 333 |
+
gr.update(value=None),
|
| 334 |
+
gr.update(value=""),
|
| 335 |
+
gr.update(value=None, visible=False),
|
| 336 |
+
gr.update(visible=False, value=""),
|
| 337 |
+
gr.update(interactive=False),
|
| 338 |
+
gr.update(interactive=True),
|
| 339 |
+
empty_rate_plot(app_config),
|
| 340 |
+
render_text_progress(app_config, None),
|
| 341 |
+
render_audio_stream(app_config, session_id=uuid.uuid4().hex),
|
| 342 |
+
*generation_button_updates(running=False),
|
| 343 |
+
"",
|
| 344 |
+
)
|
| 345 |
+
|
| 346 |
+
clear_btn.click(
|
| 347 |
+
fn=clear_generation,
|
| 348 |
+
inputs=generation_session,
|
| 349 |
+
outputs=[
|
| 350 |
+
prompt_audio,
|
| 351 |
+
target_text,
|
| 352 |
+
output_audio,
|
| 353 |
+
validation_msg,
|
| 354 |
+
submit_btn,
|
| 355 |
+
enable_speaking_rate,
|
| 356 |
+
rate_plot,
|
| 357 |
+
text_progress,
|
| 358 |
+
stream_audio,
|
| 359 |
+
pause_btn,
|
| 360 |
+
resume_btn,
|
| 361 |
+
stop_btn,
|
| 362 |
+
generation_session,
|
| 363 |
+
],
|
| 364 |
+
)
|
| 365 |
+
|
| 366 |
+
gr.Markdown("### Examples")
|
| 367 |
+
ex = gr.Examples(
|
| 368 |
+
examples=demo_examples,
|
| 369 |
+
inputs=[
|
| 370 |
+
prompt_audio,
|
| 371 |
+
target_text,
|
| 372 |
+
prompt_enhancement,
|
| 373 |
+
voice_activity_detection,
|
| 374 |
+
streaming_input,
|
| 375 |
+
speaking_rate_control,
|
| 376 |
+
enable_speaking_rate,
|
| 377 |
+
],
|
| 378 |
+
outputs=[
|
| 379 |
+
output_audio,
|
| 380 |
+
enable_speaking_rate,
|
| 381 |
+
rate_plot,
|
| 382 |
+
text_progress,
|
| 383 |
+
stream_audio,
|
| 384 |
+
pause_btn,
|
| 385 |
+
resume_btn,
|
| 386 |
+
stop_btn,
|
| 387 |
+
generation_session,
|
| 388 |
+
],
|
| 389 |
+
fn=synthesize_fn,
|
| 390 |
+
cache_examples=False,
|
| 391 |
+
)
|
| 392 |
+
|
| 393 |
+
ex.dataset.click(
|
| 394 |
+
fn=lambda: (*clear_outputs(app_config), ""),
|
| 395 |
+
inputs=[],
|
| 396 |
+
outputs=[
|
| 397 |
+
output_audio,
|
| 398 |
+
rate_plot,
|
| 399 |
+
text_progress,
|
| 400 |
+
stream_audio,
|
| 401 |
+
generation_session,
|
| 402 |
+
],
|
| 403 |
+
queue=False,
|
| 404 |
+
).then(
|
| 405 |
+
fn=validate_inputs,
|
| 406 |
+
inputs=[prompt_audio, target_text],
|
| 407 |
+
outputs=[validation_msg, submit_btn],
|
| 408 |
+
queue=False,
|
| 409 |
+
)
|
| 410 |
+
|
| 411 |
+
demo.queue(default_concurrency_limit=1).launch()
|
| 412 |
+
|
| 413 |
+
|
| 414 |
+
def main():
|
| 415 |
+
parser = argparse.ArgumentParser()
|
| 416 |
+
parser.add_argument(
|
| 417 |
+
"-c",
|
| 418 |
+
"--config",
|
| 419 |
+
type=existing_file,
|
| 420 |
+
help="Path to the config file",
|
| 421 |
+
default="configs/generator.json",
|
| 422 |
+
)
|
| 423 |
+
parser.add_argument(
|
| 424 |
+
"--app-config",
|
| 425 |
+
type=existing_file,
|
| 426 |
+
help="Path to the app config file",
|
| 427 |
+
default="configs/app.json",
|
| 428 |
+
)
|
| 429 |
+
parser.add_argument(
|
| 430 |
+
"--spk-rate-config",
|
| 431 |
+
type=existing_file,
|
| 432 |
+
help="Path to the speaking rate config file",
|
| 433 |
+
default="configs/speaking_rate.json",
|
| 434 |
+
)
|
| 435 |
+
parser.add_argument(
|
| 436 |
+
"--examples-config",
|
| 437 |
+
type=existing_file,
|
| 438 |
+
help="Path to the examples config file",
|
| 439 |
+
default="assets/examples.json",
|
| 440 |
+
)
|
| 441 |
+
args = parser.parse_args()
|
| 442 |
+
|
| 443 |
+
with open(args.config) as f:
|
| 444 |
+
config = SpeechGeneratorConfig(**json.load(f))
|
| 445 |
+
config.hf_token = os.environ.get("TOKEN")
|
| 446 |
+
|
| 447 |
+
# Loading speaker encoder.
|
| 448 |
+
torch.hub.load(
|
| 449 |
+
config.spk_enc_repo,
|
| 450 |
+
config.spk_enc_model,
|
| 451 |
+
model_name=config.spk_enc_model_name,
|
| 452 |
+
train_type=config.spk_enc_train_type,
|
| 453 |
+
dataset=config.spk_enc_dataset,
|
| 454 |
+
trust_repo=True,
|
| 455 |
+
verbose=False,
|
| 456 |
+
)
|
| 457 |
+
|
| 458 |
+
with open(args.spk_rate_config) as f:
|
| 459 |
+
spk_rate_config = json.load(f)
|
| 460 |
+
|
| 461 |
+
app_config = load_app_config(args.app_config)
|
| 462 |
+
|
| 463 |
+
with open(args.examples_config) as f:
|
| 464 |
+
examples_config = json.load(f)
|
| 465 |
+
demo_examples = examples_config.get("examples", [])
|
| 466 |
+
|
| 467 |
+
speech_generator = SpeechGenerator(config, spk_rate_config)
|
| 468 |
+
speaking_rate_state = SpeakingRateState(app_config.speaking_rate_default)
|
| 469 |
+
generation_control = GenerationControl()
|
| 470 |
+
shared_generation_state = SharedGenerationState()
|
| 471 |
+
chunk_size = int(config.mimi_sr * app_config.min_chunk_sec)
|
| 472 |
+
|
| 473 |
+
@spaces.GPU
|
| 474 |
+
def synthesize_fn(
|
| 475 |
+
prompt_audio_path,
|
| 476 |
+
target_text,
|
| 477 |
+
prompt_enhancement,
|
| 478 |
+
voice_activity_detection,
|
| 479 |
+
streaming_input,
|
| 480 |
+
speaking_rate_control,
|
| 481 |
+
enable_speaking_rate=True,
|
| 482 |
+
generation_session_id="",
|
| 483 |
+
):
|
| 484 |
+
control_session_id = generation_session_id or shared_generation_state.create(
|
| 485 |
+
speaking_rate_control
|
| 486 |
+
)
|
| 487 |
+
stream_session_id = control_session_id or uuid.uuid4().hex
|
| 488 |
+
stream_seq = 0
|
| 489 |
+
|
| 490 |
+
if not prompt_audio_path or not target_text:
|
| 491 |
+
speaking_rate_state.stop()
|
| 492 |
+
generation_control.finish()
|
| 493 |
+
shared_generation_state.finish(control_session_id)
|
| 494 |
+
yield (
|
| 495 |
+
gr.update(value=None, visible=False),
|
| 496 |
+
gr.update(interactive=True),
|
| 497 |
+
empty_rate_plot(app_config, show_target=enable_speaking_rate),
|
| 498 |
+
render_text_progress(app_config, None),
|
| 499 |
+
render_audio_stream(app_config, session_id=stream_session_id),
|
| 500 |
+
*generation_button_updates(running=False),
|
| 501 |
+
control_session_id,
|
| 502 |
+
)
|
| 503 |
+
return
|
| 504 |
+
|
| 505 |
+
if shared_generation_state.is_stopped(control_session_id):
|
| 506 |
+
speaking_rate_state.stop()
|
| 507 |
+
generation_control.finish()
|
| 508 |
+
shared_generation_state.finish(control_session_id)
|
| 509 |
+
yield (
|
| 510 |
+
gr.update(value=None, visible=False),
|
| 511 |
+
gr.update(interactive=True),
|
| 512 |
+
empty_rate_plot(app_config, show_target=enable_speaking_rate),
|
| 513 |
+
render_text_progress(app_config, None),
|
| 514 |
+
render_audio_stream(
|
| 515 |
+
app_config,
|
| 516 |
+
session_id=stream_session_id,
|
| 517 |
+
active=False,
|
| 518 |
+
final=True,
|
| 519 |
+
),
|
| 520 |
+
*generation_button_updates(running=False),
|
| 521 |
+
control_session_id,
|
| 522 |
+
)
|
| 523 |
+
return
|
| 524 |
+
|
| 525 |
+
ensure_generator_on_cuda(speech_generator)
|
| 526 |
+
speaking_rate_state.ensure_started(speaking_rate_control)
|
| 527 |
+
speaking_rate_gen = (
|
| 528 |
+
shared_generation_state.speaking_rate_values(
|
| 529 |
+
control_session_id, speaking_rate_control
|
| 530 |
+
)
|
| 531 |
+
if enable_speaking_rate
|
| 532 |
+
else None
|
| 533 |
+
)
|
| 534 |
+
text_metadata = build_text_progress_metadata(
|
| 535 |
+
target_text,
|
| 536 |
+
config=config,
|
| 537 |
+
phone_to_token=speech_generator.ctx.phone_to_token,
|
| 538 |
+
phonemizer=speech_generator.ctx.phonemizer,
|
| 539 |
+
max_phone_tokens=config.max_phone_tokens,
|
| 540 |
+
)
|
| 541 |
+
rate_window_sec = (
|
| 542 |
+
config.spk_rate_window_sec
|
| 543 |
+
if config.spk_rate_window_sec and config.spk_rate_window_sec > 0
|
| 544 |
+
else app_config.plot_window_sec
|
| 545 |
+
)
|
| 546 |
+
frame_sec = config.mimi_frame_ms / 1000.0
|
| 547 |
+
text_progress_delay_sec = (
|
| 548 |
+
app_config.audio_stream_start_delay_sec
|
| 549 |
+
+ config.audio_delay_frames * frame_sec
|
| 550 |
+
)
|
| 551 |
+
visualization = VisualizationState(
|
| 552 |
+
text_metadata=text_metadata,
|
| 553 |
+
app_config=app_config,
|
| 554 |
+
rate_window_sec=rate_window_sec,
|
| 555 |
+
frame_sec=frame_sec,
|
| 556 |
+
text_progress_delay_sec=text_progress_delay_sec,
|
| 557 |
+
show_target=enable_speaking_rate,
|
| 558 |
+
)
|
| 559 |
+
|
| 560 |
+
stream = speech_generator.generate_stream(
|
| 561 |
+
prompt_audio_path=Path(prompt_audio_path),
|
| 562 |
+
text=text_generator(target_text) if streaming_input else target_text,
|
| 563 |
+
speaking_rate=speaking_rate_gen,
|
| 564 |
+
enhance_prompt=prompt_enhancement,
|
| 565 |
+
apply_vad=voice_activity_detection,
|
| 566 |
+
return_progress=True,
|
| 567 |
+
min_streaming_rtf=app_config.min_streaming_rtf,
|
| 568 |
+
)
|
| 569 |
+
|
| 570 |
+
buffer = []
|
| 571 |
+
buffer_len = 0
|
| 572 |
+
total_buffer = []
|
| 573 |
+
stopped = False
|
| 574 |
+
|
| 575 |
+
stream_iter = iter(stream)
|
| 576 |
+
while True:
|
| 577 |
+
if not shared_generation_state.wait_if_paused(control_session_id):
|
| 578 |
+
stopped = True
|
| 579 |
+
break
|
| 580 |
+
try:
|
| 581 |
+
frame, _, progress = next(stream_iter)
|
| 582 |
+
except StopIteration:
|
| 583 |
+
break
|
| 584 |
+
if shared_generation_state.is_stopped(control_session_id):
|
| 585 |
+
stopped = True
|
| 586 |
+
break
|
| 587 |
+
|
| 588 |
+
buffer.append(frame)
|
| 589 |
+
total_buffer.append(frame)
|
| 590 |
+
buffer_len += frame.shape[0]
|
| 591 |
+
plot_update, text_update = visualization.update(progress)
|
| 592 |
+
|
| 593 |
+
if buffer_len >= chunk_size:
|
| 594 |
+
if shared_generation_state.is_stopped(control_session_id):
|
| 595 |
+
stopped = True
|
| 596 |
+
break
|
| 597 |
+
audio = np.concatenate(buffer)
|
| 598 |
+
stream_seq += 1
|
| 599 |
+
yield (
|
| 600 |
+
gr.update(),
|
| 601 |
+
gr.update(),
|
| 602 |
+
plot_update,
|
| 603 |
+
text_update,
|
| 604 |
+
render_audio_stream(
|
| 605 |
+
app_config,
|
| 606 |
+
session_id=stream_session_id,
|
| 607 |
+
seq=stream_seq,
|
| 608 |
+
sample_rate=config.mimi_sr,
|
| 609 |
+
audio=float32_to_int16(audio),
|
| 610 |
+
active=True,
|
| 611 |
+
),
|
| 612 |
+
*generation_button_updates(
|
| 613 |
+
running=True,
|
| 614 |
+
paused=shared_generation_state.is_paused(control_session_id),
|
| 615 |
+
),
|
| 616 |
+
control_session_id,
|
| 617 |
+
)
|
| 618 |
+
|
| 619 |
+
buffer = []
|
| 620 |
+
buffer_len = 0
|
| 621 |
+
|
| 622 |
+
stopped = stopped or shared_generation_state.is_stopped(control_session_id)
|
| 623 |
+
if stopped and hasattr(stream, "close"):
|
| 624 |
+
stream.close()
|
| 625 |
+
final_text = visualization.final_text()
|
| 626 |
+
|
| 627 |
+
if buffer_len > 0 and not stopped:
|
| 628 |
+
final = np.concatenate(buffer)
|
| 629 |
+
nfade = min(int(config.mimi_sr * app_config.fade_out_sec), final.shape[0])
|
| 630 |
+
if nfade > 0:
|
| 631 |
+
fade = np.linspace(1.0, 0.0, nfade, dtype=np.float32)
|
| 632 |
+
final[-nfade:] *= fade
|
| 633 |
+
stream_seq += 1
|
| 634 |
+
yield (
|
| 635 |
+
gr.update(),
|
| 636 |
+
gr.update(),
|
| 637 |
+
visualization.latest_plot,
|
| 638 |
+
visualization.latest_text,
|
| 639 |
+
render_audio_stream(
|
| 640 |
+
app_config,
|
| 641 |
+
session_id=stream_session_id,
|
| 642 |
+
seq=stream_seq,
|
| 643 |
+
sample_rate=config.mimi_sr,
|
| 644 |
+
audio=float32_to_int16(final),
|
| 645 |
+
active=True,
|
| 646 |
+
),
|
| 647 |
+
*generation_button_updates(
|
| 648 |
+
running=True,
|
| 649 |
+
paused=shared_generation_state.is_paused(control_session_id),
|
| 650 |
+
),
|
| 651 |
+
control_session_id,
|
| 652 |
+
)
|
| 653 |
+
|
| 654 |
+
if len(total_buffer) > 0:
|
| 655 |
+
full_audio = np.concatenate(total_buffer)
|
| 656 |
+
nfade = min(
|
| 657 |
+
int(config.mimi_sr * app_config.fade_out_sec), full_audio.shape[0]
|
| 658 |
+
)
|
| 659 |
+
if nfade > 0:
|
| 660 |
+
fade = np.linspace(1.0, 0.0, nfade, dtype=np.float32)
|
| 661 |
+
full_audio[-nfade:] *= fade
|
| 662 |
+
|
| 663 |
+
file_path = f"/tmp/voxtream_{uuid.uuid4().hex}.wav"
|
| 664 |
+
sf.write(file_path, float32_to_int16(full_audio), config.mimi_sr)
|
| 665 |
+
|
| 666 |
+
speaking_rate_state.stop()
|
| 667 |
+
generation_control.finish()
|
| 668 |
+
shared_generation_state.finish(control_session_id)
|
| 669 |
+
yield (
|
| 670 |
+
gr.update(value=file_path, visible=True),
|
| 671 |
+
gr.update(interactive=True),
|
| 672 |
+
visualization.latest_plot,
|
| 673 |
+
final_text,
|
| 674 |
+
render_audio_stream(
|
| 675 |
+
app_config,
|
| 676 |
+
session_id=stream_session_id,
|
| 677 |
+
seq=stream_seq + 1,
|
| 678 |
+
sample_rate=config.mimi_sr,
|
| 679 |
+
active=False,
|
| 680 |
+
final=True,
|
| 681 |
+
),
|
| 682 |
+
*generation_button_updates(running=False),
|
| 683 |
+
control_session_id,
|
| 684 |
+
)
|
| 685 |
+
else:
|
| 686 |
+
speaking_rate_state.stop()
|
| 687 |
+
generation_control.finish()
|
| 688 |
+
shared_generation_state.finish(control_session_id)
|
| 689 |
+
yield (
|
| 690 |
+
gr.update(value=None, visible=False),
|
| 691 |
+
gr.update(interactive=True),
|
| 692 |
+
visualization.latest_plot,
|
| 693 |
+
final_text,
|
| 694 |
+
render_audio_stream(
|
| 695 |
+
app_config,
|
| 696 |
+
session_id=stream_session_id,
|
| 697 |
+
seq=stream_seq + 1,
|
| 698 |
+
sample_rate=config.mimi_sr,
|
| 699 |
+
active=False,
|
| 700 |
+
final=True,
|
| 701 |
+
),
|
| 702 |
+
*generation_button_updates(running=False),
|
| 703 |
+
control_session_id,
|
| 704 |
+
)
|
| 705 |
+
|
| 706 |
+
demo_app(
|
| 707 |
+
config,
|
| 708 |
+
app_config,
|
| 709 |
+
demo_examples,
|
| 710 |
+
synthesize_fn,
|
| 711 |
+
speaking_rate_state,
|
| 712 |
+
generation_control,
|
| 713 |
+
shared_generation_state,
|
| 714 |
+
)
|
| 715 |
+
|
| 716 |
+
|
| 717 |
+
if __name__ == "__main__":
|
| 718 |
+
main()
|
assets/audio/arabic_female.wav
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:0f9a779ccd118aa0f313571172e800aa66c3c60e442f670c9132bb9ca9145d22
|
| 3 |
+
size 556844
|
assets/audio/chinese_female.wav
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:ad17aad06257940ec078474f1a246ec3dcb996444948e28832759a5ffb8fb339
|
| 3 |
+
size 912044
|
assets/audio/english_female.wav
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:70fae2c5d540e3ce715464731af88e3e8dbfc9c2f3da34435391f27a2cee8cca
|
| 3 |
+
size 973484
|
assets/audio/english_male.wav
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:5bc63165bf521c62a823075edcee28b207ce8b77607e160922381ed1f6d5fc4c
|
| 3 |
+
size 992588
|
assets/audio/french_female.wav
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:f4d85af9a029801bf4cacf89006245adae5c02105f057da02b3635f119f65665
|
| 3 |
+
size 418136
|
assets/audio/german_male.wav
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:4a8fb0671328979463473b3838e6d51e152397094cdd547e8743df3195d54f96
|
| 3 |
+
size 432044
|
assets/audio/hindi_male.wav
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:ae95131d80cced28c3b6e22717e41474561e7c83a091fb5b298dc668e7d66e15
|
| 3 |
+
size 787244
|
assets/audio/japanese_male.wav
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:5ac181ed61dd184281353ad8a6359929fa7f014fd51e2de4f6d5bcffac6ce18c
|
| 3 |
+
size 422444
|
assets/audio/portuguese_male.wav
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:9d8b09458a9d5edd3717813507d3f31cf8ba6d880982aaedec6016cc466a4f4b
|
| 3 |
+
size 345642
|
assets/audio/russian_female.wav
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:e6aae00ae7c838c3ad9ffe410851b24427ba54d4d4c9509524d885dfc186989b
|
| 3 |
+
size 710444
|
assets/audio/spanish_male.wav
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:611303d7bc58797b0c9a001d0b59506a2476fe821f311a18ee6e4439879fc783
|
| 3 |
+
size 494444
|
assets/audio/swedish_female.wav
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:149a2781409654ffa14630dc2b94f1e07adac29a59343437fef80c7277edb3f0
|
| 3 |
+
size 480044
|
assets/examples.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"examples": [
|
| 3 |
+
[
|
| 4 |
+
"assets/audio/english_male.wav",
|
| 5 |
+
"Full stream text-to-speech (TTS) for interactive systems must start speaking with minimal delay while remaining controllable as text arrives incrementally. The voice should keep a natural rhythm as each new phrase becomes available. This helps assistants respond quickly while still sounding calm and clear. Careful streaming design makes spoken interaction feel more direct and responsive. It also reduces long silent gaps during complex replies. The listener hears progress while the system continues planning the next phrase."
|
| 6 |
+
],
|
| 7 |
+
[
|
| 8 |
+
"assets/audio/english_female.wav",
|
| 9 |
+
"We present VoXtream2, a zero shot full stream text-to-speech (TTS) model with dynamic speaking rate control that can be updated during an utterance. The system can shift its pace while speech is already being produced. This allows a speaker to slow down for difficult content or move faster through simple phrases. Flexible control supports more expressive and useful speech generation. It also lets applications adapt delivery to user attention and context. The same voice can sound measured, concise, or relaxed as needed."
|
| 10 |
+
],
|
| 11 |
+
[
|
| 12 |
+
"assets/audio/chinese_female.wav",
|
| 13 |
+
"VoXtream2 combines distribution matching over duration states with classifier free guidance across conditioning signals to improve controllability and synthesis quality. These methods help the model follow timing instructions more reliably. They also preserve natural voice quality when several controls are active. The result is speech that can be guided without sounding rigid or unstable. Better alignment between text, timing, and voice style makes each output easier to shape."
|
| 14 |
+
],
|
| 15 |
+
[
|
| 16 |
+
"assets/audio/hindi_male.wav",
|
| 17 |
+
"Prompt text masking enables textless audio prompting, removing the need for prompt transcription. A user can provide a short voice reference without writing down what was said. This makes voice adaptation easier when transcripts are missing or expensive to prepare. The model can still learn useful speaker cues from the audio prompt. It can focus on tone, accent, and speaking style while ignoring unavailable words. This lowers setup effort for demos, research samples, and personal voice interfaces."
|
| 18 |
+
],
|
| 19 |
+
[
|
| 20 |
+
"assets/audio/spanish_male.wav",
|
| 21 |
+
"Across standard zero shot benchmarks and a dedicated speaking rate test set, VoXtream2 achieves competitive objective and subjective results against public baselines. The evaluation covers both measured accuracy and human listening preference. Strong results suggest that streaming control does not require a large loss in quality. This makes the approach practical for real conversational systems. Consistent performance across tests gives developers more confidence in deployment."
|
| 22 |
+
],
|
| 23 |
+
[
|
| 24 |
+
"assets/audio/arabic_female.wav",
|
| 25 |
+
"In full stream mode, it runs 4 times faster than real time with 74 ms first packet latency on a consumer graphics processor. Low latency helps the system begin speaking before the full response is complete. Faster synthesis also leaves more room for other application work. These properties are important for smooth real time dialogue. Efficient generation can support busy products without requiring unusual hardware. It also makes testing easier because responses arrive quickly during iteration."
|
| 26 |
+
],
|
| 27 |
+
[
|
| 28 |
+
"assets/audio/french_female.wav",
|
| 29 |
+
"It has long been argued that conversational agents must be able to generate speech incrementally. Human conversation often depends on quick turns and partial understanding. An agent that waits too long can make the exchange feel broken or unnatural. Incremental generation supports more fluid spoken interaction. It lets a system begin with a confident phrase while later content is still forming. This behavior can make spoken assistants feel attentive, present, and easier to interrupt."
|
| 30 |
+
],
|
| 31 |
+
[
|
| 32 |
+
"assets/audio/japanese_male.wav",
|
| 33 |
+
"Japanese audio recording can be used to prepare the examples list for a future voice sample. The entry keeps the same format as the other language examples. English text makes the placeholder clear for contributors who review the file. The audio path can be updated once the final sample is created. The placeholder supports early testing of selection flows and ordering. It also makes room for future validation of playback, captions, and voice metadata. Reviewers can confirm the option appears correctly."
|
| 34 |
+
],
|
| 35 |
+
[
|
| 36 |
+
"assets/audio/russian_female.wav",
|
| 37 |
+
"Recent progress in neural text-to-speech (TTS) synthesis has led to highly natural and intelligible speech generation. Modern models can produce voices with clear pronunciation and expressive prosody. These gains make synthetic speech useful in more demanding interactive settings. The next challenge is to keep that quality while adding fine grained control. Users expect a generated voice to remain stable when speed, style, or prompting changes. Robust models must balance realism, latency, and steering."
|
| 38 |
+
],
|
| 39 |
+
[
|
| 40 |
+
"assets/audio/swedish_female.wav",
|
| 41 |
+
"However, most contemporary systems implicitly assume that speaking rate is static across an utterance, typically allowing only coarse, global control over speed. Real speakers often vary their pace within a single response. They may pause before important words or speed through familiar details. A useful speech system should support this kind of local timing control. This makes explanations clearer and keeps long replies from feeling flat. Local control also helps match emphasis to meaning."
|
| 42 |
+
],
|
| 43 |
+
[
|
| 44 |
+
"assets/audio/portuguese_male.wav",
|
| 45 |
+
"Portuguese audio prompt can be used to test how the interface presents another language option. The example text stays in English while the entry reserves space for a future recording. This keeps the data structure ready for multilingual expansion. A final voice sample can later replace the placeholder path. The placeholder also helps verify menus, labels, and playback behavior before the asset exists. It gives reviewers a clear signal that Portuguese support is planned but not complete."
|
| 46 |
+
],
|
| 47 |
+
[
|
| 48 |
+
"assets/audio/german_male.wav",
|
| 49 |
+
"German acoustic prompt can be used to check language selection and example ordering. The English text describes the expected role of the entry without adding translated content. This makes the placeholder easy to identify during development. A complete recording can be added when the voice asset is available. The entry can also reveal layout issues in lists that include many languages. It keeps the example set balanced while the final German sample is prepared. Reviewers can test the flow early."
|
| 50 |
+
]
|
| 51 |
+
]
|
| 52 |
+
}
|
configs/app.json
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"min_chunk_sec": 0.01,
|
| 3 |
+
"fade_out_sec": 0.1,
|
| 4 |
+
"plot_window_sec": 10.0,
|
| 5 |
+
"visual_update_sec": 0.25,
|
| 6 |
+
"future_phone_limit": 25,
|
| 7 |
+
"plot_width": 1000,
|
| 8 |
+
"plot_height": 224,
|
| 9 |
+
"plot_left": 74,
|
| 10 |
+
"plot_right": 22,
|
| 11 |
+
"plot_top": 50,
|
| 12 |
+
"plot_bottom": 42,
|
| 13 |
+
"plot_y_max": 7,
|
| 14 |
+
"plot_y_tick": 1,
|
| 15 |
+
"plot_x_tick_sec": 1,
|
| 16 |
+
"audio_stream_start_delay_sec": 0.12,
|
| 17 |
+
"audio_stream_sample_rate": 24000,
|
| 18 |
+
"speaking_rate_min": 1.0,
|
| 19 |
+
"speaking_rate_max": 7.0,
|
| 20 |
+
"speaking_rate_step": 0.1,
|
| 21 |
+
"speaking_rate_default": 4.0,
|
| 22 |
+
"min_streaming_rtf": 0.95
|
| 23 |
+
}
|
configs/generator.json
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"sil_token": 120,
|
| 3 |
+
"bos_token": 123,
|
| 4 |
+
"eos_token": 124,
|
| 5 |
+
"unk_token": 122,
|
| 6 |
+
"eop_token": 122,
|
| 7 |
+
"num_codebooks": 16,
|
| 8 |
+
"num_phones_per_frame": 2,
|
| 9 |
+
"audio_delay_frames": 1,
|
| 10 |
+
"temperature": 0.9,
|
| 11 |
+
"topk": 5,
|
| 12 |
+
"top_p": 0.9,
|
| 13 |
+
"max_audio_length_ms": 60000,
|
| 14 |
+
"model_repo": "voxtream2/model",
|
| 15 |
+
"model_name": "model.safetensors",
|
| 16 |
+
"model_config_name": "config.json",
|
| 17 |
+
"mimi_sr": 24000,
|
| 18 |
+
"mimi_vocab_size": 2048,
|
| 19 |
+
"mimi_frame_ms": 80,
|
| 20 |
+
"mimi_repo": "kyutai/moshiko-pytorch-bf16",
|
| 21 |
+
"mimi_name": "tokenizer-e351c8d8-checkpoint125.safetensors",
|
| 22 |
+
"spk_enc_sr": 16000,
|
| 23 |
+
"spk_enc_repo": "IDRnD/ReDimNet",
|
| 24 |
+
"spk_enc_model": "ReDimNet",
|
| 25 |
+
"spk_enc_model_name": "M",
|
| 26 |
+
"spk_enc_train_type": "ft_mix",
|
| 27 |
+
"spk_enc_dataset": "vb2+vox2+cnc",
|
| 28 |
+
"phoneme_dict_name": "phoneme_to_token.json",
|
| 29 |
+
"max_prompt_sec": 20,
|
| 30 |
+
"min_prompt_sec": 1,
|
| 31 |
+
"max_phone_tokens": 2000,
|
| 32 |
+
"cache_prompt": false,
|
| 33 |
+
"cfg_gamma": 1.5,
|
| 34 |
+
"cfg_ac_gamma": 3.0,
|
| 35 |
+
"text_context": " context",
|
| 36 |
+
"text_context_length": 18,
|
| 37 |
+
"spk_proj_weight": 1.5,
|
| 38 |
+
"audio_pad_token": 2049,
|
| 39 |
+
"enhance_prompt": false,
|
| 40 |
+
"sidon_se_reload_model": true,
|
| 41 |
+
"reset_streaming_state": true,
|
| 42 |
+
"hf_token": null,
|
| 43 |
+
"apply_vad": false,
|
| 44 |
+
"min_speech_seg_sec": 0.3,
|
| 45 |
+
"min_look_ahead_phones": 3,
|
| 46 |
+
"phonemizer": "espeak",
|
| 47 |
+
"spk_rate_window_sec": 3.0,
|
| 48 |
+
"frame_repeat_counter": 25,
|
| 49 |
+
"punct_map": {
|
| 50 |
+
".": 117,
|
| 51 |
+
",": 118,
|
| 52 |
+
"?": 119,
|
| 53 |
+
"!": 121
|
| 54 |
+
},
|
| 55 |
+
"phoneme_index_map":{
|
| 56 |
+
"0": [0, 1], "1": [0, 2], "2": [1, 1], "3": [1, 2], "4": [2, 1], "5": [2, 2]
|
| 57 |
+
}
|
| 58 |
+
}
|
configs/speaking_rate.json
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"1": {
|
| 3 |
+
"duration_state": [
|
| 4 |
+
52, 15, 1, 2, 1, 3
|
| 5 |
+
],
|
| 6 |
+
"weight": 3.0,
|
| 7 |
+
"cfg_gamma": 1.25
|
| 8 |
+
},
|
| 9 |
+
"2": {
|
| 10 |
+
"duration_state": [
|
| 11 |
+
44, 24, 2, 2, 1, 1
|
| 12 |
+
],
|
| 13 |
+
"weight": 3.0,
|
| 14 |
+
"cfg_gamma": 1.25
|
| 15 |
+
},
|
| 16 |
+
"3": {
|
| 17 |
+
"duration_state": [
|
| 18 |
+
24, 23, 2, 3, 1, 1
|
| 19 |
+
],
|
| 20 |
+
"weight": 5.0,
|
| 21 |
+
"cfg_gamma": 1.5
|
| 22 |
+
},
|
| 23 |
+
"4": {
|
| 24 |
+
"duration_state": [
|
| 25 |
+
40, 59, 5, 12, 1, 2
|
| 26 |
+
],
|
| 27 |
+
"weight": 5.0,
|
| 28 |
+
"cfg_gamma": 1.5
|
| 29 |
+
},
|
| 30 |
+
"5": {
|
| 31 |
+
"duration_state": [
|
| 32 |
+
21, 43, 4, 13, 1, 2
|
| 33 |
+
],
|
| 34 |
+
"weight": 7.0,
|
| 35 |
+
"cfg_gamma": 2.0
|
| 36 |
+
},
|
| 37 |
+
"6": {
|
| 38 |
+
"duration_state": [
|
| 39 |
+
27, 69, 6, 30, 2, 7
|
| 40 |
+
],
|
| 41 |
+
"weight": 7.0,
|
| 42 |
+
"cfg_gamma": 2.0
|
| 43 |
+
},
|
| 44 |
+
"7": {
|
| 45 |
+
"duration_state": [
|
| 46 |
+
6, 18, 2, 9, 1, 3
|
| 47 |
+
],
|
| 48 |
+
"weight": 10.0,
|
| 49 |
+
"cfg_gamma": 2.5
|
| 50 |
+
}
|
| 51 |
+
}
|
packages.txt
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
espeak-ng
|
requirements.txt
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
voxtream==0.2.4
|