GEMINI LABJP
NANOLITE — Nano Banana 2 Lite is here: Google's fastest and most cost-efficient Gemini Image model, made for running lightweight image generation cheaplyOMNIFLASH — Gemini Omni Flash is in public preview, a natively multimodal model that lets enterprises and developers build custom, dynamic video workflowsAGENTS — Managed Agents expand with background: true for async server-side runs and polling, remote MCP server integration, and refreshing credentials across interactionsMEMORY — The Memory Bank IngestEvents API is generally available, decoupling event ingestion from memory generation so you can stream content continuouslyTHROUGHPUT — Provisioned Throughput now lets you submit up to seven pending orders for the same model and regionDEPRECATE — Image generation models shut down on August 17, and the Grok 4.1 family on the Gemini Enterprise Agent Platform on August 20NANOLITE — Nano Banana 2 Lite is here: Google's fastest and most cost-efficient Gemini Image model, made for running lightweight image generation cheaplyOMNIFLASH — Gemini Omni Flash is in public preview, a natively multimodal model that lets enterprises and developers build custom, dynamic video workflowsAGENTS — Managed Agents expand with background: true for async server-side runs and polling, remote MCP server integration, and refreshing credentials across interactionsMEMORY — The Memory Bank IngestEvents API is generally available, decoupling event ingestion from memory generation so you can stream content continuouslyTHROUGHPUT — Provisioned Throughput now lets you submit up to seven pending orders for the same model and regionDEPRECATE — Image generation models shut down on August 17, and the Grok 4.1 family on the Gemini Enterprise Agent Platform on August 20
Articles/Advanced
Advanced/2026-03-12Intermediate

Putting Gemini's TTS to Real Work — Voices, Pacing, and What It Costs to Run

Field notes on adding Gemini's text-to-speech to a solo-developer app: calling it with the google-genai SDK, the raw-PCM-to-WAV gotcha, choosing voices and pacing, and how the costs actually add up.

gemini102tts7text-to-speech4audio7speech-synthesis

When you build apps on your own, there's a day when you suddenly think, "this would land better with a voice." For me it was a screen that read out a guidance script. The text was there, but having an off-the-shelf TTS read it flatly felt clerical — there was no warmth in it.

That nagging lack of warmth is what pushed me to try Gemini's TTS. Here are the calling patterns I settled on while wiring it in, plus the snags that the docs don't print in bold.

How it differs from old-school readout

Traditional TTS was a tool that turned text into sound. Gemini's TTS leans toward reading text with an understanding of its intent.

Concretely, the same sentence shifts in tone just by telling it "calmly" or "with a lift" in the prompt. Compared with the days of hand-writing SSML tags to shape intonation, the entry point for tweaking has moved much closer.

A news script you want read evenly, versus a story narration where the pauses do the work — being able to split those with natural language instead of tags genuinely lightens the implementation load.

The shortest path to one audible clip

Current Gemini uses the google-genai package (from google import genai). Plenty of samples online still use the older google.generativeai line, but that one is being deprecated in stages, so for new work it's safer to stay on the new SDK.

from google import genai
from google.genai import types
 
client = genai.Client(api_key="YOUR_API_KEY")
 
response = client.models.generate_content(
    model="gemini-2.5-flash-preview-tts",
    contents="Read the following calmly: It's a clear day today. Perfect weather for a walk.",
    config=types.GenerateContentConfig(
        response_modalities=["AUDIO"],
        speech_config=types.SpeechConfig(
            voice_config=types.VoiceConfig(
                prebuilt_voice_config=types.PrebuiltVoiceConfig(
                    voice_name="Kore"
                )
            )
        ),
    ),
)
 
pcm = response.candidates[0].content.parts[0].inline_data.data

Model names shifted a few times during the preview period. The basic split is a Pro-tier model (gemini-2.5-pro-preview-tts) when quality matters and a Flash-tier model when you want speed and lower cost — but the exact string moves over time. Always confirm the current name in the official changelog before you build. Preview models can be shut off on a given date, and a hard-coded name is a seed for future outages.

The first snag — you get raw PCM, not a WAV

This is where I lost the better part of an hour.

If you write the inline_data.data from the code above straight to a file named output.wav, most players won't play it. What Gemini's TTS returns isn't a WAV file — it's headerless raw PCM (24kHz, 16-bit, mono). Slapping a .wav extension on bare waveform data leaves the player confused.

The right move is to wrap the PCM in a proper WAV header before saving.

import wave
 
