PBToolboxAI v4 ← Site

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 visueln_pbt_speechout
Sert àFaire entendre un texte : accessibilité, mains occupées, notification qu'on ne regarde pas
PrincipeVous donnez le texte ; le composant le découpe en phrases et vous dit laquelle il lit
DépendanceLa 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(/*text*/ "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(/*text*/ "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 quelques secondes la première fois : la liste des voix arrive tard, et il l'attend (le pointeur montre l'attente) ; la réponse est gardée cinq secondes. 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.

// Local variables
string ls_tags[]

// Read in the first language this workstation can speak
if inv_voice.of_languages(/*tags*/ ls_tags) > 0 then inv_voice.is_lang = ls_tags[1]

Propriétés #

PropriétéTypeDéfautRôle
is_langstringen-USLangue 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 — une RÉGION absente aussi (fr-CA demandé, une voix fr-FR lit). Vivante : changée pendant une lecture, elle vaut dès la phrase suivante
is_textstring""Le texte à lire. Le composant le coupe en phrases ; des balises disent COMMENT lire un morceau : [pause=500], [spell]…[/spell], [say-as=digits]…[/say-as], [mark=nom], [rate=150]…[/rate] (voir plus bas)
is_voicestring""Nom de la voix, pris dans of_voices(). Vide = la première qui parle is_lang. Vivante, comme is_lang
ii_rateinteger100Débit, en POUR CENT du débit normal (10 à 400). Changé en cours de lecture, il s'applique à la phrase suivante
ii_pitchinteger100Hauteur de la voix, en POUR CENT de la hauteur normale (0 à 200). Changé en cours de lecture, il s'applique à la phrase suivante
ii_volumeinteger100Volume en pour cent (0 muet à 100), l'échelle du lecteur vidéo. Changé en cours de lecture, il s'applique à la phrase suivante
il_timeout_mslong300000Duré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). 0 ou moins : sans limite (24 heures au plus)
is_last_errorstring""Pourquoi le dernier of_speak_sync a rendu -4 : voix impossible à créer, moteur en échec, délai écoulé, autre of_speak_sync déjà en attente ; aussi pourquoi of_pick_voice n'a pas pu lire la liste des voix
ipo_ownerpowerobjectnullL'objet visuel pour lequel cette voix travaille : la licence se vérifie sur sa classe. Posé après l'ouverture de la voix, il est transmis à l'appel suivant. Nécessaire seulement dans l'application de démonstration ; une clé de développement ou d'exécution débride la voix sans lui. Non débridée, la voix est en mode démo — 4 phrases et 400 caractères par lecture (file d'attente comprise), puis la mention démo est dite

Méthodes #

MéthodeRô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, -2 si elle n'a pas pu être créée (is_last_error dit pourquoi)
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é, -2 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é, -5 au-delà de la dernière phrase (rien n'est lu, et une lecture en cours continue), -2 si la voix n'a pas pu être créée, -4 si la page n'a pas répondu
of_pause ( )Suspend la lecture où elle en est — y compris une lecture qui attend encore la liste des voix : elle ne partira qu'à of_resume. Rend 0 (rien à suspendre sur une voix jamais ouverte, qui n'est pas créée pour ça), -4 si la page n'a pas répondu
of_resume ( )Reprend là où of_pause s'était arrêté. Rend 0, -4 si la page n'a pas répondu
of_stop ( )Arrête la lecture ; of_speak repart de la première phrase
of_is_speaking ( )Vrai depuis of_speak jusqu'à la fin de la lecture — l'attente de la liste des voix et une lecture en pause comprises. Demandé au composant, jamais une copie périmée
of_is_paused ( )Vrai tant que la lecture est en pause (of_pause), jusqu'à of_resume ou of_stop
of_sentence_count ( )Rend le nombre de phrases que le composant fait de is_text tel qu'il est à l'instant, avant toute lecture ; pendant une lecture, celui du texte lu
of_count ( ) → longCombien 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_voice_fallback le dit au moment de lire ; ceci se lit avant, avec is_lang et is_voice tels qu'ils sont à l'instant
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, de délai écoulé, ou si un autre of_speak_sync attend déjà (appelé depuis un event qu'il a levé) — is_last_error dit pourquoi
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, -2 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 (une voix jamais ouverte n'a pas de file, et n'est pas créée pour ça), -4 si la page n'a pas répondu
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_add_abbreviation ( string as_text )Une ABRÉVIATION à vous : le point qui la suit ne termine pas la phrase — Art. 5 reste une phrase. Le point final peut être omis, la casse compte. Une liste intégrée couvre déjà les usuelles des six langues de cette doc (Mr., Dr., e.g., M., Mme., z.B., Sig., p.ej.…). Rend 0 une fois ajoutée, -5 si as_text est vide ou contient un blanc
of_clear_abbreviations ( )Oublie les abréviations ajoutées par of_add_abbreviation — la liste intégrée reste. Rend 0 toujours
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. as_lang vide = is_lang. Si la liste des voix est illisible, is_voice reste tel quel et is_last_error dit pourquoi
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 : une lecture en cours finit sur son ue_stopped, levé avant le retour. Détruire l'objet ferme aussi la voix, mais ne lève AUCUN événement : les contrôles de la fenêtre peuvent déjà être détruits
of_reset ( )Remet toutes les propriétés à leur valeur d'origine

Événements #

ÉvénementDéclenché quand
ue_started (string as_lang)La voix se met réellement à parler ; as_lang est la langue qu'elle lit — celle de la voix, fr-FR pour un fr-CA absent du poste (voir ue_voice_fallback). Une pause posée avant (of_speak puis of_pause dans le même script) le retient jusqu'à of_resume
ue_stopped ( )La dernière phrase est finie, ou la lecture a été coupée — of_stop, un nouvel of_speak, of_close : toute lecture commencée finit sur un ue_stopped
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. Une phrase de plus de 250 caractères est coupée (sur une virgule, sinon un blanc), et un morceau [rate] est une phrase à lui : chaque partie a son numéro
ue_error (string as_message)Le poste n'a aucun moteur ou aucune voix, la voix échoue, ou une erreur de script survient dans sa page. Une voix simplement ABSENTE n'est pas une erreur : c'est ue_voice_fallback. Aussi quand le moteur abandonne une phrase sans rien dire (« engine did not answer ») : la lecture ne reste jamais « en cours » pour toujours
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). Toujours levé : d'après les mots que la voix rapporte, et ESTIMÉ à l'horloge quand elle n'en rapporte aucun
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 — sa RÉGION comprise (fr-CA demandé, fr-FR lit) ; as_used nomme celle qui lit à la place. Une information, pas une erreur : la lecture continue
ue_mark (string as_name)La voix atteint un [mark=nom] du texte ; as_name est ce nom. Levé au début de la phrase (une marque avant son premier mot), quand le mot qui suit la marque est atteint, ou à la fin de la phrase : une marque n'est jamais perdue

