OpenVoice for Unity

Cloned, emotionally varied voices synthesized on the device, in Unity.

Role
Architecture and engineering
Areas
AI, Tools, Research

A companion with several avatar characters needs several voices, and each voice has to carry emotion. Doing that in the cloud means a network round-trip for every sentence. In this prototype we built the voices on OpenVoice V2 and split the work so the heavy part happens once, at development time, and only light inference runs on the phone.

Authoring on the workstation

Our Python tools take a clean reference recording for each voice and extract two kinds of embedding:

  • a tone color embedding that captures the voice's timbre, for cloning;
  • style embeddings for each affect variant (neutral, warm, melancholic, tense).

The tools validate the embeddings, report how similar the voices and styles are to one another, and pack everything into a small binary bundle that ships inside the app. The same toolchain exports the model components from PyTorch to ONNX for every platform and converts them to Core ML for iOS, with quantization.

Layer diagram: a workstation layer that extracts tone color and style embeddings, validates them, packs a bundle and exports ONNX and Core ML models; the app bundle with the embedding files, models and phoneme assets; a desktop layer with a C# ONNX backend; and an iOS layer with a Swift Core ML service behind a native bridge.
We author each voice once on a workstation and ship only small embeddings and exported models to the device.

Inference in Unity

A C# backend interface hides the platform. On iOS, a Swift service runs the Core ML models and reaches Unity through a native bridge. On Windows and macOS, a C# backend runs the ONNX models. On top sits a TTS manager and an affect coordinator that picks the style variant for each sentence from upstream sentiment, so the voice can match an avatar's expression. Editor tools validate the setup and patch the iOS build.

Four cards for the affect variants: Neutral, calm with an even rhythm; Warm, happy with a slightly raised pitch; Melancholic, sad with a lower pitch and slower pace; Tense, urgent and faster with a compressed range.
We pair each affect's style embedding with its own synthesis settings, and the affect coordinator picks one per sentence.

Swapping the backbone

OpenVoice's default backbone, MeloTTS, is multilingual. Its shared phoneme and tone tables pull English vowels slightly off native American English, and it also needs a large BERT model at inference. The repo carries the Piper engine from PiperTTS for Unity so a monolingual Piper voice can stand in as the backbone while OpenVoice's tone color converter keeps the cloning. That drops the BERT model and shrinks the footprint considerably.

Two-column comparison. MeloTTS: multilingual VITS with a shared symbol table, needs BERT and tone and language IDs, exported from PyTorch. Piper: one language per voice, eSpeak-NG IPA phonemes, no BERT, no tone or language IDs, prebuilt ONNX. Both keep the OpenVoice tone color converter.
We keep the tone color converter for cloning and swap only the backbone underneath it.

This was part of our on-device voice research for Frndo. The app's voice pipeline uses a different, server-side approach, and its avatars are animated by its own lip-sync system.