.::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::[.]::[.] :: [O]nda[Q]uadra [0X0B] OQ20040308[0B] :: :: [0x05][C0DiNG] GRiLL0 PARLANTE [eazy] :: [.]:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: 1. Preambolo 2. Introduzione 2.1 Analisi a runtime 2.2 Analisi del codice 3. Codice sorgente 1. Preambolo "[...] A queste ultime parole, Pinocchio salt su tutt'infuriato e preso sul banco un martello di legno lo scagli contro il Grillo-parlante. Forse non credeva nemmeno di colpirlo: ma disgraziatamente lo colse per l'appunto nel capo, tanto che il povero Grillo ebbe appena il fiato di fare cr -cr -cr , e poi rimase l stecchito e appiccicato alla parete" (tratto da "Le avventure di Pinocchio", C. Collodi) 2. Introduzione Niente paura in questo articolo non intendo parlarvi dell'antipatico Grillo-parlante reso famoso dal racconto di Collodi bens di un simpatico sintetizzatore text-to-speech. In cosa consiste questo programma? In linea di massima possiamo dire che un sintetizzatore text-to-speech altro non che un programma in grado di riprodurre vocalmente un testo scritto dall'utente. Tale sistema in grado di generare sempre nuove frasi concatenando tra loro diverse sillabe che apprende e memorizza in un proprio database man mano che lo si utilizza. Ma come funziona? Come prima cosa il programma richiedere all'utente di digitare una frase, successivamente procede a dividerla in sillabe, ogni sillaba trovata viene cercata all'interno del database. Nel caso in cui una o pi sillabe non siano ancora presenti nel database il programma richiede all'utente di pronunciarle al microfono in modo che possano essere memorizzate e aggiunte al database. Una volta che tutte le sillabe della frase sono state individuate all' interno del database procede alla loro concatenazione e riproduce la traccia sonora da essa ottenuta. Quali sono le difficolt di scrivere tale programma? Il principale problema da affrontare consiste nel riconscimento della voce dell'utente quando si esegue l'acquisizione di una nuova sillaba e la soppressione del rumore di fondo dalle tracce. Per distinguere il rumore di fondo dal parlato necessario come prima cosa prelevare un campione del rumore di fondo, successivamente quando si acquisiranno le tracce contenenti la voce dell'utente che pronuncia la sillaba sar cura del programma ricercare al loro interno gli intervalli di silenzio prima e dopo la voce al fine di eliminarli. 2.1 Analisi a runtime La frase utilizzata nell'esempio "sopra la panca la capra campa", come vedremo le sillabe so-pra-la-pan non sono conosciute dal programma e verranno richieste all'utente per la memorizzazione nel database. Come vedremo la traccia audio di ogni sillaba pronunciata dall'utente verr analizzata per rimuovere il silenzio prima e dopo la sua voce. Ogni traccia audio composta da un numero di N campioni, la traccia viene analizzata a gruppi di M campioni, dove M un numero molto inferiore a N. In particolare il programma riconosce l'inizio della sillaba all'interno della traccia sonora cercando di isolare il primo blocco di M campioni contenente una percentuale di campioni di rumore superiore al 20%. La fine della sillaba viene individuata pressoch nello stesso modo, questa volta viene ricercato il primo blocco di M campioni contenente una percentuale di campioni di silenzio superiore all'80%. Segue l'output di esempio del programma: # ./grillo_parlante Vuoi eseguire una prova del microfono? [y/n]: n Il programma procedera' al campionamento e alla soppressione del rumore di fondo. Durante questa fase e' necessario rimanere in silenzio. Premere INVIO per iniziare. Inizio procedura tra 5...4...3...2...1...silenzio! Scrivi una frase: sopra la panca la capra campa Pronuncia la sillaba so. Premere INVIO per iniziare. Percentuale rumore: 0% Percentuale rumore: 0% Percentuale rumore: 0% Percentuale rumore: 0% Percentuale rumore: 0% Percentuale rumore: 0% Percentuale rumore: 0% Percentuale rumore: 0% Percentuale rumore: 3% Percentuale rumore: 0% Percentuale rumore: 0% Percentuale rumore: 0% Percentuale rumore: 0% Percentuale rumore: 0% Percentuale rumore: 45% <--- inizio della sillaba Percentuale silenzio: 54% Percentuale silenzio: 60% Percentuale silenzio: 75% Percentuale silenzio: 83% <--- fine sillaba Pronuncia la sillaba pra. Premere INVIO per iniziare. Percentuale rumore: 0% Percentuale rumore: 0% Percentuale rumore: 0% Percentuale rumore: 0% Percentuale rumore: 0% Percentuale rumore: 0% Percentuale rumore: 0% Percentuale rumore: 0% Percentuale rumore: 0% Percentuale rumore: 0% Percentuale rumore: 23% <--- inizio della sillaba Percentuale silenzio: 77% Percentuale silenzio: 90% <--- fine sillaba Pronuncia la sillaba la. Premere INVIO per iniziare. Percentuale rumore: 0% Percentuale rumore: 0% Percentuale rumore: 0% Percentuale rumore: 0% Percentuale rumore: 0% Percentuale rumore: 0% Percentuale rumore: 0% Percentuale rumore: 0% Percentuale rumore: 3% Percentuale rumore: 0% Percentuale rumore: 0% Percentuale rumore: 0% Percentuale rumore: 0% Percentuale rumore: 0% Percentuale rumore: 0% Percentuale rumore: 7% Percentuale rumore: 45% <--- inizio della sillaba Percentuale silenzio: 55% Percentuale silenzio: 38% Percentuale silenzio: 25% Percentuale silenzio: 51% Percentuale silenzio: 99% <--- fine sillaba Pronuncia la sillaba pan. Premere INVIO per iniziare. Percentuale rumore: 0% Percentuale rumore: 0% Percentuale rumore: 0% Percentuale rumore: 0% Percentuale rumore: 0% Percentuale rumore: 0% Percentuale rumore: 0% Percentuale rumore: 0% Percentuale rumore: 0% Percentuale rumore: 0% Percentuale rumore: 15% <--- rumore Percentuale rumore: 0% Percentuale rumore: 13% Percentuale rumore: 42% <--- inizio della sillaba Percentuale silenzio: 57% Percentuale silenzio: 56% Percentuale silenzio: 64% Percentuale silenzio: 65% Percentuale silenzio: 71% Percentuale silenzio: 91% <--- fine sillaba Infine il programma concatena le sillabe acquisite dal microfono con quelle gi presenti nel database e riproduce la frase vocalmente. 2.2 Analisi del codice Incominciamo la nostra analisi del codice sorgente partendo dalla prima parte della funzione main(): int main(int argc, char **argv) { int dev; . . . . set_device(dev); rumore_fondo(dev); for (;;) { . . . . } } Come prima cosa viene richiamata set_device() che provvede a settare i parametri relativi al campionamento: void set_device(int dev) { int arg; arg = BITS; if (ioctl(dev, SOUND_PCM_WRITE_BITS, &arg) == -1) err_quit(); if (arg != BITS) { fprintf(stderr, "Unable to set required sample bits size\n"); exit(-1); } . . . . } Esegue una serie di ioctl() sul descrittore di file relativo a /dev/dsp al fine di settare il numero di bit, il numero di canali e la frequenza a cui verranno effettuati i campionamenti. Nel nostro caso tali valori vengono assegnati sulla base di alcune costanti definite all'inizio del programma: #define BITS 8 #define CHANNELS 1 #define RATE 8000 come possiamo vedere dal valore delle costanti i campionamenti verranno effettuati a 8 bit, mono e con una frequenza di campionamento di 8000 Hz. Leggendo dal descrittore del file /dev/dsp possibile acquisire l'input dal dispositivo abilitato dal mixer, nel nostro caso il microfono, mentre scrivendoci possibile riprodurre la fonte sonora campionata. La seconda funzione chiamata da main() rumore_fondo() che serve a campionare il rumore di fondo: int min = 255, max = 0; void rumore_fondo(int dev) { int i; Audio sample; unsigned char *ptr; do { . . . . memset(&sample, 0, sizeof(sample)); if (read(dev, sample.data, SAMPLE_LEN) == -1) err_quit(); /* Ricerca i picchi massimi e minimi nel rumore di fondo campionato. I valori dei campioni possono oscillare tra 0 e 255 dove 128 equivale alla condizione di totale silenzio */ for (ptr = sample.data; ptr < sample.data + SAMPLE_LEN; ptr++) { *ptr &= MASK; if (*ptr < min) min = *ptr; if (*ptr > max) max = *ptr; } . . . . /* Se i picchi massimi e minimi riscontrati nel rumore di fondo si discostano eccessivamente da 128, che equivale alla condizione di totale silenzio, mostra un messaggio d'errore e ripete la procedura */ } while (min < MIN || max > MAX); } In questa funzione leggiamo una traccia di dimensione SAMPLE_LEN dal device /dev/dsp e la memorizziamo nella variabile sample, questa traccia conterr il rumore di fondo. La costante SAMPLE_LEN definita in maniera tale da rispecchiare lo spazio richiesto in memoria per contenere una traccia di durata pari a tre secondi: #define SECONDS 3 #define SAMPLE_LEN BITS / 8 * CHANNELS * RATE * SECONDS Per calcolare il valore di SAMPLE_LEN sappiamo che il numero di byte di un campione pu essere ottenuto dividendo per 8 il numero di bit che compone ogni campione: BITS / 8 Inoltre sappiamo che viene prelevato un campione per ogni canale, che nel nostro caso uno solo visto che lavoriamo in mono. Essendo la frequenza di campionamento pari a 8000 Hz sappiamo che per ogni secondo di traccia verranno memorizzati 8000 campioni pertanto moltiplichiamo la durata della traccia espressa in secondi per la frequenza di campionamento espressa in Hz: RATE * SECONDS Il valore di SAMPLE_LEN viene calcolato moltiplicando il numero di byte che compongono ciascun campione per il numero di canali per la frequenza di campionamento per la durata della traccia in secondi: BITS / 8 * CHANNELS * RATE * SECONDS Una volta letta la traccia audio entriamo nel ciclo for e ptr viene fatto puntare all'inizio della traccia che contiene il rumore di fondo campionato. Ad ogni iterazione del ciclo for il puntatore viene fatto avanzare di un byte, ovvero la dimensione di ogni campione (BITS / 8), al termine del for le variabili min e max conterranno rispettivamente il picco minimo e massimo presenti nel rumore di fondo. Tenendo in considerazione che il valore di ogni campione pu oscillare tra 0 e 255, dove 128 rappresenta la condizione di totale silenzio, possiamo immaginare che min avr un valore di poco inferiore a 128 mentre max avr un valore di poco superiore a tale soglia. A questo punto i valori del picco minimo e massimo vengono raffrontati con i valori di due costanti: #define MIN 120 #define MAX 140 Nel caso il valore dei picchi dovesse essere superiore a tali valori il programma proceder ad effettuare nuovamente il campionamento del rumore di fondo. Passiamo ora all'analisi della seconda parte della funzione main(): int main(int argc, char **argv) { int dev, fd, i; char buff[MAXLEN], temp[MAXLEN], *parola, *sillaba; . . . . for (;;) { printf("Scrivi una frase: "); if (fgets(buff, sizeof(buff), stdin) == NULL) err_quit(); buff[strlen(buff) - 1] = 0; memcpy(temp, buff, sizeof(buff)); for (parola = strtok(temp, " "); parola != NULL; parola = strtok(NULL, " ")) /* Divide in sillabe la parola */ for (i = 1; dividi_sillabe(parola, &sillaba, i) ; i++) { /* Se la sillaba non presente nel database procede alla sua memorizzazione */ if (trova_sillaba(fd, sillaba) == -1) memorizza_sillaba(dev, sillaba, fd); free(sillaba); } . . . . } } La funzione fgets() provvede a leggere dallo standard input una frase digitata dall'utente e a memorizzarla per successive elaborazioni. Subito dopo viene richiamata ciclicamente strtok() su tale frase per separarne le varie parole che la compongono: for (parola = strtok(temp, " "); parola != NULL; parola = strtok(NULL, " ")) . . . Ogni parola, a sua volta, viene scoposta in sillabe richiamando ricorsivamente la funzione dividi_sillabe(): /* Divide in sillabe la parola */ for (i = 1; dividi_sillabe(parola, &sillaba, i); i++) { . . . } Per ogni sillaba viene eseguita la funzione trova_sillaba() che verifica se la sillaba esiste nel database del programma: /* Se la sillaba non presente nel database procede alla sua memorizzazione */ if (trova_sillaba(fd, sillaba) == -1) memorizza_sillaba(dev, sillaba, fd); Il descrittore di file fd a cui si fa riferimento quello relativo al database delle firme che non altro che un file binario nel quale viene memorizzata ogni sillaba sia sotto forma di testo ascii che la relativa traccia audio. Se trova_sillaba() da esito negativo, ovvero la sillaba non presente nel database, viene chiamata la funzione memorizza_sillaba() che provvede ad inserire la nuova sillaba nel database. Analizziamo dunque queste due funzioni, trova_sillaba() difinita in questo modo: int trova_sillaba(int fd, char *sillaba) { Audio sample; lseek(fd, 0, SEEK_SET); memset(&sample, 0, sizeof(sample)); while (read(fd, &sample, sizeof(sample)) > 0) if (strcmp(sample.sillaba, sillaba) == 0) return 1; return -1; } Come prima cosa si sposta all'inizio del file che contiene il database delle sillabe e scorre tutte le voci contenute nel db alla ricerca di una che corrisponda alla sillaba passata come argomento della funzione. Se la sillaba viene trovata la funzione ritorna 1 altrimenti ritorna -1. In questo modo nel caso il valore di ritorno di tale funzione dovesse risultare negativo verrebbe richiamata la funzione memorizza_sillaba() che provvederebbe all'inserimento della nuova sillaba nel db. La prima parte di tale funzione si presenta cos: void memorizza_sillaba(int dev, char *sillaba, int fd) { Audio sample; int rumore, silenzio, percent_rumore, percent_silenzio; unsigned char *ptr, *ptr2; do { printf("Pronuncia la sillaba %s. Premere INVIO per iniziare.\n", sillaba); fgetc(stdin); memset(&sample, 0, sizeof(sample)); strncpy(sample.sillaba, sillaba, 9); if (read(dev, sample.data, SAMPLE_LEN) == -1) err_quit(); . . . . } while (....); . . . . } All'utente viene richiesto di pronunciare ad alta voce la sillaba passata come argomento della funzione. Successivamente la funzione esegue una lettura dal descrittore di file relativo al device audio /dev/dsp al fine di acquisire la traccia audio. Possiamo notare che la variabile sample nella quale viene memorizzata la sillaba dichiarata di tipo Audio, questo tipo una struttura definita nel modo seguente: typedef struct { int begin, end; char sillaba[10]; unsigned char data[SAMPLE_LEN]; } Audio; la variabile sillaba conterr la stringa corrispondente alla sillaba, mentre la variabile data conterr la traccia audio acquisita dal microfono. Passiamo ora alla seconda parte della stessa funzione: void memorizza_sillaba(int dev, char *sillaba, int fd) { Audio sample; int rumore, silenzio, percent_rumore, percent_silenzio; unsigned char *ptr, *ptr2; do { . . . . for (ptr = sample.data; ptr < sample.data + SAMPLE_LEN; ptr += NSAMPLE) { rumore = 0; /* Analizza un blocco di campioni per vedere la percentuale di rumore in esso contenuta */ for (ptr2 = ptr; ptr2 < ptr + NSAMPLE; ptr2++) if (*ptr2 < min || *ptr2 > max) rumore++; /* Se la percentuale di rumore contenuta nel blocco di campioni analizzato supera il 20% lo considera l'inizio della sillaba pronunciata */ printf("Percentuale rumore: %d%%\n", percent_rumore = 100 * rumore / NSAMPLE); if (percent_rumore > 20) { sample.begin = ptr - sample.data; break; } } if (!sample.begin) fprintf(stderr, "\nNon stato possibile riconoscere l'inizio della parola.\n"); . . . . } while (....); . . . . } Il puntatore ptr viene fatto puntare all'inizio della traccia audio e per ogni iterazione del ciclo for viene fatto avanzare di un numero di campioni pari al valore della costante NSAMPLE cos definita: #define NSAMPLE 500 Ad ogni itarazione del ciclo for pi esterno viene eseguito un altro for al suo interno: /* Analizza un blocco di campioni per vedere la percentuale di rumore in esso contenuta */ for (ptr2 = ptr; ptr2 < ptr + NSAMPLE; ptr2++) if (*ptr2 < min || *ptr2 > max) rumore++; Il puntatore ptr2 viene fatto puntare alla porzione di dati corrente, in questo modo la traccia la cui durata tolate di 3 secondi viene analizzata a blocchi di 500 campioni. Lo scopo del ciclo for pi interno quello di scorrere ogni elemento di ogni blocco di campioni al fine di trovare il primo blocco di campioni che rappresenti la sillaba pronunciata dall'utente. Per far questo viene analizzato il valore di ogni singolo campione che compone il blocco di 500 elementi e viene incrementata la variabile rumore nel caso in cui il valore del campione analizzato dovesse uscire dai valori di soglia che costituiscono il silenzio. /* Se la percentuale di rumore contenuta nel blocco di campioni analizzato supera il 20% lo considera l'inizio della sillaba pronunciata */ printf("Percentuale rumore: %d%%\n", percent_rumore = 100 * rumore / NSAMPLE); if (percent_rumore > 20) { sample.begin = ptr - sample.data; break; } Una volta usciti dal ciclo for interno viene calcolata la percentuale di elementi che contengono rumore e se tale percentuale supera il 20% il blocco di campioni viene considerato l'inizio della sillaba pronunciata dall'utente all'interno della traccia audio. La variabile begin contenuta all'interno della struttura sample viene fatta puntare all'inizio di tale blocco per indicare l'inizio della sillaba all'interno della traccia. Passiamo alla terza ed ultima parte della funzione: void memorizza_sillaba(int dev, char *sillaba, int fd) { Audio sample; int rumore, silenzio, percent_rumore, percent_silenzio; unsigned char *ptr, *ptr2; do { . . . . for (ptr = sample.data + sample.begin; ptr < sample.data + SAMPLE_LEN; ptr += NSAMPLE) { silenzio = 0; /* Analizza un blocco di campioni per vedere la percentuale di silenzio in esso contenuta */ for (ptr2 = ptr; ptr2 < ptr + NSAMPLE; ptr2++) if (*ptr2 >= min && *ptr2 <= max) silenzio++; /* Se la percentuale di silenzio contenuta nel blocco di campionianalizzato supera l'80% lo considera la fine della sillaba pronunciata */ printf("Percentuale silenzio: %d%%\n", percent_silenzio = 100 * silenzio / NSAMPLE); if (percent_silenzio > 80) { sample.end = ptr - sample.data; break; } } if (!sample.end) fprintf(stderr, "\nNon stato possibile riconoscere la fine della parola.\n"); } while (!sample.begin || !sample.end); /* Aggiunge la nuova sillaba al database */ lseek(fd, 0, SEEK_END); if (write(fd, &sample, sizeof(sample)) == -1) err_quit(); } Lo struttura del for in tutto e per tutto simile a quella analizzata in precedenza, pertanto valgono le stesse considerazioni fatte poco sopra. Questa volta il for viene utilizzato per trovare il blocco di campioni che rappresentano la fine della sillaba all'interno della traccia audio. Il costrutto do-while verifica che l'inizio e la fine sillaba siano state riconosciute, in caso contrario l'intera procedura verr ripetuta. Infine, la nuova sillaba viene inserita alla fine del database in modo tale che possa essere utilizzata in futuro, tale funzione costituisce l'elemento base per l'apprendimento del programma. Facciamo ora un breve salto indietro e torniamo ad analizzare la terza parte della funzione main(): int main(int argc, char **argv) { int dev, fd, i; char buff[MAXLEN], temp[MAXLEN], *parola, *sillaba; . . . . for (;;) { . . . . for (parola = strtok(buff, " "); parola != NULL; parola = strtok(NULL, " ")) { /* Divide in sillabe la parola */ for (i = 1; dividi_sillabe(parola, &sillaba, i); i++) { /* Riproduce la sillaba contenuta nel database */ if (pronuncia(dev, sillaba, fd) == -1) fprintf(stderr, "Sillaba sconosciuta\n"); free(sillaba); } usleep(300000); } } } Viene richiamata ciclicamente strtok() esattamente come avveniva per la seconda parte di main(). L'intera frase viene cos divisa in parole che vengono successivamente divise in sillabe. Per ogni sillaba ottenuta viene richiamata la funzione pronuncia() che legge il database e provvede a riprodurre la traccia relativa a tale sillaba. Vediamo come si presenta la funzione pronuncia(): int pronuncia(int dev, char *sillaba, int fd) { Audio sample; lseek(fd, 0, SEEK_SET); memset(&sample, 0, sizeof(sample)); /* Scorre l'intero database alla ricerca della sillaba */ while (read(fd, &sample, sizeof(sample)) > 0) /* Se la sillaba viene trovata la riproduce */ if (strcmp(sample.sillaba, sillaba) == 0) { write(dev, sample.data + sample.begin, sample.end - sample.begin); return 1; } return -1; } La funzione ricerca la sillaba passata come argomento tra quelle memorizzate nel database, se la sillaba viene trovata viene riprodotta scrivendo sul device /dev/dsp. Notare che la porzione della traccia audio che viene riprodotta esclusivamente quella tra sample.begin e sample.end che sono state inizializzate in memorizza_sillaba(). In questo modo il programma riproduce esclusivamente la parte parlata della traccia audio e salta la parte che contiene solo rumore di fondo. 3. Codice sorgente /* * grillo_parlante.c: Text-To-Speech synthesizer that read text aloud. * * * Copyright (c) 2003, eazy * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the * distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH * DAMAGE. * */ #include #include #include #include #include #include #include #include #include #include #define DSP "/dev/dsp" #define DB ".dizionario" #define MIN 120 #define MAX 140 #define BITS 8 #define CHANNELS 1 #define RATE 8000 #define SECONDS 3 #define SAMPLE_LEN BITS / 8 * CHANNELS * RATE * SECONDS #define MASK 0xfc #define MAXLEN 255 #define NSAMPLE 500 #define LEN 8 #define SCREEN_LEN 80 int signo = 0, min = 255, max = 0; typedef struct { int begin, end; char sillaba[10]; unsigned char data[SAMPLE_LEN]; } Audio; void err_quit(void) { perror("error"); exit(-1); } void sigint(int sig) { signo = 1; } int consonante(char lettera) { int i; char vocale[5] = {'a', 'e', 'i', 'o', 'u'}; for (i = 0; i < 5; i++) if (lettera == vocale[i]) return 0; return 1; } int vocale(char lettera) { int i; char vocale[5] = {'a', 'e', 'i', 'o', 'u'}; for (i = 0; i < 5; i++) if (lettera == vocale[i]) return 1; return 0; } int doppia(char *lettera) { if (*lettera == 0) return 0; if (*lettera == *(lettera + 1)) return 1; return 0; } int nmlr(char *lettera) { int i; char nmlr[4] = {'n', 'm', 'l', 'r'}; for (i = 0; i < 4; i++) if (*lettera == nmlr[i]) if (consonante(*(lettera + 1))) return 1; return 0; } /* Restituisce la sillaba che si trova nella posizione indicata da pos */ int dividi_sillabe(char *parola, char **sillaba, int pos) { int i; char *ptr, *begin, *end; for (i = 1, ptr = parola; ptr < parola + strlen(parola); i++) { begin = ptr; while (consonante(*ptr)) ptr++; while (vocale(*ptr)) ptr++; if (doppia(ptr)) { ptr++; goto fine; } if (nmlr(ptr)) ptr++; fine: end = ptr; if (i == pos) { *sillaba = calloc(end - begin + 1, sizeof(char)); memcpy(*sillaba, begin, end - begin); return 1; } } return 0; } int trova_sillaba(int fd, char *sillaba) { Audio sample; lseek(fd, 0, SEEK_SET); memset(&sample, 0, sizeof(sample)); while (read(fd, &sample, sizeof(sample)) > 0) if (strcmp(sample.sillaba, sillaba) == 0) return 1; return -1; } void memorizza_sillaba(int dev, char *sillaba, int fd) { Audio sample; int rumore, silenzio, percent_rumore, percent_silenzio; unsigned char *ptr, *ptr2; do { printf("Pronuncia la sillaba %s. Premere INVIO per " "iniziare.\n", sillaba); fgetc(stdin); memset(&sample, 0, sizeof(sample)); strncpy(sample.sillaba, sillaba, 9); if (read(dev, sample.data, SAMPLE_LEN) == -1) err_quit(); for (ptr = sample.data; ptr < sample.data + SAMPLE_LEN; ptr += NSAMPLE) { rumore = 0; /* Analizza un blocco di campioni per vedere la percentuale di rumore in esso contenuta */ for (ptr2 = ptr; ptr2 < ptr + NSAMPLE; ptr2++) if (*ptr2 < min || *ptr2 > max) rumore++; /* Se la percentuale di rumore contenuta nel blocco di campioni analizzato supera il 20% lo considera l'inizio della sillaba pronunciata */ printf("Percentuale rumore: %d%%\n", percent_rumore = 100 * rumore / NSAMPLE); if (percent_rumore > 20) { sample.begin = ptr - sample.data; break; } } if (!sample.begin) fprintf(stderr, "\nNon stato possibile " "riconoscere l'inizio della parola.\n"); for (ptr = sample.data + sample.begin; ptr < sample.data + SAMPLE_LEN; ptr += NSAMPLE){ silenzio = 0; /* Analizza un blocco di campioni per vedere la percentuale di silenzio in esso contenuta */ for (ptr2 = ptr; ptr2 < ptr + NSAMPLE; ptr2++) if (*ptr2 >= min && *ptr2 <= max) silenzio++; /* Se la percentuale di silenzio contenuta nel blocco di campioni analizzato supera l'80% lo considera la fine della sillaba pronunciata */ printf("Percentuale silenzio: %d%%\n", percent_silenzio = 100 * silenzio / NSAMPLE); if (percent_silenzio > 80) { sample.end = ptr - sample.data; break; } } if (!sample.end) fprintf(stderr, "\nNon stato possibile " "riconoscere la fine della parola.\n"); } while (!sample.begin || !sample.end); /* Aggiunge la nuova sillaba al database */ lseek(fd, 0, SEEK_END); if (write(fd, &sample, sizeof(sample)) == -1) err_quit(); } int pronuncia(int dev, char *sillaba, int fd) { Audio sample; lseek(fd, 0, SEEK_SET); memset(&sample, 0, sizeof(sample)); /* Scorre l'intero database alla ricerca della sillaba */ while (read(fd, &sample, sizeof(sample)) > 0) /* Se la sillaba viene trovata la riproduce */ if (strcmp(sample.sillaba, sillaba) == 0) { write(dev, sample.data + sample.begin, sample.end - sample.begin); return 1; } return -1; } unsigned char campiona(int dev) { int n; unsigned char buff[LEN]; if ((n = read(dev, &buff, LEN)) == LEN) return buff[n - 1]; return -1; } /* tnx lesion for this function :) */ void eq(int dev) { int i, l; struct sigaction act, old; act.sa_handler = sigint; sigemptyset(&act.sa_mask); act.sa_flags |= SA_RESTART; if (sigaction(SIGINT, &act, &old) == -1) err_quit(); while (!signo) { l = campiona(dev) * SCREEN_LEN / 255; for (i = 0; i < l; i++) printf("|"); printf("\n"); } signo = 0; if (sigaction(SIGINT, &old, NULL) == -1) err_quit(); } void set_mic(int dev) { int i; printf("Vuoi eseguire una prova del microfono? [y/n]: "); if (fgetc(stdin) == 'y') { printf("\nPer terminare la prova microfono premi " "Ctrl^C "); for (i = 5; i > 0; i--) { printf("%d...", i); fflush(stdout); sleep(1); } printf("\n"); eq(dev); } fgetc(stdin); } void rumore_fondo(int dev) { int i; Audio sample; unsigned char *ptr; do { set_mic(dev); fprintf(stderr, "Il programma procedera' al " "campionamento e alla soppressione del rumore\n" "di fondo. Durante questa fase e' necessario " "rimanere in silenzio.\n" "Premere INVIO per iniziare.\n"); fgetc(stdin); fprintf(stderr, "\nInizio procedura tra "); for (i = 5; i > 0; i--) { printf("%d...", i); fflush(stdout); sleep(1); } printf("silenzio!\n"); memset(&sample, 0, sizeof(sample)); if (read(dev, sample.data, SAMPLE_LEN) == -1) err_quit(); /* Ricerca i picchi massimi e minimi nel rumore di fondo campionato. I valori dei campioni possono oscillare tra 0 e 255 dove 128 equivale alla condizione di totale silenzio */ for (ptr = sample.data; ptr < sample.data + SAMPLE_LEN; ptr++) { *ptr &= MASK; if (*ptr < min) min = *ptr; if (*ptr > max) max = *ptr; } /* Se i picchi massimi e minimi riscontrati nel rumore di fondo si discostano eccessivamente da 128, che equivale alla condizione di totale silenzio, mostra un messaggio d'errore e ripete la procedura */ if (min < MIN || max > MAX) fprintf(stderr, "Operazione non riuscita. Questo" " puo' essere dovuto ad un'errata\n" "configurazione del mixer o ad un eccessivo " "rumore di fondo. Provare\n" "a diminuire il livello del mixer relativo al " "microfono e riprovare.\n"); } while (min < MIN || max > MAX); } void set_device(int dev) { int arg; arg = BITS; if (ioctl(dev, SOUND_PCM_WRITE_BITS, &arg) == -1) err_quit(); if (arg != BITS) { fprintf(stderr, "Unable to set required sample bits " "size\n"); exit(-1); } arg = CHANNELS; if (ioctl(dev, SOUND_PCM_WRITE_CHANNELS, &arg) == -1) err_quit(); if (arg != CHANNELS) { fprintf(stderr, "Unable to set required channels\n"); exit(-1); } arg = RATE; if (ioctl(dev, SOUND_PCM_WRITE_RATE, &arg) == -1) err_quit(); if (arg != RATE) { fprintf(stderr, "Unable to set required sample rate\n"); exit(-1); } } int main(int argc, char **argv) { int dev, fd, i; char buff[MAXLEN], temp[MAXLEN], *parola, *sillaba; if ((dev = open(DSP, O_RDWR)) == -1) err_quit(); if ((fd = open(DB, O_RDWR | O_CREAT, S_IRWXU)) == -1) err_quit(); set_device(dev); rumore_fondo(dev); for (;;) { printf("Scrivi una frase: "); if (fgets(buff, sizeof(buff), stdin) == NULL) err_quit(); buff[strlen(buff) - 1] = 0; memcpy(temp, buff, sizeof(buff)); for (parola = strtok(temp, " "); parola != NULL; parola = strtok(NULL, " ")) /* Divide in sillabe la parola */ for (i = 1; dividi_sillabe(parola, &sillaba, i); i++) { /* Se la sillaba non presente nel database procede alla sua memorizzazione */ if (trova_sillaba(fd, sillaba) == -1) memorizza_sillaba(dev, sillaba, fd); free(sillaba); } for (parola = strtok(buff, " "); parola != NULL; parola = strtok(NULL, " ")) { /* Divide in sillabe la parola */ for (i = 1; dividi_sillabe(parola, &sillaba, i); i++) { /* Riproduce la sillaba contenuta nel database */ if (pronuncia(dev, sillaba, fd) == -1) fprintf(stderr, "Sillaba " "sconosciuta\n"); free(sillaba); } usleep(300000); } } }