GEMINI LABJP
ROBOTICS — The ER 1.6 preview that shut down on August 31 does have a successor. Gemini Robotics ER 2 is in public preview, in both standard and streaming variantsVIDEO — ER 2 judges success and failure from live video rather than still snapshots, which is what lets it catch spills, slips, and misalignments while a task is still runningDEADLINE — Next up is September 30, when gemini-omni-flash-preview is retired. The target is gemini-omni-1.1-flash, GA since August 27, and there are now under four weeks leftAPIKEY — Every remaining standard API key, restricted ones included, stops working during September. The replacement is an auth key bound to a Google Cloud service accountPRICE — Gemini 3.7 Flash keeps its introductory $0.75/$3.75 per 1M through December 31, then moves to $1.50/$7.50 on January 1, 2027. Any estimate crossing the year needs both figuresAUDIO — Gemini 3.5 Transcribe handles language detection across 85+ languages, speaker diarization, word-level timestamps, and custom vocabulary biasing of up to 1,000 termsROBOTICS — The ER 1.6 preview that shut down on August 31 does have a successor. Gemini Robotics ER 2 is in public preview, in both standard and streaming variantsVIDEO — ER 2 judges success and failure from live video rather than still snapshots, which is what lets it catch spills, slips, and misalignments while a task is still runningDEADLINE — Next up is September 30, when gemini-omni-flash-preview is retired. The target is gemini-omni-1.1-flash, GA since August 27, and there are now under four weeks leftAPIKEY — Every remaining standard API key, restricted ones included, stops working during September. The replacement is an auth key bound to a Google Cloud service accountPRICE — Gemini 3.7 Flash keeps its introductory $0.75/$3.75 per 1M through December 31, then moves to $1.50/$7.50 on January 1, 2027. Any estimate crossing the year needs both figuresAUDIO — Gemini 3.5 Transcribe handles language detection across 85+ languages, speaker diarization, word-level timestamps, and custom vocabulary biasing of up to 1,000 terms
Articles/Dev Tools
Dev Tools/2026-07-14Advanced

Making TTS seams inaudible: prosody continuity and silence boundaries for long-form narration

Splitting a long article by sentence and synthesizing each piece leaves audible seams: prosody resets at every sentence start, clicks appear at PCM joints, and pauses feel uneven. Four layers -- context priming, zero-crossing edge fades, punctuation-aware silence, and an idempotent seam ledger -- make the joins disappear, with full code.

Gemini TTS2Speech synthesisNarrationSolo development

Premium Article

I was preparing the guidance voice for a calming app I run as an indie developer, using Gemini's TTS. I had synthesized it sentence by sentence, stitched the pieces together, and sat down to hear the whole thing for the first time. And I stopped.

At every boundary between sentences, the speaker seemed to swap for a person for an instant. Each individual clip sounded clean, yet the moment they were joined the result felt restless. A voice meant to ease someone toward sleep tensed up, faintly, at each seam.

There was not one cause but three. A reset in intonation, a small click at each PCM joint, and uneven pauses. Together they produced a recording that announced its own seams. This article separates those three, and gives working code for each. Not merely splitting and joining -- taking it all the way to where the ear stops noticing the joins.

Why seams catch the ear

You cannot voice a long article in a single request. There is a ceiling on how much one call can synthesize, and a few thousand characters will cut off partway. So you split by sentence. That much is unavoidable.

The problem is what happens when you join the pieces back. In my own runs, the discontinuity came in three flavors, each with a different character.

Seam symptomWhat you hearLayer that fixes it
Prosody resetA stiffness, as if re-reading from the top at each sentenceContext priming
Boundary clickA brief pop at the joinZero-crossing snap and edge fade
Uneven pausesSentences collide, or dragPunctuation-aware silence

Naive "split by sentence, insert a fixed pause, concatenate raw PCM" leaves the reset and the click intact. A constant pause length keeps the pacing uneven. From here, we peel the problem apart one layer at a time.

Passing the previous ending into the next call (context priming)

The most obvious offender is intonation snapping back to blank at each sentence start. The model has no idea how the previous sentence ended; each request arrives as a fresh introduction.

So we hand it a short tail of the prior sentence and ask it to continue in the same manner. Only the current sentence is spoken. The previous one is nothing more than a cue for carrying the cadence forward.

from google import genai
from google.genai import types
 
client = genai.Client()
MODEL = "gemini-3.1-flash-tts-preview"
VOICE = "Kore"
STYLE = "Read in a calm, low voice at a steady, unhurried pace."
 
def build_prompt(text: str, prev_tail: str) -> str:
    if prev_tail:
        return (
            f"{STYLE}\n"
            f"You just finished reading: \"{prev_tail}\". Keep the same timbre and the flow of intonation.\n"
            f"Now read only the following sentence:\n{text}"
        )
    return f"{STYLE}\nRead the following sentence:\n{text}"
 
def synth(text: str, prev_tail: str = "") -> bytes:
    resp = client.models.generate_content(
        model=MODEL,
        contents=build_prompt(text, prev_tail),
        config=types.GenerateContentConfig(
            response_modalities=["AUDIO"],
            speech_config=types.SpeechConfig(
                voice_config=types.VoiceConfig(
                    prebuilt_voice_config=types.PrebuiltVoiceConfig(voice_name=VOICE)
                )
            ),
        ),
    )
    cand = resp.candidates[0]
    if cand.finish_reason and cand.finish_reason.name != "STOP":
        raise RuntimeError(f"unexpected finish_reason: {cand.finish_reason.name}")
    return cand.content.parts[0].inline_data.data  # 24kHz 16bit mono PCM

It does not line up perfectly. Priming is a nudge, not a guarantee. Even so, the stiffness at sentence starts visibly dropped in my runs. I keep the tail to around 20 to 24 characters. Hand over more and the model will occasionally read the cue itself. Keep the cue short -- just enough to convey the flow.

Thank you for reading this far.

Continue Reading

What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.

WHAT YOU'LL LEARN
Separate the three distinct causes of seams at sentence boundaries and take away code that addresses each one
Context priming carries the previous sentence's cadence into the next call, while zero-crossing edge fades remove the click at the join
Take home an idempotent seam ledger -- JSON schema and regeneration code -- so re-rendering one chunk never rebuilds the whole track
Secure payment via Stripe · Cancel anytime

Unlock This Article

Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.

or
Unlock all articles with Membership →
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 $15 for lifetime access
View Membership →

Related Articles

Dev Tools2026-08-30
Why Shipped Clients Deserve a Refusal, Not a Silent Model Substitution
A model can retire, but the apps already on people's phones cannot. This is how I built a sunset ledger keyed on output contracts, and how I now back-date my own deadline from the version residue curve.
Dev Tools2026-08-28
A twice-daily batch that only ran once — reconstructing run counts from artifacts
One half of a scheduled job silently never fired, and throughput sat at half of plan for over a week without a single error in the logs. Here is how I reconstructed actual run counts from artifacts and backlog, with working code.
Dev Tools2026-08-25
Turning Logging Off in AI Studio Also Turns Off Interactions API Conversation History
When no Gemini API logs show up in AI Studio, the usual culprit is the store default, which is inverted between the two APIs. Here is the side effect that quietly stops conversation history, and how to set store per request so it never surprises you.
📚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 →