speechout — n_pbt_speechout #
← Riferimento dei componenti · Sommario della guida
Lettura ad alta voce: l'applicazione legge un testo frase per frase e ti dice a che punto è. Nessun servizio di terze parti, nessuna chiave API — la sintesi è quella della postazione.
▶ Vederlo dal vivo — Applicazione dimostrativa, riquadro Speech out: l'anteprima, il codice che la produce e questa pagina, fianco a fianco.
In breve #
| Oggetto non visuale | n_pbt_speechout |
| Serve a | Far sentire un testo: accessibilità, mani occupate, una notifica che nessuno guarda |
| Principio | Tu dai il testo; il componente lo divide in frasi e ti dice quale sta leggendo |
| Dipendenza | La sintesi vocale della postazione — nessun servizio di terze parti, nessuna chiave API |
Avvio rapido #
// 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 visuale: il cablaggio degli eventi #
Una voce non ha nulla da mostrare. Il componente quindi non disegna niente: nessuna barra di riproduzione da sistemare, nessuno spazio tolto a ciò che serve.
E per questo non devi collegare nulla: un oggetto non visuale non ha finestra, quindi niente campanello, ma il componente preleva da solo i suoi eventi sul loop PowerBuilder finché la voce è aperta e li solleva sull'oggetto. Scrivi solo i gestori ue_* (nessun ricevitore, nessun 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
Seguire la lettura nel TUO testo #
ue_sentence porta l'indice e il testo della frase in lettura. Da lì viene l'evidenziazione — nel tuo mle_, nella tua datawindow o nel tuo statictext: tu sai dov'è il tuo testo, noi mai.
Esiste anche il percorso inverso: of_speak_from() riprende da una frase precisa, cosa che si collega al clic su un paragrafo. I numeri vengono da ue_sentence, quindi indicano sempre ciò che è stato davvero letto.
Il taglio è quello del componente, non il tuo:
of_sentence_count()restituisce il suo conteggio. Non ricontare dalla tua parte, i due divergerebbero.
Ciò che la postazione sa davvero dire #
of_languages() restituisce le lingue che questa postazione sa effettivamente pronunciare, senza duplicati. È la domanda che si pone un utente: non «quali voci esistono», ma «c'è la mia lingua».
of_voices() scende di un gradino e nomina le voci stesse. Entrambi gli elenchi vengono dalla macchina, non da noi: non scrivere mai un nome fisso nel codice.
Il riconoscimento vocale non ha un equivalente, e non è una dimenticanza: il riconoscimento non porta alcun elenco delle lingue che accetta.
Senza alcun componente da parte tua, gnv_utils.of_speech_languages(as_tags[]) dà lo stesso elenco — la DLL interroga una voce nascosta, poi la elimina: è ciò che una finestra chiede prima che la voce esista, quale lingua proporre, in quale leggere. La chiamata è sincrona e può richiedere alcuni secondi la prima volta (il puntatore mostra l'attesa; la risposta viene tenuta cinque secondi): l'elenco delle voci arriva tardi, e la chiamata lo aspetta. E gnv_utils.of_speech_voices(as_names[], as_langs[]) dà le voci stesse con la loro lingua, per proporre «Hortense» o «Julie» anziché un tag; gnv_utils.of_locale_name(as_tag) dà a un tag il suo nome leggibile — «francese (Francia)» per 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]
Proprietà #
| Proprietà | Tipo | Predefinito | Ruolo |
|---|---|---|---|
is_lang | string | en-US | Lingua letta, in BCP-47 (costanti LANG_*). Decide quale voce viene scelta — finché is_voice è vuota, poiché un nome di voce fissa la propria lingua. Senza una voce per quella lingua la postazione legge con quella che ha, e ue_voice_fallback le nomina entrambe — anche una REGIONE mancante (fr-CA richiesto, legge una voce fr-FR). Viva: cambiata durante una lettura, vale dalla frase successiva |
is_text | string | "" | Il testo da leggere. Il componente lo taglia in frasi; dei tag dicono COME leggere un pezzo: [pause=500], [spell]…[/spell], [say-as=digits]…[/say-as], [mark=nome], [rate=150]…[/rate] (vedi sotto) |
is_voice | string | "" | Nome della voce, preso da of_voices(). Vuoto = la prima che parla is_lang. Viva, come is_lang |
ii_rate | integer | 100 | Velocità, in PER CENTO di quella normale (da 10 a 400). Cambiata durante la lettura, vale dalla frase successiva |
ii_pitch | integer | 100 | Altezza della voce, in PER CENTO dell'altezza normale (da 0 a 200). Cambiato durante la lettura, vale dalla frase successiva |
ii_volume | integer | 100 | Volume in percento (0 muto a 100), la scala del lettore video. Cambiato durante la lettura, vale dalla frase successiva |
il_timeout_ms | long | 300000 | Durata massima di una lettura SINCRONA: cinque minuti. Oltre, of_speak_sync rende -4 e la lettura viene interrotta (is_last_error dice perché). 0 o meno: nessun limite (24 ore al massimo) |
is_last_error | string | "" | Perché l'ultimo of_speak_sync ha reso -4: voce non creata, motore in errore, tempo scaduto, un altro of_speak_sync già in attesa; anche perché of_pick_voice non ha potuto leggere l'elenco delle voci |
ipo_owner | powerobject | null | L'oggetto visuale per cui lavora questa voce: la licenza si verifica sulla sua classe. Impostato dopo l'apertura della voce, viene trasmesso alla chiamata successiva. Necessario solo nell'applicazione dimostrativa; una chiave di sviluppo o di runtime sblocca la voce senza di esso. Non sbloccata, la voce è in modalità demo — 4 frasi e 400 caratteri per lettura (coda compresa), poi viene detta la menzione demo |
Metodi #
| Metodo | Ruolo |
|---|---|
of_open ( ) | Crea la voce. Facoltativo — of_speak lo fa — ma chiamarlo all'apertura della finestra paga il costo una volta, lontano dalla prima frase. Restituisce un numero positivo quando la voce esiste, -2 se non ha potuto essere creata (is_last_error dice perché) |
of_is_open ( ) | Vero una volta creata la voce |
of_speak ( string as_text ) | Imposta il testo e lo legge, dalla prima frase. Senza argomento, rilegge is_text. Restituisce 0 una volta inviato, -2 se la voce non ha potuto essere creata |
of_speak_from ( long al_index ) | Riprende la lettura da una frase precisa. Restituisce 0 una volta inviato, -5 oltre l'ultima frase (non viene letto nulla, e una lettura in corso continua), -2 se la voce non ha potuto essere creata, -4 se la pagina non ha risposto |
of_pause ( ) | Sospende la lettura dov'è — compresa una lettura che attende ancora l'elenco delle voci: partirà solo con of_resume. Restituisce 0 (niente da sospendere su una voce mai aperta, che non viene creata per questo), -4 se la pagina non ha risposto |
of_resume ( ) | Riprende da dove of_pause si era fermato. Restituisce 0, -4 se la pagina non ha risposto |
of_stop ( ) | Ferma la lettura; of_speak riparte dalla prima frase |
of_is_speaking ( ) | Vero da of_speak fino alla fine della lettura — l'attesa dell'elenco delle voci e una lettura in pausa comprese. Chiesto al componente, mai una copia scaduta |
of_is_paused ( ) | Vero finché la lettura è in pausa (of_pause), fino a of_resume o of_stop |
of_sentence_count ( ) | Restituisce quante frasi il componente ricava da is_text com'è in quell'istante, prima di ogni lettura; durante una lettura, quelle del testo letto |
of_count ( ) → long | Quante frasi il componente ha ricavato dal testo — lo stesso numero che rende of_sentence_count. La libreria pone questa domanda sotto un solo nome ovunque |
of_voices ( ref string as_names[] ) | Riempie l'array con le voci installate su questa postazione e ne restituisce il numero |
of_languages ( ref string as_tags[] ) | Riempie l'array con le lingue che questa postazione sa pronunciare, senza duplicati, e ne restituisce il numero |
of_voice_used ( ) | La voce che il componente consegnerà davvero al motore — non sempre quella chiesta da is_lang: una postazione porta le voci che qualcuno vi ha installato e nessun'altra. Vuoto = il componente non ne impone alcuna, e il motore prende la sua, quella della lingua di sistema. Nominarla sarebbe indovinare. ue_voice_fallback lo dice al momento di leggere; questo si legge prima, con is_lang e is_voice come sono in quell'istante |
of_speak_sync ( { string as_text } ) | Legge e ATTENDE la fine: la riga seguente gira dopo l'ultima frase, la finestra continua a dipingersi. Rende 0 alla fine, -4 in caso di errore, di tempo scaduto o se un altro of_speak_sync sta già attendendo (chiamato da un evento che ha sollevato) — is_last_error dice perché |
of_enqueue ( string as_text ) | Mette un testo in CODA: letto subito se la voce è libera, dopo la lettura in corso altrimenti, senza mai tagliarla. Rende 0, -2 se la voce non ha potuto essere creata |
of_clear_queue ( ) | Dimentica i testi in attesa, senza tagliare quello in lettura. Restituisce 0 (una voce mai aperta non ha coda, e non viene creata per questo), -4 se la pagina non ha risposto |
of_queue_count ( ) | Rende il numero di testi ancora in attesa (quello in lettura non è contato) |
of_add_replacement ( string as_from, string as_to ) | Una regola di pronuncia: ogni parola INTERA as_from è letta as_to (PB → PowerBuilder). Applicata prima dei tag, conservata dall'oggetto. Rende 0, -5 se as_from è vuoto |
of_clear_replacements ( ) | Svuota il dizionario. Rende 0 |
of_replacement_count ( ) | Rende il numero di regole del dizionario |
of_add_abbreviation ( string as_text ) | Un'ABBREVIAZIONE vostra: il punto che la segue non termina la frase — Art. 5 resta una frase. Il punto finale può essere omesso, le maiuscole contano. Un elenco integrato copre già quelle usuali delle sei lingue di questa documentazione (Mr., Dr., e.g., M., Mme., z.B., Sig., p.ej.…). Restituisce 0 una volta aggiunta, -5 se as_text è vuoto o contiene uno spazio |
of_clear_abbreviations ( ) | Dimentica le abbreviazioni aggiunte con of_add_abbreviation — l'elenco integrato resta. Restituisce sempre 0 |
of_pick_voice ( string as_lang, string as_gender ) | Sceglie una voce INSTALLATA per una lingua e, se esiste, un genere (GENDER_FEMALE, GENDER_MALE, GENDER_ANY): lingua esatta, poi la sua famiglia. La mette in is_voice e la rende; vuoto se nessuna voce parla quella lingua. Un as_lang vuoto vuol dire is_lang. Se l'elenco delle voci è illeggibile, is_voice resta com'era e is_last_error dice perché |
of_duration ( ) | Rende la durata STIMATA della lettura, in millisecondi (parole al minuto alla velocità richiesta, pause comprese): per una barra di avanzamento, non per un cronometro. Il ritmo IMPARA la voce: ogni frase letta fino in fondo misura quello reale, ricordato per voce su questa postazione |
of_position ( ) | Rende la posizione stimata della lettura, in millisecondi, affinata dalle parole che il motore riporta; 0 quando nulla si legge |
of_progress ( ) | Rende l'avanzamento stimato, da 0 a 100 |
of_spoken_text ( ) | Le frasi COME la voce le riceve, una per riga (dizionario applicato, tag risolti): il testo da mostrare per seguire parola per parola |
of_process_events ( ) | Svuota gli eventi in attesa e li solleva su questo oggetto. Il pump interno del componente lo chiama per te finché la voce è aperta — non lo chiami mai |
of_close ( ) | Libera la voce, fermando prima ciò che stava dicendo: una lettura in corso finisce con il suo ue_stopped, sollevato prima del ritorno. Distruggere l'oggetto chiude anch'esso la voce, ma non solleva ALCUN evento: i controlli della finestra possono essere già distrutti |
of_reset ( ) | Riporta tutte le proprietà al valore iniziale |
Eventi #
| Evento | Scatta quando |
|---|---|
ue_started (string as_lang) | La voce inizia davvero a parlare; as_lang è la lingua che legge — quella della voce, fr-FR per un fr-CA assente dal computer (vedi ue_voice_fallback). Una pausa posta prima (of_speak poi of_pause nello stesso script) lo trattiene fino a of_resume |
ue_stopped ( ) | L'ultima frase è finita, oppure la lettura è stata interrotta — of_stop, un nuovo of_speak, of_close: ogni lettura iniziata finisce con un ue_stopped |
ue_paused ( ) | La lettura è sospesa |
ue_resumed ( ) | La lettura riparte |
ue_sentence (long al_index, string as_text) | Per ogni frase, con il suo rango e il suo testo: così si segue la lettura altrove nella finestra. Una frase di oltre 250 caratteri viene tagliata (su una virgola, altrimenti su uno spazio), e un pezzo [rate] è una frase a sé: ogni parte ha il suo numero |
ue_error (string as_message) | La postazione non ha alcun motore o alcuna voce, la voce fallisce, o si verifica un errore di script nella sua pagina. Una voce semplicemente ASSENTE non è un errore: è ue_voice_fallback. Anche quando il motore abbandona una frase senza dire nulla («engine did not answer»): una lettura non resta mai «in corso» per sempre |
ue_voices_ready (long al_count) | Il motore ha riempito l'elenco delle voci — arriva tardi; of_voices, of_languages, of_voice_used e of_pick_voice lo attendono da soli (2,5 s al massimo), questo evento dice solo QUANDO è arrivato; al_count dice quante ne ha la postazione |
ue_word (long al_index, long al_start, long al_length) | La PAROLA in corso nella frase al_index: Mid(frase, al_start, al_length). Sempre sollevato: dalle parole che la voce riporta, e STIMATO sull'orologio quando non ne riporta |
ue_queue_done ( ) | L'ultimo testo della coda (of_enqueue) è letto |
ue_voice_fallback (string as_wanted, string as_used) | La voce o la lingua richiesta non è su questa postazione — REGIONE compresa (fr-CA richiesto, legge fr-FR); as_used nomina quella che legge al suo posto. Un'informazione, non un errore: la lettura continua |
ue_mark (string as_name) | La voce raggiunge un [mark=nome] del testo; as_name è quel nome. Sollevato all'inizio della frase (una marca prima della sua prima parola), quando si raggiunge la parola che segue la marca, o alla fine della frase: una marca non va mai persa |
Pronuncia: pause, compitazione, dizionario #
WebView2 non ha SSML. Il testo porta quindi cinque tag suoi, risolti prima del taglio in frasi, e un dizionario di parole intere applicato prima di essi. Un tag sconosciuto è letto così com'è.
| Tag | Effetto |
|---|---|
[pause=500] | Un silenzio di 500 ms (10 s al massimo). La pausa termina la frase in corso. Alla FINE del testo: un silenzio dopo l'ultima frase (per distanziare due annunci della coda) |
[spell]ABC12[/spell] | Ogni carattere detto uno per uno: «A, B, C, 1, 2». Non termina mai la frase (i suoi punti sono caratteri: un indirizzo, una versione) e si stacca da una parola che tocca |
[say-as=digits]4152[/say-as] | Le cifre una per una, non «quattromilacentocinquantadue» |
[say-as=characters]…[/say-as] | Come [spell] |
[mark=row2] | Una marca: ue_mark("row2") è sollevato quando la voce ci arriva — per evidenziare una riga della vostra finestra al momento giusto |
[rate=50]1 250 USD[/rate] | Quel pezzo al 50 % di ii_rate (da 10 a 400) — per leggere un importo più lentamente. È una frase a sé |
// 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].")
Un'abbreviazione non termina la frase: Mr. Smith, Dr., e.g., M. Dupont, z.B. restano nella frase che le porta (elenco integrato per le sei lingue di questa documentazione; etc. termina la frase solo davanti a una maiuscola). Le vostre si aggiungono con of_add_abbreviation. Una frase di oltre 250 caratteri — un promemoria incollato senza punto — viene tagliata su una virgola, altrimenti su uno spazio: un motore abbandona in silenzio gli enunciati troppo lunghi.
// 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].")
Coda, lettura sincrona, avanzamento #
of_speaktaglia,of_enqueueattende. Un'applicazione che annuncia eventi (allarme, risultato, notifica) mette in coda: due annunci ravvicinati sono entrambi uditi, eue_queue_donedice quando l'ultimo è letto.of_clear_queuedimentica ciò che attende senza tagliare;of_stopfa entrambe le cose.of_speak_syncrestituisce il controllo alla fine. Lo script attende l'ultima frase (la finestra continua a dipingersi), limitato dail_timeout_ms, che INTERROMPE la lettura una volta raggiunto — per «leggi questo, poi fai la domanda».of_duration,of_position,of_progresssono stime: il motore non dice nulla della durata, contano le parole alla velocità richiesta, affinate dalle parole che il motore riporta. Abbastanza per una barra di avanzamento letta da un timer, non per un cronometro.of_pick_voice(lingua, genere)sceglie una voce installata per lingua poi genere, e la mette inis_voice. Il genere viene dal nome che Windows dà a ogni voce; una voce dal nome sconosciuto risponde aGENDER_ANY.- Nessuna esportazione audio. La sintesi di WebView2 non rende alcun flusso: la lettura non può essere scritta in un file. Non è un'impostazione mancante, è il motore.
Esempi #
Leggere una notifica #
// Read the notification aloud, without waiting for the end
inv_voice.of_speak(/*text*/ "Order 4152 has been shipped. It arrives on Thursday.")
Evidenziare la frase in corso #
// 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)
Scegliere la voce e la velocità #
// 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()
Annunciare senza tagliare: la coda #
// 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
Evidenziare la parola in corso #
// 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 voce per lingua e genere, poi leggere aspettando la fine #
// 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
Buone pratiche #
- Una frase alla volta: il componente affida al motore una sola frase e concatena. È ciò che permette di fermarsi pulitamente fra due frasi, ed evita l'abbandono silenzioso di Chromium oltre una quindicina di secondi.
- Evidenzia la frase letta nel tuo testo, da
ue_sentence— è metà del senso di una lettura ad alta voce. - Chiama
of_languages()invece di supporre: una lingua installata da te può non esserlo dal cliente. - Non chiamare
of_speaka raffica su un insieme di dati: ogni chiamata taglia la precedente, e l'utente sente solo inizi. Per questo c'èof_enqueue.