speechout — n_pbt_speechout #
← Référence des composants · Sommaire du guide
Lecture à voix haute : l'application lit un texte phrase par phrase et vous dit où elle en est. Aucun service tiers, aucune clé d'API — la synthèse est celle du poste.
▶ Le voir en vrai — Application de démonstration, tuile Speech out : l'aperçu, le code qui le produit et cette page, côte à côte.
En bref #
| Objet non visuel | n_pbt_speechout |
| Sert à | Faire entendre un texte : accessibilité, mains occupées, notification qu'on ne regarde pas |
| Principe | Vous donnez le texte ; le composant le découpe en phrases et vous dit laquelle il lit |
| Dépendance | La synthèse vocale du poste — aucun service tiers, aucune clé d'API |
Démarrage rapide #
// once, when the window opens
inv_voice.of_open()
// then, wherever you need it
inv_voice.is_lang = inv_voice.LANG_FR_FR
inv_voice.of_speak("Bonjour. Votre commande est expediee.")
Non visuel : le câblage des événements #
Une voix n'a rien à montrer. Le composant ne dessine donc rien : pas de barre de lecture à caser sur un écran, pas de place prise à ce qu'elle sert.
Et vous n'avez rien à câbler pour cela : un objet non visuel n'a pas de fenêtre, donc pas de sonnette, mais le composant tire lui-même ses événements sur la boucle PowerBuilder tant que la voix est ouverte, et les lève sur l'objet. Vous n'écrivez que les gestionnaires ue_* (aucun receveur, aucun timer).
// Just speak -- the ue_* events arrive on their own :
inv_voice.of_speak("Good morning. Your order has shipped.")
// the ue_sentence / ue_word / ue_stopped events arrive on their own
Suivre la lecture dans VOTRE texte #
ue_sentence porte l'index et le texte de la phrase en cours de lecture. C'est de là que vient la surbrillance — dans votre mle_, votre datawindow ou votre statictext : vous savez où est votre texte, nous jamais.
Le chemin inverse existe aussi : of_speak_from() reprend à une phrase précise, ce qu'on branche sur le clic d'un paragraphe. Les numéros viennent de ue_sentence, donc ils désignent toujours ce qui a réellement été lu.
La découpe est celle du composant, pas la vôtre :
of_sentence_count()rend son compte à lui. Ne recomptez pas de votre côté, les deux dériveraient.
Ce que le poste sait vraiment dire #
of_languages() rend les langues que ce poste sait effectivement prononcer, sans doublons. C'est la question que se pose un utilisateur : non pas « quelles voix existent », mais « est-ce que ma langue est là ».
of_voices() descend d'un cran et nomme les voix elles-mêmes. Les deux listes viennent de la machine, pas de nous : ne codez jamais un nom en dur.
La reconnaissance vocale n'a pas d'équivalent, et ce n'est pas un oubli : la reconnaissance ne porte aucune liste des langues qu'elle accepte.
Sans aucun composant de votre côté, gnv_utils.of_speech_languages(as_tags[]) donne la même liste — la DLL interroge une voix cachée, puis la détruit : c'est ce qu'une fenêtre demande avant que la voix existe, quelle langue proposer, dans laquelle lire. L'appel est synchrone et peut prendre jusqu'à deux secondes la première fois : la liste des voix arrive tard, et il l'attend. Et gnv_utils.of_speech_voices(as_names[], as_langs[]) donne les voix elles-mêmes avec leur langue, pour proposer « Hortense » ou « Julie » plutôt qu'une balise ; gnv_utils.of_locale_name(as_tag) donne à une balise son nom lisible — « français (France) » pour fr-FR.
string ls_tags[]
if inv_voice.of_languages(ls_tags) > 0 then inv_voice.is_lang = ls_tags[1]
Propriétés #
| Propriété | Type | Défaut | Rôle |
|---|---|---|---|
is_lang | string | en-US | Langue lue, en BCP-47 (constantes LANG_*). Décide quelle voix est choisie — tant que is_voice est vide, un nom de voix fixant sa propre langue. Sans voix pour cette langue, le poste lit avec celle qu'il a et ue_voice_fallback nomme les deux |
is_text | string | "" | Le texte à lire. Le composant le coupe en phrases ; trois balises disent COMMENT lire un morceau : [pause=500], [spell]…[/spell], [say-as=digits]…[/say-as] (voir plus bas) |
is_voice | string | "" | Nom de la voix, pris dans of_voices(). Vide = la première qui parle is_lang |
ii_rate | integer | 100 | Débit, en POUR CENT du débit normal (10 à 400). Changé en cours de lecture, il s'applique à la phrase suivante |
ii_pitch | integer | 100 | Hauteur de la voix, en POUR CENT de la hauteur normale (0 à 200) |
ii_volume | integer | 100 | Volume en pour cent (0 muet à 100), l'échelle du lecteur vidéo |
il_timeout_ms | long | 300000 | Durée maximale d'une lecture SYNCHRONE : cinq minutes. Au-delà, of_speak_sync rend -4 et la lecture est coupée (is_last_error dit pourquoi) |
is_last_error | string | "" | Pourquoi le dernier of_speak_sync a rendu -4 : voix impossible à créer, moteur en échec, délai écoulé |
Méthodes #
| Méthode | Rôle |
|---|---|
of_open ( ) | Crée la voix. Facultatif — of_speak le fait — mais l'appeler à l'ouverture de la fenêtre paie le coût une fois, loin de la première phrase. Rend un nombre positif quand la voix existe, 0 ou moins si elle n'a pas pu être créée |
of_is_open ( ) | Vrai une fois la voix créée |
of_speak ( string as_text ) | Pose le texte et le lit, depuis la première phrase. Sans argument, relit is_text. Rend 0 une fois envoyé, -1 si la voix n'a pas pu être créée |
of_speak_from ( long al_index ) | Reprend la lecture à une phrase précise. Rend 0 une fois envoyé, -1 si la voix n'a pas pu être créée |
of_pause ( ) | Suspend la lecture où elle en est. Rend 0 une fois envoyé, -1 si la voix n'a pas pu être créée |
of_resume ( ) | Reprend là où of_pause s'était arrêté. Rend 0 une fois envoyé, -1 si la voix n'a pas pu être créée |
of_stop ( ) | Arrête la lecture ; of_speak repart de la première phrase |
of_is_speaking ( ) | Vrai tant qu'une phrase est lue — une lecture en pause compte encore. Demandé au composant, jamais une copie périmée |
of_sentence_count ( ) | Rend le nombre de phrases que le composant a faites du texte |
of_count ( ) → integer | Combien de phrases le composant a faites du texte — le même nombre que of_sentence_count. La bibliothèque pose cette question sous un seul nom partout |
of_voices ( ref string as_names[] ) | Remplit le tableau avec les voix installées sur ce poste et rend leur nombre |
of_languages ( ref string as_tags[] ) | Remplit le tableau avec les langues que ce poste sait prononcer, sans doublons, et rend leur nombre |
of_voice_used ( ) | La voix que le composant remettra réellement au moteur — pas toujours celle que is_lang demande : un poste porte les voix qu'on y a installées et pas d'autres. Vide = le composant n'en impose aucune, et le moteur prend la sienne, celle de la langue du système. La nommer serait une devinette. ue_error le dit aussi au moment de lire ; ceci se lit avant |
of_speak_sync ( { string as_text } ) | Lit et ATTEND la fin : la ligne suivante s'exécute après la dernière phrase, la fenêtre continue de se peindre. Rend 0 à la fin, -4 en cas d'échec ou de délai écoulé (is_last_error) |
of_enqueue ( string as_text ) | Met un texte en FILE : lu tout de suite si la voix est libre, après la lecture en cours sinon, sans jamais la couper. Rend 0, -1 si la voix n'a pu être créée |
of_clear_queue ( ) | Oublie les textes en attente, sans couper celui qui se lit. Rend 0, -1 si la voix n'a pu être créée |
of_queue_count ( ) | Rend le nombre de textes encore en attente (celui qui se lit n'est pas compté) |
of_add_replacement ( string as_from, string as_to ) | Une règle de prononciation : chaque mot ENTIER as_from est lu as_to (PB → PowerBuilder). Appliquée avant les balises, gardée par l'objet. Rend 0, -5 si as_from est vide |
of_clear_replacements ( ) | Vide le dictionnaire. Rend 0 |
of_replacement_count ( ) | Rend le nombre de règles du dictionnaire |
of_pick_voice ( string as_lang, string as_gender ) | Choisit une voix INSTALLÉE pour une langue et, s'il en existe, un genre (GENDER_FEMALE, GENDER_MALE, GENDER_ANY) : langue exacte, puis sa famille. La pose dans is_voice et la rend ; vide si aucune voix ne parle cette langue |
of_duration ( ) | Rend le nombre ESTIMÉ de millisecondes que prendra la lecture (mots par minute au débit demandé, pauses comprises) : pour une barre de progression, pas pour un chronomètre. Le débit APPREND la voix : chaque phrase lue jusqu'au bout mesure le vrai, retenu par voix sur ce poste |
of_position ( ) | Rend le nombre estimé de millisecondes déjà lues, affiné par les mots que le moteur rapporte ; 0 quand rien ne se lit |
of_progress ( ) | Rend l'avancement estimé, de 0 à 100 |
of_spoken_text ( ) | Les phrases TELLES QUE la voix les reçoit, une par ligne (dictionnaire appliqué, balises résolues) : le texte à afficher pour suivre mot à mot |
of_process_events ( ) | Vidange les événements en attente et les lève sur cet objet. Le pump interne du composant l'appelle pour vous tant que la voix est ouverte — vous ne l'appelez jamais |
of_close ( ) | Libère la voix, en arrêtant d'abord ce qu'elle disait. Le destructeur l'appelle |
of_reset ( ) | Remet toutes les propriétés à leur valeur d'origine |
Événements #
| Événement | Déclenché quand |
|---|---|
ue_started (string as_lang) | La lecture commence ; as_lang rappelle dans quelle langue |
ue_stopped ( ) | La dernière phrase est finie, ou of_stop a été appelé |
ue_paused ( ) | La lecture est suspendue |
ue_resumed ( ) | La lecture repart |
ue_sentence (long al_index, string as_text) | Pour chaque phrase, avec son rang et son texte : c'est ainsi qu'on suit la lecture ailleurs dans la fenêtre |
ue_error (string as_message) | Le poste n'a aucun moteur ou aucune voix, ou la voix échoue. Une voix simplement ABSENTE n'est pas une erreur : c'est ue_voice_fallback |
ue_voices_ready (long al_count) | Le moteur a fini de remplir sa liste de voix — elle arrive tard ; of_voices, of_languages, of_voice_used et of_pick_voice l'attendent d'eux-mêmes (2,5 s au plus), cet event dit seulement QUAND elle est arrivée ; al_count dit combien le poste en a |
ue_word (long al_index, long al_start, long al_length) | Le MOT en cours dans la phrase al_index : Mid(phrase, al_start, al_length). Quand le moteur rapporte les mots (la plupart des voix Windows) |
ue_queue_done ( ) | Le dernier texte de la file (of_enqueue) est lu |
ue_voice_fallback (string as_wanted, string as_used) | La voix ou la langue demandée n'est pas sur ce poste ; as_used nomme celle qui lit à la place. Une information, pas une erreur : la lecture continue |
Prononciation : pauses, épellation, dictionnaire #
WebView2 n'a pas de SSML. Le texte porte donc trois balises à lui, résolues avant la coupe en phrases, et un dictionnaire de mots entiers appliqué avant elles. Une balise inconnue est lue telle quelle.
| Balise | Effet |
|---|---|
[pause=500] | Un silence de 500 ms (10 s au plus). La pause termine la phrase en cours |
[spell]ABC12[/spell] | Chaque caractère dit un par un : « A, B, C, 1, 2 » |
[say-as=digits]4152[/say-as] | Les chiffres un par un, pas « quatre mille cent cinquante-deux » |
[say-as=characters]…[/say-as] | Comme [spell] |
// The dictionary : whole words, in the order added, case-sensitive
inv_voice.of_add_replacement("PB", "PowerBuilder")
inv_voice.of_add_replacement("Mme", "Madame")
inv_voice.of_add_replacement("4152", "[say-as=digits]4152[/say-as]") // a rule may add a tag
inv_voice.of_speak("Mme Durand, PB order 4152 [pause=600] code [spell]PBT[/spell].")
File d'attente, lecture synchrone, progression #
of_speakcoupe,of_enqueueattend. Une application qui annonce des événements (alerte, résultat, notification) met en file : deux annonces rapprochées sont entendues toutes les deux, etue_queue_donedit quand la dernière est lue.of_clear_queueoublie ce qui attend sans couper ;of_stopfait les deux.of_speak_syncrend la main à la fin. Le script attend la dernière phrase (la fenêtre continue de se peindre), borné paril_timeout_ms, qui COUPE la lecture une fois atteint — pour « lis ceci, puis pose la question ».of_duration,of_position,of_progresssont des estimations : le moteur ne dit rien de la durée, elles comptent les mots au débit demandé, affinées par les mots que le moteur rapporte. Assez pour une barre de progression lue depuis un timer, pas pour un chronomètre.of_pick_voice(langue, genre)choisit une voix installée par langue puis genre, et la pose dansis_voice. Le genre vient du prénom que Windows donne à chaque voix ; une voix au prénom inconnu répond àGENDER_ANY.- Pas d'export audio. La synthèse de WebView2 ne rend aucun flux : on ne peut pas écrire la lecture dans un fichier. Ce n'est pas un réglage manquant, c'est le moteur.
Exemples #
Lire une notification #
inv_voice.of_speak("Order 4152 has been shipped. It arrives on Thursday.")
Éclairer la phrase en cours #
// in ue_sentence, on the nonvisual object
st_read.text = as_text
// and a click on a paragraph resumes from it
inv_voice.of_speak_from(2)
Choisir la voix et le débit #
string ls_voices[]
if inv_voice.of_voices(ls_voices) > 0 then inv_voice.is_voice = ls_voices[1]
inv_voice.ii_rate = 75
inv_voice.of_speak()
Annoncer sans couper : la file #
// Each event of the application is queued : all of them are heard, in order
inv_voice.of_enqueue("Order 4152 has been shipped.")
inv_voice.of_enqueue("Order 4153 is ready.")
// ue_queue_done fires after the last one
Éclairer le mot en cours #
// in ue_sentence : keep the sentence
is_sentence = as_text
// in ue_word : the word is Mid(is_sentence, al_start, al_length)
st_read.text = Left(is_sentence, al_start - 1) + "[" + Mid(is_sentence, al_start, al_length) + "]" + Mid(is_sentence, al_start + al_length)
Une voix par langue et genre, puis lire en attendant la fin #
inv_voice.of_pick_voice(n_pbt_speechout.LANG_FR_FR, n_pbt_speechout.GENDER_FEMALE)
if inv_voice.of_speak_sync("Please confirm the order.") = 0 then
li_answer = MessageBox("Order", "Confirm ?", Question!, YesNo!)
end if
Bonnes pratiques #
- Une phrase à la fois : le composant confie au moteur une seule phrase et enchaîne. C'est ce qui permet d'arrêter proprement entre deux phrases, et ce qui évite l'abandon silencieux de Chromium au-delà d'une quinzaine de secondes.
- Éclairez la phrase lue dans votre texte, depuis
ue_sentence— c'est la moitié de ce que sert une lecture à voix haute. - Appelez
of_languages()plutôt que de supposer : une langue installée chez vous ne l'est pas forcément chez le client. - N'appelez pas
of_speaken rafale sur un traitement : chaque appel coupe le précédent, et l'utilisateur n'entend que des débuts. C'estof_enqueuequ'il faut là.