La synthèse et la reconnaissance vocales sont des outils informatiques puissants qui se développent de plus en plus aujourd’hui. Sur les systèmes d’exploitation récents, on retrouve des outils comme Cortana, Dictation, Siri ou encore des lecteurs d’écran utilisés pour l’accessibilité.

Mais qu’en est-il du Web ? Une page web, via votre navigateur, pourrait-elle écouter votre voix et vous répondre ?

À vrai dire, quelques ingénieux esprits se sont déjà penchés sur la question. L’API Web Speech existe depuis quelque temps maintenant, la spécification a été écrite en 2014, sans que des changements majeurs soient apportés depuis. Fin 2015, cette API est implémentée par Firefox (à partir de la version 44 derrière une préférence utilisateur et à partir de Firefox OS 2.5) et par Chrome.

Dans cet article, nous verrons comment fonctionne cette API et comment s’amuser avec ce que nous avons d’ores et déjà sous la main.

Comment ça marche ?

À première vue, on peut penser : « la synthèse vocale, ça doit pas être évident à implémenter ». D’une certaine façon, c’est vrai. Généralement, par défaut, les navigateurs exploitent les services présents sur le système d’exploitation. Ainsi, en utilisant la synthèse vocale sur Firefox ou Chrome sur OS X, vous utilisez indirectement Mac Speech.

La synthèse et la reconnaissance vocales sont deux parties distinctes de la même spécification pour l’API Web Speech. Elles opèrent indépendamment l’une de l’autre. Rien ne vous empêche de développer une application qui reconnaît une entrée vocale et qui « parle » ensuite à l’utilisateur mais on a bien deux fonctionnalités distinctes : la synthèse vocale d’une part et la reconnaissance vocale d’autre part.

Chacune possède un ensemble d’interfaces qui définissent leurs fonctionnalités. Au milieu de ces deux ensembles, on trouve deux interfaces de contrôle : SpeechRecognition pour la reconnaissance vocale et SpeechSynthesis pour la synthèse vocale. Dans les paragraphes qui suivent, nous verrons comment employer ces interfaces afin de construire des applications vocales.

Un peu plus de détails sur le support des navigateurs

Comme écrit avant, à l’heure actuelle, deux navigateurs implémentent cette API : Firefox et Chrome. Chrome et Chrome mobile supportent la synthèse et la reconnaissance vocales depuis la version 33 et la reconnaissance utilise des préfixes webkit.

Quant à Firefox, il supporte les deux aspects de cette API mais il faut noter quelques éléments :

  • Bien que la reconnaissance vocale soit implémentée dans Gecko, elle n’est, aujourd’hui, pas utilisable dans Firefox pour ordinateur et Android car l’interface utilisateur permettant de gérer les permissions n’a pas encore été implémentée.
  • Pour utiliser la reconnaissance et la synthèse vocale dans Firefox (pour ordinateur et Android), il faut activer les préférences media.webspeech.recognition.enable et media.webspeech.synth.enabled grâce à about:config.
  • Sous Firefox OS, pour qu’une application puisse utiliser la reconnaissance vocale, il faut qu’elle soit privilégiée et qu’elle demande les permissions de capture audio et de reconnaissance vocale (voir un exemple de manifeste).
  • Le gestionnaire d’événement onnomatch est limité et n’est pas déclenché car le moteur de reconnaissance vocale intégré dans Gecko, Pocketsphinx, ne supporte pas d’indicateur de confiance. Aussi, au lieu de pouvoir dire « cela ne correspond à aucun des choix », il indiquera « voici l’option la plus proche de ce que vous avez dit ».

Note : Chrome ne semble pas gérer de grammaires spécifiques. Il renvoie tous les résultats et vous pouvez alors les traiter comme vous le souhaitez. Cela est notamment dû à la plus grande puissance de calcul offerte par l’architecture : la reconnaissance est faite côté serveur. Firefox utilise une solution côté client. Chaque approche a ses avantages.

Démonstration

Nous allons décrire deux exemples d’applications simples pour vous permettre d’utiliser la synthèse et la reconnaissance vocales. Le premier est un sélecteur de couleur vocal et le second un synthétiseur. Les deux se trouvent sur GitHub.

Pour utiliser ces applications, des pages de démonstrations sont disponibles :

La reconnaissance vocale

