Text-to-speech · Synthesize speech with Parler-TTS5 / 5

Synthesize speech with Parler-TTS

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

The other TTS engines in this SDK pick the voice and let the model decide how it sounds. Parler-TTS adds a third input on top of text and voice: an emotion label. The engine renders that affect into the speech.

Start by selecting Parler explicitly. The checkpoint and ttsEngine: "parler" choose its schema, and the required voice must come from the voices shipped with that checkpoint. The model configuration would look like so:

const modelId = await loadModel({
  modelSrc: TTS_MINI_V1_EN_PARLER_TTS_Q8_0,
  modelConfig: {
    ttsEngine: "parler",
    voice: "Laura",
    seed: 42,
  },
});

seed: 42 pins the noise so the same text + voice + emotion renders the same audio on every run.

Reuse the text-to-speech call from the other engine lessons, then add Parler's emotion field to steer the delivery:

const result = textToSpeech({
  modelId,
  text: "Hey, how are you doing today?",
  inputType: "text",
  stream: false,
  emotion: "happy",
});

const audioBuffer = Int16Array.from(await result.buffer);
console.log(`▸ TTS complete. Total samples: ${audioBuffer.length}`);

Valid emotion labels are happy, sad, angry, neutral, confused, curious, emphatic, sympathetic, calm. The schema rejects anything outside the list.

The returned buffer is raw 44.1 kHz mono PCM. Add the 44-byte WAV header before writing the file to make it playable. The conversion would look like this:

function createWav(pcm: Int16Array, sampleRate: number): Uint8Array {
  const header = new ArrayBuffer(44);
  const view = new DataView(header);
  const byteLength = pcm.byteLength;
  const channels = 1;
  const bitsPerSample = 16;
  const blockAlign = channels * (bitsPerSample / 8);

  writeAscii(view, 0, "RIFF");
  view.setUint32(4, 36 + byteLength, true);
  writeAscii(view, 8, "WAVE");
  writeAscii(view, 12, "fmt ");
  view.setUint32(16, 16, true);
  view.setUint16(20, 1, true);
  view.setUint16(22, channels, true);
  view.setUint32(24, sampleRate, true);
  view.setUint32(28, sampleRate * blockAlign, true);
  view.setUint16(32, blockAlign, true);
  view.setUint16(34, bitsPerSample, true);
  writeAscii(view, 36, "data");
  view.setUint32(40, byteLength, true);

  const wav = new Uint8Array(44 + byteLength);
  wav.set(new Uint8Array(header), 0);
  wav.set(new Uint8Array(pcm.buffer, pcm.byteOffset, byteLength), 44);
  return wav;
}

const wav = createWav(audioBuffer, 44100);
fs.writeFileSync("output/text-to-speech/parler-output.wav", wav);

The header encodes the chunk size, the sample rate, the channel count, and the bit depth. The PCM bytes go straight after; no transformation, no resampling.

The audio is on disk, so release the Parler model before the process exits:

await unloadModel({ modelId });

Note: Parler outputs 44.1 kHz mono like Supertonic, not 24 kHz like Chatterbox. If you're building a voice pipeline that switches between engines, normalise the sample rate at the boundary so downstream consumers don't have to handle per-engine quirks.

Questions

Question 1 of 3

Which of the following is a valid emotion value for Parler?

Question 2 of 3

What has to be true about result.buffer before it can be treated as Int16Array audio samples?

Question 3 of 3

What is a key consideration behind building a 44-byte WAV header before writing result.buffer to disk?

index.ts
Loading editor...

Run your code, check your answer, or ask a question. It all shows up here.