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.
| Option | Type | Notes |
|---|---|---|
| Kokoro | Local | Free, offline, ~80MB model |
| Custom | Any | Implement ITextToSpeechProvider |
Using Kokoro (Built-in)
Prerequisites
- Install KokoroSharp NuGet package
- Add
LOREMIND_KOKOROscripting define symbol
Installation
1. Add Scripting Define
- Go to Edit > Project Settings > Player
- Expand Other Settings > Script Compilation
- Add
LOREMIND_KOKOROto Scripting Define Symbols - Click Apply
2. Enable in Project Settings
- Open Window > LoreMind > Control Panel
- Go to Voice tab
- Enable Text-to-Speech
- Select TTS Provider: Kokoro
Quick Setup
Add the Component
- Select your NPC GameObject (with
LoreMindNPC) - Add Component > LoreMind > Voice > Voice Output
The component auto-adds an AudioSource for playback.
Enable Auto-Speak
The easiest way to use voice output:
- On your
LoreMindNPC, enable Auto-Speak Responses - 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 IDAvailable Voices
Kokoro includes 20+ voices across multiple languages:
American English:
| Voice ID | Name | Gender |
|---|---|---|
af_bella | Bella | Female |
af_nicole | Nicole | Female |
af_sarah | Sarah | Female |
af_sky | Sky | Female |
am_adam | Adam | Male |
am_michael | Michael | Male |
British English:
| Voice ID | Name | Gender |
|---|---|---|
bf_emma | Emma | Female |
bf_isabella | Isabella | Female |
bm_george | George | Male |
bm_lewis | Lewis | Male |
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
| Setting | Description | Default |
|---|---|---|
| Voice ID | Kokoro voice identifier | af_bella |
| Use Streaming | Start playback before synthesis completes | true |
| Speech Rate | Playback speed (0.5 = slow, 2.0 = fast) | 1.0 |
| Volume | Audio volume | 1.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 codeNon-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”
- Check TTS is enabled in Control Panel > Voice
- Verify
LOREMIND_KOKOROscripting define is set - Ensure KokoroSharp package is installed
No audio plays
- Verify AudioSource component exists and is enabled
- Check AudioSource volume is not zero
- 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 initializesPlatform Support
| Platform | Kokoro Support |
|---|---|
| Windows | Native |
| macOS | Native |
| Linux | Native |
| iOS/Android | Via ONNX |
| WebGL | Not 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
- Voice Input - Let players speak to NPCs
- LoreMindNPC Component - Main component guide
- API Reference - Complete API documentation