Étudions d’un peu plus près le JavaScript à l’œuvre dans notre démo de sélecteur de couleur.

Support de Chrome

Comme nous l’avons vu avant, Chrome supporte la reconnaissance vocale à l’aide de propriétés préfixées. On commence donc à déclarer les interfaces de cette façon afin de s’assurer que chaque navigateur ait ce qu’il lui faut :

var SpeechRecognition = SpeechRecognition || webkitSpeechRecognition
var SpeechGrammarList = SpeechGrammarList || webkitSpeechGrammarList
var SpeechRecognitionEvent = SpeechRecognitionEvent || webkitSpeechRecognitionEvent

La grammaire

On définit ensuite la grammaire qu’on souhaite faire digérer à l’application. Ici, ce sera une simple liste de couleurs :

var grammaire = '#JSGF V1.0; grammar colors; public  = aqua | azure | beige | bisque | black | [TOUT PLEIN DE COULEURS] ;'

Le format de cette grammaire est le JSGF : JSpeech Grammar Format

Câbler la grammaire avec l’interface de reconnaissance

L’étape suivante consiste à définir une instance qui contrôlera la reconnaissance vocale de notre application. Pour cela, on utilise le constructeur SpeechRecognition). On crée aussi une nouvelle liste de grammaires vocales grâce au constructeur SpeechGrammarList).

var recognition = new SpeechRecognition();
var speechRecognitionList = new SpeechGrammarList();

Ensuite, on ajoute la grammaire déclarée au préalable dans notre liste de grammaires reconnues avec la méthode SpeechGrammarList.addFromString()). Les paramètres de cette méthode sont :

  • La grammaire qu’on souhaite ajouter
  • Un poids, optionnel, qui définit l’importance de cette grammaire par rapport aux autres grammaires de la liste. Ce facteur peut aller de 0 à 1 (au sens large).

La grammaire ajoutée est alors disponible dans la liste comme une instance de SpeechGrammar).

speechRecognitionList.addFromString(grammaire, 1);

Pour que cette liste speechRecognitionList (qui est une SpeechGrammarList) soit utilisée pour la reconnaissance vocale, il suffit de définir la valeur de la propriété grammars) avec cet objet.

recognition.grammars = speechRecognitionList;

Démarrer la reconnaissance vocale

Ensuite, on implémente le gestionnaire onclick qui agit lorsqu’on clique/touche sur l’écran : on déclenche alors le service de reconnaissance vocale avec un appel à SpeechRecognition.start()).

var diagnostic = document.querySelector('.output');
var bg = document.querySelector('html');

document.body.onclick = function() {
  recognition.start();
  console.log('Prêt à recevoir une commande de couleur.');
}

Recevoir et gérer les résultats

Une fois que la reconnaissance vocale est démarrée, on peut utiliser de nombreux gestionnaires d’événement pour obtenir différentes informations (voir la liste des gestionnaires d’événement attachés à SpeechRecognition). Celui qu’on utilisera probablement le plus souvent est SpeechRecognition.onresult) : il est déclenché à chaque fois qu’un résultat positif est détecté.

recognition.onresult = function(event) {
  var color = event.results[0][0].transcript;
  diagnostic.textContent = 'Résultat reçu : ' + color + '.';
  bg.style.backgroundColor = color;
  console.log('Niveau de correspondance : ' + event.results[0][0].confidence);
}

La deuxième ligne de ce fragment de code est plutôt complexe. Décomposons-la. La propriété SpeechRecognitionEvent.results) renvoie un objet SpeechRecognitionResultList) qui contient un ou plusieurs objets SpeechRecognitionResult). Cette liste contient un accesseur et peut donc être manipulée comme un tableau, ainsi [0] renvoie l’objet SpeechRecognitionResult à la position 0.

Chaque objet SpeechRecognitionResult contient des objets SpeechRecognitionAlternative) qui contiennent chacun des mots reconnus. Avec leurs accesseurs, on peut également manipuler les SpeechRecognitionResult comme des tableaux. Le second 0] indique qu’on accède à l’objet SpeechRecognitionAlternative en position 0. On utilise la propriété [transcript) afin d’accéder à la chaîne de caractères reconnue. Ensuite, on utilise cette valeur comme couleur pour l’arrière-plan en définissant la propriété CSS et on indique la couleur obtenue via l’interface utilisateur.

