# Send TTS Audio Back

> Play synthesized audio back to the caller with bridge.publishDownlink().

When you own the audio bridge for a call, everything that your text-to-speech
engine produces goes to the caller through one method: `publishDownlink(frame)`.
Each call pushes a single **encoded audio frame**, typically a 20 ms Opus
packet, onto the `voice/<sid>/downlink` track. The engine paces the frame to
the caller's leg. On a PSTN call, the engine transcodes to G.711 for you.

This is the send side of the
[bring-your-own ASR/TTS](/modalities/voice/runtime/byo-asr-llm-tts) pattern.
For the receive side, which feeds caller speech into your recognizer, see
[Stream Audio to ASR](/modalities/voice/cookbooks/stream-to-asr).

## Push TTS chunks as they arrive

Open the bridge with `attach()`. Then forward each synthesized chunk straight
to `publishDownlink`. `attach()` always subscribes uplink too. For this reason,
pass an `onUplink` callback even if you only care about playback here. A no-op
is fine, or wire it to your ASR.

  
```ts
import { Voice } from "clutchcall_connect/voice";

const v = new Voice({
  baseUrl: "https://engine.firstbyte.fast",
  apiKey:  process.env.CLUTCHCALL_CREDENTIALS!,
  orgId:   "org_abc",
});

const call = await v.calls.originate({
  to: "+15551234567",
  from: "+15558675309",
  trunkId: "trunk_main",
});

const bridge = await v.audioBridge.attach(call.sid, {
  codec: "opus",
  onUplink: (frame, tsUs) => {}, // playback-only here
});

// stream synthesized Opus straight to the caller
myTts.onChunk((opus) => bridge.publishDownlink(opus));
```
  
  
```python
import os
from clutchcall.voice import Voice

v = Voice(
    base_url="https://engine.firstbyte.fast",
    api_key=os.environ["CLUTCHCALL_CREDENTIALS"],
    org_id="org_abc",
)

call = v.calls.originate(
    to="+15551234567", from_="+15558675309", trunk_id="trunk_main",
)

bridge = v.audio_bridge.attach(
    call.sid,
    codec="opus",
    on_uplink=lambda frame, ts_us: None,  # playback-only here
)

# stream synthesized Opus straight to the caller
tts.on_chunk(lambda opus: bridge.publish_downlink(opus))
```
  

> **NOTE:**
> `publishDownlink` expects an **encoded** frame in the codec that you asked for
> in `attach()`. With `codec: "opus"`, push Opus packets. With `codec: "pcm16"`,
> push raw PCM16. Send one frame per call, one 20 ms tick at a time. Do not send
> a whole utterance in a single buffer. This keeps playback smooth and lets
> barge-in cut the audio off cleanly.

## Feed a model that emits raw PCM

Many realtime and TTS models give you 16-bit PCM rather than Opus. Ask for
`pcm16`. The bridge then transcodes both directions. You push exactly what the
model gives you.

  
```ts
const bridge = await v.audioBridge.attach(call.sid, {
  codec: "pcm16",
  sampleRate: 16000,
  onUplink: (pcm, tsUs) => model.appendAudio(pcm),
});

// model → caller: no re-encode in your process
model.onAudioOut((pcm) => bridge.publishDownlink(pcm));
```
  
  
```python
bridge = v.audio_bridge.attach(
    call.sid,
    codec="pcm16",
    sample_rate=16000,
    on_uplink=lambda pcm, ts_us: model.append_audio(pcm),
)

# model → caller: no re-encode in your process
model.on_audio_out(lambda pcm: bridge.publish_downlink(pcm))
```
  

## Time playback yourself

When you omit the timestamp, `publishDownlink` stamps each frame with a
monotonic clock. This is fine for live streaming. If you replay buffered audio
or splice clips, pass a microsecond timestamp so that frames land in order.

  
```ts
let tsUs = 0n;
for (const frame of opusFrames) {
  bridge.publishDownlink(frame, tsUs);
  tsUs += 20_000n; // 20 ms per frame, in microseconds
}
```
  
  
```python
ts_us = 0
for frame in opus_frames:
    bridge.publish_downlink(frame, timestamp_us=ts_us)
    ts_us += 20_000  # 20 ms per frame, in microseconds
```
  

> **WARNING:**
> Keep the `bridge` reference alive for the whole call. If the garbage collector
> removes it, both tracks drop. When the call ends, call `close()` on the bridge
> before you call `hangup()` so that the downlink track closes cleanly.

## Related

  - **[Stream Audio to ASR](/modalities/voice/cookbooks/stream-to-asr)** — The uplink companion: feed caller speech to your recognizer.
  - **[BYO ASR / LLM / TTS](/modalities/voice/runtime/byo-asr-llm-tts)** — Run the full custom-brain loop end to end.
  - **[Hand off to a human](/modalities/voice/cookbooks/transfer-human)** — Escalate the live call and keep the same sid.
  - **[Python SDK](/modalities/voice/sdk/python)** — Full AudioBridge signatures for Python.
