Docs
Alania · TTS

Streaming playback

With stream: true the body is a WAV header followed by PCM16 chunks as they are generated. Play without waiting for the file.

How it works

The first chunk starts with the 44-byte RIFF header; its length fields are 0xFFFFFFFF because this is a stream. Every following chunk is raw 16-bit little-endian samples, 24 kHz mono. Chunks may split a sample at the boundary: carry an odd trailing byte over to the next chunk.

curl -N https://voice.patientdesk.ai/v1/audio/speech \
  -H "Authorization: Bearer pd_live_..." \
  -H "Content-Type: application/json" \
  -d '{ "model": "alania-v1", "input": "Randevunuz oluşturuldu.", "stream": true }' \
  | ffplay -autoexit -nodisp -            # first audio in about a third of a second

In the browser

JavaScript
// Progressive playback: schedule each PCM16 chunk as it arrives.
const res = await fetch("https://voice.patientdesk.ai/v1/audio/speech", {
  method: "POST",
  headers: { Authorization: "Bearer " + token, "Content-Type": "application/json" },
  body: JSON.stringify({ model: "alania-v1", input: text, stream: true }),
});
const ctx = new AudioContext();
const reader = res.body.getReader();
let next = 0, header = true;
for (;;) {
  const { value, done } = await reader.read();
  if (done) break;
  let pcm = value;
  if (header) { pcm = value.subarray(44); header = false; }        // skip the RIFF header
  const buf = ctx.createBuffer(1, pcm.length / 2, 24000);
  const ch = buf.getChannelData(0);
  const dv = new DataView(pcm.buffer, pcm.byteOffset);
  for (let i = 0; i < ch.length; i++) ch[i] = dv.getInt16(i * 2, true) / 32768;
  const src = ctx.createBufferSource();
  src.buffer = buf; src.connect(ctx.destination);
  next = Math.max(next, ctx.currentTime + 0.05);
  src.start(next); next += buf.duration;
}
An AudioContext can only play after a user gesture; start the request from a click.

Measuring latency

Time to first audio (TTFA) is measured from request start to the first PCM chunk; p50 is about 310 ms. The x-queue-wait-ms header says how much of that was waiting for a slot rather than generating.

WebSocket

To speak sentence after sentence down one connection, use the WebSocket: each speak message returns an audio stream and the connection stays open. The first message announces the sample rate and encoding.

WebSocket
const ws = new WebSocket("wss://voice.patientdesk.ai/v1/audio/speech/stream?key=pd_live_...");
ws.binaryType = "arraybuffer";
ws.onopen = () => ws.send(JSON.stringify({
  type: "speak",
  model: "alania-v1",
  input: "Randevunuz yarın saat 14:05 için oluşturuldu.",
}));
ws.onmessage = (e) => {
  if (e.data instanceof ArrayBuffer) return play(new Int16Array(e.data));  // PCM16, 24 kHz mono
  const m = JSON.parse(e.data);
  if (m.type === "speech_start") console.log(m.sample_rate, m.encoding, m.disclosure);
  if (m.type === "speech_end") ws.send(JSON.stringify({ type: "stop" }));
};

Compressed formats

For non-streaming requests response_format can be mp3, opus, flac or aac, which cuts bandwidth by roughly 10× for telephony. Streaming accepts only wav and pcm: half a muxed container is not audio, so anything else is refused with a 400.