Prononciation : pauses, épellation, dictionnaire #

WebView2 n'a pas de SSML. Le texte porte donc cinq 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.

BaliseEffet
[pause=500]Un silence de 500 ms (10 s au plus). La pause termine la phrase en cours. En FIN de texte : un silence après la dernière phrase (pour espacer deux annonces de la file)
[spell]ABC12[/spell]Chaque caractère dit un par un : « A, B, C, 1, 2 ». Ne coupe jamais la phrase (ses points sont des caractères : une adresse, une version) et se détache d'un mot qu'elle touche
[say-as=digits]4152[/say-as]Les chiffres un par un, pas « quatre mille cent cinquante-deux »
[say-as=characters]…[/say-as]Comme [spell]
[mark=row2]Une marque : ue_mark("row2") est levé quand la voix y arrive — pour surligner une ligne de votre fenêtre au bon moment
[rate=50]1 250 USD[/rate]Ce morceau à 50 % de ii_rate (10 à 400) — lire un montant plus lentement. C'est une phrase à elle
// The dictionary : whole words, in the order added, case-sensitive
inv_voice.of_add_replacement(/*from*/ "PB", /*to*/ "PowerBuilder")
inv_voice.of_add_replacement(/*from*/ "Mme", /*to*/ "Madame")
inv_voice.of_add_replacement(/*from*/ "4152", /*to*/ "[say-as=digits]4152[/say-as]")   // a rule may add a tag

// Then speak : the dictionary and the tags are applied first
inv_voice.of_speak(/*text*/ "Mme Durand, PB order 4152 [pause=600] code [spell]PBT[/spell].")

Une abréviation ne termine pas la phrase : Mr. Smith, Dr., e.g., M. Dupont, z.B. restent dans la phrase qui les porte (liste intégrée pour les six langues de cette doc ; etc. ne termine la phrase que devant une majuscule). Les vôtres s'ajoutent par of_add_abbreviation. Une phrase de plus de 250 caractères — un mémo collé sans point — est coupée sur une virgule, sinon sur un blanc : un moteur abandonne en silence les énoncés trop longs.

// Your own abbreviation : "Art. 5" stays in one sentence
inv_voice.of_add_abbreviation(/*text*/ "Art")

// A mark raises ue_mark("total") when the voice gets there ; the amount is read slower
inv_voice.of_speak(/*text*/ "See Art. 5 of the contract. [mark=total]The total is [rate=70]1 250 dollars[/rate].")

File d'attente, lecture synchrone, progression #


Exemples #

Lire une notification #

// Read the notification aloud, without waiting for the end
inv_voice.of_speak(/*text*/ "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(/*index*/ 2)

Choisir la voix et le débit #

// Local variables
string ls_voices[]

// The first installed voice, a quarter slower, then speak
if inv_voice.of_voices(/*names*/ 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(/*text*/ "Order 4152 has been shipped.")
inv_voice.of_enqueue(/*text*/ "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 #

// A French female voice, then speak and wait for the end before asking
inv_voice.of_pick_voice(/*lang*/ n_pbt_speechout.LANG_FR_FR, /*gender*/ n_pbt_speechout.GENDER_FEMALE)
if inv_voice.of_speak_sync(/*text*/ "Please confirm the order.") = 0 then
	li_answer = MessageBox("Order", "Confirm ?", Question!, YesNo!)
end if

Bonnes pratiques #


← Référence des composants · Sommaire du guide