Fish Audio
Text-to-speech with FishAudioTTSService, Fish Audio's MessagePack WebSocket API, with cloned-voice support.
Overview
Fish Audio turns text into speech, including with cloned voices. pipedog ports Pipecat's
FishAudioTTSService: it streams over Fish Audio's real-time WebSocket
(wss://api.fish.audio/v1/tts/live), speaking MessagePack, and reconnects when the user
interrupts the bot.
Upstream page
Pipecat's documentation for the same service.
Source
packages/fish/src/tts.ts.
Fish Audio docs
The real-time WebSocket protocol, models and voices.
Installation
bun add @pipedog/fishimport { FishAudioTTSService } from "@pipedog/fish/tts";Prerequisites
- A Fish Audio account and an API key.
- A voice model's reference id, set as
voice. It is sent asreference_id. - Pass the key as
apiKey. The examples read it fromFISH_API_KEY.
FishAudioTTSService
Configuration
Prop
Type
Settings
Runtime-updatable settings, passed as settings: new FishAudioTTSService.Settings({...}).
| Setting | Type | Default | Description |
|---|---|---|---|
model | string | "s2.1-pro" | Fish Audio model, sent as the model handshake header: s2.1-pro, s2-pro, s1 or drama-3. |
voice | string | null | null | Voice model reference id. |
latency | string | null | "balanced" | "normal" or "balanced". |
normalize | boolean | null | true | Normalize the text. |
temperature | number | null | null | Randomness, 0.0 to 1.0. |
topP | number | null | null | Nucleus sampling, 0.0 to 1.0. |
prosodySpeed | number | null | 1.0 | Speech speed, 0.5 to 2.0. |
prosodyVolume | number | null | 0 | Volume in dB, -20 to 20. |
prosodyNormalizeLoudness | boolean | null | null | Even out the perceived volume (S2 family and drama-3). |
chunkLength | number | null | null | Text segment size, 100 to 300. |
minChunkLength | number | null | null | Minimum characters before a new chunk, 0 to 100. |
conditionOnPreviousChunks | boolean | null | null | Use earlier audio as context, for a consistent voice. |
Settings left null are not sent, so Fish Audio applies its own defaults.
Settings.fromMapping() also accepts upstream's nested prosody dict.
Usage
import { TTSUpdateSettingsFrame } from "@pipedog/core/frames/frames";
import { FishAudioTTSService } from "@pipedog/fish/tts";
const tts = new FishAudioTTSService({
apiKey: process.env.FISH_API_KEY ?? "",
settings: new FishAudioTTSService.Settings({
// A voice model's reference id from the Fish Audio library.
voice: "your-reference-id",
model: "s2.1-pro",
latency: "balanced",
prosodySpeed: 1.1,
}),
});Change settings mid-call with a TTSUpdateSettingsFrame:
// Queue it on the worker, or push it from a processor upstream of the service.
// A change to any setting reconnects, because they are all sent in the start message.
const update = new TTSUpdateSettingsFrame({
delta: new FishAudioTTSService.Settings({ prosodySpeed: 0.9, prosodyVolume: 2 }),
});Event handlers
Inherited from TTSService:
| Event | Description |
|---|---|
on_connected | Connected to Fish Audio. |
on_disconnected | Disconnected from Fish Audio. |
on_connection_error | The connection failed. |
on_tts_request | A text chunk is about to be sent for synthesis. |
Notes
- Each text chunk is sent with a
flush, which makes Fish Audio generate the audio right away. - The service reconnects on an interruption while the bot is speaking, and on any settings change.
outputFormatis"pcm"by default. The service emits the audio as it arrives and does not produce word timestamps.
Runtime notes
- The socket is the global
WebSocket. TheAuthorizationandmodelheaders go on the handshake, which works on Bun, Node and Deno, and on Cloudflare Workers, where the socket is opened throughfetch. - Upstream packs messages with
ormsgpack. pipedog uses@msgpack/msgpack, which is pure TypeScript. It writes a whole-number float such asprosodySpeed: 1.0as a MessagePack integer. - Not ported: the deprecated
InputParamsand thereferenceId,modelIdandparamsconstructor options. Usesettings.
| Runtime | Works | What it needs |
|---|---|---|
| Bun | ✅ yes | Nothing. |
| Node.js | ✅ yes | Nothing. |
| Deno | ✅ yes | Nothing. |
| Cloudflare Workers | ✅ yes | Nothing. |
Generated from pipecat.runtimes and pipecat.compat in packages/fish/package.json: ✅ means the compat smoke test passes on that runtime.
pipedog is an unofficial community TypeScript port of Pipecat. It is not affiliated with or endorsed by Daily or the Pipecat project.
Portions adapted from the Pipecat documentation, Copyright (c) 2024–2025 Daily, BSD-2-Clause (see apps/docs/LICENSE-pipecat-docs).