pipedog is an unofficial community port of Pipecat, not affiliated with Daily.
pipedog
Text-to-speech

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.

Installation

bun add @pipedog/fish
import { 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 as reference_id.
  • Pass the key as apiKey. The examples read it from FISH_API_KEY.

FishAudioTTSService

Configuration

Prop

Type

Settings

Runtime-updatable settings, passed as settings: new FishAudioTTSService.Settings({...}).

SettingTypeDefaultDescription
modelstring"s2.1-pro"Fish Audio model, sent as the model handshake header: s2.1-pro, s2-pro, s1 or drama-3.
voicestring | nullnullVoice model reference id.
latencystring | null"balanced""normal" or "balanced".
normalizeboolean | nulltrueNormalize the text.
temperaturenumber | nullnullRandomness, 0.0 to 1.0.
topPnumber | nullnullNucleus sampling, 0.0 to 1.0.
prosodySpeednumber | null1.0Speech speed, 0.5 to 2.0.
prosodyVolumenumber | null0Volume in dB, -20 to 20.
prosodyNormalizeLoudnessboolean | nullnullEven out the perceived volume (S2 family and drama-3).
chunkLengthnumber | nullnullText segment size, 100 to 300.
minChunkLengthnumber | nullnullMinimum characters before a new chunk, 0 to 100.
conditionOnPreviousChunksboolean | nullnullUse 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

tts.ts
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:

EventDescription
on_connectedConnected to Fish Audio.
on_disconnectedDisconnected from Fish Audio.
on_connection_errorThe connection failed.
on_tts_requestA 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.
  • outputFormat is "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. The Authorization and model headers go on the handshake, which works on Bun, Node and Deno, and on Cloudflare Workers, where the socket is opened through fetch.
  • Upstream packs messages with ormsgpack. pipedog uses @msgpack/msgpack, which is pure TypeScript. It writes a whole-number float such as prosodySpeed: 1.0 as a MessagePack integer.
  • Not ported: the deprecated InputParams and the referenceId, modelId and params constructor options. Use settings.
RuntimeWorksWhat it needs
Bun✅ yesNothing.
Node.js✅ yesNothing.
Deno✅ yesNothing.
Cloudflare Workers✅ yesNothing.

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).

On this page