Basic Usage
Copy
Ask AI
// Load a TTS voice
await RunAnywhere.loadTTSVoice('vits-piper-en_US-lessac-medium');
// Synthesize speech
final result = await RunAnywhere.synthesize(
'Hello! Welcome to RunAnywhere.',
rate: 1.0,
pitch: 1.0,
);
// result.samples contains PCM Float32 audio
// result.sampleRate is typically 22050 Hz
print('Duration: ${result.durationSeconds}s');
TTSResult
| Property | Type | Description |
|---|---|---|
samples | Float32List | Audio samples (PCM float) |
sampleRate | int | Sample rate in Hz (typically 22050) |
durationMs | int | Duration in milliseconds |
durationSeconds | double | Duration in seconds |
numSamples | int | Number of audio samples |
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
rate | double | 1.0 | Speech rate (0.5 to 2.0) |
pitch | double | 1.0 | Voice pitch (0.5 to 2.0) |
volume | double | 1.0 | Volume (0.0 to 1.0) |
Copy
Ask AI
// Slower, lower pitch for emphasis
final result = await RunAnywhere.synthesize(
'Important announcement!',
rate: 0.8,
pitch: 0.9,
volume: 1.0,
);
Setup
1. Register ONNX Backend
Copy
Ask AI
import 'package:runanywhere_onnx/runanywhere_onnx.dart';
await Onnx.register();
2. Add TTS Voice
Copy
Ask AI
Onnx.addModel(
id: 'vits-piper-en_US-lessac-medium',
name: 'Piper US English (Lessac)',
url: 'https://github.com/RunanywhereAI/sherpa-onnx/releases/download/runanywhere-models-v1/vits-piper-en_US-lessac-medium.tar.gz',
modality: ModelCategory.speechSynthesis,
);
3. Download & Load
Copy
Ask AI
// Download
await for (final progress in RunAnywhere.downloadModel('vits-piper-en_US-lessac-medium')) {
print('${(progress.percentage * 100).toStringAsFixed(1)}%');
if (progress.state.isCompleted) break;
}
// Load
await RunAnywhere.loadTTSVoice('vits-piper-en_US-lessac-medium');
Playing Audio
Use a package likeaudioplayers to play the synthesized audio:
Copy
Ask AI
import 'package:audioplayers/audioplayers.dart';
final player = AudioPlayer();
Future<void> speakText(String text) async {
final result = await RunAnywhere.synthesize(text);
// Convert Float32 samples to bytes for playback
final bytes = _convertToBytes(result.samples, result.sampleRate);
// Play using audioplayers
await player.play(BytesSource(bytes));
}
Uint8List _convertToBytes(Float32List samples, int sampleRate) {
// Convert Float32 PCM to Int16 PCM
final int16Samples = Int16List(samples.length);
for (var i = 0; i < samples.length; i++) {
int16Samples[i] = (samples[i] * 32767).clamp(-32768, 32767).toInt();
}
// Build WAV file: 44-byte header + PCM data
final dataSize = int16Samples.length * 2;
final header = ByteData(44);
// RIFF header
header.setUint32(0, 0x52494646, Endian.big); // "RIFF"
header.setUint32(4, 36 + dataSize, Endian.little);
header.setUint32(8, 0x57415645, Endian.big); // "WAVE"
// fmt chunk
header.setUint32(12, 0x666d7420, Endian.big); // "fmt "
header.setUint32(16, 16, Endian.little); // chunk size
header.setUint16(20, 1, Endian.little); // PCM format
header.setUint16(22, 1, Endian.little); // mono
header.setUint32(24, sampleRate, Endian.little);
header.setUint32(28, sampleRate * 2, Endian.little); // byte rate
header.setUint16(32, 2, Endian.little); // block align
header.setUint16(34, 16, Endian.little); // bits per sample
// data chunk
header.setUint32(36, 0x64617461, Endian.big); // "data"
header.setUint32(40, dataSize, Endian.little);
final wavBytes = Uint8List(44 + dataSize);
wavBytes.setAll(0, header.buffer.asUint8List());
wavBytes.setAll(44, int16Samples.buffer.asUint8List());
return wavBytes;
}
Complete Example
Copy
Ask AI
class TextToSpeechDemo extends StatefulWidget {
@override
_TextToSpeechDemoState createState() => _TextToSpeechDemoState();
}
class _TextToSpeechDemoState extends State<TextToSpeechDemo> {
final _controller = TextEditingController();
final _player = AudioPlayer();
bool _isSpeaking = false;
bool _isLoaded = false;
@override
void initState() {
super.initState();
_initTTS();
}
Future<void> _initTTS() async {
// Download if needed
final models = await RunAnywhere.availableModels();
final ttsModel = models.firstWhere(
(m) => m.id == 'vits-piper-en_US-lessac-medium',
);
if (!ttsModel.isDownloaded) {
await for (final p in RunAnywhere.downloadModel(ttsModel.id)) {
if (p.state.isCompleted) break;
}
}
// Load voice
await RunAnywhere.loadTTSVoice('vits-piper-en_US-lessac-medium');
setState(() => _isLoaded = true);
}
Future<void> _speak() async {
if (_controller.text.isEmpty) return;
setState(() => _isSpeaking = true);
try {
final result = await RunAnywhere.synthesize(_controller.text);
// Play audio (implementation depends on your audio setup)
await _playAudio(result);
} finally {
setState(() => _isSpeaking = false);
}
}
@override
Widget build(BuildContext context) {
return Column(
children: [
TextField(
controller: _controller,
decoration: InputDecoration(
hintText: 'Enter text to speak...',
),
maxLines: 3,
),
SizedBox(height: 16),
ElevatedButton.icon(
onPressed: _isLoaded && !_isSpeaking ? _speak : null,
icon: Icon(_isSpeaking ? Icons.stop : Icons.volume_up),
label: Text(_isSpeaking ? 'Speaking...' : 'Speak'),
),
],
);
}
@override
void dispose() {
_controller.dispose();
_player.dispose();
super.dispose();
}
}