All use cases

Thai interactive fiction and visual novels

Voice branching Thai stories: chat fiction, visual novels, and choice-driven games, either pre-rendered across the whole tree or spoken at runtime down the path a reader picks.

Ideas to try

Open in the playground
Open in the playground
Open in the playground
Open in the playground

Every card plays a real clip generated from its exact script with Nom Yen. Open one in the playground to make it say anything.

How a branching chapter is voiced

Branching stories are the one kind of fiction whose next line is unknown until a reader acts. Joylada publishes Thai fiction as a chat you tap through, and visual novels built on engines like Ren'Py write dialogue one line per speaker with choices between scenes. Both shapes leave a studio with the same decision. Pre-render the tree and every branch is a file, ready before anyone opens the app, and paid for whether or not a reader ever walks it. Speak at runtime and a reader pays for exactly the path they take, at the cost of a connection open while they read. Which one is cheaper depends on how much of the tree a typical reader sees, and the table below puts real numbers under that.

Sources:Joylada(opens in a new tab)Ren'Py(opens in a new tab)

  1. Flatten the tree to spansEvery node becomes a span of speech with a speaker and a voice id. A span up to 5,000 characters is one request, and a longer one splits at a sentence boundary.
  2. Pick pre-render or runtime per sceneA prologue every reader sees is worth rendering once; a rare branch is worth speaking on demand. The choice is per scene and can change after launch without touching the script.
  3. Cache what a reader already heardA line spoken at runtime is an ordinary MP3. Keeping it turns a replayed route into a cache hit, and a re-read of the same path costs nothing the second time.

Pre-render the tree, or speak the path

DeliveryCharactersCreditsCost
One reader's path, 120 lines10,800162$0.16
10,000 readers, spoken at runtime108,000,0001,620,000$1,620
The whole tree, voiced once for every reader you will ever haveabout $2.16

Computed at 15 credits per 1,000 characters over 90-character lines. Pre-rendering is paid once for the whole audience; runtime is paid per reader, so the crossover sits at roughly 13 readers.

One chapter, four endings

chapter-07.tree
scene_07a  ณิชา     nomyen       96 chars
scene_07a  ธาวิน    massaman     82 chars
  choice_1 -> scene_07b
  choice_2 -> scene_07c

scene_07b  ณิชา     nomyen       88 chars    [pre-rendered]
scene_07c  ณิชา     nomyen       94 chars    [pre-rendered]
scene_07c  คุณยาย   tako         90 chars    [pre-rendered]

# 4 branches x ~400 lines = the tree above
# a reader hears ~120 of them

The tree is the unit a studio budgets; the path is the unit a reader hears. Casting is a voice id per speaker in both cases.

From launch to steady state

LaunchThe prologue is renderedEvery reader walks the opening, so it is voiced once and served as files. Nothing about it is decided at runtime.
Week 1The rare branches speak liveDeep routes almost nobody reaches stay unrendered, and a reader who gets there hears them spoken over a live connection. A skipped line stops on a cancel and the queued remainder costs nothing.
Month 3The popular routes are cachedA line spoken once is an ordinary MP3. Keeping the ones readers actually reach turns most of the tree into files without ever deciding to render it.
Month 12The tree stops costing anythingWhat readers walk has been voiced, and a re-read is a cache hit. New spending starts again only when you write the next chapter.

One open live connection holds one of the plan's concurrent speech slots, so a runtime deployment is sized by simultaneous readers rather than by lines per day.

For developers

The TypeScript sample opens the live socket a runtime session uses, and the Python quickstart posts one span the way a pre-render batch does. The options request shows the voice, model, and format a scene pins.

// Runs on Bun as-is; on Node, `import WebSocket from "ws"` accepts
// the same constructor options. Browsers cannot send this header, and
// API keys never ship to browsers anyway.
const socket = new WebSocket("wss://api.paxalabs.com/v1/tts/live", {
  headers: { Authorization: `Bearer ${process.env.PAXA_API_KEY}` },
});
socket.binaryType = "arraybuffer";

socket.onopen = () => {
  socket.send(JSON.stringify({
    type: "start",
    model: "paxa-tts-flash-v1",
    voice: "khanomkrok",
    timestamps: "word",
  }));
  // Send text whenever it becomes available, token by token if you like.
  socket.send(JSON.stringify({ type: "text", text: "สวัสดีครับ ยินดีต้อนรับสู่ Paxa Labs" }));
  socket.send(JSON.stringify({ type: "flush" }));
  socket.send(JSON.stringify({ type: "end" }));
};

socket.onmessage = (event) => {
  if (typeof event.data === "string") {
    const message = JSON.parse(event.data);
    if (message.type === "done") socket.close();
    return;
  }
  player.feed(new Uint8Array(event.data)); // binary frames are audio
};

Full API reference

Hear the voices

  • Luk ChupFemale0:04

    Light, high, youthful voice for characters and playful spots.

    สวัสดีค่า วันนี้หนูมีเรื่องน่ารัก ๆ มาเล่าให้ฟังด้วยนะคะ

  • Khanom KrokMale0:03

    Calm, unhurried elder voice and the roster's male lead: documentary, heritage storytelling, and narration.

    สมัยผมยังหนุ่ม แถวนี้ยังเป็นทุ่งนา เดินไปตลาดทีหนึ่งก็ค่อนวันแล้วครับ

  • Nom YenFemale0:06

    Bright, energetic voice and the roster's female lead: promos, social clips, and everyday product speech.

    ยินดีต้อนรับสู่ Paxa Labs ค่ะ เสียงภาษาไทยที่สลับเป็น English ได้กลางประโยค อย่างเป็นธรรมชาติ

  • TakoFemale0:05

    Friendly young narrator for audiobooks, recaps, and explainers.

    ยามเช้าในกรุงเทพฯ แสงแดดอ่อน ๆ ทาบลงบนถนนที่เริ่มคึกคัก

A few of the 26 production voices, generated from the API with the published voice ids. The full roster plays on the Text-to-Speech page.

Short lines are their own problem

Branching fiction is written in fragments: a line of chat, a single retort, a choice label. Thai runs without spaces between words, so a four-word fragment gives a reader very little context to segment from. Paxa TTS Flash reads tone from spelling rather than from a dictionary lookup, which is what keeps a short isolated line intelligible instead of guessed at.

Common questions

Should I pre-render the tree or speak at runtime?
Pre-rendering costs about $2.16 once for the whole audience. Runtime costs about $0.16 per reader. Below roughly 13 readers, runtime is cheaper, and above it pre-rendering is.
Can each character have a different voice?
Yes, up to the catalog's 26. A voice id per speaker is held in the script, and the same id returns the same voice every run.
What happens when a reader taps past a line?
Send a cancel frame on the live socket. The line stops, the queued remainder is discarded without charge, and the same connection carries the next line.
How much of the plan's capacity does a reader use?
One open live connection holds one concurrent speech slot for as long as it stays open, so a runtime deployment is sized by simultaneous readers rather than by lines per day.
Can we ship the audio inside a game?
Output made on free credits is for personal use and evaluation. Once you buy a plan or credits, generated output becomes yours for any use, including shipping it inside a commercial title, and that covers output generated before the purchase.

Start with free credits

Sign in with Google, GitHub, or Hugging Face and spend 100 one-time free credits on your own text.