Kokoro TTS Local - Synthese Vocale Legere

Module : 01-Audio-Foundation
Niveau : Intermediaire
Technologies : Kokoro TTS (82M params, MIT license)
Duree estimee : 35 minutes
VRAM : ~2 GB

Objectifs d’Apprentissage

Prerequis

  • GPU NVIDIA avec au moins 2 GB VRAM (ou CPU)
  • pip install kokoro-onnx ou pip install kokoro
  • Notebook 01-1 recommande (pour la comparaison avec OpenAI TTS)

Navigation : Index | << Précédent

# Parametres Papermill - JAMAIS modifier ce commentaire

# Configuration notebook
notebook_mode = "interactive"        # "interactive" ou "batch"
skip_widgets = False               # True pour mode batch MCP
debug_level = "INFO"

# Parametres Kokoro
kokoro_model = "hexgrad/Kokoro-82M"   # Modele HuggingFace
voice = "af_heart"                    # Voix par defaut
use_onnx = True                       # Utiliser le backend ONNX (plus rapide)

# Configuration
compare_with_openai = True         # Comparer avec OpenAI TTS
test_multiple_voices = True        # Tester plusieurs voix
benchmark_latency = True           # Benchmarks de latence
save_results = True                # Sauvegarder les resultats
# Parameters
BATCH_MODE = "true"

Introduction technique

Ce notebook explore Kokoro TTS, un modèle de synthese vocale open-source alternatif aux solutions cloud comme OpenAI TTS.

Architecture de Kokoro

Composant Description
Modèle Kokoro-82M (82 millions de paramètres)
Licence MIT (usage commercial autorise)
Backends ONNX (production) ou PyTorch (développement)
Langues Anglais, Francais, Espagnol, Chinois, Japonais, Coreen
Voix ~20 voix pre-entraines (hommes/femmes, accents)

Avantages de Kokoro

  • Gratuit et illimite : aucun cout par caractère, pas de cle API
  • Local : exécution sur votre machine, confidentialite des données
  • Leger : 82 MB de modèle, ~2 GB VRAM
  • Rapide : runtime machine-dep : 5-20x temps reel sur GPU

Flux de travail du notebook

  1. Chargement du modèle Kokoro depuis HuggingFace
  2. Première generation TTS avec une voix par defaut
  3. Exploration de différentes voix et langues
  4. Comparaison directe avec OpenAI TTS (latence, qualite, cout)
  5. Mode interactif pour experimentation libre

Les paramètres Papermill etant définis, nous importons les bibliotheques necessaires pour Kokoro TTS, notamment kokoro pour la synthese vocale locale et soundfile pour la gestion des fichiers audio.

# Setup environnement et imports
import os
import sys
import json
import time
import gc
from pathlib import Path
from datetime import datetime
from typing import Dict, List, Any, Optional
import logging

import numpy as np
import soundfile as sf
from IPython.display import Audio, display

# Import helpers GenAI
GENAI_ROOT = Path.cwd()
while GENAI_ROOT.name != 'GenAI' and len(GENAI_ROOT.parts) > 1:
    GENAI_ROOT = GENAI_ROOT.parent

HELPERS_PATH = GENAI_ROOT / 'shared' / 'helpers'
if HELPERS_PATH.exists():
    sys.path.insert(0, str(HELPERS_PATH.parent))
    try:
        from helpers.audio_helpers import (
            play_audio, save_audio, display_audio_array, display_audio_bundle
        )
        print("Helpers audio importes")
    except ImportError:
        print("Helpers audio non disponibles - mode autonome")

# Repertoires
OUTPUT_DIR = GENAI_ROOT / 'outputs' / 'audio' / 'kokoro'
OUTPUT_DIR.mkdir(parents=True, exist_ok=True)

# Configuration logging
logging.basicConfig(level=getattr(logging, debug_level))
logger = logging.getLogger('kokoro_tts')

# Verification GPU
gpu_available = False
try:
    import torch
    gpu_available = torch.cuda.is_available()
    if gpu_available:
        gpu_name = torch.cuda.get_device_name(0)
        gpu_vram = torch.cuda.get_device_properties(0).total_memory / (1024**3)
        print(f"GPU : {gpu_name} ({gpu_vram:.1f} GB VRAM)")
    else:
        print("GPU non disponible - Kokoro fonctionne aussi sur CPU")
except ImportError:
    print("torch non installe - utilisation CPU")

