Training a TTS with the PDA Voice from Subnautica

June 6, 2026 (1w ago)

Making my devices sound like the computer from my favorite game

Disclaimer. This write-up covers only the method and the tools. There is no result, no audio, no transcript, and no trained model to be had here. The game files fall under the EULA and are the property of Unknown Worlds. They are not shared or distributed and stay on my own machine. What I share here are the approach and one small shell command, nothing more.

I leaned heavily on Claude Code (Opus 4.8) from Anthropic for this. It’s genuinely your best friend for a project like this: it will figure things out eventually. But, second disclaimer: it is still a hallucinating LLM. It can confidently do the wrong thing and break your game install. Keep backups, and don’t run anything you don’t understand.

Where this started

I play Subnautica, and anyone who does knows the voice: the PDA. A calm, neutral computer voice that talks to you as you play. Not a person, no drama, just a device that tells you plainly what’s going on. At some point I thought: why doesn’t my own computer sound like that? I want my Mac, and later my phone, to talk to me in exactly that voice. Not as a gimmick, but as an accessibility layer I actually enjoy. A notification coming in, a script finishing, a warning, read out by the PDA.

My phone is literally a PDA (Personal Digital Assistant). And yes, Siri can do the same, but it’d be cool if it sounded like one I like a lot too.

One thing mattered to me from the start: this had to be fully local. No cloud API, no subscription, no data leaving my machine. A voice I own that works offline. That fits how I set up the rest of my stuff: self-hosted, minimal dependencies, everything under my own control.

The two phases

The plan broke into two parts:

  1. Build a dataset: A tidy collection of short audio clips with matching text.
  2. Fine-tune a TTS voice: Take an existing voice model and train it further on that dataset.

The voice doesn’t come as a ready-made file. You build it from clips and text, and you train an existing model until it picks up the right sound.

The tools that did the work

The short list that made the difference:

  • Piper: A lightweight, local neural-TTS engine. Fast enough on CPU, and you can fine-tune an existing voice model on a small dataset of your own.
  • ffmpeg: To normalize all audio to a single format (mono, 22050 Hz, 16-bit PCM, what Piper expects).
  • fsb5 (Python): To unpack audio from FMOD sound banks, a format widely used in games.
  • uv: To manage Python versions and virtual environments. Indispensable on Apple Silicon, see below.

The biggest hurdle: Apple Silicon

I work on a Mac with Apple Silicon (arm64), and that caused most of the headaches:

  • Piper’s phonemizer (piper-phonemize) has no arm64-macOS build. Linux and Intel-Mac only.
  • The training stack also wants an older torch, which is awkward on arm64 too.

Solution: a complete x86_64 Python environment under Rosetta, installed via uv:

uv python install cpython-3.10-macos-x86_64

Inside it, two separate virtual environments, one for training and one for inference, so the tightly pinned training dependencies don’t get in the way. Training runs CPU-only, since there is no GPU path for this stack on this hardware. Slower, but fine for a small dataset. It ran overnight, and the next morning I had a voice.

The method: from raw audio to a clean dataset

The real work isn’t the training, it’s the dataset hygiene. Luckily Subnautica has text for everything the PDA says, like: “PDA: The builder tool…”.

A few principles that paid off:

1. One voice, and only that voice. The source contains many different voices: characters, radio, effects. I wanted exclusively the neutral narrator (PDA). The biggest risk is accidentally pulling in character voices; those pollute the model. I recognized them by the labels in the transcripts and excluded them by category. Always listen to a few clips yourself before including a whole category, because names lie sometimes.

2. Pair text and audio one-to-one. Every clip gets exactly one transcript line (LJSpeech format: id|text). Doubtful cases (no reliable transcript, broken text, or clips longer than ~15 s, where Piper performs poorly) go to a separate review/ folder and never straight into the training set.

3. Normalize everything to one audio format with ffmpeg, right at unpack time.

