Implement a voice translation pipeline on Apple silicon
Introduction
Set up the voice translation project
Transcribe microphone audio with Sherpa-ONNX
Translate text with Gemma 4 E2B
Synthesize translated speech with Supertonic 3
Build the voice translator with Gradio
Validate the voice translation pipeline
Next Steps
Implement a voice translation pipeline on Apple silicon
Capture speech with Sherpa-ONNX
With your environment and models ready, you’ll create stt.py, a Python script that records English speech and prints a transcript. The script will use sounddevice to capture five seconds of microphone audio, then Sherpa-ONNX to transcribe it with the downloaded Zipformer model.
You’ll first check your microphone and create the script, then run it to record a sentence. By testing transcription on its own, you’ll be able to identify recording or recognition problems before adding translation.
Continuing in the ~/voice-translator directory with the active virtual environment, list the audio devices visible to Python:
python -m sounddevice
Confirm that the list contains an input device, such as MacBook Pro Microphone or your external microphone.
Create the transcription script
Using a file editor of your choice, create a file named stt.py in the ~/voice-translator directory. Copy and paste the following code into the file and save it:
from pathlib import Path
import numpy as np
import sherpa_onnx
import sounddevice as sd
MODEL = Path("models/sherpa-onnx-streaming-zipformer-en-2023-06-26")
RECORDING_SECONDS = 5
def create_recogniser():
return sherpa_onnx.OnlineRecognizer.from_transducer(
tokens=str(MODEL / "tokens.txt"),
encoder=str(
MODEL
/ "encoder-epoch-99-avg-1-chunk-16-left-128.int8.onnx"
),
decoder=str(
MODEL / "decoder-epoch-99-avg-1-chunk-16-left-128.onnx"
),
joiner=str(
MODEL
/ "joiner-epoch-99-avg-1-chunk-16-left-128.int8.onnx"
),
num_threads=2,
sample_rate=16000,
feature_dim=80,
decoding_method="greedy_search",
provider="cpu",
)
def transcribe(recogniser, samples, sample_rate):
stream = recogniser.create_stream()
stream.accept_waveform(sample_rate, samples)
tail_padding = np.zeros(int(0.5 * sample_rate), dtype=np.float32)
stream.accept_waveform(sample_rate, tail_padding)
stream.input_finished()
while recogniser.is_ready(stream):
recogniser.decode_stream(stream)
return recogniser.get_result(stream).strip()
def main():
recogniser = create_recogniser()
microphone = sd.query_devices(kind="input")
sample_rate = int(microphone["default_samplerate"])
print(f"Using microphone: {microphone['name']}")
print(f"Recording for {RECORDING_SECONDS} seconds...")
samples = sd.rec(
int(RECORDING_SECONDS * sample_rate),
samplerate=sample_rate,
channels=1,
dtype="float32",
)
sd.wait()
transcript = transcribe(recogniser, samples.reshape(-1), sample_rate)
print(f"Transcript: {transcript or '[null]'}")
if __name__ == "__main__":
main()
create_recogniser() loads the model and transcribe() accepts recorded samples and returns text. Keeping the recording in main() lets the Gradio application reuse these functions with browser audio.
sounddevice records one channel of float32 samples at the microphone’s sample rate. sd.wait() waits for the five-second recording to finish before transcription starts.
Sherpa-ONNX resamples the audio to the model’s 16 kHz input rate. transcribe() creates a new stream for each recording, appends half a second of silence to flush the final words, and marks the input as finished. The decode loop then consumes the available frames and returns the transcript.
The encoder and joiner use the int8 files, while the decoder uses the separate file named in the configuration. provider="cpu" and num_threads=2 select CPU execution with two threads. The loaded recognizer can be reused, while each recording has its own stream.
Record and transcribe an utterance
Run the script:
python stt.py
The first recording can trigger a macOS permission request. Select Allow.
If recording fails, open System Settings. Select Privacy & Security, select Microphone, and enable access for your terminal application.
After the recording message appears, say:
Where is the nearest train station?
The output is similar to:
Using microphone: MacBook Pro Microphone
Recording for 5 seconds...
Transcript: WHERE IS THE NEAREST TRAIN STATION
Capitalization and punctuation might differ, but the transcript should contain the words that you spoke.
If the transcript is empty, run python -m sounddevice again. Check that the microphone that you spoke into is marked with > or *. The * marker identifies a device used for both default input and output. Also confirm the microphone permission for the terminal application in System Settings.
If the script remains at Recording for 5 seconds..., check the same device and permission settings, restart the terminal application, and run it again.
What you’ve accomplished and what’s next
You’ve set up audio transcription with Sherpa-ONNX. stt.py now records one utterance and prints its transcript. The application will pass that text to Gemma only after recognition finishes.
Next, you’ll test Gemma’s translation with typed text before connecting it to the recognizer.