Vous pourrez trouver plus de détails à propos de cette démo sur MDN.

La synthèse vocale

Voyons maintenant le fonctionnement de la démonstration du synthétiseur vocal.

Définir les variables

Tout d’abord, on fait référence à Window.speechSynthesis) qui est le point d’entrée de l’API. Cette propriété renvoie une instance de SpeechSynthesis), le contrôleur pour la synthèse vocale web. On crée également un tableau vide qui sera utilisé pour stocker les différentes voix du système (voir après).

var synth = window.speechSynthesis;
  …
var voices = [];

Ajouter des valeurs à l’élément <select>

Afin de remplir l’élément <select> avec les différentes voix offertes par l’appareil, on écrit une fonction populateVoiceList(). Elle commence par invoquer SpeechSynthesis.getVoices()) qui renvoie une liste des voix disponibles, chacune de ces voix étant un objet SpeechSynthesisVoice). On parcourt cette liste en ajoutant un élément <option> pour chaque voix (le nom est fourni par SpeechSynthesisVoice.name), la langue est fournie par SpeechSynthesisVoice.lang)), on suffixe le texte par « DEFAULT » si la voix est celle utilisée par défaut par le système (pour ça, on vérifie la valeur booléenne de SpeechSynthesisVoice.default)).

function populateVoiceList() {
  voices = synth.getVoices();

  for(i = 0; i < voices.length ; i++) {
    var option = document.createElement('option');
    option.textContent = voices[i].name + ' (' + voices[i].lang + ')';

    if(voices[i].default) {
      option.textContent += ' -- DEFAULT';
    }

    option.setAttribute('data-lang', voices[i].lang);
    option.setAttribute('data-name', voices[i].name);
    voiceSelect.appendChild(option);
  }
}

Ensuite, pour exécuter la fonction, on utilise le code suivant :

populateVoiceList();
if (speechSynthesis.onvoiceschanged !== undefined) {
  speechSynthesis.onvoiceschanged = populateVoiceList;
}

En effet, Firefox ne supporte pas SpeechSynthesis.onvoiceschanged) et renverra simplement une liste de voix lorsque la méthode SpeechSynthesis.getVoices() sera déclenchée. En revanche, avec Chrome, on doit attendre que l’événement soit déclenché avant de peupler la liste, on utilise donc ce test conditionnel.

« Parler » le texte saisi

Poursuivons, on crée un gestionnaire d’événement pour diffuser le texte qui aura été saisi dans le champ texte affiché sur la page. On utilise le gestionnaire onsubmit du formulaire afin que l’action ait lieu quand l’utilisateur appuie sur Entrée. On commence par créer une nouvelle instance de SpeechSynthesisUtterance() en utilisant son constructeur qui recevra le texte saisi comme paramètre.

Ensuite, on sélectionne la voix qu’on souhaite utiliser. Pour cela, rien de mieux que la propriété HTMLSelectElement.selectedOptions qui renvoie l’élément <option> qui est sélectionné. On utilise ensuite l’attribut data-name de cet élément pour retrouver l’objet SpeechSynthesisVoice qui correspond à cette valeur. On peut alors définir la valeur de la propriété SpeechSynthesisUtterance.voice) avec la bonne voix.

Enfin, on définit les valeurs de SpeechSynthesisUtterance.pitch) et de SpeechSynthesisUtterance.rate) grâce aux éléments <input> de type range du formulaire. Une fois que tout est prêt, on génère la déclaration grâce à la méthode SpeechSynthesis.speak()) qui prendra l’instance de SpeechSynthesisUtterance en paramètre.

inputForm.onsubmit = function(event) {

  event.preventDefault();

  var utterThis = new SpeechSynthesisUtterance(inputTxt.value);
  var selectedOption = voiceSelect.selectedOptions[0].getAttribute('data-name');
  for(i = 0; i < voices.length ; i++) {
    if(voices[i].name === selectedOption) {
      utterThis.voice = voices[i];
    }
  }
  utterThis.pitch = pitch.value;
  utterThis.rate = rate.value;
  synth.speak(utterThis);

Page about:Webspeech dans la vie conjugale

Pour finir, on appelle la méthode blur() sur l’entrée texte, notamment pour cacher le clavier sur Firefox OS :

inputTxt.blur();

Vous pourrez trouver plus de détails à propos de cette démo sur MDN