> ## Documentation Index
> Fetch the complete documentation index at: https://docs.runanywhere.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# synthesize()

> Convert text to speech with neural voices

Synthesize natural-sounding speech from text using on-device Piper TTS models.

## Basic Usage

```dart theme={null}
// 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)      |

```dart theme={null}
// 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

```dart theme={null}
import 'package:runanywhere_onnx/runanywhere_onnx.dart';

await Onnx.register();
```

### 2. Add TTS Voice

```dart theme={null}
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

```dart theme={null}
// 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 like `audioplayers` to play the synthesized audio:

```dart theme={null}
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

```dart theme={null}
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();
  }
}
```

## See Also

<CardGroup cols={2}>
  <Card title="Streaming TTS" icon="wave-square" href="/flutter/tts/stream">
    Stream audio as it's generated
  </Card>

  <Card title="Available Voices" icon="waveform" href="/flutter/tts/voices">
    Browse voice options
  </Card>
</CardGroup>
