speechout — n_pbt_speechout #
← Referencia de componentes · Índice de la guía
Lectura en voz alta: la aplicación lee un texto frase por frase y le dice por dónde va. Sin servicio de terceros, sin clave de API — la síntesis es la del equipo.
▶ Verlo en vivo — Aplicación de demostración, mosaico Speech out: la vista previa, el código que la produce y esta página, lado a lado.
En resumen #
| Objeto no visual | n_pbt_speechout |
| Sirve para | Hacer oír un texto: accesibilidad, manos ocupadas, un aviso que nadie mira |
| Principio | Usted da el texto; el componente lo corta en frases y le dice cuál está leyendo |
| Dependencia | La síntesis de voz del equipo — sin servicio de terceros, sin clave de API |
Inicio rápido #
// 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.")
No visual: el cableado de los eventos #
Una voz no tiene nada que mostrar. El componente no dibuja por tanto nada: ninguna barra de reproducción que colocar, ningún espacio quitado a lo que sirve.
Y para ello no tiene que cablear nada: un objeto no visual no tiene ventana, así que no hay timbre, pero el componente recoge sus propios eventos en el bucle de PowerBuilder mientras la voz está abierta y los lanza en el objeto. Solo escribe los manejadores ue_* (sin receptor, sin temporizador).
// 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
Seguir la lectura en SU texto #
ue_sentence lleva el índice y el texto de la frase que se está leyendo. De ahí sale el resaltado — en su mle_, su datawindow o su statictext: usted sabe dónde está su texto, nosotros nunca.
El camino inverso también existe: of_speak_from() retoma en una frase concreta, lo que se conecta al clic sobre un párrafo. Los números vienen de ue_sentence, así que siempre señalan lo que realmente se leyó.
El corte es el del componente, no el suyo:
of_sentence_count()devuelve su propia cuenta. No vuelva a contar por su lado, las dos derivarían.
Lo que el equipo sabe decir de verdad #
of_languages() devuelve los idiomas que este equipo sabe pronunciar de verdad, sin duplicados. Es la pregunta que se hace un usuario: no «qué voces existen», sino «está mi idioma».
of_voices() baja un escalón y nombra las voces mismas. Ambas listas vienen de la máquina, no de nosotros: nunca codifique un nombre fijo.
El reconocimiento de voz no tiene equivalente, y no es un olvido: el reconocimiento no lleva ninguna lista de los idiomas que acepta.
Sin ningún componente por su parte, gnv_utils.of_speech_languages(as_tags[]) da la misma lista — la DLL consulta una voz oculta y luego la descarta: es lo que una ventana pregunta antes de que la voz exista, qué idioma ofrecer, en cuál leer. La llamada es síncrona y puede tardar unos segundos la primera vez (el puntero muestra la espera; la respuesta se guarda cinco segundos): la lista de voces llega tarde, y la llamada la espera. Y gnv_utils.of_speech_voices(as_names[], as_langs[]) da las voces mismas con su idioma, para ofrecer «Hortense» o «Julie» en lugar de una etiqueta; gnv_utils.of_locale_name(as_tag) da a una etiqueta su nombre legible — «francés (Francia)» para 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]
Propiedades #
| Propiedad | Tipo | Predeterminado | Función |
|---|---|---|---|
is_lang | string | en-US | Idioma leído, en BCP-47 (constantes LANG_*). Decide qué voz se elige — mientras is_voice esté vacía, ya que un nombre de voz fija su propio idioma. Sin voz para ese idioma el equipo lee con la que tiene, y ue_voice_fallback nombra ambas — también una REGIÓN ausente (se pide fr-CA, lee una voz fr-FR). Viva: cambiada durante una lectura, vale desde la frase siguiente |
is_text | string | "" | El texto a leer. El componente lo corta en frases; unas etiquetas dicen CÓMO leer un trozo: [pause=500], [spell]…[/spell], [say-as=digits]…[/say-as], [mark=nombre], [rate=150]…[/rate] (ver más abajo) |
is_voice | string | "" | Nombre de la voz, tomado de of_voices(). Vacío = la primera que habla is_lang. Viva, como is_lang |
ii_rate | integer | 100 | Velocidad, en POR CIENTO de la normal (10 a 400). Cambiada durante la lectura, se aplica a la frase siguiente |
ii_pitch | integer | 100 | Tono de la voz, en POR CIENTO del tono normal (0 a 200). Cambiado durante la lectura, se aplica a la frase siguiente |
ii_volume | integer | 100 | Volumen en porcentaje (0 mudo a 100), la escala del reproductor de vídeo. Cambiado durante la lectura, se aplica a la frase siguiente |
il_timeout_ms | long | 300000 | Duración máxima de una lectura SÍNCRONA: cinco minutos. Después, of_speak_sync devuelve -4 y la lectura se corta (is_last_error dice por qué). 0 o menos: sin límite (24 horas como máximo) |
is_last_error | string | "" | Por qué el último of_speak_sync devolvió -4: voz no creada, fallo del motor, tiempo agotado, otro of_speak_sync ya en espera; también por qué of_pick_voice no pudo leer la lista de voces |
ipo_owner | powerobject | null | El objeto visual para el que trabaja esta voz: la licencia se comprueba en su clase. Asignado después de abrir la voz, se transmite en la llamada siguiente. Necesario solo en la aplicación de demostración; una clave de desarrollo o de ejecución desbloquea la voz sin él. Sin desbloquear, la voz está en modo demo — 4 frases y 400 caracteres por lectura (cola incluida), luego se dice la mención demo |
Métodos #
| Método | Función |
|---|---|
of_open ( ) | Crea la voz. Opcional — of_speak lo hace — pero llamarlo al abrir la ventana paga el coste una vez, lejos de la primera frase. Devuelve un número positivo cuando la voz existe, -2 si no pudo crearse (is_last_error dice por qué) |
of_is_open ( ) | Verdadero una vez creada la voz |
of_speak ( string as_text ) | Pone el texto y lo lee, desde la primera frase. Sin argumento, relee is_text. Devuelve 0 una vez enviado, -2 si la voz no pudo crearse |
of_speak_from ( long al_index ) | Retoma la lectura en una frase concreta. Devuelve 0 una vez enviado, -5 más allá de la última frase (no se lee nada, y una lectura en curso continúa), -2 si la voz no pudo crearse, -4 si la página no respondió |
of_pause ( ) | Suspende la lectura donde está — incluida una lectura que aún espera la lista de voces: solo empezará con of_resume. Devuelve 0 (nada que suspender en una voz nunca abierta, que no se crea para eso), -4 si la página no respondió |
of_resume ( ) | Retoma donde of_pause se detuvo. Devuelve 0, -4 si la página no respondió |
of_stop ( ) | Detiene la lectura; of_speak vuelve a empezar por la primera frase |
of_is_speaking ( ) | Verdadero desde of_speak hasta el final de la lectura — la espera de la lista de voces y una lectura en pausa incluidas. Preguntado al componente, nunca una copia caducada |
of_is_paused ( ) | Verdadero mientras la lectura está en pausa (of_pause), hasta of_resume u of_stop |
of_sentence_count ( ) | Devuelve cuántas frases hace el componente de is_text tal como está ahora, antes de cualquier lectura; durante una lectura, las del texto que se lee |
of_count ( ) → long | Cuántas frases ha hecho el componente del texto — el mismo número que devuelve of_sentence_count. La biblioteca hace esta pregunta bajo un solo nombre en todas partes |
of_voices ( ref string as_names[] ) | Rellena la matriz con las voces instaladas en este equipo y devuelve cuántas hay |
of_languages ( ref string as_tags[] ) | Rellena la matriz con los idiomas que este equipo sabe pronunciar, sin duplicados, y devuelve cuántos hay |
of_voice_used ( ) | La voz que el componente entregará realmente al motor — no siempre la que pide is_lang: un equipo lleva las voces que alguien instaló y ninguna otra. Vacío = el componente no impone ninguna, y el motor toma la suya, la del idioma del sistema. Nombrarla sería adivinar. ue_voice_fallback lo dice al leer; esto se lee antes, con is_lang e is_voice tal como están en ese momento |
of_speak_sync ( { string as_text } ) | Lee y ESPERA el final: la línea siguiente corre tras la última frase, la ventana sigue pintándose. Devuelve 0 al final, -4 en caso de fallo, de tiempo agotado o si otro of_speak_sync ya está esperando (llamado desde un evento que él levantó) — is_last_error dice por qué |
of_enqueue ( string as_text ) | Pone un texto en COLA: leído enseguida si la voz está libre, tras la lectura en curso si no, sin cortarla nunca. Devuelve 0, -2 si la voz no pudo crearse |
of_clear_queue ( ) | Olvida los textos en espera, sin cortar el que se lee. Devuelve 0 (una voz nunca abierta no tiene cola, y no se crea para eso), -4 si la página no respondió |
of_queue_count ( ) | Devuelve el número de textos aún en espera (el que se lee no cuenta) |
of_add_replacement ( string as_from, string as_to ) | Una regla de pronunciación: cada palabra ENTERA as_from se lee as_to (PB → PowerBuilder). Aplicada antes de las etiquetas, guardada por el objeto. Devuelve 0, -5 si as_from está vacío |
of_clear_replacements ( ) | Vacía el diccionario. Devuelve 0 |
of_replacement_count ( ) | Devuelve el número de reglas del diccionario |
of_add_abbreviation ( string as_text ) | Una ABREVIATURA propia: el punto que la sigue no termina la frase — Art. 5 sigue siendo una frase. El punto final puede omitirse, las mayúsculas cuentan. Una lista integrada ya cubre las habituales de los seis idiomas de esta documentación (Mr., Dr., e.g., M., Mme., z.B., Sig., p.ej.…). Devuelve 0 una vez añadida, -5 si as_text está vacío o contiene un espacio |
of_clear_abbreviations ( ) | Olvida las abreviaturas añadidas con of_add_abbreviation — la lista integrada se queda. Devuelve siempre 0 |
of_pick_voice ( string as_lang, string as_gender ) | Elige una voz INSTALADA para un idioma y, si existe, un género (GENDER_FEMALE, GENDER_MALE, GENDER_ANY): idioma exacto, luego su familia. La pone en is_voice y la devuelve; vacío si ninguna voz habla ese idioma. Un as_lang vacío significa is_lang. Si la lista de voces no se puede leer, is_voice queda como estaba e is_last_error dice por qué |
of_duration ( ) | Devuelve la duración ESTIMADA de la lectura, en milisegundos (palabras por minuto a la velocidad pedida, pausas incluidas): para una barra de progreso, no para un cronómetro. El ritmo APRENDE la voz: cada frase leída hasta el final mide el real, recordado por voz en este equipo |
of_position ( ) | Devuelve la posición estimada de la lectura, en milisegundos, afinada por las palabras que el motor reporta; 0 cuando nada se lee |
of_progress ( ) | Devuelve el progreso estimado, de 0 a 100 |
of_spoken_text ( ) | Las frases TAL COMO la voz las recibe, una por línea (diccionario aplicado, etiquetas resueltas): el texto a mostrar para seguir palabra a palabra |
of_process_events ( ) | Vacía los eventos pendientes y los lanza sobre este objeto. El pump interno del componente lo llama por usted mientras la voz está abierta — usted nunca lo llama |
of_close ( ) | Libera la voz, deteniendo antes lo que estuviera diciendo: una lectura en curso termina con su ue_stopped, levantado antes del retorno. Destruir el objeto también cierra la voz, pero no levanta NINGÚN evento: los controles de la ventana pueden estar ya destruidos |
of_reset ( ) | Devuelve todas las propiedades a su valor original |
Eventos #
| Evento | Se dispara cuando |
|---|---|
ue_started (string as_lang) | La voz empieza de verdad a hablar; as_lang es el idioma que lee — el de la voz, fr-FR para un fr-CA ausente del equipo (vea ue_voice_fallback). Una pausa puesta antes (of_speak y luego of_pause en el mismo script) lo retiene hasta of_resume |
ue_stopped ( ) | La última frase ha terminado, o la lectura se ha cortado — of_stop, un nuevo of_speak, of_close: toda lectura empezada termina con un ue_stopped |
ue_paused ( ) | La lectura está suspendida |
ue_resumed ( ) | La lectura se reanuda |
ue_sentence (long al_index, string as_text) | Para cada frase, con su rango y su texto: así se sigue la lectura en otra parte de la ventana. Una frase de más de 250 caracteres se corta (en una coma, si no en un espacio), y un trozo [rate] es una frase propia: cada parte tiene su número |
ue_error (string as_message) | El equipo no tiene motor ni voz alguna, la voz falla, o se produce un error de script en su página. Una voz simplemente AUSENTE no es un error: es ue_voice_fallback. También cuando el motor abandona una frase sin decir nada («engine did not answer»): una lectura nunca queda «en curso» para siempre |
ue_voices_ready (long al_count) | El motor ha llenado su lista de voces — llega tarde; of_voices, of_languages, of_voice_used y of_pick_voice la esperan por sí mismos (2,5 s como máximo), este evento solo dice CUÁNDO llegó; al_count dice cuántas tiene el equipo |
ue_word (long al_index, long al_start, long al_length) | La PALABRA en curso en la frase al_index: Mid(frase, al_start, al_length). Siempre levantado: según las palabras que la voz reporta, y ESTIMADO por el reloj cuando no reporta ninguna |
ue_queue_done ( ) | El último texto de la cola (of_enqueue) está leído |
ue_voice_fallback (string as_wanted, string as_used) | La voz o el idioma pedido no está en este equipo — su REGIÓN incluida (se pide fr-CA, lee fr-FR); as_used nombra la que lee en su lugar. Una información, no un error: la lectura continúa |
ue_mark (string as_name) | La voz alcanza un [mark=nombre] del texto; as_name es ese nombre. Se levanta al empezar la frase (una marca antes de su primera palabra), al alcanzar la palabra que sigue a la marca, o al final de la frase: una marca nunca se pierde |
Pronunciación: pausas, deletreo, diccionario #
WebView2 no tiene SSML. El texto lleva por tanto cinco etiquetas propias, resueltas antes del corte en frases, y un diccionario de palabras enteras aplicado antes. Una etiqueta desconocida se lee tal cual.
| Etiqueta | Efecto |
|---|---|
[pause=500] | Un silencio de 500 ms (10 s como máximo). La pausa termina la frase en curso. Al FINAL del texto: un silencio tras la última frase (para espaciar dos anuncios de la cola) |
[spell]ABC12[/spell] | Cada carácter dicho uno por uno: «A, B, C, 1, 2». Nunca termina la frase (sus puntos son caracteres: una dirección, una versión) y se separa de una palabra que toque |
[say-as=digits]4152[/say-as] | Los dígitos uno por uno, no «cuatro mil ciento cincuenta y dos» |
[say-as=characters]…[/say-as] | Como [spell] |
[mark=row2] | Una marca: ue_mark("row2") se levanta cuando la voz llega ahí — para resaltar una fila de su ventana en el momento justo |
[rate=50]1 250 USD[/rate] | Ese trozo al 50 % de ii_rate (10 a 400) — para leer un importe más despacio. Es una frase propia |
// 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].")
Una abreviatura no termina la frase: Mr. Smith, Dr., e.g., M. Dupont, z.B. se quedan en la frase que las lleva (lista integrada para los seis idiomas de esta documentación; etc. solo termina la frase ante una mayúscula). Las suyas se añaden con of_add_abbreviation. Una frase de más de 250 caracteres — una nota pegada sin punto — se corta en una coma, si no en un espacio: un motor abandona en silencio los enunciados demasiado largos.
// 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].")
Cola, lectura síncrona, progreso #
of_speakcorta,of_enqueueespera. Una aplicación que anuncia eventos (alerta, resultado, notificación) encola: dos anuncios cercanos se oyen ambos, yue_queue_donedice cuándo se leyó el último.of_clear_queueolvida lo que espera sin cortar;of_stophace ambas cosas.of_speak_syncdevuelve el control al final. El script espera la última frase (la ventana sigue pintándose), limitado poril_timeout_ms, que CORTA la lectura al alcanzarse — para «lee esto, luego haz la pregunta».of_duration,of_position,of_progressson estimaciones: el motor no dice nada de la duración, cuentan palabras a la velocidad pedida, afinadas por las palabras que el motor reporta. Suficiente para una barra de progreso leída desde un timer, no para un cronómetro.of_pick_voice(idioma, género)elige una voz instalada por idioma y luego género, y la pone enis_voice. El género viene del nombre que Windows da a cada voz; una voz de nombre desconocido responde aGENDER_ANY.- Sin exportación de audio. La síntesis de WebView2 no entrega ningún flujo: la lectura no puede escribirse en un archivo. No es un ajuste que falte, es el motor.
Ejemplos #
Leer una notificación #
// Read the notification aloud, without waiting for the end
inv_voice.of_speak(/*text*/ "Order 4152 has been shipped. It arrives on Thursday.")
Resaltar la frase en curso #
// 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)
Elegir la voz y la velocidad #
// 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()
Anunciar sin cortar: la cola #
// 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
Resaltar la palabra en curso #
// 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)
Una voz por idioma y género, luego leer esperando el final #
// 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
Buenas prácticas #
- Una frase cada vez: el componente confía al motor una sola frase y encadena. Eso permite detenerse limpiamente entre dos frases, y evita el abandono silencioso de Chromium más allá de unos quince segundos.
- Resalte la frase leída en su texto, desde
ue_sentence— es la mitad de para lo que sirve una lectura en voz alta. - Llame a
of_languages()en lugar de suponer: un idioma instalado en su máquina puede no estarlo en la del cliente. - No llame a
of_speaken ráfaga sobre un conjunto de datos: cada llamada corta la anterior, y el usuario solo oye comienzos. Para eso estáof_enqueue.