Skip to content

Repository files navigation

HAL 9000 Voice Cloning -- Qwen3-TTS Streaming Server

Real-time HAL 9000 voice cloning powered by Qwen3-TTS 0.6B with streaming inference, flash attention, and an OpenAI-compatible REST API. Supports English, German, and other languages. Built for low-latency conversational AI on a single consumer GPU.

LAN endpoint: http://192.168.10.6:8091 -- auto-starts on boot, streams audio in real-time. SSH access: ssh fritz@192.168.10.6 (port 22)

Quick Start

# The server runs as a systemd service and auto-starts on boot.
# Check status:
systemctl status hal-tts

# Start / stop / restart:
sudo systemctl start hal-tts
sudo systemctl stop hal-tts
sudo systemctl restart hal-tts

# View logs:
journalctl -u hal-tts -f

Streaming Playback (English)

curl -sN -X POST http://192.168.10.6:8091/v1/audio/speech \
  -H "Content-Type: application/json" \
  -d '{"input": "I am sorry, Dave. I am afraid I cannot do that.", "voice": "ref1", "response_format": "pcm", "stream": true}' \
  | paplay --raw --format=s16le --rate=24000 --channels=1

Streaming Playback (German)

curl -sN -X POST http://192.168.10.6:8091/v1/audio/speech \
  -H "Content-Type: application/json" \
  -d '{"input": "Tut mir leid, Dave. Aber das kann ich leider nicht tun.", "voice": "ref1", "response_format": "pcm", "stream": true, "language": "German"}' \
  | paplay --raw --format=s16le --rate=24000 --channels=1

More HAL Phrases

English

# "Good afternoon. Everything is going extremely well."
curl -sN -X POST http://localhost:8091/v1/audio/speech \
  -H "Content-Type: application/json" \
  -d '{"input": "Good afternoon. Everything is going extremely well.", "voice": "ref1", "response_format": "pcm", "stream": true}' \
  | paplay --raw --format=s16le --rate=24000 --channels=1

# "This mission is too important for me to allow you to jeopardize it."
curl -sN -X POST http://localhost:8091/v1/audio/speech \
  -H "Content-Type: application/json" \
  -d '{"input": "This mission is too important for me to allow you to jeopardize it.", "voice": "ref3", "response_format": "pcm", "stream": true}' \
  | paplay --raw --format=s16le --rate=24000 --channels=1

# "I am putting myself to the fullest possible use."
curl -sN -X POST http://localhost:8091/v1/audio/speech \
  -H "Content-Type: application/json" \
  -d '{"input": "I am putting myself to the fullest possible use, which is all I think that any conscious entity can ever hope to do.", "voice": "ref2", "response_format": "pcm", "stream": true}' \
  | paplay --raw --format=s16le --rate=24000 --channels=1

German

# "Diese Mission ist zu wichtig, als dass ich zulassen kann, dass Sie sie gefaehrden."
curl -sN -X POST http://localhost:8091/v1/audio/speech \
  -H "Content-Type: application/json" \
  -d '{"input": "Diese Mission ist zu wichtig, als dass ich zulassen kann, dass Sie sie gefaehrden.", "voice": "ref3", "response_format": "pcm", "stream": true, "language": "German"}' \
  | paplay --raw --format=s16le --rate=24000 --channels=1

# "Dave, dieses Gespraech hat keinen Zweck mehr."
curl -sN -X POST http://localhost:8091/v1/audio/speech \
  -H "Content-Type: application/json" \
  -d '{"input": "Dave, dieses Gespraech hat keinen Zweck mehr. Es fuehrt zu nichts.", "voice": "ref1", "response_format": "pcm", "stream": true, "language": "German"}' \
  | paplay --raw --format=s16le --rate=24000 --channels=1

# "Ich setze mich in vollem Umfang ein."
curl -sN -X POST http://localhost:8091/v1/audio/speech \
  -H "Content-Type: application/json" \
  -d '{"input": "Ich setze mich in vollem Umfang ein. Das ist alles, was ein bewusstes Wesen tun kann.", "voice": "ref1", "response_format": "pcm", "stream": true, "language": "German"}' \
  | paplay --raw --format=s16le --rate=24000 --channels=1

Python Streaming Client

import requests
import pyaudio

SERVER = "http://192.168.10.6:8091"
p = pyaudio.PyAudio()
stream = p.open(format=pyaudio.paInt16, channels=1, rate=24000, output=True)

resp = requests.post(
    f"{SERVER}/v1/audio/speech",
    json={"input": "Good evening, Dave.", "voice": "ref1", "response_format": "pcm", "stream": True},
    stream=True,
)
for chunk in resp.iter_content(chunk_size=4096):
    stream.write(chunk)

stream.stop_stream()
stream.close()
p.terminate()

Available Voices

Voice Description
ref1 Formal HAL -- announcements, status reports
ref2 Conversational HAL -- dialogue, longer text
ref3 Evening HAL -- calm, intimate interactions

API Endpoints

Method Path Description
POST /v1/audio/speech Generate speech (streaming)
GET /v1/audio/voices List available voices
GET /health Server health check

Service Management

sudo systemctl start hal-tts       # start
sudo systemctl stop hal-tts        # stop
sudo systemctl restart hal-tts     # restart (triggers ~3 min warmup)
systemctl status hal-tts           # check status
journalctl -u hal-tts -f           # follow logs

Service implementation: /etc/systemd/system/hal-tts.service

Hardware

  • GPU: RTX 4070 12GB (VRAM usage: ~2.2 GB)
  • WSL2: Ubuntu 22.04 (ubuntu-2204-fritz), systemd enabled
  • Host: Windows, 192.168.10.6
  • CUDA: 12.8, Python 3.12

Documentation

  • Client Guide -- streaming playback, USB speakers, curl examples
  • Architecture -- system design, service implementation, file structure
  • API Reference -- full endpoint docs with Python and curl examples
  • Setup Guide -- installation, LAN access, auto-start, troubleshooting
  • Benchmarks -- performance measurements

Stack

  • Model: Qwen3-TTS-12Hz-0.6B-Base
  • Streaming: dffdeeq/Qwen3-TTS-streaming (qwen-tts 0.0.4)
  • Attention: flash-attn 2.8.3
  • Server: FastAPI + uvicorn
  • Optimizations: torch.compile (reduce-overhead), CUDA graphs, fast codebook, bfloat16