Text-to-speech · Stream TTS audio with bufferStream4 / 4
  1. 01
  2. 02
  3. 03

Stream TTS audio with bufferStream

Example on GitHub(packages/sdk/examples/tts/supertonic.ts)

Now that we can synthesize a full utterance, we're going to surface the audio as it synthesizes. For low-latency voice, the reader wants each chunk as soon as the engine produces it.

textToSpeech({ stream: true }) returns { buffer, bufferStream, done } instead of just { buffer }. The samples live on result.bufferStream as an AsyncGenerator<number>.

Setting stream: true is what flips the result shape to { buffer, bufferStream, done }. You would call it like so:

const result = textToSpeech({
  modelId,
  text: "Streaming chunks as the engine synthesizes them is the right latency for low-latency voice.",
  inputType: "text",
  stream: true,
});

Iterating result.bufferStream with for await is the canonical way to consume samples:

let totalSamples = 0;
for await (const sample of result.bufferStream) {
  void sample;
  totalSamples += 1;
}
console.log(`▸ Streamed ${totalSamples} samples`);

Each number is a single PCM sample. Iterating with for await yields samples in the order they were synthesized. result.buffer is empty when stream: true; the samples live on the generator, not the promise.

result.done is a Promise<boolean> that resolves when synthesis finishes. Await it from a separate branch if you need to know when the stream terminates.

Note: this is a different shape from textToSpeechStream, which is a duplex session for piping tokens from a streaming LLM into TTS. Use textToSpeech({ stream: true }) when you already have the full text and want a stream of audio chunks.

Put it to the test

  1. Call textToSpeech({ modelId, text, inputType: "text", stream: true }).
  2. Iterate result.bufferStream with for await (const sample of result.bufferStream) and count the samples.
  3. Log the total sample count. Don't await result.buffer; it stays empty when streaming.
index.ts

$ Run your code to see results

$