speechout — n_pbt_speechout #
← Referência dos componentes · Índice do guia
Leitura em voz alta: a aplicação lê um texto frase a frase e diz-lhe onde vai. Sem serviço de terceiros, sem chave de API — a síntese é a do posto de trabalho.
▶ Ver ao vivo — Aplicação de demonstração, mosaico Speech out: a pré-visualização, o código que a produz e esta página, lado a lado.
Em resumo #
| Objeto não visual | n_pbt_speechout |
| Serve para | Fazer ouvir um texto: acessibilidade, mãos ocupadas, um aviso que ninguém olha |
| Princípio | Dá o texto; o componente corta-o em frases e diz-lhe qual está a ler |
| Dependência | A síntese de voz do posto — sem serviço de terceiros, sem chave de API |
Início 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.")
Não visual: a ligação dos eventos #
Uma voz não tem nada para mostrar. O componente não desenha portanto nada: nenhuma barra de leitura para arranjar espaço, nenhum lugar tirado àquilo que serve.
E para isso não tem de ligar nada: um objeto não visual não tem janela, logo não há campainha, mas o componente recolhe os seus eventos no ciclo do PowerBuilder enquanto a voz está aberta e dispara-os no objeto. Só escreve os tratadores ue_* (sem recetor, sem 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 a leitura no SEU texto #
ue_sentence transporta o índice e o texto da frase que está a ser lida. É daí que vem o realce — no seu mle_, na sua datawindow ou no seu statictext: você sabe onde está o seu texto, nós nunca.
O caminho inverso também existe: of_speak_from() retoma numa frase precisa, o que se liga ao clique num parágrafo. Os números vêm de ue_sentence, logo apontam sempre para o que foi realmente lido.
O corte é o do componente, não o seu:
of_sentence_count()devolve a contagem dele. Não volte a contar do seu lado, as duas divergiriam.
O que o posto sabe realmente dizer #
of_languages() devolve os idiomas que este posto sabe efetivamente pronunciar, sem duplicados. É a pergunta que um utilizador faz: não «que vozes existem», mas «o meu idioma está lá».
of_voices() desce um degrau e nomeia as próprias vozes. Ambas as listas vêm da máquina, não de nós: nunca escreva um nome fixo no código.
O reconhecimento de voz não tem equivalente, e não é um esquecimento: o reconhecimento não traz qualquer lista dos idiomas que aceita.
Sem qualquer componente da sua parte, gnv_utils.of_speech_languages(as_tags[]) dá a mesma lista — a DLL consulta uma voz oculta e depois descarta-a: é o que uma janela pergunta antes de a voz existir, que idioma propor, em qual ler. A chamada é síncrona e pode demorar alguns segundos da primeira vez (o ponteiro mostra a espera; a resposta é guardada cinco segundos): a lista de vozes chega tarde, e a chamada espera por ela. E gnv_utils.of_speech_voices(as_names[], as_langs[]) dá as vozes em si com o seu idioma, para propor «Hortense» ou «Julie» em vez de uma etiqueta; gnv_utils.of_locale_name(as_tag) dá a uma etiqueta o seu nome legível — «francês (França)» 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]
Propriedades #
| Propriedade | Tipo | Predefinição | Função |
|---|---|---|---|
is_lang | string | en-US | Idioma lido, em BCP-47 (constantes LANG_*). Decide que voz é escolhida — enquanto is_voice estiver vazia, pois um nome de voz fixa a sua própria língua. Sem voz para essa língua o posto lê com a que tem, e ue_voice_fallback nomeia as duas — também uma REGIÃO ausente (pede-se fr-CA, lê uma voz fr-FR). Viva: alterada durante uma leitura, vale a partir da frase seguinte |
is_text | string | "" | O texto a ler. O componente corta-o em frases; etiquetas dizem COMO ler um pedaço: [pause=500], [spell]…[/spell], [say-as=digits]…[/say-as], [mark=nome], [rate=150]…[/rate] (ver abaixo) |
is_voice | string | "" | Nome da voz, tirado de of_voices(). Vazio = a primeira que fala is_lang. Viva, como is_lang |
ii_rate | integer | 100 | Velocidade, em POR CENTO da normal (10 a 400). Alterada durante a leitura, aplica-se à frase seguinte |
ii_pitch | integer | 100 | Altura da voz, em POR CENTO da altura normal (0 a 200). Alterado durante a leitura, aplica-se à frase seguinte |
ii_volume | integer | 100 | Volume em percentagem (0 mudo a 100), a escala do leitor de vídeo. Alterado durante a leitura, aplica-se à frase seguinte |
il_timeout_ms | long | 300000 | Duração máxima de uma leitura SÍNCRONA: cinco minutos. Depois, of_speak_sync devolve -4 e a leitura é cortada (is_last_error diz porquê). 0 ou menos: sem limite (24 horas no máximo) |
is_last_error | string | "" | Porque o último of_speak_sync devolveu -4: voz não criada, falha do motor, tempo esgotado, outro of_speak_sync já à espera; também porque of_pick_voice não pôde ler a lista de vozes |
ipo_owner | powerobject | null | O objeto visual para o qual esta voz trabalha: a licença verifica-se na sua classe. Definido depois de a voz abrir, é transmitido na chamada seguinte. Necessário apenas na aplicação de demonstração; uma chave de desenvolvimento ou de execução desbloqueia a voz sem ele. Não desbloqueada, a voz está em modo demo — 4 frases e 400 caracteres por leitura (fila incluída), depois é dita a menção demo |
Métodos #
| Método | Função |
|---|---|
of_open ( ) | Cria a voz. Facultativo — of_speak fá-lo — mas chamá-lo ao abrir a janela paga o custo uma vez, longe da primeira frase. Devolve um número positivo quando a voz existe, -2 se não pôde ser criada (is_last_error diz porquê) |
of_is_open ( ) | Verdadeiro assim que a voz existe |
of_speak ( string as_text ) | Define o texto e lê-o, desde a primeira frase. Sem argumento, relê is_text. Devolve 0 uma vez enviado, -2 se a voz não pôde ser criada |
of_speak_from ( long al_index ) | Retoma a leitura numa frase precisa. Devolve 0 uma vez enviado, -5 para além da última frase (nada é lido, e uma leitura em curso continua), -2 se a voz não pôde ser criada, -4 se a página não respondeu |
of_pause ( ) | Suspende a leitura onde está — incluindo uma leitura que ainda espera a lista de vozes: só começará com of_resume. Devolve 0 (nada a suspender numa voz nunca aberta, que não é criada para isso), -4 se a página não respondeu |
of_resume ( ) | Retoma onde of_pause parou. Devolve 0, -4 se a página não respondeu |
of_stop ( ) | Para a leitura; of_speak recomeça pela primeira frase |
of_is_speaking ( ) | Verdadeiro desde of_speak até ao fim da leitura — a espera da lista de vozes e uma leitura em pausa incluídas. Perguntado ao componente, nunca uma cópia desatualizada |
of_is_paused ( ) | Verdadeiro enquanto a leitura está em pausa (of_pause), até of_resume ou of_stop |
of_sentence_count ( ) | Devolve quantas frases o componente faz de is_text tal como está nesse momento, antes de qualquer leitura; durante uma leitura, as do texto lido |
of_count ( ) → long | Quantas frases o componente fez do texto — o mesmo número que of_sentence_count devolve. A biblioteca faz esta pergunta sob um único nome em todo o lado |
of_voices ( ref string as_names[] ) | Preenche o vetor com as vozes instaladas neste posto e devolve quantas são |
of_languages ( ref string as_tags[] ) | Preenche o vetor com os idiomas que este posto sabe pronunciar, sem duplicados, e devolve quantos são |
of_voice_used ( ) | A voz que o componente entregará realmente ao motor — nem sempre a que is_lang pede: um posto carrega as vozes que alguém lá instalou e nenhuma outra. Vazio = o componente não impõe nenhuma, e o motor toma a sua, a da língua do sistema. Nomeá-la seria adivinhar. ue_voice_fallback di-lo ao ler; isto lê-se antes, com is_lang e is_voice tal como estão nesse momento |
of_speak_sync ( { string as_text } ) | Lê e ESPERA o fim: a linha seguinte corre após a última frase, a janela continua a pintar-se. Devolve 0 no fim, -4 em caso de falha, de tempo esgotado ou se outro of_speak_sync já está à espera (chamado a partir de um evento que ele levantou) — is_last_error diz porquê |
of_enqueue ( string as_text ) | Põe um texto em FILA: lido de imediato se a voz está livre, após a leitura em curso caso contrário, sem nunca a cortar. Devolve 0, -2 se a voz não pôde ser criada |
of_clear_queue ( ) | Esquece os textos em espera, sem cortar o que se lê. Devolve 0 (uma voz nunca aberta não tem fila, e não é criada para isso), -4 se a página não respondeu |
of_queue_count ( ) | Devolve o número de textos ainda em espera (o que se lê não conta) |
of_add_replacement ( string as_from, string as_to ) | Uma regra de pronúncia: cada palavra INTEIRA as_from é lida as_to (PB → PowerBuilder). Aplicada antes das etiquetas, guardada pelo objeto. Devolve 0, -5 se as_from estiver vazio |
of_clear_replacements ( ) | Esvazia o dicionário. Devolve 0 |
of_replacement_count ( ) | Devolve o número de regras do dicionário |
of_add_abbreviation ( string as_text ) | Uma ABREVIATURA sua: o ponto que a segue não termina a frase — Art. 5 continua a ser uma frase. O ponto final pode ser omitido, as maiúsculas contam. Uma lista integrada já cobre as habituais das seis línguas desta documentação (Mr., Dr., e.g., M., Mme., z.B., Sig., p.ej.…). Devolve 0 uma vez adicionada, -5 se as_text estiver vazio ou contiver um espaço |
of_clear_abbreviations ( ) | Esquece as abreviaturas adicionadas com of_add_abbreviation — a lista integrada fica. Devolve sempre 0 |
of_pick_voice ( string as_lang, string as_gender ) | Escolhe uma voz INSTALADA para um idioma e, se existir, um género (GENDER_FEMALE, GENDER_MALE, GENDER_ANY): idioma exato, depois a sua família. Põe-na em is_voice e devolve-a; vazio se nenhuma voz fala esse idioma. Um as_lang vazio significa is_lang. Se a lista de vozes estiver ilegível, is_voice fica como estava e is_last_error diz porquê |
of_duration ( ) | Devolve a duração ESTIMADA da leitura, em milissegundos (palavras por minuto ao ritmo pedido, pausas incluídas): para uma barra de progresso, não para um cronómetro. O ritmo APRENDE a voz: cada frase lida até ao fim mede o real, guardado por voz neste posto |
of_position ( ) | Devolve a posição estimada da leitura, em milissegundos, afinada pelas palavras que o motor reporta; 0 quando nada se lê |
of_progress ( ) | Devolve o progresso estimado, de 0 a 100 |
of_spoken_text ( ) | As frases TAL COMO a voz as recebe, uma por linha (dicionário aplicado, etiquetas resolvidas): o texto a mostrar para seguir palavra a palavra |
of_process_events ( ) | Esvazia os eventos pendentes e levanta-os neste objeto. O pump interno do componente chama-o por si enquanto a voz está aberta — nunca o chama |
of_close ( ) | Liberta a voz, parando antes o que estivesse a dizer: uma leitura em curso termina com o seu ue_stopped, levantado antes do retorno. Destruir o objeto também fecha a voz, mas não levanta NENHUM evento: os controlos da janela podem já estar destruídos |
of_reset ( ) | Repõe todas as propriedades no valor inicial |
Eventos #
| Evento | Disparado quando |
|---|---|
ue_started (string as_lang) | A voz começa realmente a falar; as_lang é a língua que lê — a da voz, fr-FR para um fr-CA ausente do computador (ver ue_voice_fallback). Uma pausa posta antes (of_speak e depois of_pause no mesmo script) retém-no até of_resume |
ue_stopped ( ) | A última frase terminou, ou a leitura foi cortada — of_stop, um novo of_speak, of_close: toda a leitura começada termina com um ue_stopped |
ue_paused ( ) | A leitura está suspensa |
ue_resumed ( ) | A leitura recomeça |
ue_sentence (long al_index, string as_text) | Para cada frase, com o seu lugar e o seu texto: é assim que se segue a leitura noutro ponto da janela. Uma frase com mais de 250 caracteres é cortada (numa vírgula, senão num espaço), e um pedaço [rate] é uma frase própria: cada parte tem o seu número |
ue_error (string as_message) | O posto não tem motor nem voz alguma, a voz falha, ou ocorre um erro de script na sua página. Uma voz simplesmente AUSENTE não é um erro: é ue_voice_fallback. Também quando o motor abandona uma frase sem dizer nada («engine did not answer»): uma leitura nunca fica «em curso» para sempre |
ue_voices_ready (long al_count) | O motor preencheu a sua lista de vozes — chega tarde; of_voices, of_languages, of_voice_used e of_pick_voice esperam por ela sozinhos (2,5 s no máximo), este evento só diz QUANDO chegou; al_count diz quantas o posto tem |
ue_word (long al_index, long al_start, long al_length) | A PALAVRA em curso na frase al_index: Mid(frase, al_start, al_length). Sempre levantado: pelas palavras que a voz reporta, e ESTIMADO pelo relógio quando não reporta nenhuma |
ue_queue_done ( ) | O último texto da fila (of_enqueue) está lido |
ue_voice_fallback (string as_wanted, string as_used) | A voz ou o idioma pedido não está neste posto — a sua REGIÃO incluída (pede-se fr-CA, lê fr-FR); as_used nomeia a que lê em seu lugar. Uma informação, não um erro: a leitura continua |
ue_mark (string as_name) | A voz atinge um [mark=nome] do texto; as_name é esse nome. Levantado no início da frase (uma marca antes da sua primeira palavra), quando a palavra que segue a marca é atingida, ou no fim da frase: uma marca nunca se perde |
Pronúncia: pausas, soletração, dicionário #
O WebView2 não tem SSML. O texto traz por isso cinco etiquetas próprias, resolvidas antes do corte em frases, e um dicionário de palavras inteiras aplicado antes delas. Uma etiqueta desconhecida é lida tal como está.
| Etiqueta | Efeito |
|---|---|
[pause=500] | Um silêncio de 500 ms (10 s no máximo). A pausa termina a frase em curso. No FIM do texto: um silêncio após a última frase (para espaçar dois anúncios da fila) |
[spell]ABC12[/spell] | Cada carácter dito um a um: «A, B, C, 1, 2». Nunca termina a frase (os seus pontos são caracteres: um endereço, uma versão) e separa-se de uma palavra que toque |
[say-as=digits]4152[/say-as] | Os dígitos um a um, não «quatro mil cento e cinquenta e dois» |
[say-as=characters]…[/say-as] | Como [spell] |
[mark=row2] | Uma marca: ue_mark("row2") é levantado quando a voz lá chega — para realçar uma linha da sua janela no momento certo |
[rate=50]1 250 USD[/rate] | Esse pedaço a 50 % de ii_rate (10 a 400) — para ler um montante mais devagar. É uma frase própria |
// 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].")
Uma abreviatura não termina a frase: Mr. Smith, Dr., e.g., M. Dupont, z.B. ficam na frase que as traz (lista integrada para as seis línguas desta documentação; etc. só termina a frase antes de uma maiúscula). As suas acrescentam-se com of_add_abbreviation. Uma frase com mais de 250 caracteres — uma nota colada sem ponto — é cortada numa vírgula, senão num espaço: um motor abandona em silêncio os enunciados demasiado longos.
// 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].")
Fila, leitura síncrona, progresso #
of_speakcorta,of_enqueueespera. Uma aplicação que anuncia eventos (alerta, resultado, notificação) põe em fila: dois anúncios próximos são ambos ouvidos, eue_queue_donediz quando o último foi lido.of_clear_queueesquece o que espera sem cortar;of_stopfaz as duas coisas.of_speak_syncdevolve o controlo no fim. O script espera a última frase (a janela continua a pintar-se), limitado poril_timeout_ms, que CORTA a leitura quando é atingido — para «lê isto, depois faz a pergunta».of_duration,of_position,of_progresssão estimativas: o motor nada diz da duração, contam palavras ao ritmo pedido, afinadas pelas palavras que o motor reporta. Suficiente para uma barra de progresso lida a partir de um timer, não para um cronómetro.of_pick_voice(idioma, género)escolhe uma voz instalada por idioma e depois género, e põe-na emis_voice. O género vem do nome próprio que o Windows dá a cada voz; uma voz de nome desconhecido responde aGENDER_ANY.- Sem exportação áudio. A síntese do WebView2 não devolve nenhum fluxo: a leitura não pode ser escrita num ficheiro. Não é uma definição em falta, é o motor.
Exemplos #
Ler uma notificação #
// Read the notification aloud, without waiting for the end
inv_voice.of_speak(/*text*/ "Order 4152 has been shipped. It arrives on Thursday.")
Realçar a frase em 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)
Escolher a voz e a velocidade #
// 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 sem cortar: a fila #
// 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
Realçar a palavra em 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)
Uma voz por idioma e género, depois ler esperando o fim #
// 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
Boas práticas #
- Uma frase de cada vez: o componente entrega ao motor uma só frase e encadeia. É isso que permite parar limpamente entre duas frases, e evita a desistência silenciosa do Chromium para lá de uns quinze segundos.
- Realce a frase lida no seu texto, a partir de
ue_sentence— é metade daquilo para que serve ler em voz alta. - Chame
of_languages()em vez de supor: um idioma instalado na sua máquina pode não estar na do cliente. - Não chame
of_speakem rajada sobre um conjunto de dados: cada chamada corta a anterior, e o utilizador só ouve inícios. Para isso existeof_enqueue.