Giving Jarvis a Voice 🎙️
Typing is fine. Talking is Jarvis. Speech in, speech out, wrapped around the chat loop you already built. The concepts are simple; the trade-offs are the interesting part.
Three pieces, one loop
A voice assistant is your existing chat loop with two converters bolted on either end:
🎤 you speak
|
v
[ Speech-to-Text ] turns audio into text (STT, "transcription")
|
v
[ your chat loop ] the Jarvis you already built (Lessons 55-58)
|
v
[ Text-to-Speech ] turns text into audio (TTS)
|
v
🔊 Jarvis speaks
The middle is done. This lesson is about the two ends, and about the honest cost of doing it well.
Speech to text
You record audio (or stream it live) and send it to a transcription model, which returns text. The dominant open model is OpenAI's Whisper, which you can run through a cloud API or, notably, entirely on your own machine.
# Cloud transcription: send an audio file, get text back.
# (Shape shown; the exact SDK depends on your provider.)
def transcribe(audio_path):
"""Turn a recorded audio file into text."""
from openai import OpenAI # pip install openai
client = OpenAI()
with open(audio_path, "rb") as audio:
result = client.audio.transcriptions.create(
model="whisper-1",
file=audio,
)
return result.text
# text = transcribe("question.wav")
# then feed `text` into your chat loop exactly as if the user had typed it
print("Record audio -> transcribe -> feed the text into the chat loop.")
Record audio -> transcribe -> feed the text into the chat loop.
# Local transcription: nothing leaves your machine. Private and free to run.
def transcribe_locally(audio_path):
"""Transcribe with a Whisper model running on your own hardware."""
import whisper # pip install openai-whisper
model = whisper.load_model("base") # tiny/base/small/medium/large
result = model.transcribe(audio_path)
return result["text"]
# Slower on a laptop, but your voice never leaves the room.
print("Local Whisper: private, free per-use, needs a decent machine.")
Local Whisper: private, free per-use, needs a decent machine.
Text to speech
The reverse: text in, audio out. Quality ranges from the flat robotic voice built into your operating system to cloud voices so natural they are unsettling.
# Cloud TTS: high quality, costs per character, needs the network.
def speak_cloud(text, out_path="reply.mp3"):
"""Generate natural-sounding speech from text."""
from openai import OpenAI
client = OpenAI()
audio = client.audio.speech.create(
model="tts-1",
voice="alloy",
input=text,
)
audio.stream_to_file(out_path)
return out_path
print("Cloud TTS: natural voices, per-character cost, sends your text out.")
# Local/offline TTS: robotic but private, free, and works with no internet.
def speak_locally(text):
"""Speak using a fully offline engine."""
import pyttsx3 # pip install pyttsx3
engine = pyttsx3.init()
engine.say(text)
engine.runAndWait()
# The voice is dated, but nothing is sent anywhere and there is no per-use cost.
print("Local TTS: robotic, private, free, offline.")
Local TTS: robotic, private, free, offline.
The full voice loop
# The assembled shape. Each converter is a swappable function.
def voice_assistant(transcribe, chat_reply, speak):
"""A voice loop: listen, think, speak, repeat. Converters are injected."""
messages = []
while True:
audio_path = record_until_silence() # capture the user speaking
user_text = transcribe(audio_path)
print(f"You said: {user_text}")
if "goodbye" in user_text.lower():
speak("Goodbye.")
break
messages.append({"role": "user", "content": user_text})
reply = chat_reply(messages) # your Lesson 55 loop
messages.append({"role": "assistant", "content": reply})
print(f"Jarvis: {reply}")
speak(reply)
def record_until_silence():
"""Capture microphone audio until the speaker pauses. Uses a mic library."""
... # sounddevice / pyaudio; returns a path to the recorded clip
print("The loop is Lesson 55 with a microphone on the front and a speaker on the back.")
Notice the design: transcribe, chat_reply and speak
are passed in, so you can swap cloud for local without touching the loop. That is the
dependency-injection idea from Lesson 37, and it is what lets you choose per-piece between
quality and privacy.
The trade-offs, honestly
| Choice | Cloud | Local |
|---|---|---|
| Quality | Excellent, natural voices | STT is now very good; TTS is robotic |
| Privacy | Your voice and words leave your machine | Nothing leaves the room |
| Cost | Per use, small but real | Free per use; needs decent hardware |
| Latency | Network round-trips add up | Depends on your machine; can be faster or slower |
| Offline | No | Yes, works with no internet |
Latency is the thing that makes or breaks a voice assistant, and it is easy to underestimate. You now have three sequential steps, each with its own delay: transcribe, generate, synthesise. Add them up and a 'quick' spoken exchange can take five seconds, which feels broken.
This is why serious voice assistants stream everything: they start transcribing while you are still speaking, start generating from the partial transcript, and start speaking the first sentence of the reply before the rest is written. Streaming (Lesson 56) is not a nicety here. It is the difference between usable and unusable.
The privacy question you must answer
A voice assistant that is always listening is a microphone in your home wired to the internet. Before you build one, decide honestly: does the audio leave your machine, and if so, to whom, and what do they keep? For a private assistant, a fully local pipeline (local Whisper, local TTS, and even a local model from Lesson 60) means your voice never leaves the room. That is a genuine, achievable option, and for a personal Jarvis it is often the right one.
Choose a pipeline for a scenario
For each, decide cloud or local for STT and TTS, and say why.
- A hands-free assistant for cooking, in your own kitchen.
- A voice bot handling customer calls for a business.
- A tool that transcribes confidential therapy sessions.
- A talking toy for a child, sold to the public.
Reveal solution
- Either, leaning local. It is your kitchen; local keeps it private and works if the wifi drops. Cloud is fine if you value the nicer voice and trust the provider.
- Cloud, almost certainly. You need top quality and low latency at scale, and you cannot run local models on every call. But you now owe the callers clear disclosure and a privacy policy.
- Local, without question. Confidential health data must not be sent to a third party without extraordinary care and legal basis. Local Whisper on a machine you control is the responsible choice, and even then you handle the recordings carefully.
- This is the hard one. A cloud pipeline means a child's voice goes to a company's servers, which is a serious child-privacy and legal question (COPPA and similar laws). Local is safer but harder to build into a cheap toy. The honest answer may be 'do not ship this without expert legal and safety review'.
The technical choice and the ethical choice are the same choice here. That is the theme of the whole level, and it arrives in full in Lesson 62.
Why is my voice assistant so slow?
Someone built a voice loop and complains each exchange takes about six seconds. Their pipeline: record the full clip, upload it, wait for the full transcript, send it to the model, wait for the full reply, generate all the audio, then play it. Diagnose and prescribe.
Reveal solution
Every step is sequential and each waits for the previous to fully finish, so the delays stack: recording + upload + full transcription + full generation + full synthesis, one after another. Nothing overlaps, so the user waits for the sum of everything.
The fix is to overlap by streaming at every stage: transcribe while they are still speaking, start the model on the partial transcript, stream the reply (Lesson 56), and start synthesising and playing the first sentence while the rest of the reply is still being written. Done well, the assistant starts answering within a second of you stopping, because the later work happens while the earlier audio plays. Six seconds of dead waiting becomes one second of latency and five seconds of overlapped, invisible work.