# @mapmap/voice reference (beta)

> Generated from the published **@mapmap/voice 0.1.0-beta.1** (beta) type declarations by `website/scripts/sdk-reference.mjs`. Do not edit by hand: change the JSDoc in the package, or the examples in `website/content/sdk-reference/voice.examples.md`, and regenerate.

**Beta.** Every public export of the voice guidance phrasebook and registry, at the published beta. The API may change before 0.1.0; the repository is already ahead of this release, and this page describes what npm serves. The guide is in [SDKs & installation](/docs/sdks).

```bash
npm install @mapmap/voice@0.1.0-beta.1
```

Other SDK references: [@mapmap/maps](/docs/maps-reference), [@mapmap/points](/docs/points-reference), [@mapmap/core](/docs/core-reference), [@mapmap/tile](/docs/tile-reference), [@mapmap/react-native](/docs/react-native-reference), [MapMapKit](/docs/ios-reference), [ai.mapmap:core](/docs/android-reference).

67 exports across 1 entry point: 24 functions, 15 interfaces, 18 types, 10 constants.

## How to read this page

Each entry gives the declaration as published, the JSDoc description, and a table of parameters or properties with their types. The **Default** column quotes the default the source states (an `@default` tag or a "Default ..." sentence in the JSDoc). "required" means the property must be given; "not stated" means the source gives no default, so none is claimed here. Classes, factories and the main entry functions each carry a minimal example; examples marked *from the source* are the package's own `@example`.

## Contents

