Skip to Content
Loremind Unity SDKVoice Output

Voice Output

Let NPCs speak their responses aloud with text-to-speech. Voice output is optional - LoreMindNPC works without it.

Overview

The SDK includes built-in support for Kokoro, a local TTS engine that runs entirely on-device. You can also bring your own TTS solution by implementing the ITextToSpeechProvider interface.

OptionTypeNotes
KokoroLocalFree, offline, ~80MB model
CustomAnyImplement ITextToSpeechProvider

Using Kokoro (Built-in)

Prerequisites

  1. Install KokoroSharp NuGet package
  2. Add LOREMIND_KOKORO scripting define symbol

Installation

1. Add Scripting Define

  1. Go to Edit > Project Settings > Player
  2. Expand Other Settings > Script Compilation
  3. Add LOREMIND_KOKORO to Scripting Define Symbols
  4. Click Apply

2. Enable in Project Settings

  1. Open Window > LoreMind > Control Panel
  2. Go to Voice tab
  3. Enable Text-to-Speech
  4. Select TTS Provider: Kokoro

Quick Setup

Add the Component

  1. Select your NPC GameObject (with LoreMindNPC)
  2. Add Component > LoreMind > Voice > Voice Output

The component auto-adds an AudioSource for playback.

Enable Auto-Speak

The easiest way to use voice output:

  1. On your LoreMindNPC, enable Auto-Speak Responses
  2. Assign the Voice Output component

Now every NPC response is automatically spoken.

Manual Control

For more control, trigger speech from code:

