# murmr

Python SDK for the [murmr](https://murmr.dev) TTS API. Async-first with full sync support.

```bash
pip install murmr
```

## Quick Start

### Voice Design (describe any voice in natural language)

```python
from murmr import MurmrClient

client = MurmrClient(api_key="murmr_sk_live_...")

# Generate speech with a voice description
wav = client.voices.design(
    input="Hello, welcome to murmr!",
    voice_description="A warm, friendly female voice with a slight British accent",
)

with open("output.wav", "wb") as f:
    f.write(wav)
```

### Saved Voices (batch via RunPod Serverless)

```python
# Submit a batch job
job = client.speech.create(input="Hello world", voice="voice_abc123")

# Wait for completion
result = client.speech.create_and_wait(input="Hello world", voice="voice_abc123")
audio = result.audio_bytes  # decoded WAV
```

### Streaming

```python
# Stream PCM audio chunks
with client.speech.stream(input="Hello world", voice="voice_abc123") as stream:
    for chunk in stream:
        pcm = chunk.audio_bytes  # 24kHz mono 16-bit PCM
        if chunk.done:
            break
```

### Async

```python
import asyncio
from murmr import AsyncMurmrClient

async def main():
    async with AsyncMurmrClient(api_key="murmr_sk_live_...") as client:
        wav = await client.voices.design(
            input="Hello from async!",
            voice_description="A deep male voice",
        )

asyncio.run(main())
```

### Long-Form Audio

```python
# Automatically chunks, retries, and concatenates
result = client.speech.create_long_form(
    input=very_long_text,
    voice="voice_abc123",
    on_progress=lambda current, total, pct: print(f"{pct}%"),
)

with open("long_form.wav", "wb") as f:
    f.write(result.audio)
```

## API Reference

### Clients

| Class | Description |
|-------|-------------|
| `MurmrClient(api_key=...)` | Sync client (context manager) |
| `AsyncMurmrClient(api_key=...)` | Async client (async context manager) |

### Speech (`client.speech`)

| Method | Returns | Description |
|--------|---------|-------------|
| `create(input, voice, ...)` | `AsyncJobResponse` | Submit batch job |
| `create_and_wait(input, voice, ...)` | `JobStatus` | Submit and poll until done |
| `stream(input, voice, ...)` | Context manager yielding `AudioStreamChunk` | Stream PCM chunks |
| `create_long_form(input, voice, ...)` | `LongFormResult` | Chunk + concat long text |

### Voices (`client.voices`)

| Method | Returns | Description |
|--------|---------|-------------|
| `design(input, voice_description, ...)` | `bytes` (WAV) | Generate with voice description |
| `design_stream(input, voice_description, ...)` | Context manager yielding `AudioStreamChunk` | Stream voice design |

### Jobs (`client.jobs`)

| Method | Returns | Description |
|--------|---------|-------------|
| `get(job_id)` | `JobStatus` | Get job status |
| `wait_for_completion(job_id, ...)` | `JobStatus` | Poll until done/failed |

## Text Formatting

Newline characters in your input text affect prosody:

- `\n` (single newline) creates a **sentence-level breath pause**
- `\n\n` (double newline) creates a **paragraph-level pause** with prosodic reset
- No newlines in long text produces rushed, flat delivery

**Best practice:** Insert `\n` between sentences and `\n\n` between paragraphs. Avoid text with hard line wraps every 60-80 characters (e.g., from PDFs or terminals) — this produces choppy output.

See the [Text Formatting Guide](https://murmr.dev/en/docs/text-formatting) for details and preprocessing examples.

## Supported Languages

Chinese, English, Japanese, Korean, German, French, Russian, Portuguese, Spanish, Italian

## License

MIT
