kokoro_tts_flutter 0.1.0 copy "kokoro_tts_flutter: ^0.1.0" to clipboard
kokoro_tts_flutter: ^0.1.0 copied to clipboard

A Flutter implementation of Kokoro-TTS using ONNX Runtime

example/lib/main.dart

import 'dart:io';
import 'package:flutter/material.dart';
import 'package:path_provider/path_provider.dart';
import 'package:just_audio/just_audio.dart';
import 'package:kokoro_tts_flutter/kokoro_tts_flutter.dart';

void main() {
  runApp(const KokoroTTSApp());
}

class KokoroTTSApp extends StatelessWidget {
  const KokoroTTSApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Kokoro TTS Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.blueAccent),
        useMaterial3: true,
      ),
      home: const KokoroTTSDemoPage(),
    );
  }
}

class KokoroTTSDemoPage extends StatefulWidget {
  const KokoroTTSDemoPage({super.key});

  @override
  State<KokoroTTSDemoPage> createState() => _KokoroTTSDemoPageState();
}

class _KokoroTTSDemoPageState extends State<KokoroTTSDemoPage> {
  final TextEditingController _textController = TextEditingController();
  final AudioPlayer _audioPlayer = AudioPlayer();
  
  Kokoro? _kokoro;
  bool _isInitialized = false;
  bool _isGenerating = false;
  bool _isPlaying = false;
  String _phonemes = '';
  String _selectedVoice = 'en_sarah';
  double _speed = 1.0;
  
  List<String> _availableVoices = [];
  String _statusMessage = '';

  @override
  void initState() {
    super.initState();
    _initializeKokoro();
  }
  
  Future<void> _initializeKokoro() async {
    setState(() {
      _statusMessage = 'Initializing Kokoro TTS engine...';
    });

    try {
      // Get application documents directory
      final appDocDir = await getApplicationDocumentsDirectory();
      final modelPath = '${appDocDir.path}/kokoro-model.tflite';
      final voicesPath = '${appDocDir.path}/kokoro-voices.bin';
      
      // Check if model files exist, in a real app you would download them if not
      if (!File(modelPath).existsSync()) {
        setState(() {
          _statusMessage = 'Model file not found. In a real app, you would download it.';
        });
        return;
      }
      
      if (!File(voicesPath).existsSync()) {
        setState(() {
          _statusMessage = 'Voices file not found. In a real app, you would download it.';
        });
        return;
      }
      
      // Create configuration
      final config = KokoroConfig(
        modelPath: modelPath,
        voicesPath: voicesPath,
      );
      
      // Initialize Kokoro TTS
      _kokoro = Kokoro(config);
      await _kokoro!.initialize();
      
      // Get available voices
      _availableVoices = _kokoro!.getVoices();
      
      setState(() {
        _isInitialized = true;
        _statusMessage = 'Kokoro TTS engine initialized successfully!';
        if (_availableVoices.isNotEmpty) {
          _selectedVoice = _availableVoices.first;
        }
      });
    } catch (e) {
      setState(() {
        _statusMessage = 'Error initializing Kokoro TTS: $e';
      });
    }
  }
  
  Future<void> _generateSpeech() async {
    if (!_isInitialized || _textController.text.isEmpty) return;
    
    setState(() {
      _isGenerating = true;
      _statusMessage = 'Generating speech...';
    });
    
    try {
      // Generate speech
      final result = await _kokoro!.createTTS(
        text: _textController.text,
        voice: _selectedVoice,
        speed: _speed,
        lang: 'en-us',
      );
      
      setState(() {
        _phonemes = result.phonemes;
      });
      
      // Save audio to temp file for playback
      final tempDir = await getTemporaryDirectory();
      final audioFile = File('${tempDir.path}/kokoro_audio.wav');
      
      // Write WAV file (simplified - in a real implementation you'd properly format a WAV file)
      final int16PCM = result.toInt16PCM();
      await audioFile.writeAsBytes(int16PCM.buffer.asUint8List());
      
      // Set up audio player
      await _audioPlayer.setFilePath(audioFile.path);
      
      setState(() {
        _isGenerating = false;
        _statusMessage = 'Speech generated successfully!';
      });
      
      // Play audio
      await _playAudio();
      
    } catch (e) {
      setState(() {
        _isGenerating = false;
        _statusMessage = 'Error generating speech: $e';
      });
    }
  }
  
  Future<void> _playAudio() async {
    if (_audioPlayer.audioSource == null) return;
    
    setState(() {
      _isPlaying = true;
    });
    
    await _audioPlayer.play();
    
    // Wait for audio to complete
    await _audioPlayer.playerStateStream.firstWhere(
      (state) => state.processingState == ProcessingState.completed
    );
    
    setState(() {
      _isPlaying = false;
    });
  }
  
