Skip to content
Version v0.35 ยท supported
[WIP] Pending copyedit and approval.

Speech and prompts

A script can talk back and it can ask. Text-to-speech (TTS) reads text aloud through your output device, so a command can say "shields up" or read out a status. The spoken prompt goes further: it speaks a question and waits for a spoken answer, which is the confirm-or-deny pattern without touching the keyboard.

All of this lives under amri.speech. TTS is on by default and needs no setup. If no voice content is installed, speech degrades to silence and logs one line, so a script should not assume it was heard.

Speaking

amri.speech.speak(text[, opts]) queues an utterance and returns immediately. Speech plays one utterance at a time, so back-to-back speak calls play in spoken order rather than overlapping.

amri.speech.speak("Shields up")

amri.speech.speak_sync(text[, opts]) speaks the same way but blocks the script until the utterance is presented in turn. Use it when the next line depends on the words having been said.

Function Effect
amri.speech.speak(text[, opts]) Queue an utterance. Returns immediately.
amri.speech.speak_sync(text[, opts]) Speak and block until the utterance plays in turn.
amri.speech.stop() Stop the current utterance.
amri.speech.speaking() Whether TTS is currently speaking.

Options

opts is an optional table for speak and speak_sync:

Key Effect
voice A voice name (not a path), as reported by amri.speech.voices() or an alias from your engine.toml [voices] table. An unknown name falls back to the default voice.
volume Playback volume for this utterance.
barge_in true cuts the current utterance, drops the queue, and plays now.
amri.speech.speak("Reloading", {voice = "en_GB-alan-medium", volume = 0.8})

Asking

amri.speech.ask(responses[, opts]) speaks nothing on its own: you speak first, then call it to listen for one of a fixed set of responses. It blocks until one of them is heard and returns that word, or returns nil when opts.timeout seconds pass with no match. This is the confirm-or-deny primitive.

amri.speech.speak("Jettison cargo. Are you sure?")
local answer = amri.speech.ask({"confirm", "abort"}, {timeout = 8})
if answer == "confirm" then
    amri.input.tap("j")
    amri.speech.speak("Cargo away")
else
    -- nil (timeout) or "abort" both cancel
    amri.speech.speak("Cancelled")
end

The response set is constrained: only the listed words can be heard, which keeps a confirmation reliable under game audio. A nil return means no answer arrived in time and should be treated as a cancel.

Listening state

amri.speech.listen(on) does not capture free text. It mutes command listening when on is false and resumes it when on is true. amri.speech.listening() returns whether command listening is currently active. Use these to silence recognition during a cutscene or a menu, then turn it back on.

amri.speech.listen(false)   -- stop reacting to spoken commands
-- ... later ...
amri.speech.listen(true)    -- resume

For free-form transcription (speaking arbitrary text into a document), use the dictation window under amri.speech.dictate.*, which is a separate engine from the command recognizer. See the listening modes page.

Voices

amri.speech.voices() returns a table of the installed voice names, the same names you pass as opts.voice. The table is empty when TTS is unavailable, which is the check a script uses before assuming it can speak.

-- Pick the first British voice if one is installed, else the default.
for _, name in ipairs(amri.speech.voices()) do
    if name:find("^en_GB") then
        amri.speech.speak("Ready", {voice = name})
        break
    end
end

TTS is configured in the [tts] table of your engine.toml. Setting enabled = false there turns spoken output off, and a stripped-down install may ship without any voice content. In both cases amri.speech.speak(...) becomes a silent no-op, so never rely on audio alone to convey something the player must act on.

Speech can be silent

A profile is portable across machines that do not have your exact voice, and TTS can be off entirely. Treat speak as best-effort output, and gate anything that depends on being heard on amri.speech.voices() returning a non-empty list.

A runnable script

A confirm-before-firing command, saved beside the profile and pointed at with script = "confirm_fire.lua":

-- confirm_fire.lua
amri.speech.speak_sync("Arming main gun")
local answer = amri.speech.ask({"fire", "hold"}, {timeout = 6})
if answer == "fire" then
    amri.input.tap("space")
    amri.speech.speak("Firing")
else
    amri.speech.speak("Holding")
end

See also