config/voicedesign_voices.json is generated on every container start from the
voice library's designed voices, so it carries the user's own character design
prompts and made the working tree permanently dirty. It is now gitignored.
The 8 bundled vd_* presets were only ever stored in that file, so untracking it
alone would leave a fresh clone with no built-in voices at all. They now live in
config/voicedesign_voices.presets.json, which is tracked:
- generate_voices.py seeds the registry from the presets file when no vd_*
entries survive in the generated one (i.e. on a fresh checkout).
- run_voicedesign_server.py starts from the presets file when the registry does
not exist yet, instead of crashing on the missing path. Hot-reload picks up
the real registry as soon as the voice-clone container writes it.
Also documents the v6.8 engine fixes from 9836261 in the README changelog, which
that commit did not touch.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Two engine-level bugs made custom voices unusable in different ways.
Stale speaker embeddings (voice clone):
A .pt embedding is a cache baked from a specific pairing of reference audio
and reference transcript. Re-recording or re-designing a voice replaces those
sources but leaves the old .pt in place, and generate_voices.py pointed at it
unconditionally — so the server kept cloning from an embedding whose audio
tokens no longer matched the transcript stored alongside them. Confirmed on
73 of 155 voices: generation ignored the requested text entirely, emitting
short unrelated filler ("Thank you.") or fragments of the previous reference
transcript. Voices that had never been re-recorded were unaffected, which is
why this looked specific to designed voices.
Now an embedding older than its reference audio/transcript is treated as
invalid and removed so the server recomputes it. Verified: 73 embeddings
regenerated, and voices that previously returned unrelated text now transcribe
back to exactly the requested input.
Unregistered Voice Design voices:
For the VoiceDesign model a voice's identity IS its instruct prompt, but
designed voices were never written into voicedesign_voices.json, so the server
only knew its 8 bundled presets. resolve_voice() silently substituted the first
one, answering requests for a German male character with 'vd_british_male' —
the source of the apparent gender flips between takes.
- generate_voices.py now mirrors designed voices into the VoiceDesign registry
(it runs in the clone container, which is the one with the voice library
mounted; /config is shared with the VoiceDesign container).
- The VoiceDesign server hot-reloads its registry, matching what the clone
server already did, so voices designed while it is running resolve without
a restart.
- An unknown voice no longer becomes a different one: if the request carries
its own instruct that is used, otherwise it is a 404 rather than a silent
substitution.
- A request instruct is now combined with the voice's registered instruct
instead of replacing it. Previously any line carrying an emotion discarded
the character's identity and re-rolled a voice from a few words of
direction, which made a character drift between lines.
- Per-voice temperature/top_p/top_k are honored. Deliberately no seed:
generate_voice_design() takes no seed parameter, so designed voices cannot
be pinned that way — consistency comes from low temperature/top_p.
Verified end to end: three consecutive takes of the same designed voice now
hold 86-91 Hz median F0 (was flipping register between takes), and emotional
lines stay within 84-86 Hz instead of losing the character entirely.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
POST /voice-seed — writes or clears the seed for a named voice in
voices.json directly from the server, enabling the voice creator GUI
to save the chosen seed with one click.
Also updates find_best_seed.py to support --all-voices, per-voice
subdirectories, and the mixed DE/EN test sentence.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Generates WAV samples with different seeds for a given voice so the
user can listen and pick the most natural-sounding one. Uses the
server's hot-reload to switch seeds without restarting.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Each API request now seeds PyTorch's CPU and CUDA RNGs before generation.
The seed is auto-derived from the voice name (stable MD5 hash) if not set
in voices.json, so all existing voices get consistent voice character with
zero config changes. Override per-voice with "seed": <int> in voices.json.
Previously the sampling RNG was unseeded, causing pitch/modulation drift
across requests even when speaker embeddings (.pt files) were identical.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add :latest-streaming and :v6-streaming as explicit tags for the streaming
service — same image as :latest/:v6 but semantically distinct so compose
files and version pins are unambiguous. Suggested by forum user.
Update docker/docker-compose.yml streaming service to :latest-streaming.
Update README image table and tag summary.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The :streaming tag was the pre-v5 image with non_streaming_mode=False,
causing the same voice drift bug as voiceclone had before v5. The streaming
service runs identical code (run_server.py -> openai_server.py) so it uses
:latest which has the non_streaming_mode=True fix. The QWEN_TTS_* env vars
were unused since the command overrides them; removed for clarity.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
generate_voices.py previously overwrote voices.json completely on every
start, discarding any manually added temperature/top_k/top_p fields.
Now loads existing voices.json first and merges user-added fields into
freshly generated entries, so per-voice sampling params survive restarts.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Re-add --max-seq-len 2048 to voiceclone command now that v6 image supports it.
Update README changelog to accurately describe all v6 changes.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The upstream openai_server.py removed the instruct parameter from the
generate_voice_clone call, breaking the old patch context. Regenerated
from a fresh upstream clone with all four changes:
- non_streaming_mode=True in _stream_chunks and create_speech
- per-voice temperature/top_k/top_p in both generation paths
- --max-seq-len argument added to _parse_args
- max_seq_len=args.max_seq_len passed to FasterQwen3TTS.from_pretrained
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The upstream openai_server.py gained --max-seq-len after v5 was built.
Add it to the patch so every build gets explicit sequence length control.
Also add max_seq_len=args.max_seq_len to FasterQwen3TTS.from_pretrained()
so the argument is actually used.
Remove || true from git apply so patch failures fail the build instead
of silently producing a broken image.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
--max-seq-len is not in the current :latest image's openai_server.py (upstream
added it after v5 was built). Remove it from voiceclone command so the
container starts; upstream default (4096) applies. Will be re-added explicitly
once v6 image is built with current upstream.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The VoiceClone server was using non_streaming_mode=False, a mode designed
for streaming LLM->TTS pipelines. In that mode only one text token enters
the model's KV cache during prefill; the rest feed via trailing_text_hiddens
at one step per codec frame. For a 54-word paragraph this provides only ~4s
of text guidance for ~18s of speech — 77% generated with no text conditioning.
Without text context the model free-runs and drifts, sometimes changing gender.
Fix: switch to non_streaming_mode=True (already the default for VoiceDesign
and CustomVoice) so the full text is in the prefill throughout generation.
Also lower default temperature 0.9->0.8 and add top_p=0.9 to reduce
accumulated sampling noise over long runs. Temperature, top_k, and top_p
are now configurable per voice in voices.json.
- patches/openai_server.patch: updated for new upstream HEAD; both streaming
(WAV/PCM) and non-streaming (MP3) paths now use non_streaming_mode=True
- config/run_server.py: align warmup call to non_streaming_mode=True
- README.md: bump image tags v4->v5, add changelog section
- Version: v5
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add run_customvoice_server.py: OpenAI-compatible TTS server for Qwen3-TTS
CustomVoice models with speaker-based voice selection and per-request
language/instruct/max_new_tokens overrides
- Add customvoice_voices.json: voice config for 9 built-in speakers across
English, Chinese, Japanese, and Korean
- Add benchmark_api.py: API benchmarking tool reporting TTFA, total time,
RTF, and speed multiplier across short/medium/long sentences
- Refactor run_voicedesign_server.py: extract _request_generation_params()
helper, add per-request language/instruct/max_new_tokens override support,
and wire DEFAULT_MAX_NEW_TOKENS to --max-seq-len arg
- Add faster-qwen3-tts.code-workspace for VS Code project config
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- generate_voices.py: scan /config/speakers and /voices recursively,
support .ogg and .m4a (M4A auto-converted via ffmpeg), sanitise voice IDs
- auto_transcribe.py: scan both host paths recursively, support all formats,
use parakeet-asr on port 8010
- docker-compose.yml: mount /home/sparky/Projekte/TTS_Voices/speakers as
/voices, add faster-qwen3-tts-voicedesign service on port 8021
- run_voicedesign_server.py: OpenAI-compatible server for VoiceDesign model
- voicedesign_voices.json: 8 British/German VoiceDesign voices
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- run_server.py: warm up CUDA graphs at server startup via lifespan
event so the first real request does not pay the 7-8s graph-
compilation penalty; uses modern lifespan API instead of deprecated
on_event
- generate_voices.py: set chunk_size=4 per voice so streaming clients
receive first audio after ~333ms instead of ~1s
- docker-compose.yml: add --max-seq-len 2048 (halves static KV cache,
reduces VRAM and graph-capture time)
- config/benchmark_api.py: new script to measure TTFA, RTF and speed
against the live API endpoint
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Run Qwen3-TTS with CUDA graph acceleration on NVIDIA DGX Spark
(ARM64 / SM 121 / CUDA 13) as an OpenAI-compatible TTS API server.
- Dockerfile targeting nvidia/cuda:13.0.2-base-ubuntu24.04 with ARM64 cu130 PyTorch wheels
- Patch for max-seq-len support to handle long reference audio without crashes
- OpenWebUI + SillyTavern compatible API endpoints (/v1/models, /v1/audio/voices, /speakers)
- Voice management: auto-generate voices.json from speaker reference audio files
- Auto-transcription helper script for generating reference text from audio
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>