# `ALLM.SpeechRequest`
[🔗](https://github.com/cykod/ALLM/blob/v0.6.1/lib/allm/speech_request.ex#L1)

A text-to-speech request. Layer A serializable data.

    iex> req = ALLM.SpeechRequest.new(input: "Hello.", voice: "alloy", format: :mp3)
    iex> ALLM.Validate.speech_request(req)
    :ok

## Fields

- `:input`: the text to speak. `""` is constructible, and
  `ALLM.Validate.speech_request/1` is what rejects it.
- `:model`: `nil` means late-resolved (the engine's speech model, then
  the adapter's default).
- `:voice`: a provider-shaped voice name, forwarded as given. ALLM keeps
  no voice catalogue, because valid names differ per provider and per
  model. `nil` lets the adapter choose.
- `:format`: the audio file format to produce, one of `formats/0`, or
  `nil` for the provider's default. The atoms name file formats, not any
  one provider's parameter. A provider that cannot produce a format
  refuses the request, and the atoms are protocol-legal rather than a
  promise that every provider accepts every one. The response reports the
  format that actually arrived (`ALLM.SpeechResponse`'s `:format`).
  `:ulaw` and `:alaw` are headerless G.711 µ-law and A-law telephony
  audio, 8,000 Hz mono; of the bundled adapters only
  `ALLM.Providers.ElevenLabs.Speech` produces them, and
  `ALLM.Providers.OpenAI.Speech` refuses them with `:unsupported_feature`.
- `:instructions`: optional delivery guidance for models that accept it.
- `:sample_rate`: the sample rate in Hz of the audio to produce, or
  `nil` for the adapter's default for the format. It matters most for
  `:pcm`, which is headerless: the bytes cannot be played without knowing
  the rate. The cross-provider default for `:pcm` is 24,000 Hz, so
  switching providers does not silently change the playback rate. The
  adapter contract is to accept its own set of rates per format, refuse
  any other with `:unsupported_feature`, and report the rate that arrived
  in `ALLM.SpeechResponse`'s `:sample_rate` (`nil` when the adapter cannot
  tell).
- `:speed`: optional playback speed. Only "a number greater than zero"
  is checked here, and each provider enforces its own range.
- `:options`: a raw provider-body passthrough for fields ALLM does not
  model. An adapter merges it *under* the fields it sets itself, so an
  option can never override `:input`, `:model` or any other structural
  field, and fields that would change the response shape are dropped.
- `:metadata`: caller-owned. Use string keys when it will round-trip
  through JSON.

There is no `:stream` field. Streaming is a separate call, not a request
flag: the same request goes to `ALLM.stream_synthesize/3`, and
`ALLM.stream_synthesize_input/3` takes one whose `:input` is ignored.

## Construction

`new/1` is a bare `struct!/2` pass-through: unknown keys raise
`KeyError`, and nothing else is checked. `new(sample_rate: -1)` constructs,
and `ALLM.Validate.speech_request/1` is what rejects it.

# `format`

```elixir
@type format() :: :mp3 | :opus | :aac | :flac | :wav | :pcm | :ulaw | :alaw
```

A synthesized audio file format.

# `t`

```elixir
@type t() :: %ALLM.SpeechRequest{
  format: format() | nil,
  input: String.t(),
  instructions: String.t() | nil,
  metadata: map(),
  model: String.t() | nil,
  options: map(),
  sample_rate: pos_integer() | nil,
  speed: number() | nil,
  voice: String.t() | nil
}
```

# `formats`

```elixir
@spec formats() :: [format()]
```

The closed list of `:format` values, and the single source for the
validator and every adapter's format mapping.

An exhaustive `case` over `t:format/0` needs a clause for each. `:ulaw`
and `:alaw` (8 kHz G.711 telephony audio) were added after the first six.

## Examples

    iex> ALLM.SpeechRequest.formats()
    [:mp3, :opus, :aac, :flac, :wav, :pcm, :ulaw, :alaw]

# `new`

```elixir
@spec new(keyword()) :: t()
```

Build a `%SpeechRequest{}` from keyword opts.

Unknown keys raise `KeyError` via `struct!/2`. Call
`ALLM.Validate.speech_request/1` to check the field rules.

## Examples

    iex> req = ALLM.SpeechRequest.new(input: "Hi.")
    iex> {req.input, req.format, req.options}
    {"Hi.", nil, %{}}

---

*Consult [api-reference.md](api-reference.md) for complete listing*