def save_as_wav(pcm_bytes, path, rate=24000, channels=1, width=2):
    # Gemini TTS output is 24kHz, 16-bit, mono raw PCM
    with wave.open(path, "wb") as wf:
        wf.setnchannels(channels)
        wf.setsampwidth(width)   # 16-bit = 2 bytes
        wf.setframerate(rate)
        wf.writeframes(pcm_bytes)
 
save_as_wav(pcm, "output.wav")

The moment I added that one step, the silent output finally became a voice. The official samples do mention it, but the failure shows up as "a silent WAV gets generated" with no error, so it takes a surprisingly long time to trace the cause.

Decide voice and pacing in two layers — prompt plus timbre

The impression of a readout is decided by the combination of the timbre (voice_name) and the prompt instructions. At first I tried voice after voice and despaired that "none of them feel right" — but half the cause was on the prompt side.

Think of timbre as the base character and the prompt as the performance in the moment; separating them makes tuning faster.

contents = """Read the following guidance in a tone that gently leads the listener.
Take a slightly longer pause at full stops, and a beat before technical terms:
 
Now, let's walk through the first setup together.
Press the gear icon in the top-right of the screen."""

Just adding performance cues like "a beat before" or "gently" in plain text changes the impression even with the same timbre. If you want to read multi-speaker dialogue distinctly, multi_speaker_voice_config lets you assign a voice per speaker — but starting with a single voice and getting the warmth right first makes the tuning instincts easier to build.

Running cost lands by the second, not the character

Audio has a different cost shape from text generation. Synthesizing a long script in full on every call adds up steadily, even when the per-character figure is small.

The measure I took in practice was caching the generated audio. Fixed-wording lines like guidance prompts get synthesized once, saved, and reused; only the parts that change dynamically get synthesized on demand. Drawing that line alone brought my monthly API cost down noticeably. For a solo indie developer running apps on AdMob revenue, that gap feeds straight into how much breathing room the project has.

A readout that doesn't need to be real-time doesn't need to be synthesized every time a user arrives. Building "when do we synthesize" into the design is the dividing line for whether you can keep TTS in your app long-term.

When not to add TTS

Finally, a note on the cases where I chose not to.

For very short notification sounds, or text that changes character by character, Gemini TTS wasn't a fit. Synthesis latency turns directly into perceived sluggishness, and the cost doesn't pay off. For those, the device's built-in OS TTS was plenty.

Gemini TTS shows its strengths exactly where expressiveness is needed — narration, guidance, story readouts. Rather than replacing everything, narrowing it to "places that need warmth" is my own realistic way of using it.

If you try it next

Start by passing a single sentence to gemini-2.5-flash-preview-tts, wrapping the raw PCM into a WAV, and playing it through. Once you have sound, swap the timbre two or three times and listen to how the same sentence changes. Once that minimal loop is turning, the rest is just adding performance cues in the prompt and dialing in a voice that fits your app.

I hope this helps anyone who, like me, has felt that "this would be better with a voice here" — as a first step.

Share

Thank You for Reading

Gemini Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • Copy-paste ready implementation code
  • New advanced guides published daily
  • $5/mo or $10 for lifetime access
View Membership →

If you found this article helpful, a small tip ($1.50) would mean a lot to us. Your support helps keep this site ad-free and covers server and hosting costs.

Related Articles

Advanced2026-07-12
Spend Deep Reasoning Only Where It's Needed: Per-Request thinking_level Routing in Gemini
Running every request at high thinking_level bloats latency and cost; forcing low drops accuracy on hard questions. This walks through a router that picks Gemini 3.x thinking_level per request from an inexpensive difficulty estimate, keeping p95 latency inside a mobile budget while reserving deep reasoning for the questions that need it — with measured numbers and working code.
Advanced2026-07-11
A Risk-Tiered Approval Gate for Gemini Function Calling
Handing full autonomy to an agent is unnerving. This walks through a Gemini function-calling loop that routes tool calls into auto-run and hold-for-approval by risk tier, then feeds the result back to the model after a human signs off.
Advanced2026-06-27
Your Gemini Completion Event Will Arrive Twice — An Idempotent Sink That Makes Webhook + Reconciliation Effectively Once-Only
Once you receive Gemini long-running operations over a Webhook and back it up with a reconciliation poller, the same completion arrives twice and publishing or billing runs twice. Build an idempotent sink with a normalized key and a claim-run-commit pattern that keeps side effects effectively once-only.
📚RECOMMENDED BOOKS
Build a Large Language Model (From Scratch)
Sebastian Raschka
LLM Dev
Prompt Engineering for LLMs
Berryman & Ziegler
Prompting
AI Engineering
Chip Huyen
AI Eng
* Contains affiliate links
See all →