4. Deduplicate by transcript: if the same line appears more than once, keep the shortest, cleanest take.

The result of phase 1: a handful of minutes of crystal-clear clips, all the same voice, each with a correct transcript. Small but pure, exactly what fine-tuning needs.

The reusable core: the extraction engine

Game sound banks (FMOD .bank) are, in practice, RIFF containers with an FSB5 block at some offset. The trick is simple: find the FSB5 marker, load from there with fsb5, decode each sample, and convert with ffmpeg. This piece is completely game-agnostic, which is why it’s the core I reuse across different voices. My plan isn’t to stop at this one voice: there are a few more on my list.

On top of that engine sits a small selection script per voice that (a) decides which samples are the neutral narrator and (b) matches each sample to its transcript. That selection part is the only game-specific bit, and I deliberately keep it short: no more than a list of name patterns plus a text file of transcripts.

This isn’t a step-by-step guide, so I’m leaving the extraction and selection code out. It’s the kind of thing Claude Code can rebuild for you in an afternoon if you describe what you want.

Phase 2: fine-tuning

With the dataset ready, training is surprisingly straightforward:

  1. Preprocess the dataset with Piper (phonemize, cache).
  2. Fine-tune from an existing base model (I started with a neutral British voice) instead of from scratch. That saves an enormous amount of data and time.
  3. Export the result to ONNX and run it with the Piper CLI.

A few hundred fine-tune epochs on such a small set is enough for a recognizable voice. The final polish you do without retraining, via inference knobs: speed (--length-scale), pause after each sentence (--sentence-silence), and timing variation (--noise-w). Pauses within sentences only improve with more training; pacing between sentences you tune live.

The one thing worth sharing: a pda command

The whole point was to use this every day without thinking about it. So I wrapped the voice in a tiny shell command. Type pda "your text" and you hear it, spoken by the PDA.

The model and Piper live in a hidden virtual environment in my home folder (~/.piper-venv). The command is a one-line script that pipes text into Piper and plays the result straight away, no file left behind:

cat > ~/.piper-venv/pda << 'EOF'
#!/bin/sh
echo "$*" | ~/.piper-venv/bin/piper --model ~/.piper-venv/subnautica-zero-pda.onnx --output_raw 2>/dev/null | \
  ffplay -f s16le -ar 22050 -nodisp -autoexit - >/dev/null 2>&1
EOF
chmod +x ~/.piper-venv/pda

Add the folder to your PATH (zsh in my case) so the command works anywhere:

echo 'export PATH="$HOME/.piper-venv:$PATH"' >> ~/.zshrc
source ~/.zshrc

And that’s it:

pda "Anomaly detected. Please investigate."

Each new voice is just another one-line script pointing at a different .onnx model. No parameters, no growing script to maintain. If I ever want voice-switching with a flag instead, that’s an afternoon of work, or two minutes with Claude Code.

What I’d pass on

  • The dataset is 90% of the work. The model is only as good as the purity of your clips. One stray wrong voice and you’ll hear it.
  • Fine-tuning beats from-scratch for small projects. Start from a good base model.
  • Plan around your platform. On Apple Silicon, “just install it” was the biggest pitfall; an x86_64 environment under Rosetta solved everything in one go.
  • Keep it local and tidy. This is a personal accessibility voice. I don’t share the audio, the transcripts, or the trained model, only the method and my own scripts.

The tools that did the work: Piper, ffmpeg, fsb5, and uv. Four small, free tools that together form a complete, local voice pipeline.

Where it’s going

I’ll admit it: I’m a fan. I started with the Below Zero PDA, because that British voice was the one I wanted first. Then I went back and did the original Subnautica PDA too, so now I’ve got both. Next on the list are the Subnautica 2 PDA and NoA (the Noetic Advisor). Each one is just another one-line command like pda and pda-zero, waiting to read me my notifications. My machines are slowly turning into the inside of a Cyclops, and I’m completely fine with that.