- [`@mapmap/voice`](#mapmapvoice): 67 exports

## `@mapmap/voice`

```ts
import { /* … */ } from "@mapmap/voice";
```

### Functions (@mapmap/voice)

[`allowedVoice`](#allowedvoice), [`bucket`](#bucket), [`clipKey`](#clipkey), [`defaultGenderFor`](#defaultgenderfor), [`definePhrases`](#definephrases), [`enumerateFixed`](#enumeratefixed), [`formatDistance`](#formatdistance), [`formatRate`](#formatrate), [`fromValhalla`](#fromvalhalla), [`fromValhallaRoute`](#fromvalhallaroute), [`gendersFor`](#gendersfor), [`headingFromBearing`](#headingfrombearing), [`isRef`](#isref), [`ladder`](#ladder), [`normaliseClipText`](#normalisecliptext), [`normaliseRef`](#normaliseref), [`parseRegistry`](#parseregistry), [`phrase`](#phrase), [`providerClass`](#providerclass), [`render`](#render), [`sha256Hex`](#sha256hex), [`supportedLocale`](#supportedlocale), [`voiceById`](#voicebyid), [`voiceFor`](#voicefor)

#### `allowedVoice`

*Function*

```ts
function allowedVoice(voice: VoiceEntry, providers: readonly ProviderClass[], from?: Registry): VoiceEntry | undefined;
```

A voice, or the first voice down its `fallback` chain whose provider is
in `providers`. `undefined` when the chain has none.

| Parameter | Type | Default | Description |
| --- | --- | --- | --- |
| `voice` | [`VoiceEntry`](#voiceentry) | required |  |
| `providers` | [`readonly ProviderClass[]`](#providerclass-1) | required |  |
| `from?` | [`Registry`](#registry) | not stated |  |

Options and types: [`VoiceEntry`](#voiceentry), [`ProviderClass`](#providerclass-1), [`Registry`](#registry).

#### `bucket`

*Function*

```ts
function bucket(units: Units, metres: number): Rung;
```

The rung a distance is spoken as. Negative and non-finite distances count as zero.

| Parameter | Type | Default | Description |
| --- | --- | --- | --- |
| `units` | [`Units`](#units) | required |  |
| `metres` | `number` | required |  |

Options and types: [`Units`](#units).

#### `clipKey`

*Function*

```ts
function clipKey(voice: VoiceEntry | string, text: string, options: ClipKeyOptions): string;
```

The content address of one clip, 64 lower-case hex digits.

- Voices with `languageCode: null` (Amelia) keep the legacy key, so every
  existing clip stays valid:
  `sha256("<provider>|<voiceId>|<model>|<format>|<rate 2dp>|<text>")`.
- Voices with a `languageCode` use v2:
  `sha256("v2|<provider>|<voiceId>|<model>|<languageCode>|<seed or ->|<stability or ->|<dictionary or ->|<format>|<rate 2dp>|<text>")`.

`text` is normalised first (see `normaliseClipText`). Throws when the
voice has no provider id yet and none is passed in `options.voiceId`.

| Parameter | Type | Default | Description |
| --- | --- | --- | --- |
| `voice` | `VoiceEntry \| string` | required |  |
| `text` | `string` | required |  |
| `options` | [`ClipKeyOptions`](#clipkeyoptions) | required |  |

Options and types: [`VoiceEntry`](#voiceentry), [`ClipKeyOptions`](#clipkeyoptions).

**Example**

```ts
import { clipKey, voiceFor } from "@mapmap/voice";

// The content address of one clip, as the gateway computes it.
const key = clipKey(voiceFor("en-GB"), "Turn left onto Baldwin Street.", { format: "m4a" });
console.log(key.length); // 64
```

#### `defaultGenderFor`

*Function*

```ts
function defaultGenderFor(locale: Locale): Gender;
```

The gender a locale speaks in when the caller names none.

| Parameter | Type | Default | Description |
| --- | --- | --- | --- |
| `locale` | [`Locale`](#locale) | required |  |

Options and types: [`Locale`](#locale).

#### `definePhrases`

*Function*

```ts
function definePhrases(locale: Locale, table: Record<string, Template>): void;
```

Registers an app's own phrases for a locale (Pacmap's chunk and rig
lines, for example). Every slot is closed except at most one `"name"`
slot, which must end the template. Core ids cannot be overridden.
Registering an id again replaces it. The object form's `byGender` gives
the text for one speaker gender where it differs (same placeholders);
`phrase` and `enumerateFixed` pick it by the speaker's gender.

| Parameter | Type | Default | Description |
| --- | --- | --- | --- |
| `locale` | [`Locale`](#locale) | required |  |
| `table` | `Record<string, Template>` | required |  |

Options and types: [`Locale`](#locale), [`Template`](#template).

#### `enumerateFixed`

*Function*

```ts
function enumerateFixed(locale: Locale, units: Units, options?: SpeakerOptions): string[];
```

Every fixed part text the locale can produce in `units`: the bake list
for a `splice` voice, and the closed half of every `sentence`. Includes
the core phrases and the app phrases registered so far with
`definePhrases` (their closed slots expanded). Sorted, no duplicates.
The list is for one speaker: `options.gender` (default: the locale's
`defaultGender`), because a voice only ever says its own gender's text.

| Parameter | Type | Default | Description |
| --- | --- | --- | --- |
| `locale` | [`Locale`](#locale) | required |  |
| `units` | [`Units`](#units) | required |  |
| `options?` | [`SpeakerOptions`](#speakeroptions) | not stated |  |

Options and types: [`Locale`](#locale), [`Units`](#units), [`SpeakerOptions`](#speakeroptions).

#### `formatDistance`

*Function*

```ts
function formatDistance(locale: Locale, units: Units, metres: number): Utterance;
```

The spoken words for a distance, as the preview would say it without "In": "four hundred yards".

| Parameter | Type | Default | Description |
| --- | --- | --- | --- |
| `locale` | [`Locale`](#locale) | required |  |
| `units` | [`Units`](#units) | required |  |
| `metres` | `number` | required |  |

Options and types: [`Locale`](#locale), [`Units`](#units).

#### `formatRate`

*Function*

```ts
function formatRate(rate: number): string;
```

The rate at two decimals, as the key spells it. Pass rates that are
already at two decimals (1, 0.95, 1.1): an exact binary tie such as
1.125 rounds up here and half-to-even in Rust's `{:.2}`.

| Parameter | Type | Default | Description |
| --- | --- | --- | --- |
| `rate` | `number` | required |  |

#### `fromValhalla`

*Function*

```ts
function fromValhalla(m: ValhallaManeuver, context?: ValhallaContext): GuidanceIntent;
```

Builds the intent for one Valhalla manoeuvre. Pass `context` for the two
facts a single manoeuvre does not carry (see `ValhallaContext`).

| Parameter | Type | Default | Description |
| --- | --- | --- | --- |
| `m` | [`ValhallaManeuver`](#valhallamaneuver) | required |  |
| `context?` | [`ValhallaContext`](#valhallacontext) | not stated |  |

Options and types: [`ValhallaManeuver`](#valhallamaneuver), [`ValhallaContext`](#valhallacontext).

**Example**

```ts
import { fromValhalla, render } from "@mapmap/voice";

// One manoeuvre from a Valhalla route response (type 15 is a left turn).
const intent = fromValhalla({ type: 15, street_names: ["Baldwin Street"] });
console.log(render("en-GB", { intent, stage: "imperative", units: "metric" })?.text);
```

#### `fromValhallaRoute`

*Function*

```ts
function fromValhallaRoute(maneuvers: readonly ValhallaManeuver[]): GuidanceIntent[];
```

Intents for a whole manoeuvre list, with each manoeuvre's neighbours
passed as context.

| Parameter | Type | Default | Description |
| --- | --- | --- | --- |
| `maneuvers` | [`readonly ValhallaManeuver[]`](#valhallamaneuver) | required |  |

Options and types: [`ValhallaManeuver`](#valhallamaneuver).

**Example**

```ts
import { fromValhallaRoute, render, type ValhallaManeuver } from "@mapmap/voice";

declare const maneuvers: ValhallaManeuver[]; // trip.legs[0].maneuvers from a Valhalla /route response
for (const intent of fromValhallaRoute(maneuvers)) {
  console.log(render("en-GB", { intent, stage: "imperative", units: "imperial_uk" })?.text);
}
```

#### `gendersFor`

*Function*

```ts
function gendersFor(locale: Locale): Gender[];
```

The genders a locale has a default voice for, in `GENDERS` order.

| Parameter | Type | Default | Description |
| --- | --- | --- | --- |
| `locale` | [`Locale`](#locale) | required |  |

Options and types: [`Locale`](#locale).

#### `headingFromBearing`

*Function*

```ts
function headingFromBearing(bearing: number): Heading;
```

Bearing in degrees to the nearest of eight compass points.

| Parameter | Type | Default | Description |
| --- | --- | --- | --- |
| `bearing` | `number` | required |  |

#### `isRef`

*Function*

```ts
function isRef(text: string): boolean;
```

True when `text` (after `normaliseRef`) reads as a road reference.

| Parameter | Type | Default | Description |
| --- | --- | --- | --- |
| `text` | `string` | required |  |

#### `ladder`

*Function*

```ts
function ladder(units: Units): readonly Rung[];
```

Every rung of a ladder, shortest first.

| Parameter | Type | Default | Description |
| --- | --- | --- | --- |
| `units` | [`Units`](#units) | required |  |

Options and types: [`Units`](#units).

#### `normaliseClipText`

*Function*

```ts
function normaliseClipText(text: string): string;
```

The gateway's text normalisation: trim, collapse whitespace runs to one
space, Unicode NFC. Nothing else (no case folding, no lexicon).

| Parameter | Type | Default | Description |
| --- | --- | --- | --- |
| `text` | `string` | required |  |

#### `normaliseRef`

*Function*

```ts
function normaliseRef(text: string): string;
```

Removes the spaces inside a ref: "B 2" -> "B2", "A 1 (M)" -> "A1(M)".

| Parameter | Type | Default | Description |
| --- | --- | --- | --- |
| `text` | `string` | required |  |

#### `parseRegistry`

*Function*

```ts
function parseRegistry(raw: unknown): Registry;
```

Validates a registry document (version 3): every voice has a gender and
a known provider; every default names a voice of that locale and gender;
every locale has a `defaultGender` with a default voice; every
`fallback` is an open-source voice of the same locale and gender, with no
cycles. Exported for tests and for tools that load an override file.

| Parameter | Type | Default | Description |
| --- | --- | --- | --- |
| `raw` | `unknown` | required |  |

#### `phrase`

*Function*

```ts
function phrase(locale: Locale, id: PhraseId, params?: Record<string, string | number>, options?: SpeakerOptions): Utterance;
```

A fixed phrase by id: a core id, or one registered with `definePhrases`
for this locale. Throws on an unknown id or a value outside a closed slot.
`options.gender` is the speaker's gender (default: the locale's
`defaultGender`).

| Parameter | Type | Default | Description |
| --- | --- | --- | --- |
| `locale` | [`Locale`](#locale) | required |  |
| `id` | [`PhraseId`](#phraseid) | required |  |
| `params?` | `Record<string, string \| number>` | not stated |  |
| `options?` | [`SpeakerOptions`](#speakeroptions) | not stated |  |

Options and types: [`Locale`](#locale), [`PhraseId`](#phraseid), [`SpeakerOptions`](#speakeroptions).

#### `providerClass`

*Function*

```ts
function providerClass(provider: VoiceProvider): ProviderClass;
```

The class a provider belongs to.

| Parameter | Type | Default | Description |
| --- | --- | --- | --- |
| `provider` | [`VoiceProvider`](#voiceprovider) | required |  |

Options and types: [`VoiceProvider`](#voiceprovider).

#### `render`

*Function*

```ts
function render(locale: Locale, input: RenderInput, options?: SpeakerOptions): Utterance | null;
```

Renders one prompt. Returns null when there is nothing to say (for
example a `depart` with neither a heading nor a road).

- `preview` with `distanceM`: "In four hundred yards," + the clause.
  A `continue` preview is the post-manoeuvre confirmation instead:
  "Continue for two miles on York Street." A preview without a distance
  is the imperative.
- `imperative`: the clause on its own.
- `then`: the clause as a stand-alone follow-up ("Then turn left.").
- `then` in the input chains a second clause (", then turn left"). The
  chained clause carries a name only when the first clause has none, so
  an utterance never has more than one open slot.

`options.gender` is the speaker's gender (default: the locale's
`defaultGender`). No v1 locale changes its words with it; the argument
exists for languages that do, and so that the text a voice hashes is
the text that voice would say.

| Parameter | Type | Default | Description |
| --- | --- | --- | --- |
| `locale` | [`Locale`](#locale) | required |  |
| `input` | [`RenderInput`](#renderinput) | required |  |
| `options?` | [`SpeakerOptions`](#speakeroptions) | not stated |  |

Options and types: [`Locale`](#locale), [`RenderInput`](#renderinput), [`SpeakerOptions`](#speakeroptions).

**Example**

```ts
import { render } from "@mapmap/voice";

const utterance = render("en-GB", {
  intent: { kind: "turn", side: "right", target: { kind: "name", text: "York Street" } },
  stage: "preview",
  units: "imperial_uk",
  distanceM: 370,
});
console.log(utterance?.text);
```

#### `sha256Hex`

*Function*

```ts
function sha256Hex(str: string): string;
```

SHA-256 of the UTF-8 encoding of `str`, as 64 lower-case hex digits.

| Parameter | Type | Default | Description |
| --- | --- | --- | --- |
| `str` | `string` | required |  |

#### `supportedLocale`

*Function*

```ts
function supportedLocale(deviceLocale: string): {
    locale: Locale;
    fallback: boolean;
};
```

The phrasebook locale for a device locale ("de_AT", "fr-CA", "en-AU",
"pt-BR"). A device in a supported language gets that language, with
`fallback: false`. Anything else gets en-GB with `fallback: true`, and
because the numbers are words from the en-GB ladder the old
"In 1,5 kilometres" defect cannot recur.

| Parameter | Type | Default | Description |
| --- | --- | --- | --- |
| `deviceLocale` | `string` | required |  |

#### `voiceById`

*Function*

```ts
function voiceById(id: string): VoiceEntry | undefined;
```

A registry voice by id, or `undefined`.

| Parameter | Type | Default | Description |
| --- | --- | --- | --- |
| `id` | `string` | required |  |

#### `voiceFor`

*Function*

```ts
function voiceFor(locale: Locale, gender?: Gender, options?: VoiceForOptions): VoiceEntry;
```

The default voice for a locale and gender. With no gender, the locale's
`defaultGender` (female for every locale today), so `voiceFor(locale)`
means what it meant before voices had a gender. With
`options.providers`, a default outside those provider classes is
replaced by its fallback (`voiceFor("en-GB", "female", { providers:
["open"] })` is the open-source female voice). Throws when the locale
has no voice of that gender (see `gendersFor`), or none the providers
allow.

This is the voice a request asks for. The gateway can still answer a
clip with the fallback voice (when ElevenLabs fails), so a client takes
each clip's hash and url from the response, never from `clipKey` of the
voice it asked for.

| Parameter | Type | Default | Description |
| --- | --- | --- | --- |
| `locale` | [`Locale`](#locale) | required |  |
| `gender?` | [`Gender`](#gender) | not stated |  |
| `options?` | [`VoiceForOptions`](#voiceforoptions) | not stated |  |

Options and types: [`Locale`](#locale), [`Gender`](#gender), [`VoiceForOptions`](#voiceforoptions).

**Example**

```ts
import { voiceFor } from "@mapmap/voice";

const voice = voiceFor("en-GB");                                      // the locale's default voice
const open = voiceFor("en-GB", "female", { providers: ["open"] });    // only open-source models
console.log(voice.id, open.provider);
```

### Interfaces (@mapmap/voice)

[`ClipKeyOptions`](#clipkeyoptions), [`GuidanceIntent`](#guidanceintent), [`Part`](#part), [`Registry`](#registry), [`RenderInput`](#renderinput), [`Rung`](#rung), [`SpeakerOptions`](#speakeroptions), [`Target`](#target), [`Utterance`](#utterance), [`ValhallaContext`](#valhallacontext), [`ValhallaManeuver`](#valhallamaneuver), [`ValhallaSign`](#valhallasign), [`ValhallaSignElement`](#valhallasignelement), [`VoiceEntry`](#voiceentry), [`VoiceForOptions`](#voiceforoptions)

#### `ClipKeyOptions`

*Interface*

```ts
interface ClipKeyOptions
```

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `format` | `string` | required | Audio format as the gateway names it ("m4a", "opus"). |
| `rate?` | `number` | 1 | Playback rate, 0.8 to 1.2 on the gateway. Default 1. |
| `voiceId?` | `string` | not stated | Override the provider voice id, as `SN_VOICE_AMELIA_ID` does on the gateway, or to key a candidate voice before the registry names one. |

#### `GuidanceIntent`

*Interface*

```ts
interface GuidanceIntent
```

A locale-neutral description of one manoeuvre. It contains no words
except the target's bare name.

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `kind` | [`Kind`](#kind) | required |  |
| `side?` | [`Side`](#side) | not stated |  |
| `sharpness?` | [`Sharpness`](#sharpness) | not stated |  |
| `exit?` | `number` | not stated | Roundabout exit ordinal, 1..n. Ordinals are spoken up to ten. |
| `exitNumber?` | `string` | not stated | Motorway junction number as signed ("12"). Spoken for 1 to 99. |
| `target?` | [`Target`](#target) | not stated | At most one. |
| `arriveSide?` | `"left" \| "right" \| "ahead"` | not stated |  |
| `heading?` | [`Heading`](#heading) | not stated | Contract addition: compass heading for `depart`. |
| `stay?` | `boolean` | not stated | Contract addition: the target is the road the driver is already on ("Keep right to stay on the A33"). Only meaningful with a name or ref target. |

#### `Part`

*Interface*

```ts
interface Part
```

One clip's worth of an utterance. `fixed` parts come from the closed
phrasebook (see `enumerateFixed`); a `name` part is the open slot, the
bare target text, and is synthesised once per name and locale.

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `kind` | `"fixed" \| "name"` | required |  |
| `text` | `string` | required |  |

#### `Registry`

*Interface*

```ts
interface Registry
```

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `version` | `number` | required |  |
| `defaults` | `Readonly<Record<Locale, GenderDefaults>>` | required | Locale to its default voice id per gender. |
| `defaultGender` | `Readonly<Record<Locale, Gender>>` | required | Locale to the gender used when a caller names none. |
| `voices` | [`readonly VoiceEntry[]`](#voiceentry) | required |  |

#### `RenderInput`

*Interface*

```ts
interface RenderInput
```

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `intent` | [`GuidanceIntent`](#guidanceintent) | required |  |
| `stage` | [`Stage`](#stage) | required |  |
| `units` | [`Units`](#units) | required |  |
| `distanceM?` | `number` | not stated | Preview only; bucketed by the locale ladder. |
| `then?` | [`GuidanceIntent`](#guidanceintent) | not stated | "then" chaining for close manoeuvres. |

#### `Rung`

*Interface*

```ts
interface Rung
```

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `unit` | [`DistanceUnit`](#distanceunit) | required |  |
| `value` | `number` | required | In `unit`: 150 (m), 1.5 (km), 0.25 (mi). |
| `metres` | `number` | required | The rung's length in metres, used for bucketing. |

#### `SpeakerOptions`

*Interface*

```ts
interface SpeakerOptions
```

Who is speaking. Some languages change the words with the speaker's
gender (a Thai polite particle, Hebrew or Hindi verb agreement), so the
text of a sentence can depend on the voice that will say it. Absent,
the gender is the locale's `defaultGender` from the registry.

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `gender?` | [`Gender`](#gender) | not stated |  |

#### `Target`

*Interface*

```ts
interface Target
```

The one open slot. `text` is the bare form as signed: no article, no case
ending, no contraction. A `ref` is normalised by removing spaces
("B 2" -> "B2", "A 1 (M)" -> "A1(M)"); hyphens stay ("A-6", "I-95").

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `kind` | `"name" \| "ref" \| "toward"` | required |  |
| `text` | `string` | required |  |

#### `Utterance`

*Interface*

```ts
interface Utterance
```

A rendered sentence. `text` is the whole utterance, the string a
`sentence`-mode voice synthesises and hashes. `parts` is the same
sentence cut at clip boundaries for a `splice`-mode voice; parts carry
no joining whitespace and the name part carries no terminal punctuation.

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `parts` | [`Part[]`](#part) | required |  |
| `text` | `string` | required |  |
| `locale` | [`Locale`](#locale) | required |  |

#### `ValhallaContext`

*Interface*

```ts
interface ValhallaContext
```

Neighbouring manoeuvres. Contract addition, optional: a roundabout's
own `street_names` is the roundabout, so the road it is left onto comes
from the following exit manoeuvre (`next`); "to stay on" needs the road
being driven before the manoeuvre (`previous`).

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `previous?` | [`ValhallaManeuver`](#valhallamaneuver) | not stated |  |
| `next?` | [`ValhallaManeuver`](#valhallamaneuver) | not stated |  |

#### `ValhallaManeuver`

*Interface*

```ts
interface ValhallaManeuver
```

The fields of a Valhalla manoeuvre this package reads. Others may be present and are ignored.

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `type` | `number` | required |  |
| `street_names?` | `string[]` | not stated |  |
| `begin_street_names?` | `string[]` | not stated |  |
| `sign?` | [`ValhallaSign`](#valhallasign) | not stated |  |
| `roundabout_exit_count?` | `number` | not stated |  |
| `bearing_before?` | `number` | not stated |  |
| `bearing_after?` | `number` | not stated |  |
| `highway?` | `boolean` | not stated |  |
| `instruction?` | `string` | not stated | Present in real responses; never read. |

#### `ValhallaSign`

*Interface*

```ts
interface ValhallaSign
```

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `exit_number_elements?` | [`ValhallaSignElement[]`](#valhallasignelement) | not stated |  |
| `exit_branch_elements?` | [`ValhallaSignElement[]`](#valhallasignelement) | not stated |  |
| `exit_toward_elements?` | [`ValhallaSignElement[]`](#valhallasignelement) | not stated |  |
| `exit_name_elements?` | [`ValhallaSignElement[]`](#valhallasignelement) | not stated |  |

#### `ValhallaSignElement`

*Interface*

```ts
interface ValhallaSignElement
```

Valhalla manoeuvre -> `GuidanceIntent`, from STRUCTURED fields only.

Read: `type`, `street_names`, `begin_street_names`, `sign.*_elements`,
`roundabout_exit_count`, `bearing_after`, `highway`. Never read:
`instruction` and every `verbal_*` string. Those are English (or whatever
language the route was requested in), and pattern-matching them is what
broke the moment a route came back in another language.

Field reference: Valhalla turn-by-turn API, "Maneuvers" and "Sign"
(https://valhalla.github.io/valhalla/api/turn-by-turn/api-reference/).

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `text` | `string` | required |  |
| `consecutive_count?` | `number` | not stated |  |
| `is_route_number?` | `boolean` | not stated |  |

#### `VoiceEntry`

*Interface*

```ts
interface VoiceEntry
```

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `id` | `string` | required | Registry id, the value clients send as `voice` ("amelia"). |
| `locale` | [`Locale`](#locale) | required |  |
| `gender` | [`Gender`](#gender) | required | The voice's gender. A locale has at most one default voice per gender. |
| `provider` | [`VoiceProvider`](#voiceprovider) | required |  |
| `voiceId` | `string \| null` | required | Provider voice id (the speaker id for an open-source model). `null` means not chosen yet: the gateway refuses synthesis (409 voice_not_ready). |
| `model` | `string` | required | Synthesis model. Open-source voices pin a revision: `kokoro-82m@<rev>`, `supertonic-3@<rev>`. |
| `languageCode` | `string \| null` | required | `null` keeps the legacy (v1) clip key. |
| `seed` | `number \| null` | required |  |
| `stability` | `string \| null` | required |  |
| `dictionary` | `string \| null` | required |  |
| `compose` | [`ComposeMode`](#composemode) | required | `sentence`: one clip per utterance. `splice`: fixed clips plus a name clip. |
| `status` | [`VoiceStatus`](#voicestatus) | required | `candidate` voices are used only behind a developer flag until a native speaker signs off. |
| `fallback` | `string \| null` | required | The voice the gateway speaks with when this one cannot: an ElevenLabs synthesis that fails (quota, payment, rate limit, provider down), or a request that forbids this voice's provider. Same locale and gender, never an ElevenLabs voice. `null` when there is none. |

#### `VoiceForOptions`

*Interface*

```ts
interface VoiceForOptions
```

Options for `voiceFor`.

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `providers?` | [`readonly ProviderClass[]`](#providerclass-1) | not stated | Provider classes the caller allows. A default voice outside them is replaced by the first voice down its `fallback` chain that is inside them (`["open"]` is the council policy: never ElevenLabs). Absent allows every provider. |

### Types (@mapmap/voice)

[`ComposeMode`](#composemode), [`CorePhraseId`](#corephraseid), [`DistanceUnit`](#distanceunit), [`Gender`](#gender), [`GenderDefaults`](#genderdefaults), [`Heading`](#heading), [`Kind`](#kind), [`Locale`](#locale), [`PhraseId`](#phraseid), [`ProviderClass`](#providerclass-1), [`Sharpness`](#sharpness), [`Side`](#side), [`SlotSpec`](#slotspec), [`Stage`](#stage), [`Template`](#template), [`Units`](#units), [`VoiceProvider`](#voiceprovider), [`VoiceStatus`](#voicestatus)

#### `ComposeMode`

*Type*

```ts
type ComposeMode = "sentence" | "splice";
```

#### `CorePhraseId`

*Type*

```ts
type CorePhraseId = "off_route" | "rerouting" | "back_on_route" | "arrived" | "arrive_left" | "arrive_right" | "gps_lost" | "gps_back" | "level_crossing" | "speed_camera" | "ferry_ahead" | "toll_ahead" | "faster_route" | "average_speed";
```

The phrase ids every locale defines.

#### `DistanceUnit`

*Type*

```ts
type DistanceUnit = "m" | "km" | "yd" | "ft" | "mi";
```

The closed distance ladders. A spoken distance is always one rung of a
ladder, so the set of distance phrases is finite and can be baked.

Rungs (phrasing research §3.1, shared by every locale):
  metric       50, 100, 150, 200, 250, 300, 400, 500, 600, 700, 800 m;
               1, 1.5, 2, 2.5, 3 km; 4 to 10 km; 15 to 100 km in fives
  imperial_uk  50 to 400 yd in fifties; a quarter, half, three quarters
               of a mile; 1, 1.5, 2, 2.5, 3 mi; 4 to 10 mi; 15 to 100 mi
  imperial_us  100, 200, 300, 400, 500, 600, 800, 1000 ft; then the same
               miles as imperial_uk. Never yards.

A distance goes to the NEAREST rung (linear), clamped to the ends.

#### `Gender`

*Type*

```ts
type Gender = "female" | "male";
```

A voice's gender. Every locale has a default gender (`defaultGender` in
the registry) and may have one voice of each.

#### `GenderDefaults`

*Type*

```ts
type GenderDefaults = Readonly<Partial<Record<Gender, string>>>;
```

A locale's default voice ids by gender. A gender is absent until a voice exists for it.

#### `Heading`

*Type*

```ts
type Heading = "n" | "ne" | "e" | "se" | "s" | "sw" | "w" | "nw";
```

Eight-point compass heading, used by `depart` only. Contract addition.

#### `Kind`

*Type*

```ts
type Kind = "depart" | "continue" | "name_change" | "turn" | "uturn" | "keep" | "merge" | "ramp_on" | "exit" | "roundabout_enter" | "roundabout_exit" | "ferry_enter" | "ferry_exit" | "arrive";
```

What kind of manoeuvre is being described. Closed set.

#### `Locale`

*Type*

```ts
type Locale = "en-GB" | "en-US" | "fr-FR" | "de-DE" | "es-ES" | "it-IT" | "nl-NL";
```

The locales the phrasebook speaks in v1.

#### `PhraseId`

*Type*

```ts
type PhraseId = CorePhraseId | (string & {});
```

A core id, or an id an app registered with `definePhrases`. The
`string & {}` keeps editor completion for the core ids.

#### `ProviderClass`

*Type*

```ts
type ProviderClass = "elevenlabs" | "open";
```

The provider classes a caller can allow or forbid (`providers` on
`POST /v1/voice/clips`, and on `voiceFor`): `elevenlabs`, or `open` for
every self-hosted open-source model.

#### `Sharpness`

*Type*

```ts
type Sharpness = "slight" | "normal" | "sharp";
```

#### `Side`

*Type*

```ts
type Side = "left" | "right" | "straight";
```

#### `SlotSpec`

*Type*

```ts
type SlotSpec = readonly (string | number)[] | {
    cardinal: readonly [number, number];
} | "name";
```

A closed slot in an app template: a list of allowed values, or a
cardinal range the locale spells out in words. `"name"` marks the single
open slot, which must be the template's last placeholder.

#### `Stage`

*Type*

```ts
type Stage = "preview" | "imperative" | "then";
```

`preview`: the early prompt, led by a distance when one is given
("In four hundred yards, turn right onto York Street.").
`imperative`: the prompt at the manoeuvre ("Turn right onto York Street.").
`then`: the intent spoken as a follow-up on its own ("Then turn left.").

#### `Template`

*Type*

```ts
type Template = string | {
    text: string;
    slots?: Readonly<Record<string, SlotSpec>>;
    byGender?: Readonly<Partial<Record<Gender, string>>>;
};
```

An app phrase. A plain string has no placeholders. The object form names
each `{slot}` in `text`. Contract addition: the contract names the type
without defining it.

#### `Units`

*Type*

```ts
type Units = "metric" | "imperial_uk" | "imperial_us";
```

The distance system the driver chose. It is a setting of its own, not a
property of the language: a French driver in the UK can hear miles.
`imperial_uk` speaks yards below a quarter of a mile; `imperial_us`
speaks feet and never yards.

#### `VoiceProvider`

*Type*

```ts
type VoiceProvider = "elevenlabs" | "kokoro" | "supertonic";
```

Who synthesises a voice. `elevenlabs` is the hosted provider; `kokoro`
(Kokoro-82M) and `supertonic` (Supertonic 3) are open-source models the
gateway runs itself.

#### `VoiceStatus`

*Type*

```ts
type VoiceStatus = "live" | "candidate";
```

### Constants (@mapmap/voice)

[`CORE_PHRASE_IDS`](#core_phrase_ids), [`GENDERS`](#genders), [`HEADINGS`](#headings), [`KINDS`](#kinds), [`LOCALES`](#locales), [`PROVIDER_CLASSES`](#provider_classes), [`registry`](#registry-1), [`REGISTRY_VERSION`](#registry_version), [`UNITS`](#units-1), [`VOICE_PROVIDERS`](#voice_providers)

#### `CORE_PHRASE_IDS`

*Constant*

```ts
const CORE_PHRASE_IDS: readonly CorePhraseId[];
```

#### `GENDERS`

*Constant*

```ts
const GENDERS: readonly Gender[];
```

Every voice gender.

#### `HEADINGS`

*Constant*

```ts
const HEADINGS: readonly Heading[];
```

#### `KINDS`

*Constant*

```ts
const KINDS: readonly Kind[];
```

#### `LOCALES`

*Constant*

```ts
const LOCALES: readonly Locale[];
```

Every supported locale, in registry order.

#### `PROVIDER_CLASSES`

*Constant*

```ts
const PROVIDER_CLASSES: readonly ProviderClass[];
```

Every provider class.

#### `registry`

*Constant*

```ts
const registry: Registry;
```

The shared voice registry (`voices.json`), validated.

#### `REGISTRY_VERSION`

*Constant*

```ts
const REGISTRY_VERSION = 3;
```

Registry format this package reads. Version 3 added open-source providers and `fallback`.

#### `UNITS`

*Constant*

```ts
const UNITS: readonly Units[];
```

#### `VOICE_PROVIDERS`

*Constant*

```ts
const VOICE_PROVIDERS: readonly VoiceProvider[];
```

Every provider a registry voice may name.
