Skip to Content
DocsLoremind SdkVoice Output

Voice Output

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

Providers

LoreMind supports two TTS providers:

ProviderTypeCostQualityLatency
KokoroLocalFREEHigh (#1 on HuggingFace)~50-200ms
ElevenLabsCloudPaidVery High~200-500ms

Recommendation:

  • Development/Testing: Use Kokoro (free, offline, no API costs)
  • Production: Either works - Kokoro is viable for PC/console, ElevenLabs for mobile/WebGL

Prerequisites

Kokoro (Local TTS)

Kokoro runs entirely on-device using the Kokoro-82M model (~80MB).

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

ElevenLabs (Cloud TTS)

  1. Create account at elevenlabs.io 
  2. Get API key from your dashboard
  3. Enter API key in Window > LoreMind > Control Panel > Voice

Installation

1. Add Scripting Define (Kokoro only)

  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 or ElevenLabs)
  5. For ElevenLabs: Enter your API key

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 // Or override provider for this NPC only voiceOutput.OverrideProvider = true; voiceOutput.ProviderOverride = TTSProviderType.ElevenLabs;

Kokoro Voices

Voice IDNameAccentGender
af_bellaBellaAmericanFemale
af_nicoleNicoleAmericanFemale
af_sarahSarahAmericanFemale
af_skySkyAmericanFemale
am_adamAdamAmericanMale
am_michaelMichaelAmericanMale
bf_emmaEmmaBritishFemale
bf_isabellaIsabellaBritishFemale
bm_georgeGeorgeBritishMale
bm_lewisLewisBritishMale

ElevenLabs Voices

Voice IDs are fetched from your ElevenLabs account. Check the Voice Output inspector or call:

TTSVoiceInfo[] voices = voiceOutput.GetAvailableVoices(); foreach (var voice in voices) { Debug.Log($"{voice.name}: {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 IDProvider-specific voice identifier(default)
Use StreamingStart playback before synthesis completestrue
Speech RatePlayback speed (0.5 = slow, 2.0 = fast)1.0
VolumeAudio volume1.0

Provider Override

SettingDescriptionDefault
Override ProviderUse different provider for this NPCfalse
Provider OverrideProvider to use when overridingNone

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; } bool OverrideProvider { get; set; } TTSProviderType ProviderOverride { 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 scripting define (LOREMIND_KOKORO) is set
  3. For ElevenLabs: Check API key is valid

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

Audio sounds choppy (streaming)

Try disabling streaming for this NPC:

voiceOutput.UseStreaming = false;

ElevenLabs rate limit

ElevenLabs has usage limits. If you hit rate limits:

  • Reduce speech frequency
  • Cache common responses
  • Consider Kokoro for development

Platform Support

PlatformKokoroElevenLabs
WindowsNativeAPI
macOSNativeAPI
LinuxNativeAPI
iOS/AndroidVia ONNXAPI
WebGLNot supportedAPI

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";

Next Steps

Last updated on