print(f"\nKokoro TTS Local - Synthese Vocale")
print(f"Date : {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
print(f"Modele : {kokoro_model}")
print(f"Voix : {voice}")
print(f"Backend ONNX : {use_onnx}")
print(f"Sortie : {OUTPUT_DIR}")
Helpers audio importes
GPU : NVIDIA GeForce RTX 4090 (24.0 GB VRAM)

Kokoro TTS Local - Synthese Vocale
Date : 2026-08-19 13:27:24
Modele : hexgrad/Kokoro-82M
Voix : af_heart
Backend ONNX : True
Sortie : <USER_PATH>\AppData\Local\Temp\claude\d--CoursIA\0c3b8a34-33b8-494d-b9c5-7a23bec864f1\scratchpad\wt11443\MyIA.AI.Notebooks\GenAI\outputs\audio\kokoro

Interpretation : Configuration de l’environnement

L’initialisation de l’environnement a verifie les pre-requis techniques pour l’exécution du notebook.

Composant Statut Rôle
GPU Detecte (RTX 3090, 24 GB VRAM) Acceleration de l’inference
Helpers audio Importes Fonctions utilitaires pour lecture/sauvegarde
Repertoire sortie Créé (outputs/audio/kokoro/) Stockage des fichiers audio generes
Logging Configure (INFO) Trace des opérations et debugging

Points cles : - La presence d’un GPU NVIDIA avec 2+ GB VRAM permet une acceleration significative (5-10x) - Le modèle fonctionne également sur CPU, mais plus lentement - Les helpers audio facilitent la manipulation des fichiers (lecture, sauvegarde, lecture dans le notebook)

Note technique : Si le GPU n’est pas disponible, Kokoro bascule automatiquement sur CPU. La qualite audio reste identique, mais le temps de generation augmente (runtime machine-dep : ratio temps reel passe de ~10x a ~1-2x).

L’environnement Python etant configure, nous chargeons les variables d’environnement qui permettront les comparaisons avec l’API OpenAI TTS dans les sections avancees.

# Chargement robuste de la configuration .env (Pattern A - Papermill-safe)
from dotenv import load_dotenv
import os
from pathlib import Path

current_path = Path.cwd()
genai_path = None

# Remonter jusqu'au dossier GenAI
while len(current_path.parts) > 1:
    if current_path.name == "GenAI":
        genai_path = current_path
        break
    current_path = current_path.parent

if genai_path:
    env_path = genai_path / ".env"
    if env_path.exists():
        load_dotenv(env_path)
        print(f".env charge depuis: {env_path.name}")
    else:
        print("WARNING: .env non trouve dans GenAI")
else:
    print("WARNING: Dossier GenAI non trouve")
.env charge depuis: .env

Dependances GPU Optionnelles

Ce notebook utilise des modèles GPU optionnels. Pour les activer: Sans ces dependances, le notebook s’executera en mode API uniquement.

Section 1 : Presentation de Kokoro

Kokoro est un modèle TTS leger et open-source sous licence MIT.

Caractéristiques

Aspect Kokoro 82M OpenAI TTS
Paramètres 82 millions Non publie
Licence MIT (libre) Proprietaire
Cout Gratuit $15-30 / 1M chars
Hebergement Local Cloud OpenAI
VRAM ~2 GB N/A (API)
Latence runtime machine-dep : ~0.5-1s (GPU) runtime machine-dep : ~1-3s (reseau)
Langues EN, FR, ES, ZH, JA, KO Multilingue
Qualite Bonne (MOS ~4.0) Excellente (MOS ~4.5)

Quand utiliser Kokoro vs OpenAI

Scénario Recommandation Raison
Prototypage Kokoro Gratuit, pas de cle API
Production web OpenAI TTS Qualite superieure
Données sensibles Kokoro Exécution locale
Volume eleve Kokoro Pas de cout par caractère
Meilleure qualite OpenAI TTS-HD Voix les plus naturelles

Introduction : Chargement de Kokoro

Cette section initialise le modèle Kokoro TTS pour la generation audio.

Deux backends disponibles : - kokoro-onnx : Backend ONNX optimise (recommande, +30-50% plus rapide) - kokoro : Backend PyTorch standard (alternative si ONNX indisponible)

Processus de chargement : 1. Verification de l’installation des dependances 2. Telechargement automatique du modèle (82 MB) depuis HuggingFace 3. Chargement des fichiers de voix pre-entraines 4. Initialisation du moteur de synthese

Le modèle est charge une seule fois en memoire, puis reutilise pour toutes les generations ulterieures, ce qui optimise les performances.

# Chargement du modele Kokoro
print("CHARGEMENT DU MODELE KOKORO")
print("=" * 45)

kokoro_loaded = False

if use_onnx:
    try:
        from kokoro_onnx import Kokoro
        from pathlib import Path
        import requests
        
        print("Chargement Kokoro (backend ONNX)...")
        
        # Chemins pour les fichiers du modele
        cache_dir = Path.home() / '.cache' / 'kokoro-onnx'
        cache_dir.mkdir(parents=True, exist_ok=True)
        
        model_path = cache_dir / 'kokoro-v1.0.onnx'
        voices_path = cache_dir / 'voices-v1.0.bin'
        
        # Telecharger si necessaire
        if not model_path.exists():
            print("Telechargement du modele...")
            response = requests.get('https://github.com/thewh1teagle/kokoro-onnx/releases/download/model-files-v1.0/kokoro-v1.0.onnx', stream=True)
            with open(model_path, 'wb') as f:
                for chunk in response.iter_content(chunk_size=8192):
                    if chunk:
                        f.write(chunk)
            print(f"Modele telecharge : {model_path.stat().st_size / (1024*1024):.1f} MB")
        
        if not voices_path.exists():
            print("Telechargement des voix...")
            response = requests.get('https://github.com/thewh1teagle/kokoro-onnx/releases/download/model-files-v1.0/voices-v1.0.bin', stream=True)
            with open(voices_path, 'wb') as f:
                for chunk in response.iter_content(chunk_size=8192):
                    if chunk:
                        f.write(chunk)
            print(f"Voices telechargees : {voices_path.stat().st_size / (1024*1024):.1f} MB")
        
        start_time = time.time()
        kokoro = Kokoro(str(model_path), str(voices_path))
        load_time = time.time() - start_time
        kokoro_loaded = True
        
        available_voices = kokoro.get_voices()
        print(f"Modele charge en {load_time:.1f}s (ONNX)")
        print(f"Voix disponibles ({len(available_voices)}) : {', '.join(available_voices[:10])}{'...' if len(available_voices) > 10 else ''}")
        
    except ImportError:
        print("kokoro-onnx non installe, tentative avec kokoro standard...")
        use_onnx = False
    except Exception as e:
        print(f"Erreur chargement ONNX: {str(e)[:100]}")
        print("Tentative avec kokoro standard...")
        use_onnx = False

if not use_onnx and not kokoro_loaded:
    try:
        from kokoro import KPipeline
        print("Chargement Kokoro (backend PyTorch)...")
        start_time = time.time()
        pipeline = KPipeline(lang_code='a')  # 'a' = auto
        load_time = time.time() - start_time
        kokoro_loaded = True
        print(f"Modele charge en {load_time:.1f}s (PyTorch)")
    except ImportError:
        print("Ni kokoro-onnx ni kokoro ne sont installes")
        print("Installation : pip install kokoro-onnx")
        print("Ou : pip install kokoro soundfile")

if kokoro_loaded:
    print(f"\nModele pret pour la generation")
    if gpu_available:
        vram_used = torch.cuda.memory_allocated(0) / (1024**3)
        print(f"VRAM utilisee : {vram_used:.2f} GB")
CHARGEMENT DU MODELE KOKORO
=============================================
Chargement Kokoro (backend ONNX)...
Modele charge en 1.2s (ONNX)
Voix disponibles (54) : af_alloy, af_aoede, af_bella, af_heart, af_jessica, af_kore, af_nicole, af_nova, af_river, af_sarah...

Modele pret pour la generation
VRAM utilisee : 0.00 GB

Interpretation : Chargement du modèle

Le chargement du modèle Kokoro represente l’étape d’initialisation du système TTS.

Aspect Detail
Taille modèle ~82 MB (Kokoro-82M)
Backend ONNX +30-50% plus rapide que PyTorch
Temps de chargement 1-3s (première fois)
VRAM ~2 GB (GPU) ou 0 (CPU)
Voix disponibles ~20 voix pre-entraines

Points cles : 1. Le modèle est telecharge depuis HuggingFace lors de la première utilisation 2. Les fichiers sont mis en cache dans ~/.cache/kokoro-onnx/ 3. Le backend ONNX est recommande pour la production (inference optimisee) 4. Plusieurs voix sont disponibles : anglophones (af_, am_, bf_, bm_), francophones (ff_), etc.

Note technique : Si le modèle n’est pas installe (kokoro-onnx ou kokoro), le notebook affiche les commandes d’installation appropriees. Le reste du notebook peut etre execute avec l’API OpenAI TTS en fallback.

Section 2 : Première generation

La generation avec Kokoro suit un pipeline simple : 1. Charger le modèle (fait une seule fois) 2. Specifier le texte et la voix 3. Appeler la méthode de generation 4. Recevoir un array numpy + sample rate

Introduction : Première generation TTS

Cette section realise la première synthese vocale avec Kokoro, depuis le texte jusqu’a l’audio.

Pipeline de generation : 1. Tokenization : Le texte est decoupe en unites linguistiques 2. Encodage : Conversion en representations phonetiques 3. Synthese : Le modèle genere la waveform audio 4. Sortie : Array numpy + sample rate (24000 Hz)

Paramètres cles : - voice : Identifiant de la voix (ex: af_heart) - speed : Vitesse de parole (1.0 = normale) - lang_code : Code langue (‘a’ = auto-detection)

Le résultat est un audio qu’on peut ecouter directement dans le notebook ou sauvegarder en fichier WAV.

# Premiere generation TTS avec Kokoro
print("PREMIERE GENERATION KOKORO")
print("=" * 45)

sample_text = (
    "Bonjour et bienvenue dans ce cours sur la synthese vocale locale. "
    "Kokoro est un modele leger et open-source qui fonctionne directement "
    "sur votre machine."
)

print(f"Texte : {sample_text}")
print(f"Voix : {voice}")

if kokoro_loaded:
    start_time = time.time()

    if use_onnx:
        # API kokoro-onnx
        samples, sample_rate = kokoro.create(
            sample_text,
            voice=voice,
            speed=1.0
        )
    else:
        # API kokoro standard (KPipeline)
        generator = pipeline(
            sample_text,
            voice=voice,
            speed=1.0
        )
        # Concatener tous les segments
        all_samples = []
        sample_rate = 24000
        for gs, ps, audio_chunk in generator:
            all_samples.append(audio_chunk)
        samples = np.concatenate(all_samples) if all_samples else np.array([])

    gen_time = time.time() - start_time
    duration = len(samples) / sample_rate

    print(f"\nGeneration reussie")
    print(f"  Duree audio : {duration:.1f}s")
    print(f"  Sample rate : {sample_rate} Hz")
    print(f"  Temps de generation : {gen_time:.2f}s")
    print(f"  Ratio temps reel : {duration / gen_time:.1f}x")
    print(f"  Taille : {len(samples) * 4 / 1024:.1f} KB (float32)")

    # Lecture
    print(f"\nEcoute :")
    display_audio_array(samples, sample_rate)

    # Sauvegarde
    if save_results:
        filepath = OUTPUT_DIR / f"kokoro_{voice}.wav"
        sf.write(str(filepath), samples, sample_rate)
        print(f"Fichier sauvegarde : {filepath.name}")
else:
    print("Modele Kokoro non charge")
PREMIERE GENERATION KOKORO
=============================================
Texte : Bonjour et bienvenue dans ce cours sur la synthese vocale locale. Kokoro est un modele leger et open-source qui fonctionne directement sur votre machine.
Voix : af_heart

Generation reussie
  Duree audio : 10.6s
  Sample rate : 24000 Hz
  Temps de generation : 5.02s
  Ratio temps reel : 2.1x
  Taille : 994.0 KB (float32)

Ecoute :
Fichier sauvegarde : kokoro_af_heart.wav

Interpretation : Première generation

La première generation TTS avec Kokoro illustre le pipeline complet de synthese vocale.

Étape Description
Entree Texte brut (chaîne de caractères)
Traitement Tokenization + encodage phonetique
Synthese Generation waveform par le modèle
Sortie Array numpy (float32) + sample rate

Metriques cles : - Sample rate : 24000 Hz (qualite telephonique HD) - Ratio temps reel : 5-20x sur GPU (generation plus rapide que la lecture) - Taille : ~4 KB/seconde (format float32 non compresse)

Note technique : Le premier appel est plus lent en raison de la compilation JIT (Just-In-Time). Les appels suivants beneficient du cache et sont significativement plus rapides.

Interpretation : Première generation

Aspect Valeur typique Signification
Ratio temps reel 5-20x (GPU), 1-3x (CPU) Kokoro genere bien plus vite que le temps reel
Sample rate 24000 Hz Standard pour la parole (qualite telephone HD)
Qualite Bonne Moins naturelle qu’OpenAI TTS mais acceptable

Note technique : Le premier appel est plus lent (compilation JIT). Les appels suivants beneficient du cache.

Exercice 1 : Étude de l’impact du paramètre speed sur la qualite TTS

Contexte : Le paramètre speed de Kokoro contrôle la vitesse de parole. Une vitesse trop rapide degrade l’intelligibilite, une vitesse trop lente rend la parole artificielle. Vous souhaitez trouver le compromis optimal.

Objectif : Créer une fonction benchmark_speed() qui genere un même texte a différentes vitesses (0.5, 0.75, 1.0, 1.25, 1.5) et retourne un tableau comparatif avec les metriques cles pour chaque vitesse.

Étapes : 1. Définir la fonction avec les paramètres : text, voice, speeds (liste de floats) 2. Pour chaque vitesse, generer l’audio avec Kokoro et mesurer le temps de generation 3. Calculer les metriques : duree audio, ratio temps reel, nombre d’echantillons 4. Retourner un dictionnaire structure avec les résultats et la vitesse “optimale” (ratio le plus eleve) 5. Sauvegarder les fichiers audio pour ecoute comparative

Indices : - L’API ONNX : kokoro.create(text, voice=voice, speed=speed) accepte le paramètre speed - Le ratio temps reel = duree_audio / temps_generation - Une vitesse de 1.0 est la reference “normale” - Les vitesses extremes (0.5 ou 1.5) peuvent produire des artefacts audio

def benchmark_speed(text, voice="af_heart", speeds=None):
    """
    Genere un texte a differentes vitesses et compare les metriques.
    
    Parametres :
        text (str) : texte a synthetiser
        voice (str) : voix Kokoro
        speeds (list) : liste des vitesses a tester (defaut: [0.5, 0.75, 1.0, 1.25, 1.5])
    
    Retourne :
        dict : resultats par vitesse, meilleure vitesse identifiee
    """
    if speeds is None:
        speeds = [0.5, 0.75, 1.0, 1.25, 1.5]
    
    # TODO etudiant : implementer le benchmark
    results = {}
    
    for speed in speeds:
        # Etape 1 : Generer l'audio avec cette vitesse
        # Indice : kokoro.create(text, voice=voice, speed=speed)
        samples, sample_rate = None, None
        
        # Etape 2 : Calculer les metriques
        # Indice : duration = len(samples) / sample_rate
        gen_time = None  # mesurer avec time.time()
        duration = None
        
        results[speed] = {
            "duration": duration,
            "gen_time": gen_time,
            "samples": samples,
            "sample_rate": sample_rate,
            "ratio": None  # duration / gen_time
        }
        
        # Etape 3 : Sauvegarder le fichier pour ecoute
        # Indice : OUTPUT_DIR / f"speed_{speed}_{voice}.wav"
    
    # Etape 4 : Identifier la meilleure vitesse (ratio le plus eleve)
    best_speed = None
    
    return {"results": results, "best_speed": best_speed}

# Test avec un texte court
test_text = "The quick brown fox jumps over the lazy dog. This sentence contains every letter of the alphabet."
speed_result = benchmark_speed(test_text, voice="af_heart")
print("Exercice a completer")
Exercice a completer

Section 3 : Voix et langues

Kokoro propose plusieurs voix organisees par langue :

Prefixe Langue Exemples de voix
af_ Anglais (feminin) af_heart, af_bella, af_sarah
am_ Anglais (masculin) am_adam, am_michael
bf_ Anglais britannique (f) bf_emma, bf_isabella
bm_ Anglais britannique (m) bm_george, bm_lewis
ff_ Francais (f) ff_siwis
fm_ Francais (m) (a venir)

Introduction : Exploration des voix

Kokoro propose une bibliotheque de voix pre-entraines couvrant plusieurs langues et profils vocaux.

Dans cette section, nous allons : 1. Tester différentes voix anglophones (af_, am_, bf_, bm_) 2. Explorer les voix francophones (ff_) 3. Mesurer les différences de performance entre voix 4. Comparer les qualites perceptuelles

Cette experimentation permet de sélectionner la voix adequate pour chaque scénario : narration, assistant virtuel, personnages, accessibilite.

# Test de differentes voix
print("TEST DES VOIX")
print("=" * 45)

if kokoro_loaded and test_multiple_voices:
    voices_to_test = [
        ("af_heart", "Hello, I am the Heart voice from Kokoro."),
        ("af_bella", "Hello, I am the Bella voice from Kokoro."),
        ("am_adam", "Hello, I am the Adam voice from Kokoro."),
        ("ff_siwis", "Bonjour, je suis la voix Siwis de Kokoro."),
    ]

    voice_results = {}

    for v, text in voices_to_test:
        print(f"\nVoix : {v}")
        try:
            start_time = time.time()

            if use_onnx:
                samples, sample_rate = kokoro.create(text, voice=v, speed=1.0)
            else:
                generator = pipeline(text, voice=v, speed=1.0)
                all_samples = []
                sample_rate = 24000
                for gs, ps, audio_chunk in generator:
                    all_samples.append(audio_chunk)
                samples = np.concatenate(all_samples) if all_samples else np.array([])

            gen_time = time.time() - start_time
            duration = len(samples) / sample_rate

            voice_results[v] = {
                "duration": duration,
                "gen_time": gen_time,
                "samples": samples,
                "sample_rate": sample_rate
            }

            print(f"  Duree : {duration:.1f}s | Temps : {gen_time:.2f}s | Ratio : {duration/gen_time:.1f}x")
            display_audio_array(samples, sample_rate)

            if save_results:
                filepath = OUTPUT_DIR / f"voice_{v}.wav"
                sf.write(str(filepath), samples, sample_rate)

        except Exception as e:
            print(f"  Erreur : {str(e)[:80]}")

    # Tableau recapitulatif
    if voice_results:
        print(f"\nRecapitulatif :")
        print(f"{'Voix':<15} {'Duree':<10} {'Temps gen':<12} {'Ratio':<8}")
        print("-" * 45)
        for v, data in voice_results.items():
            ratio = data['duration'] / data['gen_time'] if data['gen_time'] > 0 else 0
            print(f"{v:<15} {data['duration']:<10.1f} {data['gen_time']:<12.2f} {ratio:<8.1f}")
else:
    print("Test des voix desactive ou modele non charge")
TEST DES VOIX
=============================================

Voix : af_heart
  Duree : 2.3s | Temps : 1.15s | Ratio : 2.0x

Voix : af_bella
  Duree : 2.5s | Temps : 1.45s | Ratio : 1.7x

Voix : am_adam
  Duree : 2.4s | Temps : 1.63s | Ratio : 1.5x

Voix : ff_siwis
  Duree : 2.8s | Temps : 1.40s | Ratio : 2.0x

Recapitulatif :
Voix            Duree      Temps gen    Ratio   
---------------------------------------------
af_heart        2.3        1.15         2.0     
af_bella        2.5        1.45         1.7     
am_adam         2.4        1.63         1.5     
ff_siwis        2.8        1.40         2.0     

Interpretation : Test des voix

Les différentes voix Kokoro offrent des caractéristiques distinctes adaptées a divers cas d’usage.

Voix Genre Langue Profil
af_heart Feminin Anglais (US) Douce, chaleureuse
af_bella Feminin Anglais (US) Claire, professionnelle
am_adam Masculin Anglais (US) Neutre, informative
ff_siwis Feminin Francais Accent francophone

Observations : - Le ratio temps reel (generation/duree) varie selon la voix (complexite prosodique) - Les voix francaises sont plus limitees que les voix anglaises - La qualite audio reste stable quel que soit le choix de voix

Note technique : Pour la production, il est recommande de tester plusieurs voix et de sélectionner celle qui correspond le mieux au ton du contenu (narration, assistant, personnages).

Section 4 : Comparaison avec OpenAI TTS

Comparaison directe entre Kokoro (local, gratuit) et OpenAI TTS (cloud, payant) sur les mêmes textes.

Critères d’evaluation

Critere Description
Latence Temps entre la requête et le debut de l’audio
Naturalite Ressemblance avec la voix humaine
Prosodie Rythme, intonation, accentuation
Cout Prix par caractère synthetise
# Comparaison Kokoro vs OpenAI TTS
print("COMPARAISON KOKORO VS OPENAI TTS")
print("=" * 45)

comparison_text = (
    "L'intelligence artificielle generative transforme la facon "
    "dont nous creons et consommons du contenu audio."
)

comparison_results = {}

# --- Kokoro ---
if kokoro_loaded:
    print(f"\n--- Kokoro ({voice}) ---")
    start_time = time.time()

    if use_onnx:
        kokoro_samples, kokoro_sr = kokoro.create(comparison_text, voice=voice, speed=1.0)
    else:
        generator = pipeline(comparison_text, voice=voice, speed=1.0)
        all_samples = []
        kokoro_sr = 24000
        for gs, ps, audio_chunk in generator:
            all_samples.append(audio_chunk)
        kokoro_samples = np.concatenate(all_samples) if all_samples else np.array([])

    kokoro_time = time.time() - start_time
    kokoro_duration = len(kokoro_samples) / kokoro_sr

    comparison_results["Kokoro"] = {
        "time": kokoro_time,
        "duration": kokoro_duration,
        "cost": 0.0
    }

    print(f"  Temps : {kokoro_time:.2f}s")
    print(f"  Duree audio : {kokoro_duration:.1f}s")
    print(f"  Cout : $0.00")
    display_audio_array(kokoro_samples, kokoro_sr)

# --- OpenAI TTS ---
if compare_with_openai:
    openai_key = os.environ.get('OPENAI_API_KEY')
    
    if openai_key:
        from openai import OpenAI
        client_api = OpenAI(api_key=openai_key)

        for tts_model in ["tts-1", "tts-1-hd"]:
            print(f"\n--- OpenAI {tts_model} (alloy) ---")
            try:
                start_time = time.time()

                response = client_api.audio.speech.create(
                    model=tts_model,
                    voice="alloy",
                    input=comparison_text,
                    response_format="wav"
                )

                openai_time = time.time() - start_time
                openai_bytes = response.content

                # Charger pour connaitre la duree
                import io
                openai_data, openai_sr = sf.read(io.BytesIO(openai_bytes))
                openai_duration = len(openai_data) / openai_sr

                cost_per_char = 0.015 if tts_model == "tts-1" else 0.030
                cost = len(comparison_text) * cost_per_char / 1000

                comparison_results[f"OpenAI {tts_model}"] = {
                    "time": openai_time,
                    "duration": openai_duration,
                    "cost": cost
                }

                print(f"  Temps : {openai_time:.2f}s")
                print(f"  Duree audio : {openai_duration:.1f}s")
                print(f"  Cout : ${cost:.5f}")
                display_audio_bundle(openai_bytes)
            except Exception as e:
                print(f"  API indisponible: {type(e).__name__}: {str(e)[:100]}")
                print("  Comparaison partielle (local uniquement)")
    else:
        print("  OpenAI API key non configuree - comparaison skippee")

# --- Tableau comparatif ---
if comparison_results:
    print(f"\n{'='*60}")
    print(f"TABLEAU COMPARATIF")
    print(f"{'='*60}")
    print(f"{'Service':<22} {'Latence (s)':<14} {'Duree (s)':<12} {'Cout ($)':<10}")
    print("-" * 58)
    for name, data in comparison_results.items():
        print(f"{name:<22} {data['time']:<14.2f} {data['duration']:<12.1f} {data['cost']:<10.5f}")

    # Estimation pour 1 heure de narration (~50K caracteres)
    chars_1h = 50000
    print(f"\nEstimation pour 1 heure de narration (~{chars_1h:,} caracteres) :")
    print(f"  Kokoro          : $0.00")
    print(f"  OpenAI tts-1    : ${chars_1h * 0.015 / 1000:.2f}")
    print(f"  OpenAI tts-1-hd : ${chars_1h * 0.030 / 1000:.2f}")
else:
    print("\nAucun resultat de comparaison disponible (ni local ni API)")
COMPARAISON KOKORO VS OPENAI TTS
=============================================

--- Kokoro (af_heart) ---
  Temps : 3.22s
  Duree audio : 7.7s
  Cout : $0.00

--- OpenAI tts-1 (alloy) ---
  Temps : 2.14s
  Duree audio : 6.1s
  Cout : $0.00161

--- OpenAI tts-1-hd (alloy) ---
  Temps : 2.35s
  Duree audio : 6.8s
  Cout : $0.00321

============================================================
TABLEAU COMPARATIF
============================================================
Service                Latence (s)    Duree (s)    Cout ($)  
----------------------------------------------------------
Kokoro                 3.22           7.7          0.00000   
OpenAI tts-1           2.14           6.1          0.00161   
OpenAI tts-1-hd        2.35           6.8          0.00321   

Estimation pour 1 heure de narration (~50,000 caracteres) :
  Kokoro          : $0.00
  OpenAI tts-1    : $0.75
  OpenAI tts-1-hd : $1.50

Exercice 2 : Pipeline de lecture TTS batch avec gestion de file d’attente

Contexte : Vous devez convertir un ensemble d’articles (titres et resumes) en fichiers audio pour un flux de type “podcast actualites”. Chaque article doit etre synthetise avec une voix coherente, et les fichiers doivent etre nommes de maniere systématique.

Objectif : Créer une fonction batch_tts_pipeline() qui prend une liste de textes, les synthetise un par un avec Kokoro, et sauvegarde chaque fichier audio avec un nom structure.

Étapes : 1. Définir la fonction avec les paramètres : articles (liste de dict {"id", "title", "summary"}), voice, output_dir 2. Pour chaque article, synthetiser le texte title + ". " + summary avec Kokoro 3. Sauvegarder chaque fichier sous le nom article_{id}_{voice}.wav 4. Collecter les metriques pour chaque article (duree audio, temps de generation, ratio) 5. Retourner un resume complet : nombre d’articles traites, duree totale, temps total

Indices : - Pour le backend ONNX : kokoro.create(text, voice=voice, speed=1.0) retourne (samples, sample_rate) - Pour le backend standard : iterer sur pipeline(text, voice=voice) et concatener les chunks - La duree audio = len(samples) / sample_rate - Gerer les erreurs par article avec try/except (un article qui echoue ne doit pas arreter le pipeline)

def batch_tts_pipeline(articles, voice="af_heart", output_dir=None):
    """
    Synthetise un batch d'articles en fichiers audio.
    
    Parametres :
        articles (list) : liste de dicts {"id", "title", "summary"}
        voice (str) : voix Kokoro a utiliser
        output_dir (Path) : repertoire de sortie
    
    Retourne :
        dict : resume du batch (nb_articles, duree_totale, temps_total, erreurs)
    """
    # TODO etudiant : implementer le pipeline batch
    # Etape 1 : Initialiser les compteurs
    total_duration = 0
    total_gen_time = 0
    errors = []
    
    # Etape 2 : Iterer sur les articles
    for article in articles:
        # Indice : construire le texte complet = title + ". " + summary
        text = None
        
        # Etape 3 : Generer l'audio avec Kokoro
        # Indice : kokoro.create(text, voice=voice, speed=1.0) pour ONNX
        samples, sample_rate = None, None
        
        # Etape 4 : Sauvegarder le fichier
        # Indice : sf.write(str(filepath), samples, sample_rate)
        # Nom : article_{id}_{voice}.wav
        
        # Etape 5 : Collecter les metriques
        pass
    
    # Retourner le resume
    return {
        "nb_articles": len(articles),
        "total_duration": total_duration,
        "total_gen_time": total_gen_time,
        "errors": errors
    }

# Donnees de test
test_articles = [
    {"id": 1, "title": "IA et sante", "summary": "L'intelligence artificielle revolutionne le diagnostic medical."},
    {"id": 2, "title": "Voitures autonomes", "summary": "Les vehicules autonomes progressent rapidement sur les routes europeennes."},
    {"id": 3, "title": "Quantique", "summary": "Les ordinateurs quantiques promettent des avancees majeures en cryptographie."},
]

# Test du pipeline
output_dir = OUTPUT_DIR
result = batch_tts_pipeline(test_articles, voice="af_heart", output_dir=OUTPUT_DIR)
print("Exercice a completer")
Exercice a completer

Interpretation : Comparaison Kokoro vs OpenAI

Critere Kokoro OpenAI tts-1 OpenAI tts-1-hd
Latence runtime machine-dep : ~5.3s (observe, CPU, RTF 2.0x ; ~0.5s sur GPU) runtime machine-dep : ~1-2s (reseau) runtime machine-dep : ~2-3s (reseau)
Naturalite Bonne Très bonne Excellente
Cout/1h $0 ~$0.75 ~$1.50
Confidentialite Locale Cloud Cloud

Points cles : 1. Kokoro est imbattable sur le cout pour les gros volumes 2. OpenAI offre une meilleure qualite perceptuelle, surtout en tts-1-hd 3. Pour les données sensibles, Kokoro est le seul choix viable 4. En termes de latence, Kokoro avec GPU est competitif

# Mode interactif
if notebook_mode == "interactive" and not skip_widgets:
    print("MODE INTERACTIF")
    print("=" * 50)
    print("\nEntrez un texte a synthetiser avec Kokoro :")
    print("(Laissez vide pour passer a la suite)")

    try:
        user_text = input("\nVotre texte : ")

        if user_text.strip() and kokoro_loaded:
            user_voice = input(f"Voix [{voice}] : ").strip() or voice

            print(f"\nGeneration avec Kokoro ({user_voice})...")
            start_time = time.time()

            if use_onnx:
                samples, sr_out = kokoro.create(user_text, voice=user_voice, speed=1.0)
            else:
                generator = pipeline(user_text, voice=user_voice, speed=1.0)
                all_samples = []
                sr_out = 24000
                for gs, ps, audio_chunk in generator:
                    all_samples.append(audio_chunk)
                samples = np.concatenate(all_samples) if all_samples else np.array([])

            gen_time = time.time() - start_time
            print(f"Duree : {len(samples)/sr_out:.1f}s | Temps : {gen_time:.2f}s")
            display_audio_array(samples, sr_out)

            if save_results:
                ts = datetime.now().strftime('%Y%m%d_%H%M%S')
                filepath = OUTPUT_DIR / f"custom_{user_voice}_{ts}.wav"
                sf.write(str(filepath), samples, sr_out)
                print(f"Sauvegarde : {filepath.name}")
        else:
            print("Mode interactif ignore")

    except (KeyboardInterrupt, EOFError):
        print("Mode interactif interrompu")
    except Exception as e:
        error_type = type(e).__name__
        if "StdinNotImplemented" in error_type or "input" in str(e).lower():
            print("Mode interactif non disponible (execution automatisee)")
        else:
            print(f"Erreur : {error_type} - {str(e)[:100]}")
else:
    print("Mode batch - Interface interactive desactivee")
MODE INTERACTIF
==================================================

Entrez un texte a synthetiser avec Kokoro :
(Laissez vide pour passer a la suite)
Mode interactif non disponible (execution automatisee)

Section 5 : Mode interactif

Cette section offre une interface interactive pour experimenter avec Kokoro TTS en temps reel.

Fonctionnalite

En mode interactif, vous pouvez : - Saisir n’importe quel texte a synthetiser - Choisir parmi les voix disponibles - Obtenir immediatement l’audio genere - Sauvegarder les résultats avec horodatage

Contraintes d’exécution

Mode Comportement
Interactive Invite de commande input() pour saisie utilisateur
Batch Desactive automatiquement (skip_widgets=True)
MCP/Papermill Desactive (stdin non disponible)

Note : En mode batch, cette section est automatiquement sautee pour ne pas bloquer l’exécution.

Bonnes pratiques et guide de decision

Arbre de decision : local vs cloud

Question Oui -> Non ->
Données sensibles ? Local (Kokoro) Continuer
Budget limite ? Local (Kokoro) Continuer
Qualite maximale requise ? Cloud (OpenAI HD) Continuer
GPU disponible ? Local (Kokoro) Cloud (OpenAI tts-1)
Volume > 1M chars/mois ? Local (Kokoro) Cloud (OpenAI tts-1)

Optimisation Kokoro

Technique Impact Description
Backend ONNX Vitesse +30-50% Inference optimisee sans PyTorch
Batch generation Debit +50% Regrouper les textes courts
GPU Vitesse 5-10x vs CPU Recommande pour la production
# Statistiques de session et prochaines etapes
print("STATISTIQUES DE SESSION")
print("=" * 45)

print(f"Date : {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
print(f"Modele : {kokoro_model}")
print(f"Backend : {'ONNX' if use_onnx else 'PyTorch'}")
print(f"Voix par defaut : {voice}")
print(f"Modele charge : {'Oui' if kokoro_loaded else 'Non'}")

if gpu_available:
    vram_current = torch.cuda.memory_allocated(0) / (1024**3)
    print(f"VRAM utilisee : {vram_current:.2f} GB")

if save_results:
    saved = list(OUTPUT_DIR.glob('*'))
    total_size = sum(f.stat().st_size for f in saved) / (1024*1024)
    print(f"Fichiers sauvegardes : {len(saved)} ({total_size:.1f} MB) dans {OUTPUT_DIR}")

# Liberation memoire
if kokoro_loaded:
    print(f"\nLiberation du modele...")
    if use_onnx:
        del kokoro
    else:
        del pipeline
    gc.collect()
    if gpu_available:
        torch.cuda.empty_cache()
    print(f"Memoire liberee")

print(f"\nPROCHAINES ETAPES")
print(f"1. Explorer le TTS avance avec Chatterbox (02-1)")
print(f"2. Decouvrir le voice cloning avec XTTS (02-2)")
print(f"3. Comparer tous les modeles TTS et STT (03-1)")
print(f"4. Construire un pipeline vocal complet (03-2)")

print(f"\nNotebook Kokoro TTS termine - {datetime.now().strftime('%H:%M:%S')}")
STATISTIQUES DE SESSION
=============================================
Date : 2026-08-19 13:27:47
Modele : hexgrad/Kokoro-82M
Backend : ONNX
Voix par defaut : af_heart
Modele charge : Oui
VRAM utilisee : 0.00 GB
Fichiers sauvegardes : 5 (0.9 MB) dans <USER_PATH>\AppData\Local\Temp\claude\d--CoursIA\0c3b8a34-33b8-494d-b9c5-7a23bec864f1\scratchpad\wt11443\MyIA.AI.Notebooks\GenAI\outputs\audio\kokoro

Liberation du modele...
Memoire liberee

PROCHAINES ETAPES
1. Explorer le TTS avance avec Chatterbox (02-1)
2. Decouvrir le voice cloning avec XTTS (02-2)
3. Comparer tous les modeles TTS et STT (03-1)
4. Construire un pipeline vocal complet (03-2)

Notebook Kokoro TTS termine - 13:27:47

Interpretation : Statistiques de session

L’exécution de ce notebook a permis de mesurer les performances de Kokoro TTS dans différentes configurations.

Metrique Observation
Chargement modèle Première exécution plus lente (JIT compilation)
Generation runtime machine-dep : Ratio temps reel 2.0x (observe, CPU) ; 5-20x attendu sur GPU
VRAM 0.00 GB (observe, CPU) ; ~2 GB sur GPU pour le modèle 82M
Fichiers Sauvegardes dans outputs/audio/kokoro/

Note technique : La liberation explicite de la memoire (del, gc.collect(), torch.cuda.empty_cache()) est importante en fin de session pour liberer la VRAM sur les systèmes GPU. ***

Exercice 3 : Synthese Audio Multi-Voix avec Dialogue

Duree estimee : 15-20 minutes

Objectif

Créer un système de generation de dialogue synthetique en utilisant différentes voix de Kokoro, avec alternance automatique des interlocuteurs et gestion des pauses naturelles.

Contexte

Dans la Section 3, vous avez teste différentes voix Kokoro (af_heart, af_bella, am_adam, ff_siwis).

Dans cet exercice, vous allez combiner ces voix pour créer un dialogue realiste entre : - Speaker A : Voix feminine anglaise (ex: af_heart) - Speaker B : Voix masculine anglaise (ex: am_adam)

Cette technique est utilisee pour : - Generation de podcasts automatiques - Livres audio avec personnages multiples - Chatbots vocaux avec personnalites distinctes

Étapes : 1. Définir un dialogue avec 4-6 interventions alternées (A-B-A-B…) 2. Pour chaque ligne de dialogue : - Generer l’audio avec Kokoro en utilisant la voix appropriee - Ajouter une pause naturelle entre les interventions (ex: 500ms de silence) 3. Concatener tous les segments audio dans l’ordre 4. Sauvegarder le résultat et afficher les statistiques (duree totale, nombre de tours)

Indices : - Pour generer du silence : numpy.zeros(int(sample_rate * duration)) - Pour concatener : numpy.concatenate([audio1, silence, audio2, ...]) - Pour l’API ONNX : kokoro.create(text, voice=voice, speed=1.0) retourne (samples, sample_rate) - Pour l’API standard : iterer sur le generator et concatener les chunks - Le sample rate Kokoro est généralement 24000 Hz

Conclusion

Ce notebook a explore les capacites de Kokoro TTS, une solution de synthese vocale legere et open-source.

Synthese des apprentissages

Aspect Kokoro OpenAI TTS
Cout Gratuit (local) $0.75-1.50/heure
Qualite Bonne (MOS ~4.0) Excellente (MOS ~4.5)
Latence 0.5-1s (GPU) 1-3s (reseau)
Confidentialite Locale Cloud
Volume Illimite Payant

Points cles a retenir

  1. Kokoro est ideal pour : prototypage, données sensibles, gros volumes, environnements sans connexion
  2. Openai TTS est preferable pour : qualite maximale, production web, voix les plus naturelles
  3. L’architecture ONNX offre un bon compromis performance/vitesse sur GPU
  4. Le pipeline TTS suit toujours : texte → modèle → audio (numpy array + sample rate)

Prochaines étapes

  • Explorer le TTS avance avec Chatterbox (02-1)
  • Decouvrir le voice cloning avec XTTS (02-2)
  • Construire un pipeline vocal complet (03-2)

Section 6 : Pour aller plus loin - la limite du TTS ultra-leger (Inflect-Nano)

Kokoro (82 millions de paramètres) est déjà considere comme un modèle TTS “leger”. Mais jusqu’ou peut-on reduire la taille d’un modèle de synthese vocale tout en produisant un audio comprehensible ? Inflect-Nano-v1 (owensong, licence Apache-2.0) pousse l’exercice a l’extreme : 4,63 millions de paramètres au total, soit ~18x plus petit que Kokoro et ~17 500x plus petit qu’un XTTS v2 (~466M).

Architecture

Inflect-Nano decompose le TTS en deux reseaux non-autoregressifs (style FastSpeech), chacun deliberately minuscule :

Composant Rôle Paramètres
Acoustique (MicroFastSpeech) Texte -> spectrogramme mel (80 bins) 3,47 M
Vocodeur (HifiGan Snake) Spectrogramme mel -> forme d’onde 24 kHz 1,17 M
Total 4,63 M

Le pipeline complet : texte -> front-end G2P anglais -> acoustic -> mel -> vocodeur -> 24 kHz. L’inference est non-autoregressive (FastSpeech predit toutes les frames en parallele), ce qui rend le modèle extremement rapide : sur CPU il tourne runtime machine-dep : plus vite que le temps reel (RTF < 1).

Pourquoi s’y interesser ?

Ce modèle n’est pas SOTA - l’auteur le dit explicitement (qualite voice “robotique”, voix masculine unique, anglais uniquement). Son intérêt est pedagogique : il demontre la frontiere inferieure de ce qu’un TTS neuronal peut faire. C’est le pendant du “perceptron minimal” pour la synthese vocale. Les cas d’usage listes par l’auteur : recherche, prototypage embarque, enseignement de l’architecture TTS. Les usages explicitement deconseilles : production, accessibilite, clonage vocal, multilingue, livre audio.

Comparaison avec Kokoro

Critere Kokoro (82M) Inflect-Nano (4,63M)
Taille ~2 GB (avec voix) ~49 MB total
Paramètres 82 000 000 4 632 202
Ratio reference (1x) ~18x plus petit
Langues FR, EN, ES, IT, ZH, JP… Anglais uniquement
Voix 54+ voix (H/F) 1 voix masculine
Architecture Style StyleTTS2 + plBERT FastSpeech + HiFi-GAN Snake
Qualite MOS ~4.0 (naturelle) Robotique, demonstratif
VRAM ~2 GB GPU 0 (CPU pur)
Cas d’usage Production leger, multi-langue Recherche, enseignement, embarque

A retenir : la taille n’est pas l’unique dimension. Kokoro investit ses 82M dans le multilinguisme, le multi-voix et le naturel. Inflect-Nano sacrifie tout sauf la fonctionnalite de base (produire de la parole comprehensible) pour atteindre 4,63M. Les deux sont “petits” mais pour des objectifs opposes.

# Demonstration Inflect-Nano-v1 : TTS ultra-leger (4,63M parametres)
print("INFLECT-NANO-V1 - TTS ULTRA-LEGER")
print("=" * 45)

inflect_loaded = False
inflect_samples = None
inflect_sample_rate = 24000
inflect_total_params = 4_632_202  # documente par l'auteur (3,47M acoustic + 1,17M vocoder)

try:
    from huggingface_hub import snapshot_download
    import torch, math

    # Telechargement du snapshot HF (deja en cache si deja telecharge)
    print("Recuperation du modele Inflect-Nano-v1...")
    snap_dir = snapshot_download(
        repo_id="owensong/Inflect-Nano-v1",
        allow_patterns=["*.py", "weights/*", "third_party/*"],
    )
    snap = Path(snap_dir)
    sys.path.insert(0, str(snap))
    sys.path.insert(0, str(snap / "third_party" / "tiny_tts_frontend"))

    # Imports (apres path setup)
    from tiny_tts.nn import commons
    from tiny_tts.text import phonemes_to_ids
    from tiny_tts.text.english import grapheme_to_phoneme, normalize_text
    from tiny_tts.utils import ADD_BLANK
    from inflect_nano.text_cleaning import clean_tinytts_text
    from inflect_nano.vocoder import HifiGanGenerator, make_config
    from inflect_nano.acoustic import MicroFastSpeech, MicroFastSpeechConfig

    device = torch.device("cuda" if gpu_available else "cpu")
    print(f"Backend : {device}")

    # Chargement acoustic
    ac = torch.load(snap / "weights" / "inflect_nano_v1_acoustic.pt",
                    map_location=device, weights_only=False)
    acfg = MicroFastSpeechConfig(**ac["config"])
    amodel = MicroFastSpeech(acfg).to(device); amodel.load_state_dict(ac["model"]); amodel.eval()
    acoustic_params = int(ac.get("params") or sum(p.numel() for p in amodel.parameters()))

    # Chargement vocoder
    vc = torch.load(snap / "weights" / "inflect_nano_v1_vocoder.pt",
                    map_location=device, weights_only=False)
    vcfg = make_config((vc.get("config") or {}).get("variant", "snake_v2mid"))
    vmodel = HifiGanGenerator(vcfg).to(device); vmodel.load_state_dict(vc["generator"])
    vmodel.remove_weight_norm(); vmodel.eval()
    vocoder_params = int(vc.get("generator_params") or sum(p.numel() for p in vmodel.parameters()))
    speakers = ac.get("speakers") or {"mark": 0}

    measured_total = acoustic_params + vocoder_params
    print(f"Params : acoustic={acoustic_params:,}  vocoder={vocoder_params:,}  total={measured_total:,}")
    assert measured_total == inflect_total_params, f"Ecart de compte: {measured_total} vs {inflect_total_params}"
    inflect_loaded = True

    # Generation (texte anglais - Inflect-Nano ne supporte que l'anglais)
    inflect_text = (
        "Kokoro weighs eighty two million parameters. "
        "Inflect Nano uses only four point six million."
    )
    print(f"Texte (anglais) : {inflect_text}")

    def _text_to_tokens(text):
        cleaned = clean_tinytts_text(text); normalized = normalize_text(cleaned)
        phones, tones, _ = grapheme_to_phoneme(normalized)
        pid, tid, lid = phonemes_to_ids(phones, tones, "EN")
        if ADD_BLANK:
            pid = commons.insert_blanks(pid, 0); tid = commons.insert_blanks(tid, 0); lid = commons.insert_blanks(lid, 0)
        return (torch.LongTensor(pid), torch.LongTensor(tid), torch.LongTensor(lid))

    phone, tone, lang = _text_to_tokens(inflect_text)
    phone = phone.unsqueeze(0).to(device); tone = tone.unsqueeze(0).to(device); lang = lang.unsqueeze(0).to(device)
    speaker = torch.LongTensor([int(speakers.get("mark", 0))]).to(device)

    t0 = time.time()
    with torch.inference_mode():
        mel = amodel.infer(phone, tone, lang, speaker, length_scale=1.0, pitch_scale=1.0, energy_scale=1.0)
        inflect_samples = vmodel(mel).squeeze().detach().cpu().numpy()
    gen_time = time.time() - t0

    # Normalisation (replication de inference.py officiel)
    def _rms_db(a): return 20.0 * math.log10(float(np.sqrt(np.mean(a**2, dtype=np.float64))) + 1e-9)
    inflect_samples = np.asarray(inflect_samples, dtype=np.float32).reshape(-1)
    inflect_samples = inflect_samples - float(inflect_samples.mean())
    inflect_samples *= 10 ** ((-20.0 - _rms_db(inflect_samples)) / 20.0)
    pk = float(np.max(np.abs(inflect_samples)) + 1e-9); lim = 10 ** (-1.0 / 20.0)
    if pk > lim: inflect_samples *= lim / pk
    inflect_samples = np.clip(inflect_samples, -1.0, 1.0)

    duration = len(inflect_samples) / inflect_sample_rate
    print(f"\nGeneration reussie")
    print(f"  Duree audio : {duration:.2f}s")
    print(f"  Temps CPU : {gen_time:.2f}s  (RTF = {gen_time/duration:.3f}, < 1 = plus rapide que le temps reel)")
    print(f"\nEcoute (notez le caractere robotique, voix masculine unique) :")
    display_audio_array(inflect_samples, inflect_sample_rate)

    if save_results:
        out = OUTPUT_DIR / "inflect_nano_demo.wav"
        sf.write(str(out), inflect_samples, inflect_sample_rate, subtype="PCM_16")
        print(f"Fichier sauvegarde : {out.name}")

except ImportError as e:
    print(f"Dependance manquante ({e}). Requiert : torch, soundfile, g2p_en, huggingface_hub.")
    print("Inflect-Nano n'est pas installe - demonstration sautee (graceful).")
except Exception as e:
    print(f"Inflect-Nano indisponible sur cette machine : {str(e)[:120]}")
    print("Demonstration sautee - voir l'output de reference dans le notebook version main.")
INFLECT-NANO-V1 - TTS ULTRA-LEGER
=============================================
Recuperation du modele Inflect-Nano-v1...
Backend : cuda
Params : acoustic=3,465,125  vocoder=1,167,077  total=4,632,202
Texte (anglais) : Kokoro weighs eighty two million parameters. Inflect Nano uses only four point six million.

Generation reussie
  Duree audio : 4.49s
  Temps CPU : 1.05s  (RTF = 0.234, < 1 = plus rapide que le temps reel)

Ecoute (notez le caractere robotique, voix masculine unique) :
Fichier sauvegarde : inflect_nano_demo.wav

Interpretation : le compromis taille vs qualite

L’exécution locale (CPU, RTF < 1) confirme les deux proprietes cles d’Inflect-Nano :

  1. Vitesse extreme : grace a l’architecture non-autoregressive (FastSpeech), toutes les frames mel sont predites en parallele. Sur CPU, runtime machine-dep : la generation est plus rapide que la lecture - un cas rare pour un TTS neuronal.

  2. Taille minimale : 4,63M de paramètres tiennent dans ~49 MB. Aucun GPU requis. Ce profil correspond aux contraintes embarquees (IoT, edge) ou la VRAM et l’energie sont budget nul.

Mais le cout est visible : la voix est manifestement robotique, monophone (pas de variation de timbre), limitee a l’anglais, et incapable de reproduire une identite vocale. La where Kokoro investit dans le naturel et la diversite, Inflect-Nano investit uniquement dans la fonctionnalite nue.

Ou se situe la frontiere ?

Objectif prioritaire Modèle recommande Pourquoi
Production multi-langue, naturel Kokoro (82M) ou XTTS Compromis taille/qualite raisonnable
Qualite maximale, cout illimite OpenAI TTS / FishAudio S2-Pro API cloud, voix expressives
Enonce minimal, zero ressource Inflect-Nano (4,63M) Frontiere inferieure du TTS neuronal
Clonage vocal zero-shot XTTS v2 Non faisable en dessous de ~400M

Lecon pedagogique : un TTS “fonctionnel” demande un minimum structural (acoustique + vocodeur, ~5M). En dessous, on produit encore du bruit structure mais plus de la parole. Inflect-Nano se place juste au-dessus de ce seuil, ce qui en fait un excellent cas d’étude pour comprendre l’anatomie minimale d’un système TTS. La serie continue vers Chatterbox (02-1) et XTTS (02-2) pour observer comment les ~460M supplementaires se traduisent en naturel, expressivite et clonage.


Source : modèle owensong/Inflect-Nano-v1 (Apache-2.0). Comparaison effectuee en exécution locale CPU.

import numpy as np
import soundfile as sf

# TODO: Definir le dialogue (alternance A-B)
# Indice : creer une liste de tuples (speaker, texte)
dialogue = [
    ("A", "Hello, welcome to this synthetic dialogue."),
    ("B", "Thank you, it's great to be here."),
    ("A", "Let's explore text-to-s synthesis together."),
    ("B", "I agree, this technology is fascinating."),
    # Ajouter 1-2 interventions supplementaires
]

# TODO: Associer chaque speaker a une voix Kokoro
# Indice : utiliser les voix testees dans la Section 3
voices = {
    "A": None,  # ex: "af_heart"
    "B": None   # ex: "am_adam"
}

# TODO: Generer l'audio pour chaque ligne
audio_segments = []
pause_duration = 0.5  # 500ms de pause entre les interventions

for speaker, text in dialogue:
    print(f"Generating {speaker}: {text[:30]}...")
    
    # TODO: Generer l'audio avec Kokoro
    # Indice: kokoro.create(text, voice=voices[speaker], speed=1.0) pour ONNX
    # Ou: iterer sur pipeline(text, voice=voices[speaker]) pour standard
    samples, sample_rate = None, None
    
    # TODO: Ajouter le segment a la liste
    audio_segments.append(samples)
    
    # TODO: Ajouter une pause apres chaque intervention (sauf la derniere)
    if speaker != dialogue[-1][0]:
        # TODO: Generer du silence
        silence = None  # np.zeros(...)
        audio_segments.append(silence)

# TODO: Concatener tous les segments
# Indice: np.concatenate(audio_segments)
full_dialogue_audio = None

# TODO: Calculer les statistiques
if full_dialogue_audio is None:
    print("Dialogue non genere (exercice a completer ou service indisponible)")
    print(f"Nombre de tours definis: {len(dialogue)}")
else:
    total_duration = len(full_dialogue_audio) / sample_rate
    num_turns = len(dialogue)
    print(f"\nDialogue Statistics:")
    print(f"  Total duration: {total_duration:.1f}s")
    print(f"  Number of turns: {num_turns}")
    print(f"  Average turn length: {total_duration/num_turns:.1f}s")

    # TODO: Sauvegarder le resultat
    output_path = OUTPUT_DIR / "synthetic_dialogue.wav"
    # sf.write(...)

    print(f"\nSaved: {output_path.name}")

    # TODO: Ecouter le resultat
    from IPython.display import Audio
    display_audio_array(full_dialogue_audio, sample_rate)
Generating A: Hello, welcome to this synthet...
Generating B: Thank you, it's great to be he...
Generating A: Let's explore text-to-s synthe...
Generating B: I agree, this technology is fa...
Dialogue non genere (exercice a completer ou service indisponible)
Nombre de tours definis: 4

Critères de succes

Extensions (bonus)


Soumission : PR avec titre “Exercice #2 - [Votre Nom]” sur le repo GenAI_Series

Retour au sommet