  @override
  void dispose() {
    _textController.dispose();
    _audioPlayer.dispose();
    _kokoro?.dispose();
    super.dispose();
  }
  
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Kokoro TTS Demo'),
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
      ),
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: [
            TextField(
              controller: _textController,
              decoration: const InputDecoration(
                labelText: 'Enter text to speak',
                border: OutlineInputBorder(),
              ),
              maxLines: 3,
              enabled: _isInitialized && !_isGenerating,
            ),
            const SizedBox(height: 16),
            
            // Voice selection
            DropdownButtonFormField<String>(
              value: _selectedVoice,
              decoration: const InputDecoration(
                labelText: 'Voice',
                border: OutlineInputBorder(),
              ),
              items: _availableVoices.map((voice) {
                return DropdownMenuItem<String>(
                  value: voice,
                  child: Text(voice),
                );
              }).toList(),
              onChanged: _isInitialized && !_isGenerating 
                ? (value) {
                    if (value != null) {
                      setState(() {
                        _selectedVoice = value;
                      });
                    }
                  }
                : null,
            ),
            const SizedBox(height: 16),
            
            // Speed slider
            Row(
              children: [
                const Text('Speed:'),
                Expanded(
                  child: Slider(
                    value: _speed,
                    min: 0.5,
                    max: 2.0,
                    divisions: 15,
                    label: _speed.toStringAsFixed(1),
                    onChanged: _isInitialized && !_isGenerating
                      ? (value) {
                          setState(() {
                            _speed = value;
                          });
                        }
                      : null,
                  ),
                ),
                Text('${_speed.toStringAsFixed(1)}x'),
              ],
            ),
            const SizedBox(height: 16),
            
            // Generate and play buttons
            Row(
              children: [
                Expanded(
                  child: ElevatedButton(
                    onPressed: _isInitialized && !_isGenerating && !_isPlaying
                      ? _generateSpeech
                      : null,
                    child: _isGenerating
                      ? const SizedBox(
                          width: 20,
                          height: 20,
                          child: CircularProgressIndicator(strokeWidth: 2),
                        )
                      : const Text('Generate Speech'),
                  ),
                ),
                const SizedBox(width: 8),
                Expanded(
                  child: ElevatedButton(
                    onPressed: _isInitialized && !_isGenerating && !_isPlaying && _audioPlayer.audioSource != null
                      ? _playAudio
                      : null,
                    child: _isPlaying
                      ? const SizedBox(
                          width: 20,
                          height: 20,
                          child: CircularProgressIndicator(strokeWidth: 2),
                        )
                      : const Text('Play Again'),
                  ),
                ),
              ],
            ),
            const SizedBox(height: 24),
            
            // Status
            Text(
              _statusMessage,
              style: TextStyle(
                color: _statusMessage.contains('Error')
                  ? Colors.red
                  : Colors.green,
              ),
            ),
            const SizedBox(height: 16),
            
            // Phonemes display
            const Text(
              'Phonetic Representation:',
              style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
            ),
            const SizedBox(height: 8),
            Container(
              padding: const EdgeInsets.all(16),
              decoration: BoxDecoration(
                border: Border.all(color: Colors.grey),
                borderRadius: BorderRadius.circular(4),
              ),
              child: SelectableText(
                _phonemes.isEmpty
                  ? 'Enter text and press Generate Speech'
                  : _phonemes,
                style: const TextStyle(
                  fontFamily: 'monospace',
                  fontSize: 16,
                ),
              ),
            ),
            const SizedBox(height: 24),
            
            // Example inputs
            const Text(
              'Example Inputs:',
              style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
            ),
            const SizedBox(height: 8),
            Wrap(
              spacing: 8,
              children: [
                _exampleChip('Hello world!'),
                _exampleChip('Kokoro TTS is amazing.'),
                _exampleChip('How are you today?'),
                _exampleChip('This is a test of the Kokoro TTS engine.'),
              ],
            ),
          ],
        ),
      ),
    );
  }
  
  Widget _exampleChip(String text) {
    return InputChip(
      label: Text(text),
      onPressed: _isInitialized && !_isGenerating && !_isPlaying
        ? () {
            _textController.text = text;
            _generateSpeech();
          }
        : null,
    );
  }
}
7
likes
0
points
22
downloads

Publisher

unverified uploader

Weekly Downloads

A Flutter implementation of Kokoro-TTS using ONNX Runtime

Homepage
Repository (GitHub)
View/report issues

License

unknown (license)

Dependencies

async, collection, ffi, flutter, flutter_onnxruntime, http, just_audio, malsami, path, path_provider

More

Packages that depend on kokoro_tts_flutter