Neural Text-to-Speech Pipelines

AIAudioBackendAPI
Share on LinkedIn

An audiobook app buffers 8 seconds before playback starts because it waits for the full chapter MP3. A navigation app cuts off street names mid-syllable when the next instruction arrives. Neural TTS—waveform models like VITS, Tortoise, and commercial APIs from ElevenLabs and Polly—produces human-like speech, but pipeline design determines whether users notice the technology at all.

Provider landscape

Provider Latency (first byte) Voice cloning Best for
ElevenLabs 200–400 ms Yes Creative apps, cloning
OpenAI TTS 300–500 ms No General purpose, simple API
Amazon Polly Neural 100–300 ms No AWS-native, IVR scale
Google Cloud TTS 150–350 ms Custom voices Enterprise, 40+ languages
Coqui XTTS (self-host) 500 ms–2 s Yes On-prem, cost control

Abstract behind an internal SynthesizeRequest → AudioStream interface.

Basic synthesis

OpenAI:

from openai import OpenAI
from pathlib import Path

client = OpenAI()
response = client.audio.speech.create(
    model="tts-1-hd",
    voice="nova",
    input="Your order shipped this morning and arrives Thursday.",
    response_format="opus",
)
response.stream_to_file(Path("notification.opus"))

ElevenLabs streaming:

import requests

response = requests.post(
    f"https://api.elevenlabs.io/v1/text-to-speech/{voice_id}/stream",
    headers={"xi-api-key": API_KEY},
    json={
        "text": text,
        "model_id": "eleven_turbo_v2_5",
        "voice_settings": {"stability": 0.5, "similarity_boost": 0.75},
    },
    stream=True,
)
for chunk in response.iter_content(chunk_size=1024):
    yield chunk

eleven_turbo_v2_5 trades slight quality for 2x speed—use for interactive; eleven_multilingual_v2 for narration.

Text preprocessing

Raw LLM output makes terrible speech input:

import re

def prepare_for_tts(text: str) -> str:
    text = re.sub(r"https?://\S+", "link", text)
    text = text.replace("API", "A P I")
    text = text.replace("$", "dollars ")
    text = re.sub(r"(\d+)%", r"\1 percent", text)
    text = re.sub(r"\s+", " ", text).strip()
    return text

Split long text at sentence boundaries:

import re

def sentence_chunks(text: str, max_chars: int = 500) -> list[str]:
    sentences = re.split(r"(?<=[.!?])\s+", text)
    chunks, current = [], ""
    for s in sentences:
        if len(current) + len(s) > max_chars and current:
            chunks.append(current.strip())
            current = s
        else:
            current += " " + s
    if current.strip():
        chunks.append(current.strip())
    return chunks

Synthesize each chunk; concatenate audio with 100 ms silence gaps.

SSML for prosody control

<speak>
  <prosody rate="95%" pitch="-2%">
    Your appointment is confirmed for
    <say-as interpret-as="date" format="mdy">8/16/2025</say-as>
    at <say-as interpret-as="time">2:30pm</say-as>.
  </prosody>
  <break time="500ms"/>
  <emphasis level="moderate">Please arrive 10 minutes early.</emphasis>
</speak>

Not all providers support full SSML. Map to provider-specific tags or fall back to plain text with preprocessing.

Streaming playback architecture

Text chunks → TTS stream → Audio buffer (200ms) → Web Audio / native player
const audioCtx = new AudioContext();
const queue = [];

async function playStream(stream) {
  const reader = stream.getReader();
  while (true) {
    const { done, value } = await reader.read();
    if (done) break;
    const buffer = await audioCtx.decodeAudioData(value.buffer);
    queue.push(buffer);
    if (!playing) playNext();
  }
}

Start playback after buffering 200 ms of audio. Adjust based on network jitter measurements.

Caching strategy

Hash (text, voice_id, speed) → S3 key. Common phrases ("Your call is important to us") should never hit the API twice. Cache invalidation: voice model version in the hash prefix.

def cache_key(text: str, voice: str, model_version: str) -> str:
    payload = f"{model_version}:{voice}:{text}"
    return hashlib.sha256(payload.encode()).hexdigest()[:16]

Quality evaluation

Run MOS (Mean Opinion Score) listening tests with 20+ utterances monthly. Track:

Build a pronunciation dictionary for product names:

LEXICON = {"Kubernetes": "koo-ber-NET-eez", "nginx": "engine X"}

Voice selection for product UX

Match voice characteristics to use case:

Use case Voice traits Provider notes
IVR support Warm, moderate pace Polly Joanna, ElevenLabs Rachel
News/audio articles Authoritative, clear OpenAI onyx
Character/brand Distinct, consistent Custom cloned voice
Accessibility Slow, high clarity SSML prosody rate="slow"

Clone brand voices only with legal consent — voice likeness rights are actively litigated. Document training data provenance for enterprise contracts.

Latency vs quality tradeoffs

Real-time conversational AI needs first-byte latency under 300ms:

Batch overnight jobs (audiobook chapters) can prioritize quality over latency — use larger models and higher sample rates.

Accessibility and SSML

SSML improves comprehension for screen-reader-adjacent audio products:

<speak>
  Your balance is <say-as interpret-as="currency">$1,234.56</say-as>.
  <break time="500ms"/>
  Press 1 to confirm.
</speak>

Test with users who rely on audio interfaces — robotic pacing on phone numbers and codes is a common failure mode.

Pair with multimodal document understanding when TTS reads extracted document summaries aloud.

Common production mistakes

Teams get multimodal text to speech neural wrong in predictable ways:

Production implementations of multimodal text to speech neural fail when staging mirrors production topology poorly, rollback is untested, and on-call runbooks describe the happy path only.

Resources

Frequently asked questions

When should I use streaming TTS vs batch synthesis?

Stream when the user waits for audio in real time—voice assistants, audiobook players starting playback, IVR prompts. Batch when you pre-generate content—podcast intros, course narration, notification audio files stored in CDN.

How do I make TTS sound natural for long passages?

Split text at sentence boundaries, not arbitrary character limits. Insert SSML breaks between paragraphs. Pre-generate and cache common phrases. Avoid synthesizing URLs, code, or abbreviations without expansion rules.

What audio format should I deliver to clients?

Opus at 48 kbps for web streaming—best quality per byte. MP3 at 128 kbps for broad compatibility. WAV/FLAC only for editing workflows. Generate once, transcode to client-preferred format.

Hiring a senior Android / Flutter engineer?

I architect and ship production mobile software — Kotlin, Jetpack Compose, Flutter — for robotics, EV infrastructure, fintech, and real-time systems. Open to remote roles in Europe and the US.

Get in touch →