using UnityEngine; using Peek.LoreMind; using Peek.LoreMind.Voice; public class NPCVoiceDemo : MonoBehaviour { [SerializeField] private LoreMindNPC npc; [SerializeField] private LoreMindVoiceOutput voiceOutput; void Start() { // Speak when NPC responds npc.OnResponseReceived.AddListener(response => { voiceOutput.Speak(response); }); } }

Per-NPC Voice Configuration

Each NPC can have its own unique voice:

// Set voice in code voiceOutput.VoiceId = "am_adam"; // Kokoro voice ID

Available Voices

Kokoro includes 20+ voices across multiple languages:

American English:

Voice IDNameGender
af_bellaBellaFemale
af_nicoleNicoleFemale
af_sarahSarahFemale
af_skySkyFemale
am_adamAdamMale
am_michaelMichaelMale

British English:

Voice IDNameGender
bf_emmaEmmaFemale
bf_isabellaIsabellaFemale
bm_georgeGeorgeMale
bm_lewisLewisMale

Other Languages: Kokoro supports Spanish (es-ES), French (fr-FR), Hindi (hi-IN), Italian (it-IT), Japanese (ja-JP), Portuguese (pt-BR), and Chinese (zh-CN).

List Available Voices

TTSVoiceInfo[] voices = voiceOutput.GetAvailableVoices(); foreach (var voice in voices) { Debug.Log($"{voice.name} ({voice.gender}): {voice.id}"); }

Complete Example

using UnityEngine; using UnityEngine.UI; using Peek.LoreMind; using Peek.LoreMind.Voice; public class TalkingNPC : MonoBehaviour { [SerializeField] private LoreMindNPC npc; [SerializeField] private LoreMindVoiceOutput voiceOutput; [Header("UI")] [SerializeField] private Text subtitleText; [SerializeField] private Image speakingIndicator; void Start() { // Configure voice voiceOutput.VoiceId = "am_adam"; // Male voice // Wire up voice events voiceOutput.OnSpeechStarted.AddListener(() => { speakingIndicator.color = Color.green; }); voiceOutput.OnSpeechCompleted.AddListener(() => { speakingIndicator.color = Color.gray; }); voiceOutput.OnError.AddListener(error => { Debug.LogError($"TTS Error: {error}"); speakingIndicator.color = Color.red; }); // Show subtitles and speak npc.OnResponseReceived.AddListener(response => { subtitleText.text = response; voiceOutput.Speak(response); }); } // Public method for UI button public void StopSpeaking() { voiceOutput.Stop(); } }

Configuration

Playback Settings

SettingDescriptionDefault
Voice IDKokoro voice identifieraf_bella
Use StreamingStart playback before synthesis completestrue
Speech RatePlayback speed (0.5 = slow, 2.0 = fast)1.0
VolumeAudio volume1.0

Events

// Speech lifecycle voiceOutput.OnSpeechStarted.AddListener(() => ShowSpeakingUI()); voiceOutput.OnSpeechCompleted.AddListener(() => HideSpeakingUI()); // Error handling voiceOutput.OnError.AddListener(error => ShowErrorMessage(error));

API Summary

Properties

bool IsSpeaking { get; } bool IsSynthesizing { get; } string LastSpokenText { get; } AudioSource AudioSource { get; } string VoiceId { get; set; }

Methods

void Speak(string text) Task SpeakAsync(string text) void Stop() TTSVoiceInfo[] GetAvailableVoices() string GetProviderName() Task ReinitializeProviderAsync()

Streaming vs Non-Streaming

Streaming (Default)

Audio starts playing while still being synthesized. Lower perceived latency.

voiceOutput.UseStreaming = true; // Inspector or code

Non-Streaming

Waits for complete audio before playing. Useful for short responses or when you need the full audio clip.

voiceOutput.UseStreaming = false;

Async/Await Pattern

For precise control over speech completion:

public async void HandleResponse(string response) { // Wait for speech to complete await voiceOutput.SpeakAsync(response); // Speech finished, continue with next action ShowNextDialogueOption(); }

Combining Voice Input and Output

Create a full voice conversation loop:

using UnityEngine; using Peek.LoreMind; using Peek.LoreMind.Voice; public class VoiceConversation : MonoBehaviour { [SerializeField] private LoreMindNPC npc; [SerializeField] private LoreMindVoiceInput voiceInput; [SerializeField] private LoreMindVoiceOutput voiceOutput; void Start() { // Player speaks -> NPC responds -> NPC speaks voiceInput.OnTranscription.AddListener(playerText => { npc.Respond(playerText); }); npc.OnResponseReceived.AddListener(response => { voiceOutput.Speak(response); }); } }

Troubleshooting

”TTS provider not available”

  1. Check TTS is enabled in Control Panel > Voice
  2. Verify LOREMIND_KOKORO scripting define is set
  3. Ensure KokoroSharp package is installed

No audio plays

  1. Verify AudioSource component exists and is enabled
  2. Check AudioSource volume is not zero
  3. Check AudioListener exists in scene

First synthesis is slow

Kokoro loads the model on first use (~1-2 seconds). Subsequent calls are fast.

// Pre-warm at scene start await voiceOutput.SpeakAsync(""); // Empty string, just initializes

Platform Support

PlatformKokoro Support
WindowsNative
macOSNative
LinuxNative
iOS/AndroidVia ONNX
WebGLNot supported

Best Practices

Provide Visual Feedback

Always indicate when the NPC is speaking:

voiceOutput.OnSpeechStarted.AddListener(() => { npcNameplate.color = Color.green; speakingBubble.SetActive(true); }); voiceOutput.OnSpeechCompleted.AddListener(() => { npcNameplate.color = Color.white; speakingBubble.SetActive(false); });

Show Subtitles

Not all players can use audio. Always show text:

npc.OnResponseReceived.AddListener(response => { subtitleText.text = response; voiceOutput.Speak(response); });

Handle Interruptions

Let players skip or interrupt speech:

void Update() { if (Input.GetKeyDown(KeyCode.Space) && voiceOutput.IsSpeaking) { voiceOutput.Stop(); } }

Different Voices for Different NPCs

Give each NPC character a unique voice:

// Gruff blacksmith - male British blacksmithVoice.VoiceId = "bm_george"; // Young apprentice - female American apprenticeVoice.VoiceId = "af_sky"; // Wise elder - male American elderVoice.VoiceId = "am_adam";

Custom TTS Provider

If you prefer a different TTS solution, implement the ITextToSpeechProvider interface:

using Peek.LoreMind.Services; using UnityEngine; using System.Threading.Tasks; public class MyCustomTTS : ITextToSpeechProvider { public string ProviderName => "MyCustomTTS"; public bool IsReady { get; private set; } public bool SupportsStreaming => false; public async Task<bool> InitializeAsync() { // Initialize your TTS engine IsReady = true; return true; } public async Task<AudioClip> SynthesizeAsync( string text, string voiceId = null, TTSSynthesisOptions options = null) { // Generate audio from text using your TTS engine // Return an AudioClip with the synthesized speech } public Task SynthesizeStreamingAsync( string text, AudioSource audioSource, string voiceId = null, TTSSynthesisOptions options = null) { // Optional: implement streaming for lower latency throw new System.NotImplementedException(); } public TTSVoiceInfo[] GetAvailableVoices() { return new[] { new TTSVoiceInfo { id = "default", name = "Default Voice" } }; } public string GetDefaultVoiceId() => "default"; public void Dispose() { // Clean up resources } }

Next Steps

Last updated on