> ## 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 `8000`; higher values are clamped) 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>
  the current python sdk uses the original wav and raw pcm defaults. to request
  opus, pcm, mulaw, alaw, or mp3, use the direct api examples in
  [audio formats](/audio-formats).
</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` | legacy wire-compatibility metadata. |

## 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` | legacy wire-compatibility metadata. |

**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. `credits_used` is retained as wire-compatibility metadata. |
| `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.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.