Differenze tra sintesi vocali, problematiche e indicazioni per la creazione di un dizionario specifico.

Differenze tra sintesi vocali, problematiche e indicazioni per la creazione di un dizionario specifico.
Di Adriano Barbieri.
**********
Ogni sintesi vocale può gestire la punteggiatura, ma non sempre allo stesso modo; è importante quindi sapere come poter interagire rispettando il loro modus operandi.
La punteggiatura serve a creare una pausa più o meno lunga, durante la lettura di un testo, il punto fermo è quello più delicato. Infatti, se è seguito da almeno uno spazio, si trovi a fine riga, o inserito in un acronimo del tipo "s.p.a.", la sintesi vocale si comporterà in modo diverso a seconda dei casi; in effetti non sempre il punto fermo indica una pausa.
Per far sì che la pausa venga rispettata occorre che il punto fermo sia seguito da almeno uno spazio, o sia a fine riga e seguito da il carattere di ritorno a capo.
Queste condizioni sono sufficienti per alcune sintesi vocali, mentre per altre come e-speak, non bastano, occorre che anche l'iniziale che segue sia maiuscola; ma non solo; che prima del punto fermo vi siano almeno due caratteri.
Quest'ultima condizione è comune a molte sintesi vocali, se ne avete più d'una, provate questi semplici test:
• a. b. (due minuscole separate da punto e spazio)
• a.b. (due minuscole separate da un punto).
• A. B. (due maiuscole separate da punto e spazio).
• A.B. (due maiuscole separate da un punto).
• a.b.c. (tre minuscole separate da un punto).
• A.B.C. (tre maiuscole come sopra).
• a. b. c. (tre minuscole separate da un punto più uno spazio).
• A. B. C. (tre maiuscole come sopra).
• ab. c. (due minuscole e una minuscola separata da un punto più uno spazio).
• ab. C. (due minuscole e una maiuscola separata come sopra).
• AB. C. (due maiuscole e una maiuscola separata come sopra).
Noterete che ogni sintesi interagirà inserendo le pause in modi diversi come ho spiegato sopra.
E-Speak richiede almeno due caratteri prima del punto, uno spazio, e una lettera maiuscola, quindi, è neccessario creare un dizionario ad hoc, che permetta di conservare il formato dell'iniziale di ogni parola e nei limiti del possibile anche tutto il resto di quest'ultima.
Prima occorre, però, spiegare alcune problematiche, perchè capendo il loro meccanismo saremo in grado di aggirarle.
Prendiamo ancora ad esempio il nome Sophia. Il metodo per processare e successivamente correggere il nome Sophia, in Sophìa, con l'ultima "ì" accentata al posto della "i" normale, potrebbe essere così:
"SOPHIA|[sS]ophia(\W)",parolainsostituzione"sophìa\1".
Questo approccio è funzionale solamente nel matching, però non permette di preservare l'iniziale corretta, limitandosi a correggere la parola, utilizzando una forma minuscola e fissa nella parola in sostituzione, compresa l'iniziale "s".
• Test. sophia, con iniziale minuscola.
• Test. Sophia, con iniziale maiuscola.
Se si prova le due righe di test appena descritte utilizzando una sintesi della Loquendo, si potrà notare che la pausa che precede il nome "Sophia" verrà rispettata a prescindere dalla iniziale della parola.
Cosa diversa se si provasse con Silvia della Nuance Real Speak, o con e-speak di Nvda.
La soluzione di usare sistematicamente tutte le iniziali delle parole nel campo di sostituzione in maiuscolo avrebbe degli ulteriori inconvenienti.
Inserendo una regex come questa:
#guaina.
"GUAINA|[gG]uaina(\W)",einsostituzione"Guàina\1".
La quale si limita a sostituire l'iniziale con una analoga maiuscola e la prima "a", con la "à accentata".
L'iniziale fissa maiuscola consente il rispetto della pausa con sintesi come silvia, ed e-speak ; ma la parola "Guaina", è contenuta ad esempio anche nella parola "Sguaina", "Inguaina", "Sguainala", eccetera.
Incontrando tale parola, notare l'iniziale "s" maiuscola, la nostra regex la correggerebbe sostituendo "guaina" in "Guàina".
La correzione formerebbe una parola così: "SGuàina", notare le prime due lettere "SG" maiuscole.
Come mai la vocalizzazione della s risulta "staccata"?
Questa interferenza è generata dal dizionario "builtin.dic" di Nvda, la seguente regex ne è la causa:
#Break away a words tarting with a capital from a fully uppercase word
"([A-Z])([A-Z][a-z])", e in sostituzione: "\1 \2".
Come si può notare per chi mastica inglese, la regex si incarica di staccare quelle parole composte da una completamente maiuscola seguita da un'altra con l'iniziale maiuscola, come ad esempio: "WORDPad", "FIREFox", "OPENOffice", eccetera.
L'uso di un'iniziale fissa maiuscola limita la versatilità delle Regex; la Regex builtin ci obbligherebbe a inserire una nuova voce al nostro dizionario per la parola "Sguaina", dovendo inserire alla sua sinistra anche il metacarattere "\b"(barra rovesciata diagonale b) per evitare un conflitto tra le due, e limitando il più possibile ogni contatto di ulteriori parole, così:
#Guaina.
"(\b)(GUAINA|[gG]uaina)(\W)",e in sostituzione: "\1Guàina\3".
il problema però si può aggirare sfruttando la capacità di memorizzazione dei gruppi tra parentesi tonde, e l'aiuto di una classe; salviamo quindi l'iniziale di "guaina", in questo modo: "([gG])".
La classe "[gG]" permette di scegliere uno dei due caratteri predefiniti, mentre il gruppo serve a memorizzarlo. Il contenuto memorizzato lo si può richiamare nel campo della parola in sostituzione con "\1" (barra diagonale rovesciata uno), come nell'esempio che segue:
"([gG])(UAINA|uaina)(\W)", e in sostituzione: "\1uàina\3".
Abbiamo fatto sì che l'iniziale della nostra parola sia variabile e non più costante; se si incapperà nuovamente nella parola "Sguaina", la nostra Regex si adatterà al resto della parola in sostituzione, ottenendo "Sguàina", notare che sta volta di maiuscola c'è rimasta solo la "S". Abbiamo così aggirato un problema, ma non sarà l'unico, vediamo perchè.
La nostra regex funziona bene solo se incontra una parola nella forma in minuscolo, anche se in realtà è in grado di riconoscere la parola nei due formati; il motivo è che abbiamo lasciato una parte fissa in minuscolo nel campo di sostituzione. Ecco come viene rielaborata la parola incontrata nei due formati:
• Guàina. (in minuscolo; la prima "à" accentata ci salva).
• SGuàina. (in maiuscolo; stavolta no, perchè?).
Per l'ennesima volta il dizionario "Builtin", è la causa di questo secondo problema:
#break up words that use a capital letter to denote another word
"([a-z])([A-Z])", e in sostituzione: "\1 \2".
Analogamente alla precedente, questa si occupa di "staccare" le parole, quelle però che hanno l'iniziale maiuscola da quelle in minuscolo che la precedono, come WinFax, WordPad, AdrianoBarbieri, eccetera.
È un'altra regex utile, ma limitante perchè impedisce l'uso di regex più complesse, preposte a processare più ricorrenze di una stessa parola, come ad esempio questa:
#Leggici/mi/ti/la/le/li/lo.
"([lL])(EGGI|eggi)([cCmMtT][iI]|[lL].)(\W)", e in sostituzione: "\1èggi\3\4".
Che può processare le seguenti varianti:
• leggici
• leggimi
• leggiti
• leggila
• leggile
• leggili
• leggilo
La regex mette la giusta iniziale nel gruppo uno, e sostituisce la prima vocale "e", con la "è accentata", aggiungendo il valore processato nei gruppi tre e quattro, sfruttando una parte fissa in minuscolo della parola "ggi", ottenendo una cosa del genere:
leggimi = lèggimi; tutto in minuscolo va bene.
LEGGIMI = LèggiMI; in maiuscolo, la "MI" alla fine viene staccata dal resto della parola in minuscolo a causa della Regex del dizionario "builtin" di Nvda.
Ovviamente l'effetto è ancora più pronunciato con parole più lunghe.
Quelle "due" ci attaccano da due fronti, ed hanno sempre la priorità sui nostri dizionari. Non vogliamo e non conviene eliminarle, perché, dopotutto, hanno la loro utilità.
Una soluzione è di aggirare queste due regex "builtin" sfruttando il loro "tallone d'Achille", le vocali accentate, che per nostra fortuna non vengono considerate, per ora!
Sfruttando davvero appieno i "gruppi" che permettono di memorizzare il risultato di un processo in esso contenuto, per salvare e recuperare l'iniziale, ma anche altre parti di una parola, combinati opportunamente con le vocali accentate, ecco più o meno come sarà la nostra nuova Regex:
"([lL])[eE](GGI|ggi)([cCmMtT][iI]|[lL].)(\W)", e in sostituzione: "\1è\2\3\4".

