> ## Documentation Index
> Fetch the complete documentation index at: https://docs.rumik.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# synthesis

> batch, streaming, sessions, and async with the rumik-ai client.

you can synthesize in three ways, depending on how soon you need the audio and whether
it is a one-shot clip or an ongoing conversation: **batch** (`create`), **streaming**
(`stream`), and **sessions** (`session`). all three come in a sync and an async form.

## batch synthesis

`create` is the simplest path: send text, get an [`Audio`](#the-audio-object) object
back (a ready-to-play 24 kHz mono WAV). reach for it whenever you can wait for the whole
clip before playing it.

<Tabs>
  <Tab title="sync">
    ```python theme={null}
    from rumikai import Rumik

    client = Rumik()
    audio = client.speech.create(text="[happy] Namaste! Kaise hain aap?", model="muga")
    audio.save("hello.wav")
    ```
  </Tab>

  <Tab title="async">
    ```python theme={null}
    import asyncio
    from rumikai import AsyncRumik

    async def main():
        async with AsyncRumik() as client:
            audio = await client.speech.create(
                text="[happy] Namaste! Kaise hain aap?", model="muga"
            )
            audio.save("hello.wav")

    asyncio.run(main())
    ```
  </Tab>
</Tabs>

`create(text, model="muga", ...)`. only `text` is required.

| parameter            | type    | default  | notes                                                                                     |
| -------------------- | ------- | -------- | ----------------------------------------------------------------------------------------- |
| `text`               | `str`   | required | the text to synthesize. empty text raises `ValueError`.                                   |
| `model`              | `str`   | `"muga"` | `"muga"` or `"mulberry"`.                                                                 |
| `description`        | `str`   | `None`   | required for `mulberry`. natural-language voice description.                              |
| `speaker`            | `str`   | `None`   | `mulberry` only, optional. a named voice, e.g. `"ira"`.                                   |
| `temperature`        | `float` | `None`   | sampling temperature.                                                                     |
| `top_p`              | `float` | `None`   | nucleus sampling.                                                                         |
| `top_k`              | `int`   | `None`   | top-k sampling.                                                                           |
| `repetition_penalty` | `float` | `None`   | penalize repeated tokens.                                                                 |
| `max_new_tokens`     | `int`   | `None`   | output length cap. raise it (up to `8192`) if long `mulberry` audio comes back truncated. |
| `timeout`            | `float` | `None`   | per-request timeout override, in seconds.                                                 |
| `extra_headers`      | `dict`  | `None`   | extra HTTP headers for this request only.                                                 |

**steering by model:** `muga` uses an inline tone tag (`[happy]`, `[sad]`, `[excited]`,
`[angry]`, `[whisper]`, or `neutral`); `mulberry` always takes a `description`, plus
an optional named `speaker`. see [prompting muga](/prompting-muga) and
[prompting mulberry](/prompting-mulberry).

### the audio object

`create` returns an `Audio` holding the WAV bytes and request metadata.

| member          | description                                  |
| --------------- | -------------------------------------------- |
| `save(path)`    | write the audio to a `.wav` file.            |
| `bytes(audio)`  | the raw bytes (`__bytes__`).                 |
| `len(audio)`    | number of bytes (`__len__`).                 |
| `.is_wav`       | whether the bytes include a WAV header.      |
| `.content_type` | the response content type.                   |
| `.request_id`   | the server request id (from `x-request-id`). |

<Note>
  `create` audio is always a WAV. streaming and session audio is raw PCM; use
  `pcm_to_wav()` or the built-in `save()` helpers to add a WAV header.
</Note>

## streaming

`stream` opens a websocket and hands you raw PCM (24 kHz mono 16-bit) as it is
generated, so playback can start before the sentence finishes synthesizing. it returns
a `SpeechStream` you iterate over inside a `with` block.

<Warning>streaming needs the `ws` extra: `pip install "rumik-ai[ws]"`.</Warning>

<Tabs>
  <Tab title="sync">
    ```python theme={null}
    with client.speech.stream(text="Streaming in real time.", model="mulberry") as stream:
        for chunk in stream:      # raw PCM (24kHz mono 16-bit) as it's generated
            play(chunk)           # feed your audio device
    # or: stream.save("out.wav")
    ```
  </Tab>

  <Tab title="async">
    ```python theme={null}
    async with client.speech.stream(text="Streaming in real time.", model="mulberry") as stream:
        async for chunk in stream:
            await play(chunk)
    ```
  </Tab>
</Tabs>

`stream(text, model="muga", description=None, speaker=None, timeout=None,
idle_timeout=30)`. `idle_timeout` closes the socket after that many seconds with no
audio.

| member          | description                              |
| --------------- | ---------------------------------------- |
| iterate         | yields raw PCM chunks (`bytes`).         |
| `.read()`       | all remaining PCM as one `bytes` object. |
| `.save(path)`   | drain the stream and write a WAV file.   |
| `.request_id`   | the server request id.                   |
| `.credits_used` | credits billed for the stream.           |

## sessions and voice agents

a session keeps a single websocket open across a whole conversation. you `send` text,
read back **events** as they arrive, and call `interrupt()` the moment the user starts
talking over the agent. this is what you build a real-time voice agent on.

<Warning>sessions need the `ws` extra: `pip install "rumik-ai[ws]"`.</Warning>

```python theme={null}
from rumikai import Rumik, AudioChunk, UtteranceDone

client = Rumik()
with client.speech.session(
    model="mulberry",
    description="a warm 30s female voice, smooth timbre, conversational pacing",
    speaker="ira",
) as session:
    session.send("Hello! How can I help you today?")
    for event in session:
        if isinstance(event, AudioChunk):
            play(event.data)
            if user_started_talking():        # barge-in
                session.interrupt()
                session.send("Sorry, go ahead.")
        elif isinstance(event, UtteranceDone):
            break
```

`session(model="muga", description=None, speaker=None, timeout=None, idle_timeout=30)`
returns a `SpeechSession`, a context manager.

| member               | description                                                            |
| -------------------- | ---------------------------------------------------------------------- |
| `send(text)`         | synthesize a new utterance. sending again mid-utterance is a barge-in. |
| `interrupt()`        | stop the current utterance immediately.                                |
| `close()`            | end the session (or leave the `with` block).                           |
| iterate              | yields session events as they arrive.                                  |
| `iter_audio()`       | yields only the PCM chunks, skipping control events.                   |
| `.last_request_id`   | request id of the most recent utterance.                               |
| `.last_credits_used` | credits billed for the most recent utterance.                          |

**events** yielded while iterating:

| event                | attributes                     | meaning                                                                                                                    |
| -------------------- | ------------------------------ | -------------------------------------------------------------------------------------------------------------------------- |
| `AudioChunk`         | `.data`                        | a raw PCM chunk (24 kHz mono 16-bit).                                                                                      |
| `UtteranceDone`      | `.request_id`, `.credits_used` | the utterance finished normally.                                                                                           |
| `UtteranceCancelled` | `.request_id`, `.reason`       | the utterance was stopped. `.reason` is `"interrupt"` (a new `send` replaced it) or `"cancel"` (you called `interrupt()`). |

<Warning>
  a session does **not** auto-reconnect if the socket drops. detect the error, open a
  new session, and resend. input is always **whole utterances**; there is no partial or
  streaming text input.
</Warning>

## async

`AsyncRumik` is the same client with awaitable calls, so it drops straight into an async
app. `create`, `stream`, and `session` all work with `await`, `async with`, and
`async for`.

```python theme={null}
import asyncio
from rumikai import AsyncRumik

async def main():
    async with AsyncRumik() as client:
        clips = await asyncio.gather(
            client.speech.create(text="One"),
            client.speech.create(text="Two"),
            client.speech.create(text="Three"),
        )
        for i, audio in enumerate(clips):
            audio.save(f"clip_{i}.wav")

asyncio.run(main())
```

`asyncio.gather` runs requests in parallel over one shared connection pool. reuse a
single `AsyncRumik` rather than one per request.