Qui, ogni singolo gruppo processa e memorizza la parte di parola ad esso assegnata, escludendo l'unica classe esterna ai gruppi, i quali, richiamati, come pezzi di un puzzle si occupano di aggiungere le varie parti che completano la nostra parola, mantenendo il formato dell'originale. In tal modo le ricorrenze di prima vengono convertite così:
• leggimi = lèggimi. (in minuscolo).
• Leggimi = Lèggimi. (iniziale maiuscola e resto minuscolo).
• LEGGIMI = LèGGIMI. (tutto maiuscolo).
E così via per le altre ricorrenze precedentemente elencate. Tutta la parola viene preservata e ricomposta nella sua forma originale, a parte la vocale nella classe "[eE]",che nel nostro caso viene sostituita dalla "è accentata".
Nel tentativo di rendere più chiara possibile la comprensione della sintassi delle Regex, non ho usato una forma "nidificata" che solitamente preferisco adottare, altrimenti ecco come sarebbe stata:
"([lL])[eE]((GGI|ggi)([cCmMtT][iI]|[lL].)\W)", e in sostituzione: "\1è\2".
La forma nidificata la trovo più comoda nel caso di modifica. Vi ricordo che i "gruppi" vanno contati da sinistra verso destra, e nei gruppi nidificati ha priorità il primo gruppo più esterno che ne contiene; negli esempiche seguono userò una classe vuota indicante la vocale da accentare, e nel campo di sostituzione userò un punto esclamativo per indicarne il punto d'inserimento:
"(1)[](2)(3)" = "\1!\2\3".
"(1(2))[](3)" = "\1!\3".
"(1(2)(3))[](4)(5)" = "\1!\4\5".
"(1(2))[](3(4)(5)(6))" = "\1!\3".
La struttura della nostra nuova Regex si adatterebbe a quasi tutte le sintesi vocali, ma E-speak ha un ulteriore problema, non vocalizza allo stesso modo le vocali accentate, c'è una lieve differenza di inflessione da una minuscola a una maiuscola. Quindi, non è sempre possibile sfruttare le Regex a struttura "multipla" che processano la parola nei due formati maiuscolo/minuscolo.
Con E-Speak occorrono due Regex, una per ogni formato della parola; questo, almeno per quelle espressioni regolari preposte a gestire più ricorrenze di una stessa parola.
Per ognuna occorre usare una vocale maiuscola o minuscola a seconda del formato da processare, quindi, partendo dall'esempio dell'ultima Regex, ecco come deve essere convertita nel formato esteso:
#Leggici/mi/ti/la/e/i/ominuscolo.
"([lL])e(ggi)([cmt]i|l[aeio])(\W)", e in sostituzione: "\1è\2\3\4".
#Leggici/mi/ti/la/e/i/o,maiuscolo.
"(L)E(GGI)([CMT]I|L[AEIO])(\W)",e in sostituzione: "\1È\2\3\4".
Ecco la stessa coppia di Espressioni regolari ma nella versione nidificata:
#Leggici/mi/ti/la/e/i/o,minuscolo.
([lL])e(ggi([cmt]i|l[aeio])\W)", e in sostituzione: "\1è\2".
#Leggici/mi/ti/la/e/i/o,maiuscolo.
(L)E(GGI([CMT]I|L[AEIO])\W)", e in sostituzione: "\1È\2".
Questo sistema a doppia Regex si può evitarlo nelle parole uniche che non hanno ricorrenze come ad esempio "Sophia":
"([sS])(OPHIA|ophia)(\W)", e in sostituzione: "\1ophìa\3".
In ogni caso, qualsiasi metodo si adotti, bisogna sempre stare attenti ai possibili conflitti tra Regex con parole che potrebero essere contenute all'interno di altre. In tal caso si deve fare in modo di "chiudere" queste regex utilizzando il giusto metacarattere escaped, nella maggior parte dei casi si può usare "\b"(barra diagonale rovesciata b minuscola) all'inizio, mentre alla fine "\W"(barra diagonale rovesciata W maiuscola) alla fine dell'espressione regolare; in alcuni casi la "\b"può essere anche usata alla fine in combinazione con una classe che neghi, ad esempio, anche le vocali: "(\b[^ÀÈÉÌÒÓÙàèéìòóù])".
Le vocali accentate "filtrano", quindi l'uso dei soli metacaratteri escaped a volte va affiancato alla negazione sistematica delle vocali accentate, prendete ad esempio questa parola: "url". Essa è contenuta anche in "urlo", o "urlò", o "urlìo", e tante altre.
Chiudere questa parola con "\W" funziona bene con "urlo", ma non con "urlò", o urlìo". Le ultime due parole, infatti, hanno una vocale accentata che nessun metacarattere può escludere; quello più efficace è "\b",ma anche questo lascia filtrare le vocali accentate. Ma con una classe di negazione come "[^ÀÈÉÌÒÓÙàèéìòóù]"di rinforzo si può.
#Url.
"\b[uU])([rR])([lL])(\b[^àèéìòóùÁÉÈÍÒÓÚ])", e in sostituzione: "\1\2\3\4".
Per essere sicuro che una nuova Regex inserita nel mio dizionario, funzioni senza conflitti, aggiungo temporaneamente un", ok" al termine della stringa di sostituzione.
Può succedere benissimo che la nuova Regex non funzioni, non perchè sbagliata, ma perchè un'altra molto simile "non chiusa" interviene prima perchè essendo più in cima ne assume la priorità.
In tal caso se quest'ultima la si "chiude", o la si sposta subito sotto, tutto funziona. Tenetelo sempre in mente.
Ecco un valido motivo del perchè il modulo dizionario non ordini alfabeticamente le voci in modo automatico, come ad esempio invece fa Jaws che al momento non usa le Regex.
La cosa che invece manca è un campo di ricerca e la possibilità di inserire una voce nel punto desiderato, mentre attualmente il modulo si limita ad "accodare" le nuove immissioni, obbligando a sistemare manualmente i conflitti con un editor esterno come il blocco note, che purtroppo non è affidabile con files molto grossi.
Ecco perchè io uso NotePad++ ;).

Adriano Barbieri.