Sei sulla pagina 1di 58

UNIVERSIT DEGLI STUDI DI URBINO CARLO BO

Facolt di Scienze e Tecnologie


Corso di Laurea in Informatica Applicata
Tesi di Laurea
IMPLEMENTAZIONE DI UNA ARCHITETTURA
SOFTWARE PER LELABORAZIONE DI
SEGNALI DIGITALI AUDIO IN TEMPO REALE
Relatore: Candidato:
Chiar.mo Prof. Edoardo Bont Giovanni Bedetti
Anno Accademico 2007-2008
Alla mia famiglia
ii
Indice
1 Introduzione 1
1.1 Contesto . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
1.2 Obiettivi . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
1.3 Applicazioni . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
2 Architettura 5
2.1 Struttura dellapplicazione e usso dei dati . . . . . . . . . . . . 5
2.1.1 Struttura . . . . . . . . . . . . . . . . . . . . . . . . . . 5
2.1.2 Flusso dei dati . . . . . . . . . . . . . . . . . . . . . . . 6
2.1.3 Convolver . . . . . . . . . . . . . . . . . . . . . . . . . . 7
2.2 Struttura ideale . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
3 Progettazione, implementazione e test 10
3.1 Use cases . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
3.1.1 UC1 - Input . . . . . . . . . . . . . . . . . . . . . . . . . 11
3.1.2 UC2 - Output . . . . . . . . . . . . . . . . . . . . . . . . 11
3.1.3 UC3 - Filtro . . . . . . . . . . . . . . . . . . . . . . . . . 12
3.1.4 UC4 - Lunghezza segmento . . . . . . . . . . . . . . . . 12
3.1.5 UC5 - Durata . . . . . . . . . . . . . . . . . . . . . . . . 12
3.1.6 UC6 - Volume . . . . . . . . . . . . . . . . . . . . . . . . 12
3.1.7 UC7 - Start . . . . . . . . . . . . . . . . . . . . . . . . . 12
3.2 Classi . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
3.2.1 Test . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
3.2.2 Gui . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
3.2.3 Graphic . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
3.2.4 Filters . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
3.2.5 Errors . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
3.2.6 T1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
3.2.7 MyAudioInputStream . . . . . . . . . . . . . . . . . . . 15
3.2.8 MyAudioOutputStream . . . . . . . . . . . . . . . . . . 16
3.2.9 WaveHeader . . . . . . . . . . . . . . . . . . . . . . . . . 17
3.2.10 Fourier . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
3.3 Algoritmi . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
iii
3.3.1 Trasformata rapida di Fourier e convoluzione . . . . . . 18
3.3.2 Analisi dei risultati . . . . . . . . . . . . . . . . . . . . . 23
4 Uso del programma 26
4.1 Interfaccia graca . . . . . . . . . . . . . . . . . . . . . . . . . . 26
4.1.1 Input File . . . . . . . . . . . . . . . . . . . . . . . . . . 26
4.1.2 Output File . . . . . . . . . . . . . . . . . . . . . . . . . 26
4.1.3 Segment Length . . . . . . . . . . . . . . . . . . . . . . 29
4.1.4 Filter . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
4.1.5 Durata . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
4.1.6 Start . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
4.1.7 Audio In . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
4.1.8 Audio Out . . . . . . . . . . . . . . . . . . . . . . . . . . 30
4.1.9 Volume . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
4.2 Versione batch . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
4.2.1 Utilizzo . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
5 Conclusioni 32
5.1 Lavoro svolto . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
5.2 Sviluppi futuri . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
5.2.1 Thread . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
5.2.2 Generatore di ltri . . . . . . . . . . . . . . . . . . . . . 33
5.2.3 Visualizzazione graca dei ltri . . . . . . . . . . . . . . 33
5.2.4 Visualizzazione graca del segnale . . . . . . . . . . . . 33
5.2.5 Segmentazione con sovrapposizione . . . . . . . . . . . . 34
5.2.6 Formati audio . . . . . . . . . . . . . . . . . . . . . . . . 34
A Complementi di analisi numerica dei segnali 35
A.1 Risposta allimpulso e risposta in frequenza . . . . . . . . . . . 35
A.1.1 Stabilit dei sistemi . . . . . . . . . . . . . . . . . . . . 37
A.1.2 I sistemi a tempo discreto nel dominio delle frequenze . 38
A.2 Z-trasformata e funzione di trasferimento . . . . . . . . . . . . 40
A.2.1 La convoluzione mediante Z-trasformata . . . . . . . . . 41
A.2.2 La Z-trasformata come funzione di trasferimento . . . . 42
A.3 La trasformata discreta di Fourier . . . . . . . . . . . . . . . . . 43
A.4 Convoluzione periodica, circolare e lineare . . . . . . . . . . . . 46
A.5 La trasformata rapida di Fourier (FFT) . . . . . . . . . . . . . 49
Bibliograa 52
Ringraziamenti 53
iv
Elenco delle gure
2.1 Struttura dellapplicazione . . . . . . . . . . . . . . . . . . . . . 6
2.2 Data-ow diagram . . . . . . . . . . . . . . . . . . . . . . . . . 6
2.3 Schema del sistema esteso di elaborazione audio . . . . . . . . . 8
3.1 Use Cases . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
3.2 Diagramma delle classi . . . . . . . . . . . . . . . . . . . . . . . 13
4.1 Finestra principale del programma . . . . . . . . . . . . . . . . 27
4.2 Dialog allavvio del programma per la selezione del le dei ltri 27
4.3 Dialog per la selezione di un le di input . . . . . . . . . . . . . 28
4.4 Dialog per il salvataggio di un le di output . . . . . . . . . . . 28
4.5 Selettore della lunghezza del segmento . . . . . . . . . . . . . . 29
4.6 Selettore di uno dei ltri dal le lters.t . . . . . . . . . . . . . 30
4.7 Selettore della scheda audio . . . . . . . . . . . . . . . . . . . . 30
A.1 Schema della trasformazione ingresso-uscita . . . . . . . . . . . 36
v
Capitolo 1
Introduzione
1.1 Contesto
Lelaborazione numerica dei segnali, meglio conosciuta come DSP, ossia digi-
tal signal processing, si occupa della manipolazione di segnali espressi come
sequenze discrete di numeri o simboli. In altre parole, lelaborazione di segna-
li audio lalterazione intenzionale degli stessi con lobiettivo di ottenere un
segnale risultante in uscita dal sistema che eettua lelaborazione.
I segnali audio possono essere rappresentati in formato digitale o analogi-
co. Processare laudio nel dominio analogico signica operare direttamente sul
segnale elettrico, grazie ad opportuni circuiti. Lelaborazione digitale, invece,
lavora matematicamente sulla rappresentazione binaria dei suddetti segnali,
facendo svolgere i calcoli ad un elaboratore elettronico.
Luomo in grado di percepire stimoli acustici (onde di pressione che si
propagano attraverso laria) che vanno in frequenza dai 20 Hz ai 20.000 Hz.
Tali stimoli possono essere convertiti nel dominio discreto grazie al processo
di campionamento. A partire da un segnale continuo nel tempo (o meglio,
unonda sonora) catturato, ad esempio, da un microfono, si pu ottenere una
rappresentazione discreta di tale segnale valutandone lampiezza a intervalli di
tempo regolari (cio a una frequenza di campionamento). Quantizzando oppor-
tunamente il valore di ampiezza di ogni campione (ricavandone, in sostanza, un
valore numerico intero approssimante) possibile ottenere una stringa binaria
che esprime il segnale continuo originario. Si pu dire quindi che la rappresen-
tazione digitale esprime la pressione della forma donda come una sequenza di
simboli, generalmente binari. Ci permette di utilizzare, come gi accennato,
microprocessori ed elaboratori elettronici per eettuare i calcoli.
Un importante processo di elaborazione il ltraggio. Esistono varie ca-
tegorie di ltri, ma per i nostri propositi considereremo ltri lineari tempo-
invarianti.
Un ltro lineare una trasformazione lineare dei campioni in ingresso. I
ltri lineari soddisfano il principio di sovrapposizione, ovvero se linput una
1.2 Obiettivi 2
combinazione lineare di vari segnali, loutput allo stesso modo una combina-
zione lineare dei corrispondenti, o meglio, loutput prodotto dalla somma di
pi fattori uguale alla somma degli output che i fattori avrebbero prodotto
se considerati singolarmente. Un ltro detto tempo-invariante se mantie-
ne propriet costanti nel tempo. I ltri lineari tempo-invarianti soddisfano
ovviamente tutte queste propriet.
La sequenza di input ottenuta dal campionamento si trova inevitabilmente
nel dominio del tempo, mentre per eettuare loperazione di ltraggio avremo
bisogno di convertire il segnale in input e il ltro che vogliamo applicare nel
dominio delle frequenze. Lapplicazione del ltro al segnale in questo dominio
si ottiene con unoperazione di ordine O(nlog n) su segmenti di lunghezza
n (normalmente pari a 512, 1024, 2048 campioni) del segnale di input, quindi
sucientemente rapida per poter essere utilizzata in tempo reale. Lo strumento
matematico che viene utilizzato per tale trasformazione la trasformata di
Fourier (vedi Appendice A). Essa converte le informazioni del segnale in coppie
ampiezza-fase per ogni frequenza. Nella teoria dei segnali, la trasformata di
Fourier lo strumento che permette di scomporre un segnale generico in una
somma di sinusoidi con frequenze, ampiezze e fasi diverse. Se il segnale in
oggetto un segnale periodico, la sua trasformata di Fourier un insieme
discreto di valori (spettro discreto, o a pettine): la frequenza pi bassa
detta fondamentale ed pari a quella del segnale stesso mentre tutte le altre
frequenze sono multipli della fondamentale e prendono il nome di armoniche.
Se il segnale ha un valor medio diverso da zero la serie restituisce anche una
componente costante che lo rappresenta.
Lapplicazione del ltro viene eettuata grazie alloperazione di convoluzio-
ne: una volta ottenuta la sequenza da elaborare e la sequenza che descrive il
ltro, esse verranno convolute, ossia moltiplicate nel dominio della frequenza.
La sequenza risultante verr ritrasformata nel dominio dei tempi grazie alla
trasformata di Fourier inversa, completando cos il processo.
1.2 Obiettivi
Lo scopo di questo lavoro creare una architettura software essibile, modula-
re, in grado di eettuare loperazione di ltraggio di un segnale in ingresso in un
tempo reale stretto. Il linguaggio di programmazione scelto Java. Il principa-
le pregio della tecnologia Java quello di consentire la creazione di applicazioni
indipendenti dalla piattaforma. Inoltre permette di creare software robusto,
ha introdotto una notevole sicurezza grazie al runtime environment, possiede
degli strati di astrazione in pi rispetto, ad esempio, al C++, e implementa una
serie di automatismi, come il garbage collector, che fanno risparmiare tempo
ed errori in fase di sviluppo dei programmi. Tutti i suoi pregi, per, inevi-
tabilmente introducono dei limiti in termini di performance, soprattutto per
1.3 Applicazioni 3
quanto concerne le applicazioni di tipo CPU intensive come quelle in ambito
DSP.
Il software creato dovr rendere possibile, grazie anche allo sviluppo di
unopportuna interfaccia graca, la scelta dello stream di input e di quello di
output. Verranno considerati come casi base linput/output da/a le e da/a
scheda audio, trascurando linput/output da/verso url. Sia in entrata che in
uscita potranno essere utilizzati formati audio di vario tipo, quali Wave (WA-
VEform audio format) e Ai (Apple/Audio Interchange File Format). Non
vengono considerati formati compressi come lmp3 perch prevedono lutilizzo
di algoritmi di compressione soggetti a copyright. Altre caratteristiche fonda-
mentali del codice prodotto saranno la riusabilit e la modularit: ci permette
di migliorare la manutenibilit del codice stesso e facilita implementazioni fu-
ture. Il usso di esecuzione nel corrente progetto sar sequenziale, ma i moduli
potranno essere associati a dierenti thread, costituendo cos architetture soft-
ware concorrenti. Tali architetture sono caratterizzate da pi ussi sequenziali
di istruzioni che eseguono parallelamente e possono interagire gli uni con gli
altri. Un thread un usso di esecuzione, non necessariamente legato ad uno
spazio di indirizzamento privato, che fa riferimento ad una porzione di codice
del programma; il processo principale del programma pu contenere diversi th-
read, i quali possono condividere lo spazio di indirizzamento e quindi scambiarsi
dei messaggi.
1.3 Applicazioni
Il ltraggio mediante convoluzione permette un campo molto vasto di possi-
bili risultati: nel nostro caso lelaborazione pu portare a una equalizzazio-
ne del segnale, cio lenfatizzazione o lattenuazione di determinate bande di
frequenze.
La rimozione di alcune componenti del segnale alla base della sintesi
sottrattiva, largamente utilizzata nei primi sintetizzatori portatili quali Moog e
ARP, alla ne degli anni 70. La sintesi sottrattiva in eetti un meccanismo
relativamente semplice per ottenere suoni interessanti, e grazie ai ltri risonanti
lo spettro risultante pu raggiungere una complessit dicile da ottenere con la
sintesi additiva, molto pi costosa in quanto computazionalmente pi esigente.
Si possono inoltre ottenere eetti di vario tipo, di cui il pi rilevante
sicuramente il riverbero basato sulla convoluzione: un processo per simulare
digitalmente la riverberazione di uno spazio sico o virtuale. Per ottenerlo
avremo bisogno di un ltro che descriva la risposta impulsiva dello spazio che
si vuole modellare.
Unaltra applicazione molto interessante e utilizzata nellambito degli spet-
tacoli dal vivo lequalizzazione adattiva: dati una sorgente acustica e un
ascoltatore, il segnale che viene percepito da questultimo aetto dal contri-
buto che lambiente in cui essi si trovano conferisce al segnale originale. Anche
1.3 Applicazioni 4
se normalmente ci tollerato, possibile fare in modo che ci che lascolta-
tore sente sia esattamente il segnale originale. Questo si ottiene compensando
il contributo dellambiente grazie a un ltro che inverte la risposta impulsiva
dellambiente stesso.
Pi in generale, il canale trasmissivo interposto tra sorgente ed ascolta-
tore non costituito soltanto dallambiente sico di propagazione del suono,
ma comprende anche leventuale catena di amplicazione del segnale me-
diante impianti elettronici quali mixer, amplicatore, altoparlanti. Le tecniche
di compensazione possono essere applicate anche allintero canale trasmissi-
vo qualora esso sia riconducibile ad un sistema lineare tempo-invariante (vedi
Appendice A).
Capitolo 2
Architettura
In questo capitolo verr illustrata la struttura dellarchitettura progettata. Ver-
r descritto il processo che permette lacquisizione, lelaborazione e la succes-
siva riproduzione o salvataggio su le dello stream audio scelto. Sar inoltre
delineata la struttura ideale che sar necessario implementare in futuro per
sopperire alle carenze di quella attuale.
2.1 Struttura dellapplicazione e usso dei dati
In questa fase dello sviluppo stata creata una architettura essibile e modu-
lare in cui sono stati implementati:
acquisizione del segnale da scheda audio e da le;
riproduzione del segnale su scheda audio o salvataggio di un le Wave;
algoritmi di elaborazione;
caricamento di le contenenti ltri preesistenti;
interfaccia graca;
versione batch per test da riga di comando.
2.1.1 Struttura
La gura 2.1 descrive la struttura dellapplicazione. Vi sono 3 componenti
principali: input, elaborazione e output. Dal momento che input e output
hanno due alternative di utilizzo ciascuna, emergono 4 combinazioni di utilizzo
del programma:
1. Editor (A - C): il le selezionato viene elaborato e il risultato viene salvato
in un nuovo le.
2.1 Struttura dellapplicazione e usso dei dati 6
Figura 2.1: Struttura dellapplicazione
2. Player (A - D): lo stream proviene da un le, viene elaborato e riprodotto
in tempo reale.
3. Recorder (B - C): lo stream proviene da un ingresso della scheda audio
selezionata, viene elaborato e salvato su le.
4. Real-Time DSP (B - D): lo stream proviene da un ingresso della scheda
audio selezionata, viene elaborato e riprodotto in tempo reale.
2.1.2 Flusso dei dati
La gura 2.2 mostra il usso sequenziale dei dati:
Figura 2.2: Data-ow diagram
Segmentazione
Dopo che lo stream stato acquisito, esso viene spezzato in segmenti la cui
lunghezza pu essere decisa dallutente; la suddivisione pu essere contigua,
cio dove termina un segmento inizia il segmento successivo, oppure si pu far
iniziare il nuovo segmento prima della ne del precedente, cos da ottenere una
miglior qualit complessiva. La versione attuale del software implementa solo
la segmentazione contigua.
2.1 Struttura dellapplicazione e usso dei dati 7
Trasformazione del formato in input
necessario trasformare il formato dello stream per poter applicare gli algorit-
mi ecientemente. A partire da una sequenza di byte (proporzionale alla lun-
ghezza del segmento stabilita) che descrive il formato Wave (8/16 bit, Little/-
Big Endian, mono/stereo) si otterr una sequenza di double (rappresentazione
interna).
2.1.3 Convolver
Il segmento, dopo essere stato trasformato nel dominio della frequenza grazie
allalgoritmo della FFT, viene convoluto con un ltro (ottenuto da un le) an-
chesso nel dominio della frequenza. Il risultato viene ritrasformato nel dominio
dei tempi grazie alla trasformata inversa.
Trasformazione del formato in output
Dopo lapplicazione degli algoritmi al singolo segmento, questo viene riconver-
tito in una sequenza di byte.
Ricomposizione
Il segmento viene viene ricombinato con gli altri gi elaborati, ricreando una se-
quenza continua di byte che pu essere inviata alla scheda audio per la riprodu-
zione oppure salvata in un le Wave, aggiungendo in testa al le le informazioni
(header) che descrivono il formato.
2.2 Struttura ideale 8
2.2 Struttura ideale
Limplementazione ideale del software di elaborazione audio in tempo reale,
come gi accennato, si avvale dellutilizzo dei thread. Possiamo suddividere
concettualmente il processo principale in cinque thread distinti. Due dovranno
gestire linput e loutput dello stream audio, due si occuperanno dellelabo-
razione del segnale e della sintesi del ltro da applicargli e inne un thread
di controllo dar la possibilit allutente di settare i parametri a suo piaci-
mento mentre lapplicazione in esecuzione. Questi thread saranno attivi dal
momento del lancio dellapplicazione no al momento della sua chiusura, ed
eseguiranno indipendentemente, interagendo luno con laltro come descritto
dalla gura 2.3. Lo scopo nale dellarchitettura descritta [1] acquisire e ri-
Figura 2.3: Schema del sistema esteso di elaborazione audio
produrre uno stream audio e permettere allutente di cambiare in tempo reale
gli eetti da applicare allo stream. Lo stream audio, ottenuto da un le op-
pure dalla scheda audio (Input, I), viene passato sotto forma di segmenti di
lunghezza nita al Sound Processor (SP), che in base ai parametri descritti
dallutente tramite interfaccia (Console, C) inoltra un descrittore con le carat-
teristiche delleetto da ottenere al generatore di ltri (Eect Generator, EG);
questultimo genera il ltro e lo invia al SP, il quale eettua lelaborazione e
inoltra il segmento processato allOutput, O. Lutente ha la possibilit di far
cominciare o fermare lo streaming in ogni momento, inoltrando uno start o uno
stop allinput. Quando I riceve un messaggio di end of stream, eos, lo inoltra
al SP che a sua volta lo passa ad O, il quale interrompe la riproduzione; essa
potr riprendere quando O ricever unaltra serie di segmenti processati.
Per fare in modo che il sistema mantenga una suciente denizione e non
introduca rumore o ritardo udibile tra lelaborazione di un segmento e il suc-
cessivo, sia I che O dovranno implementare un buer interno. Quando I viene
avviato il buer relativo comincia a riempirsi proporzionalmente alla frequenza
di campionamento della scheda audio o del le di input. Dal buer verr letto
2.2 Struttura ideale 9
un segmento composto da un numero dato di campioni. Questa operazione non
sar eseguita se nel buer c un numero di campioni minore della lunghezza
del segmento. In caso contrario, se non c richiesta di input, il buer saturer
e i campioni pi vecchi saranno sovrascritti.
Parallelamente, il buer in output dovr svuotarsi proporzionalmente alla
frequenza di campionamento della scheda audio o del le di output. Nel buer
verr scritto un segmento composto da un certo numero di campioni processati.
Loperazione di output si bloccher se tenter di scrivere un segmento che
pi lungo delle capacit residue del buer di uscita.
Dal momento che le due istruzioni per far interagire ingresso e uscita sono
bloccanti, i thread che li gestiscono dovranno essere mantenuti sincronizzati
alla stessa frequenza di campionamento, e questa frequenza non deve essere in
alcun modo alterata dal SP. La sincronizzazione dei due thread possibile se il
tempo di processamento del segmento, quindi il tempo per applicare un eetto
a un segmento e di altre operazioni come la gestione della creazione degli eetti,
minore del tempo di playout del segmento. Perci, dobbligo introdurre un
certo delay prima di mandare in playout il primo segmento richiesto, per poter
compensare il rallentamento dovuto al tempo di processamento dei segmenti
successivi.
Capitolo 3
Progettazione, implementazione
e test
In questo capitolo si passa dalla fase di progettazione e analisi alla implementa-
zione del codice e alla successiva verica dei risultati. Prima di tutto verranno
descritti i possibili casi duso dellapplicazione. Essi forniscono una rappresen-
tazione chiara delle possibili interazioni di un utente con linterfaccia graca
del programma. Saranno poi elencate ed analizzate le classi create, illustrando
le relazioni che incorrono tra esse. Da ultimo verr fornita una spiegazione
del funzionamento degli algoritmi che eettuano lelaborazione, quello per la
trasformata rapida di Fourier e quello per la convoluzione. Saranno eettuati
dei test su di essi per vericare che la manipolazione dei dati sia corretta.
3.1 Use cases
Illustriamo grazie ai casi duso le operazioni che lutente pu eettuare tramite
interfaccia graca. Non necessariamente devono essere eseguite tutte, n in
questo ordine. Una volta selezionati linput e loutput possibile infatti inizia-
re lesecuzione (start) utilizzando i parametri di default. Il programma potr
essere eseguito in due modalit: batch, quindi da riga di comando con pas-
saggio di parametri, e da interfaccia graca. La versione batch stata creata
pi che altro per eettuare rapidamente dei test con dei parametri preceden-
temente selezionati e salvati. Dal momento che non user-friendly (necessita
del passaggio di 7 parametri per eseguire), descriviamo la possibile interazione
dellutente con linterfaccia. In ogni caso la versione batch e la versione con
interfaccia non sono due versioni distinte, in quanto condividono la totalit del
codice. Per la precisione, la versione GUI utilizza la versione batch passandogli
i parametri selezionati dallutente.
3.1 Use cases 11
Figura 3.1: Use Cases
3.1.1 UC1 - Input
Ammettiamo che lapplicazione sia stata lanciata e linterfaccia graca sia stata
visualizzata. Non possibile far partire lesecuzione prima di aver selezionato
input e output del programma. Lutente seleziona una delle due modalit di
input: da le o da scheda audio. Nel caso scelga il le viene aperto il dialog
per la selezione di un le audio preesistente. Se il le non viene selezionato la
selezione dellinput rimane in sospeso, e non sar possibile riprodurre alcun-
ch. Se lutente seleziona la scheda audio, pu scegliere quale periferica vuole
utilizzare come ingresso. Schede audio con ingressi multipli appariranno come
selezioni diverse. Se lutente deseleziona la scelta fatta, come gi detto non
sar possibile dare lo start alla riproduzione.
3.1.2 UC2 - Output
Ci che stato appena descritto vale anche per la scelta delloutput. Quando
sono stati selezionati sia input che output il tasto start si abilita ed possibile
iniziare la riproduzione con i parametri di default. Se uno dei due o entrambi
vengono deselezionati, ovviamente il tasto start si disabilita.
3.1 Use cases 12
3.1.3 UC3 - Filtro
La selezione del ltro avviene staticamente prima dellesecuzione, non cio
ancora stata implementata la possibilit di cambiare il ltro e i suoi parametri
durante lesecuzione. Il caricamento dei ltri viene eettuato allavvio del pro-
gramma, quando esso cerca il le contenente i ltri nella cartella corrente. Se
non lo trova mostra un dialog chiedendo di selezionarne uno. Ovviamente non
possibile iniziare lesecuzione prima di aver scelto un ltro dal le selezionato.
Se non viene selezionato alcun le il programma termina.
3.1.4 UC4 - Lunghezza segmento
La scelta della lunghezza del segmento in cui spezzare lo stream in ingresso,
determina, combinata alla lunghezza del ltro selezionato, la dimensione del-
larray su cui verranno applicati gli algoritmi di elaborazione. Verr spiegato in
seguito il meccanismo in questione. Se non viene selezionato alcuna lunghezza
dallutente, viene utilizzata quella di default.
3.1.5 UC5 - Durata
Dal momento che nella versione corrente non sono ancora stati implementati i
thread che gestiscono lo start/stop e lelaborazione dinamica, avremo bisogno
di selezionare una durata della porzione di stream che vogliamo elaborare. Nel
caso dellinput da le, ovviamente la durata selezionata non pu superare la
sua durata massima. Quindi anche se il valore selezionato superiore a tale
durata, lelaborazione terminer appena raggiunta la ne del le. Se la durata
non viene selezionata verr utilizzato un valore di default.
3.1.6 UC6 - Volume
Anche il volume non gestito dinamicamente, quindi pu essere stabilito solo
prima di iniziare lelaborazione. da notare che il volume del segnale potrebbe
essere controllato anche tramite il solo ltro, moltiplicando i suoi valori per un
valore compreso tra 0 e 1. Per ora il volume gestito tramite un moltiplicatore
sul segmento in input.
3.1.7 UC7 - Start
Alla pressione del tasto start inizia lelaborazione dello stream selezionato e il
relativo playout/salvataggio su le. Lelaborazione termina dopo gli x secondi
stabiliti oppure nel caso in cui venga raggiunta la ne dello stream.
3.2 Classi 13
3.2 Classi
Entriamo ora pi nello specico descrivendo le classi create. Le classi princi-
pali sono quelle che gestiscono input (MyAudioInputStream) e output (MyAu-
dioOutputStream) e quella con gli algoritmi che elaborano il segnale (Fourier).
Per il caricamento dei ltri stata creata una classe apposita (Filters). Stesso
discorso vale per linterfaccia graca (Graphic), che verr invocata da una clas-
se (Gui) che contiene la funzione main. In contrapposizione a ci avremo una
classe (Test), contenente anchessa una funzione main, che gestisce lesecuzione
da riga di comando. Graphic e Test oltre ad utilizzare la classe Filters e una
classe (Errors) per la gestione degli errori, utilizzeranno metodi di una classe
intermedia (T1) per la gestione di input, output ed elaborazione. Per rendere
possibile il salvataggio su un le Wave dello stream acquisito stato necessario
creare una classe (Waveheader) che potesse creare un header allo stream in
output.
Vediamo ora una rappresentazione delle classi appena descritte e delle loro
dipendenze. I punti di ingresso del programma sono la classe Test e la classe
Gui.
Figura 3.2: Diagramma delle classi
3.2 Classi 14
3.2.1 Test
Questa classe stata creata per eettuare dei test prima della creazione dellin-
terfaccia graca. Contiene quindi il metodo main per lanciare lapplicazione.
Lancia delle eccezioni nel caso in cui:
non trovi i le che vengono inseriti;
non supporti il le audio scelto;
si verichino problemi di lettura o scrittura (IO)
non riesce ad ottenere linput o loutput da scheda audio.
Il metodo main non fa altro che chiamare un metodo statico (lo stesso che verr
utilizzato dalla versione con interfaccia graca) che accetta come parametri un
array di stringhe. Le eccezioni di questo metodo sono ovviamente quelle appena
descritte. In base ai valori passati come parametri carica un ltro tramite la
classe Filters e chiama uno dei 4 metodi statici della classe T1:
da le a le;
da le a scheda audio;
da scheda audio a le;
da scheda audio a scheda audio.
Restituisce un errore nel caso in cui i parametri che riceve sono errati.
3.2.2 Gui
La classe Gui contiene il metodo main che crea un oggetto di tipo Graphic,
facendo partire linterfaccia graca.
3.2.3 Graphic
Questa classe implementa linterfaccia graca. Utilizza gli ActionListener e gli
ItemListener per reagire alle selezioni eettuate dallutente. Allavvio cerca un
le dei ltri (estensione .t) nella cartella corrente e se lo trova carica con il
relativo metodo statico della classe Filters i ltri in esso contenuti; altrimenti
chiede allutente di sceglierne uno aprendo un dialog. Chiama in seguito il
metodo statico della classe Test passandogli come parametri le selezioni attuali
in un array di stringhe.
3.2 Classi 15
3.2.4 Filters
la classe che si occupa della gestione del le dei ltri. Ha un costruttore
protetto che viene chiamato dal metodo statico lterInit che accetta come pa-
rametro la stringa contenente il percorso del le di ltri da caricare. Contiene
vari metodi per maneggiare il le e il suo contenuto, tra cui metodi per conosce-
re il numero di ltri contenuti nel le, per la selezione di un ltro e per relativa
stampa a video, oltre che il metodo per eettuare il padding del ltro corrente
per poter eettuare la convoluzione. In caso non trovi il le selezionato lancia
una eccezione.
3.2.5 Errors
Contiene i messaggi di errore da visualizzare in caso di eccezioni generate dal
programma. Viene utilizzata solo dalle classi Test e Graphic.
3.2.6 T1
Questa la classe che si occupa delle chiamate (tramite i 4 metodi descritti nella
classe Test) alle classi che gestiscono linput (MyAudioInputStream) e loutput
(MyAudioOutputStream) e alla classe che eettua lelaborazione (Fourier). Im-
plementa i metodi per testare input e output cos, in caso venga chiamata dalla
classe Graphic, mostra come selezionabili solo ingressi/uscite che non lanciano
eccezioni. Al suo interno denito il formato audio di default:
frequenza di campionamento: 44100.0 (oating point);
dimensione in bit del singolo campione: 16bit;
canali: 1, mono;
con segno (da 2
16
a 2
16
1);
notazione: little endian.
3.2.7 MyAudioInputStream
Questa classe estende la classe Java AudioInputStream e possiede due costrut-
tori protetti (uno per linput da le e laltro per linput da scheda audio). Essi
vengono chiamati da altrettanti metodi pubblici statici. Nel caso dellinput da
le il metodo accetta come parametri la stringa contenente il percorso del le
audio e la lunghezza dei segmenti in cui lo si vuole dividere. Viene creato un
nuovo oggetto di tipo File passandogli il percorso e tale le viene utilizzato
per ottenere un oggetto di tipo AudioInputStream dalla classe AudioSystem.
Questo oggetto verr passato come parametro al costruttore relativo assieme
alla lunghezza del segmento.
3.2 Classi 16
Tale procedimento ci permette di sfruttare lereditariet per creare un og-
getto di tipo MyAudioIntputStream che potr essere gestito in seguito indipen-
dentemente dalla fonte da cui esso stato creato. Infatti la classe AudioInpu-
tStream ha un costruttore che richiede un InputStream, che una superclasse
di AudioInputStream, e quindi vale per la lettura da le; ha per anche un
costruttore che accetta un oggetto di tipo TargetDataLine che potr essere
creato a partire dal numero di mixer del sistema passato come parametro dal
secondo metodo statico.
I due costruttori inizializzano un campo bArr, un array di byte avente
lunghezza doppia rispetto a quella del segmento passato come parametro. In
bArr saranno scritti i dati letti dallo stream di input. Il passo successivo
quello di convertire i dati dello stream di input, che sono byte, in double.
Questo perch le successive elaborazioni si avvantaggiano della precisione di
tale formato.
Possiamo quindi ora ridenire i metodi start, stop e close della classe Au-
dioInputStream cos che possano essere utilizzati indipendentemente dalla fonte
di input. Il meccanismo di divisione in segmenti dello stream implementato
nel metodo readDouble, che accetta come parametro un array di double della
dimensione precedentemente stabilita dallutente. Il metodo legge da bArr,
converte quanto letto in double e lo scrive nellarray di double.
3.2.8 MyAudioOutputStream
Questa classe concettualmente identica a MyAudioInputStream. Implementa
anchessa due costruttori protetti chiamati da due metodi pubblici statici. La
dierenza tra le due classi dovuta alla mancanza di una superclasse da cui
MyAudioOutputStream possa ereditare.
I due costruttori si occupano della creazione/inizializzazione degli ogget-
ti necessari per eettuare un salvataggio su le o per riprodurre i segmenti
processati tramite scheda audio. Entrambi comunque, allo stesso modo di
MyAudioInputStream, inizializzano un array di byte su cui verranno scritti i
dati, una volta convertiti da double a byte, grazie a un metodo apposito.
Il processo di scrittura in output ha dovuto tener conto del fatto che nel
momento in cui iniziamo la scrittura dello stream per destinarlo ad un le,
non sappiamo ancora quanto lungo sar lo stream. Dal momento che lheader
contiene tra le sue informazioni anche la lunghezza totale del le, esso non
pu essere scritto allinizio del processo di scrittura. Perci si dovuto creare
una classe (WaveHeader) che implementasse la scrittura dellheader del le una
volta che si raggiunge la ne dello stream. In pratica per lheader viene scritto
gi al momento della chiamata al costruttore e poi, una volta raggiunta la ne
dello stream, viene aggiornata solo linformazione sulla lunghezza del le.
Unaltra questione di cui si dovuto tener conto stata la gestione dei
delay conseguenti alla suddivisione dello stream di input in segmenti e alla
3.3 Algoritmi 17
loro elaborazione. stato quindi implementato un buer per la riproduzio-
ne su scheda audio che comincia a riprodurre segnale solo una volta che ha
accumulato un numero suciente di segmenti gi processati.
3.2.9 WaveHeader
Come gi accennato nel paragrafo precedente, lintento di questa classe crea-
re un header Wave per permettere il salvataggio di un le in tale formato.
Lheader di un le in tale formato occupa i primi 44 byte del le, e contiene
informazioni sul formato stesso, sulla dimensione del le, sul numero dei canali,
sulla frequenza di campionamento, sui byte medi per secondo, sui bit per cam-
pione. Dal momento che queste informazioni devono essere scritte sullheader
in diversi tipi di dati (int, byte, short) sono state implementate funzioni a tale
scopo. Quando viene chiamato il metodo close della classe MyAudioOutpu-
tStream si scrive il contenuto del le creato in precedenza, il cui header non
ha linformazione corretta sulla sua lunghezza, in opportune strutture dati e si
modica solo la porzione dellheader relativa a tale informazione.
3.2.10 Fourier
Introduciamo ora la classe che contiene gli algoritmi necessari per eettuare le-
laborazione del segnale. I principali sono limplementazione della trasformata
rapida di Fourier e della convoluzione. La classe si compone di soli meto-
di statici, chiamati dalla classe T1. Rimandiamo alla prossima sezione una
spiegazione approfondita del codice relativo.
3.3 Algoritmi
Fino alla met degli anni 60, la quantit di calcolo ritenuta necessaria per
ottenere la trasformata discreta di Fourier su N punti era di ordine O(N
2
) [2].
Grazie al lavoro di J.W. Cooley e J.W. Tukey, divenne nota lesistenza di un
algoritmo chiamato trasformata rapida di Fourier, o FFT, che pu essere cal-
colato in O(N log
2
N) operazioni. La dierenza tra N log
2
N e N
2
immensa.
Se N = 10
6
, ad esempio, la dierenza tra circa 30 secondi di CPU time e 2
settimane di CPU time su un computer con tempo di clock in microsecondi. Si
sa ora per che metodi ecienti per calcolare la DFT erano stati scoperti indi-
pendentemente, e in qualche caso anche implementati, da almeno una dozzina
di studiosi, a partire da Gauss nel 1805.
Una riscoperta della FFT, quella di Danielson e Lanczos nel 1942, fornisce
una delle pi chiare derivazioni dellalgoritmo. Danielson e Lanczos mostrarono
che una trasformata discreta di Fourier di lunghezza N pu essere riscritta come
somma di due trasformate di Fourier discrete, ciascuna di lunghezza N/2, in cui
una formata dai punti di posizione pari e laltra da quelli di posizione dispari
3.3 Algoritmi 18
rispetto agli N punti originali. Questo assunto, detto lemma di Danielson-
Lanczos, ha dalla sua il fatto che pu essere utilizzato ricorsivamente: una
volta divisa la prima trasformata di N punti in due da N/2 punti, possiamo
dividere ciascuna di queste in due da N/4 punti e cos via.
Sebbene ci sia modo di trattare altri casi, il pi semplice quello in cui lN
originale una potenza del 2. Di fatto, la FFT viene applicata solo con N che
sono potenze del due. Se la lunghezza del segmento non fosse una potenza del
2, si possono aggiungere degli 0 in coda al segmento no a raggiungere una
dimensione potenza del 2.
Con questa restrizione, evidente che si pu continuare ad applicare il
Lemma di Danielson-Lanczos nch il segmento non stato diviso no ad
arrivare a trasformate di lunghezza 1. La trasformata di Fourier di lunghezza
1 di fatto loperazione identit, cio copia il numero in input nel relativo slot
in output.
3.3.1 Trasformata rapida di Fourier e convoluzione
Lalgoritmo seguente rimpiazza il contenuto dellarray data[0...2*nn-1] con
la sua trasformata di Fourier discreta, se isign 1, oppure da nn volte la sua
trasformata inversa se isign -1. data un array complesso di lunghezza
nn o, equivalentemente, un array reale di lunghezza 2*nn. nn deve essere una
potenza del due, quindi sar implementato un metodo a monte che controlli la
dimensione dellarray in input.
protected static void swap (double a[], int index1 , int index2) {
double temp = a[index1 ];
a[index1] = a[index2 ];
a[index2] = temp ;
}
protected static void four1(double data [], int nn , int isign) {
int n, mmax , m, j, istep , i;
double wtemp , wr , wpr , wpi , wi , theta , tempr , tempi;
n = nn * 2;
j = 0;
Questa la sezione che inverte i bit. Il metodo swap scambia tra loro due
numeri complessi.
for (i = 0; i < n - 1; i += 2) {
if (j > i) {
swap (data , j, i);
swap (data , j + 1, i + 1);
}
m = nn;
while ((m >= 2) && (j > m - 1)) {
j -= m;
m /= 2;
}
j += m;
3.3 Algoritmi 19
}
Qui inizia la sezione di Danielson-Lanczos.
mmax = 2;
while (n > mmax ) {
istep = mmax * 2;
Qui viene inizializzata la ricorrenza trigonometrica theta.
theta = -isign * (2 * Math .PI / mmax );
wtemp = Math.sin (0.5 * theta);
wpr = -2.0 * wtemp * wtemp;
wpi = Math .sin(theta);
wr = 1.0;
wi = 0.0;
Ecco i due loop innestati per scorrere larray.
for (m = 0; m < mmax - 1; m += 2) {
Questa la formula di Danielson-Lanczos:
for (i = m; i < n; i += istep) {
j = i + mmax ;
tempr = wr * data [j] - wi * data [j+1];
tempi = wr * data [j+1] + wi * data [j];
data [j] = data [i] - tempr;
data [j+1] = data [i+1] - tempi;
data [i] += tempr;
data [i+1] += tempi;
}
Ricorrenza trigonometrica.
wr = (wtemp = wr) * wpr - wi * wpi + wr;
wi = wi * wpr + wtemp * wpi + wi;
}
mmax = istep;
}
}
Il seguente algoritmo calcola la trasformata di Fourier di un insieme di n
punti di dati reali. Questi dati sono salvati nellarray data. Essi vengono rim-
piazzati dalla met positiva della frequenza della sua trasformata di Fourier
complessa. Il primo e lultimo componente reale della trasformata vengono
ritornati come elementi data[1] e data[2] rispettivamente. Come per il pre-
cedente, n deve essere una potenza del 2. Questa routine calcola anche la
trasformata inversa di un array di dati complessi se la trasformata di dati
reali (il risultato in questo caso deve essere moltiplicato per 2/n).
protected static void realFour1 (double data [],
int n,
int isign) {
int i, i1 , i2 , i3 , i4 , np1;
double c1 = 0.5, c2 , h1r , h1i , h2r , h2i;
3.3 Algoritmi 20
Qui viene inizializzata la ricorrenza trigonometrica theta.
double wr , wi , wpr , wpi , wtemp , theta = Math .PI / (n / 2);
if (isign == 1) {
c2 = -0.5;
La trasformata diretta qui.
four1(data , n / 2, -1);
}
Altrimenti settiamo i parametri per la trasformata inversa.
else {
c2 = 0.5;
theta = -theta;
}
wtemp = Math .sin (0.5 * theta);
wpr = -2.0 * wtemp * wtemp;
wpi = Math .sin(theta);
wr = 1.0 + wpr;
wi = wpi;
np1 = n + 1;
for (i = 1; i < (n / 4); i++) {
i4 = 1 + (i3 = np1 - (i2 = 1 + (i1 = i + i)));
Qui di seguito vengono separati i dati delle due trasformate separate.
h1r = c1 * (data [i1] + data[i3]);
h1i = c1 * (data [i2] - data[i4]);
h2r = -c2 * (data [i2] + data[i4]);
h2i = c2 * (data [i1] - data[i3]);
Qui vengono ricombinati per formare la vera trasformata dei dati reali originali.
data [i1] = h1r + wr * h2r - wi * h2i;
data [i2] = h1i + wr * h2i + wi * h2r;
data [i3] = h1r - wr * h2r + wi * h2i;
data [i4] = -h1i + wr * h2i + wi * h2r;
La ricorrenza.
wr = (wtemp = wr) * wpr - wi * wpi + wr;
wi = wi * wpr + wtemp * wpi + wi;
}
if (isign == 1) {
Qui vengono ricombinati per formare la vera trasformata dei dati reali originali.
data [0] = (h1r = data [0]) + data [1];
data [1] = h1r - data [1];
}
3.3 Algoritmi 21
else {
data [0] = c1 * (( h1r = data [0]) + data [1]) ;
data [1] = c1 * (h1r - data [1]) ;
Questa la trasformata inversa nel caso in cui isign sia uguale a -1
four1(data , n / 2, 1);
}
}
I seguenti metodi pubblici vengono chiamate dalla classe T1. Entrambe ope-
rano nel dominio dei numeri reali. La prima calcola la trasformata di Fourier
diretta (chiama realFour1 passando 1 al parametro isign) e la seconda quella
inversa, normalizzando larray dei risultati: ogni valore in esso contenuto viene
diviso per la met della lunghezza di n.
public static void realFFT(double data [], int n) {
realFour1 (data , n, 1);
}
public static void realIFFT (double data [], int n) {
realFour1 (data , n, -1);
for (int i = 0; i < n; i++)
data [i] /= n / 2 ;
}
Gli ultimi due metodi sono due versioni diverse dello stesso algoritmo, quello
che eettua la convoluzione. La prima eettua anche lo zero-padding sullarray
dei dati e su quello dei ltri, il secondo suppone che ci venga fatto a priori.
public static void conv (double [] x, double[] y, double [] z) {
int lenZ = z.length;
int i, j, k;
double [] xPadded = new double[lenZ ];
double [] yPadded = new double[lenZ ];
for(i = 0; i < x.length; i++) {
xPadded[i] = x[i];
}
for(i = x.length; i < lenZ ; i++) {
xPadded[i] = 0;
}
for(j = 0; j < y.length; j++) {
yPadded[j] = y[j];
}
for(j = y.length; j < lenZ ; j++) {
yPadded[j]=0;
}
realFFT(xPadded , lenZ );
realFFT(yPadded , lenZ );
z[0] = xPadded [0] * yPadded [0];
z[1] = xPadded [1] * yPadded [1];
for (k = 2; k < lenZ ; k += 2) {
3.3 Algoritmi 22
z[k] = xPadded [k] * yPadded [k]
- xPadded [k + 1] * yPadded [k + 1];
z[k + 1] = xPadded [k + 1] * yPadded [k]
+ xPadded [k] * yPadded [k + 1];
}
realIFFT (z, lenZ );
}
public static double [] convolve (double [] dArr ,
double [] filter ,
double [] z) {
double tempRe = 0, tempIm = 0;
for (int k = 0; k < dArr .length; k++) {
z[k] = dArr[k];
}
for (int j = dArr .length; j < z.length; j++) {
z[j] = 0;
}
Fourier.realFFT (z, z.length);
z[0] *= filter [0];
z[1] *= filter [1];
for (int k = 2; k < z.length; k += 2) {
tempRe = z[k] * filter[k] - z[k+1] * filter[k + 1];
tempIm = z[k + 1] * filter[k] + z[k] * filter[k + 1];
z[k] = tempRe;
z[k + 1] = tempIm;
}
Fourier.realIFFT (z, z.length);
for (int i = 0; i < dArr .length; i++) {
dArr [i] = z[i];
}
return dArr ;
}
3.3 Algoritmi 23
3.3.2 Analisi dei risultati
Eettueremo ora un confronto dei risultati della convoluzione eettuata utiliz-
zando lapplicazione Octave [7] con quelli ottenuti dagli algoritmi creati.
FFT con Octave
octave -3.0.0:1 > x = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0]
x =
1 2 3 4 5 6 7 8
octave -3.0.0:4 > y = fft (x)
y =
Columns 1 through 3:
36.0000 + 0.0000i -4.0000 + 9.6569i -4.0000 + 4.0000i
Columns 4 through 6:
-4.0000 + 1.6569i -4.0000 + 0.0000i -4.0000 - 1.6569i
Columns 7 and 8:
-4.0000 - 4.0000i -4.0000 - 9.6569i
octave -3.0.0:5 > z = ifft (y)
z =
1 2 3 4 5 6 7 8
FFT con gli algoritmi sviluppati
Segmento
-------------------
[0]: 1.0
[1]: 2.0
[2]: 3.0
[3]: 4.0
[4]: 5.0
[5]: 6.0
[6]: 7.0
[7]: 8.0
Segmento trasformato
-------------------
[0]: 36.0
[1]: -4.0
[2]: -8.137071184544089
[3]: 25.136697460629243
[4]: -4.0
[5]: -9.656854249492378
3.3 Algoritmi 24
[6]: 3.3800855955782265
[7]: 7.48302881332744
[8]: -4.0
[9]: -4.0
[10]: 4.276768653914156
[11]: 3.340893189596493
[12]: -4.0
[13]: -1.6568542494923806
[14]: 4.480216935051706
[15]: 0.9945618368982867
Segmento antitrasformato
-------------------
[0]: 1.0
[1]: 1.9999999999999996
[2]: 3.0000000000000004
[3]: 4.000000000000001
[4]: 5.000000000000001
[5]: 5.999999999999999
[6]: 6.999999999999998
[7]: 7.9999999999999964
[8]: 0.0
[9]: 4.440892098500626 E-16
[10]: 8.881784197001252 E-16
[11]: -8.881784197001252 E-16
[12]: -8.881784197001252 E-16
[13]: 8.881784197001252 E-16
[14]: 0.0
[15]: 3.552713678800501 E-15
Convoluzione con Octave
octave -3.0.0:12 > x = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0]
x =
1 2 3 4 5 6 7 8
octave -3.0.0:13 > y = [1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
y =
1 0 0 0 0 0 0 0
octave -3.0.0:22 > conv (x, y)
ans =
1 2 3 4 5 6 7 8 0 0 0 0 0 0 0
Convoluzione con gli algoritmi sviluppati
Segmento
-------------------
[0]: 1.0
[1]: 2.0
[2]: 3.0
3.3 Algoritmi 25
[3]: 4.0
[4]: 5.0
[5]: 6.0
[6]: 7.0
[7]: 8.0
Filtro
-------------------
[0]: 1.0
[1]: 0.0
[2]: 0.0
[3]: 0.0
[4]: 0.0
[5]: 0.0
[6]: 0.0
[7]: 0.0
Filtro trasformato
-------------------
[0]: 1.0
[1]: 1.0
[2]: 1.0
[3]: 0.0
[4]: 1.0
[5]: 0.0
[6]: 1.0
[7]: 0.0
[8]: 1.0
[9]: 0.0
[10]: 1.0
[11]: 0.0
[12]: 1.0
[13]: 0.0
[14]: 1.0
[15]: 0.0
Risultato della convoluzione
-------------------
[0]: 1.0
[1]: 1.9999999999999996
[2]: 3.0000000000000004
[3]: 4.000000000000001
[4]: 5.000000000000001
[5]: 5.999999999999999
[6]: 6.999999999999998
[7]: 7.9999999999999964
Si nota come i risultati ottenuti siano corretti, con una approssimazione della
parte decimale a 10
15
, dovuta allutilizzo di tipi di dato double.
Capitolo 4
Uso del programma
Il programma pu essere lanciato in due modalit:
interfaccia graca
batch
Per compilare il codice sorgente del programma, dopo essersi portati nella
cartella relativa, digitare da terminale:
$ make
4.1 Interfaccia graca
Per lanciare linterfaccia graca, digitare al prompt:
$ java Gui
Linterfaccia graca si presenta come in Figura 4.1. Se allinterno della cartella
dove si trovano le classi non c un le lters.t verr mostrato un dialog che
chiede di selezionarlo, come in Figura 4.1.
4.1.1 Input File
Selezionando il checkbox Input File si apre un dialog per la selezione del le di
input, come si pu vedere nella gura 4.3. La versione corrente del programma
supporta le Wave mono a 16bit con frequenza di campionamento a 44100Hz.
Una volta selezionato il le, lo sfondo della label Input File diventa verde. Lo
slider dei secondi aggiorna la durata massima alla durata del le selezionato.
Se il checkbox viene deselezionato lo sfondo torna grigio.
4.1.2 Output File
Selezionando il checkbox Output File si apre un dialog di salvataggio del le
di output, come si pu vedere nella gura 4.4. Il nome del le deve terminare
con lestensione .wav.
4.1 Interfaccia graca 27
Figura 4.1: Finestra principale del programma
Figura 4.2: Dialog allavvio del programma per la selezione del le dei ltri
4.1 Interfaccia graca 28
Figura 4.3: Dialog per la selezione di un le di input
Figura 4.4: Dialog per il salvataggio di un le di output
4.1 Interfaccia graca 29
4.1.3 Segment Length
La lunghezza dei segmenti in cui suddividere lo stream pu essere selezionato
tra quelli disponibili, come mostrato in gura 4.5.
Figura 4.5: Selettore della lunghezza del segmento
4.1.4 Filter
Pu essere cambiato il le dei ltri utilizzando il pulsante Open Filter File.
Si apre un dialog come nel caso non venga trovato un le dei ltri allavvio
dellapplicazione (Figura 4.2). Viene aggiornato il selettore che mostra i l-
tri contenuti nel le, come in gura 4.6. Le informazioni sul le dei ltri
attualmente caricate vengono mostrate sul terminale.
4.1.5 Durata
La durata dellelaborazione pu essere cambiata grazie allo slider orizzontale
al centro dellinterfaccia. La durata massima di default 60 secondi. Il valore
selezionato di default 10 secondi. Se viene caricato un le la durata massima
viene aggiornata alla durata massima del le, in secondi.
4.1.6 Start
Il pulsante Start si abilita quando sono state selezionati input e output. Se
una di queste viene deselezionata, il bottone si disabilita.
4.1 Interfaccia graca 30
Figura 4.6: Selettore di uno dei ltri dal le lters.t
4.1.7 Audio In
Selezionare questo box signica utilizzare la scheda audio come fonte di input.
Le schede audio supportate dal programma sono elencate nel selettore apposito,
come illustrato nella gura 4.7
Figura 4.7: Selettore della scheda audio
4.1.8 Audio Out
Seleziona la scheda audio come uscita dello stream. Anche in questo caso la
scheda utilizzata quella selezionata tra le elencate.
4.2 Versione batch 31
4.1.9 Volume
Il volume pu essere stabilito solo prima dellinizio della riproduzione, e non
durante. Questa limitazione pu essere superata impiegando pi thread di
controllo (vedi paragrafo 2.2).
4.2 Versione batch
Il programma pu essere utilizzato senza interfaccia graca, scrivendo da ter-
minale
$ java Test
4.2.1 Utilizzo
La sintassi la seguente:
$ java Test <input > <output > <filter file > <filter number >
<segment length > <volume > <duration >
dove:
input: pu essere un numero intero che indica la scheda audio selezionata
in input, oppure un percorso di un le audio in formato Wave.
output: pu essere un numero intero che indica la scheda audio selezionata
in input, oppure un percorso di un le audio in formato Wave.
lter le: se 0 viene utilizzato il le lters.t allinterno della cartel-
la corrente, altrimenti deve essere un percorso di un le dei ltri (con
estensione .t).
lter number: il numero del ltro utilizzato tra quelli presenti nel le
dei ltri caricato.
segment length: indica la lunghezza del segmento in cui dividere lo stream,
deve essere una potenza del 2.
volume: [0-100] il volume del segnale in uscita.
duration: indica la durata in secondi dello stream di input che viene
elaborato, a partire dallinizio.
Capitolo 5
Conclusioni
5.1 Lavoro svolto
Il software creato acquisisce uno stream, lo elabora, e produce un segnale in
output. Il programma pu essere lanciato in due modalit:
interfaccia graca
batch
Linterfaccia graca orientata allutilizzo diretto da parte di un utente, men-
tre la versione batch (sicuramente meno user-friendly in quanto necessita del
passaggio di 7 parametri) che stata utilizzata per la fase di testing pu essere
impiegata per integrare facilmente il software allinterno di altre applicazio-
ni. Sia linput che loutput possono provenire da scheda audio oppure da le,
quindi emergono 4 combinazioni di utilizzo del programma:
1. Editor: il le selezionato viene elaborato e il risultato viene salvato in un
nuovo le.
2. Player: lo stream proviene da un le, viene elaborato e riprodotto in
tempo reale.
3. Recorder: lo stream proviene da un ingresso della scheda audio selezio-
nata, viene elaborato e salvato su le.
4. Real-Time DSP: lo stream proviene da un ingresso della scheda audio
selezionata, viene elaborato e riprodotto in tempo reale.
Le possibili combinazioni possono essere facilmente selezionate sullinterfaccia
graca, cos come il ltro da utilizzare per lelaborazione. Questultimo pu
essere scelto tra quelli disponibili allinterno del le dei ltri caricato allav-
vio dellapplicazione, oppure in alternativa pu essere caricato un nuovo le
contenente altri ltri. possibile inoltre scegliere la durata totale dellelabora-
zione, ossia quanti secondi dello stream acquisito si desidera ltrare. Nel caso
5.2 Sviluppi futuri 33
del salvataggio su le, quindi, si otterr un le audio della durata scelta. Il
bottone start fa iniziare lintero processo. Nel caso dellinput da scheda audio
linterfaccia graca rende disponibili solo le schede audio (o i vari ingressi di
una scheda audio dotata di ingressi multipli) supportate, nascondendo quelle
che genererebbero eccezioni se venissero selezionate. Il usso di esecuzione se-
quenziale, ma i moduli potranno essere associati a dierenti thread, costituendo
cos architetture software concorrenti.
Larchitettura software realizzata soddisfa i requisiti di modularit, essi-
bilit, riusabilit ed espandibilit che erano stati stabiliti come obiettivo nale.
Linterfaccia graca attuale chiara e di facile utilizzo.
5.2 Sviluppi futuri
La versione attuale del software piuttosto incompleta. Analizziamo nel
dettaglio i principali sviluppi possibili.
5.2.1 Thread
Lobiettivo ricreare la struttura descritta nel paragrafo 2.2 e rappresentata
dalla gura 2.3. necessario che i moduli gi implementati vengano estesi alla
classe Thread. Dovr poi essere aggiunto un modulo di controllo per lavvio di
tali thread e per la successiva sincronizzazione tra loro.
5.2.2 Generatore di ltri
Per un utilizzo agevole del software non si pu prescindere da un generatore di
ltri. Si dovr implementare una opportuna interfaccia graca con cui lutente
potr interagire per scegliere le caratteristiche del ltro da generare. Questo
modulo dovr anchesso essere esteso alla classe Thread ed essere avviato as-
sieme agli altri, cosicch il ltro applicato al segnale possa essere modicato
durante lesecuzione.
5.2.3 Visualizzazione graca dei ltri
Per una comprensione migliore delle caratteristiche del ltro utilizzato si rende
necessaria una rappresentazione graca dello stesso, nel dominio della frequen-
za. Questo modulo dovr reagire alle modiche in tempo reale operate sui
parametri del ltro, quindi dovr anchesso essere esteso alla classe Thread ed
essere avviato assieme agli altri dal modulo di controllo.
5.2.4 Visualizzazione graca del segnale
Funzioni comuni nei software di elaborazione digitale audio sono la rappresen-
tazione della forma donda del segnale nel dominio del tempo e la rappresen-
tazione nel dominio della frequenza. Se rappresentate in due nestre separate,
5.2 Sviluppi futuri 34
saranno utili nel confronto tra segnale in input e segnale elaborato. Mentre
abbastanza ovvio che la forma donda del segnale in ingresso debba rimanere
invariata durante lesecuzione, quella del segnale elaborato sar soggetta al-
le modiche del ltro applicatogli. Perch essa possa reagire in tempo reale,
dovr anchessa estendere la classe Thread.
5.2.5 Segmentazione con sovrapposizione
Per ottenere una maggior qualit complessiva dellelaborazione, si pu fare in
modo che la suddivisione dello stream in segmenti sia eettuata in maniera
tale per cui il segmento successivo inizi prima della ne del precedente.
5.2.6 Formati audio
Una carenza della versione attuale del software la limitazione allapertura e
salvataggio di le audio in formato Wave, monofonici. Oltre ad implementare
la stereofonia per i Wave necessario estendere i formati audio possibili ai
seguenti: Aif, Au, Ogg-Vorbis sia nel formato mono che stereo.
Appendice A
Complementi di analisi
numerica dei segnali
Lintera appendice fa riferimento al testo di Oppenheim e Schafer [4]. La
principale dierenza rispetto al testo originale riguarda il segno dellesponente
delle trasformate positivo per la trasformata diretta e negativo per quella
inversa basato sulla convenzione matematica e geologica [2, 3] piuttosto che
su quella in uso nel digital signal processing.
A.1 Risposta allimpulso e risposta in frequenza
I segnali a tempo discreto sono rappresentati da successioni, o sequenze, di
valori, spesso derivanti dal campionamento temporale di forme donda ana-
logiche. Una qualsiasi successione di valori [ ... , x
k
, ... , x
0
, ... , x
j
, ... ]
generalmente descritta attraverso le notazioni {x
n
} o x(n) ove n lindice del
tempo discreto.
La sequenza campione unitario, denita come la successione (n) con
valori
(n) =

0, n = 0
1, altrimenti
(A.1)
Per comodit, la sequenza campione viene altres indicata come impulso a
tempo discreto o semplicemente come impulso. Si dice che la sequenza y(n)
una versione ritardata o traslata della sequenza x(n) se y(n) ha valori
y(n) = x(n n
0
) (A.2)
dove n
0
un numero intero.
Una generica sequenza x(n) pu essere espressa in funzione dellimpulso
come
x(n) =
+

k=
x(k)(n k) (A.3)
A.1 Risposta allimpulso e risposta in frequenza 36
La notazione (A.3), come si vedr tra breve, particolarmente interessante per
lanalisi della risposta dei sistemi.
Un sistema denito matematicamente come una trasformazione univoca
o un operatore che mappa una sequenza di ingresso x(n) in una sequenza di
uscita, altres detta risposta, y(n). Questo viene indicato con
y(n) = T[x(n)] (A.4)
ed rappresentato gracamente come illustrato in Figura A.1:



Figura A.1: Schema della trasformazione ingresso-uscita
Imponendo dei vincoli sulla trasformazione T[ ] si possono denire delle
classi di sistemi a tempo discreto.
La classe dei sistemi lineari denita mediante il principio di sovrapposi-
zione. Se y
1
(n) e y
2
(n) sono le risposte rispettivamente agli ingressi x
1
(n) e
x
2
(n), un sistema lineare se e solo se
T[ax
1
(n) +bx
2
(n)] = aT[x
1
(n)] +bT[x
2
(n)] = ay
1
(n) +by
1
(n) (A.5)
ove a e b sono costanti arbitrarie. Si visto dalla equazione (A.3) che una
sequenza arbitraria x(n) pu essere rappresentata come somma di sequenze
di impulsi ritardati e scalati. Questa rappresentazione, insieme con la (A.4),
suggerisce che un sistema lineare possa essere completamente caratterizzato
dalla sua risposta allimpulso. In particolare, si supponga che h
k
(n) sia la
risposta a (n k), cio ad un campione unitario che si manifesta quando
n = k. Allora dalla (A.3) si ha
y(n) = T

k=
x(k)(n k)

(A.6)
e usando la (A.4) possiamo scrivere
y(n) =
+

k=
x(k)T [(n k)] =
+

k=
x(k)h
k
(n) (A.7)
La risposta del sistema pu dunque essere espressa secondo la relazione (A.7)
mediante la risposta del sistema a (nk). Se si impone soltanto la condizione
di linearit, h
k
(n) dipender sia da n che da k.
La classe dei sistemi stazionari (o sistemi invarianti alla traslazione)
caratterizzata dalla propriet che, se y(n) la risposta a x(n),y(n k) la
risposta a x(n k) dove k un intero positivo o negativo. Quando lindice n
A.1 Risposta allimpulso e risposta in frequenza 37
associato al tempo, la stazionariet corrisponde con la tempo invarianza del
sistema. Dalla propriet di stazionariet consegue che, se h(n) la risposta a
(n), la risposta a (n k) semplicemente h(nk). La (A.7) diventa quindi
y(n) =
+

k=
x(k)h(n k) (A.8)
Ogni sistema lineare stazionario dunque completamente caratterizzato dalla
risposta allimpulso h(n).
Lespressione (A.8) comunemente chiamata somma di convoluzione. Se
y(n) una sequenza i cui valori sono in relazione coi valori di due sequenze
h(n) e x(n) secondo la (A.8), diciamo che y(n) la convoluzione di x(n) con
h(n) e indichiamo questo con la notazione
y(n) = x(n) h(n) (A.9)
Con una sostituzione di variabili nella (A.8), otteniamo lespressione alternativa
y(n) =
+

k=
h(k)x(n k) = h(n) x(n) (A.10)
Quindi lordine secondo cui avviene la convoluzione delle due sequenze non
importante e ne consegue che luscita del sistema la stessa se i ruoli dellin-
gresso e della risposta al campione unitario sono scambiati. In altri termini, un
sistema lineare stazionario con ingresso x(n) e risposta allimpulso h(n) avr
la stessa uscita di un sistema lineare stazionario con ingresso h(n) e risposta
allimpulso x(n).
A.1.1 Stabilit dei sistemi
Viene denito sistema stabile un sistema per cui ogni ingresso limitato provoca
unuscita limitata. I sistemi lineari stazionari sono stabili se e solo se la relativa
risposta allimpulso h(n) ha modulo sommabile, ovvero
S =
+

k=
|h(k)| < (A.11)
Ci pu essere dimostrato come segue. Se la (A.11) vera e x limitata, cio
|x(n)| < M per ogni n, allora dalla (A.10) segue che
|y(n)| =

k=
x(k)h(n k)

M
+

k=
|h(k)| < (A.12)
A.1 Risposta allimpulso e risposta in frequenza 38
Perci y limitata. Si dimostra che la condizione (A.11) anche necessaria
facendo vedere che se S = , allora si pu trovare un ingresso limitato che d
luogo ad una uscita non limitata. Tale la sequenza con valori
x(n) =

(n)
|h(n)|
, h(n) = 0
0, h(n) = 0
(A.13)
ove h

(n) il complesso coniugato di h(n); chiaramente x(n) limitata. Il


valore delluscita per n = 0
y(0) =
+

k=
x(k)h(k) =
+

k=
|h(k)|
2
|h(k)|
= S (A.14)
Perci se S = , la sequenza di uscita non limitata.
A.1.2 I sistemi a tempo discreto nel dominio delle frequenze
Una propriet fondamentale dei sistemi lineari stazionari legata alla rap-
presentazione di segnali sotto forma di sinusoidi o di esponenziali complessi
(rappresentazioni di Fourier). Supponiamo, ad esempio, che la sequenza din-
gresso sia x(n) = e
in
per < n < , cio un esponenziale complesso
discreto con pulsazione . Allora, utilizzando la (A.10), luscita
y(n) =
+

k=
h(k)e
i(nk)
= e
in
+

k=
h(k)e
ik
(A.15)
Se deniamo
H() = H(e
i
) =
+

k=
h(k)e
ik
(A.16)
si pu scrivere
y(n) = H()ei
n
(A.17)
Dallespressione (A.17) si vede che lesponenziale complesso subisce sempli-
cemente una variazione di ampiezza (complessa) descritta da H() che una
funzione della frequenza. La quantit H() detta risposta in frequenza (o, im-
propriamente, funzione di trasferimento) del sistema la cui risposta allimpulso
h(n). H() una funzione continua rispetto ad . Inoltre una funzione
periodica di con periodo 2. Questa propriet deriva direttamente dalla
(A.16) dato che e
i(+2)k
= e
ik
. Il fatto che la risposta in frequenza abbia lo
stesso valore per e +2 signica semplicemente che il sistema risponde in
identica maniera agli esponenziali complessi di queste due frequenze. Siccome
le due sequenze esponenziali non sono distinguibili, tale comportamento del
tutto ragionevole.
Considerando che H() una funzione periodica di , essa pu essere
A.1 Risposta allimpulso e risposta in frequenza 39
rappresentata da una serie di Fourier. In eetti la (A.16) esprime H() nella
forma di una serie di Fourier in cui i coecienti di Fourier corrispondono ai
valori della risposta al campione unitario h(n). Da questa osservazione deriva
la possibilit di calcolare h(n) da H() per mezzo della relazione usata per
ottenere i coecienti di Fourier di una funzione periodica, cio
h(n) =
1
2

H()e
in
d (A.18)
ove
H() =
+

n=
h(n)e
in
(A.19)
Queste espressioni consentono una interpretazione alternativa della sequenza
h(n). Pi precisamente utile considerare la (A.18) come la rappresentazione
della sequenza h(n) sotto forma di sovrapposizione (integrale) di segnali espo-
nenziali, le cui ampiezze complesse sono fornite dalla (A.19). Perci le (A.18) e
(A.19) sono una coppia di trasformate di Fourier per la sequenza h(n), dove la
(A.19) gioca il ruolo di trasformata diretta della sequenza h(n) e la (A.18) la
trasformata di Fourier inversa. Tale rappresentazione esiste se la serie (A.19)
converge.
Se h(n) sommabile in modulo, ossia vale la relazione (A.11), la serie
(A.19) si dice assolutamente convergente e converge uniformemente ad una
funzione continua (dunque limitata) di . Pertanto la risposta in frequenza di
un sistema stabile converger sempre.
La rappresentazione di una sequenza per mezzo della trasformata (A.19)
non circoscritta alla risposta allimpulso di un sistema, ma pu essere ap-
plicata a qualsiasi sequenza, purch la trasformata converga. Si pu dunque
denire per una generica sequenza x(n) la trasformata di Fourier come
X() =
+

n=
x(n)e
in
(A.20)
e la trasformata inversa come
x(n) =
1
2

X()e
in
d (A.21)
Il fatto che le sequenze possano essere rappresentate come sovrapposizione
di esponenziali complessi molto importante nellanalisi dei sistemi lineari
stazionari, in virt del principio di sovrapposizione e del fatto che la risposta
di tali sistemi ad esponenziali complessi completamente determinata dalla
risposta in frequenza, H().
Se si intende la (A.21) come la sovrapposizione di esponenziali complessi
di ampiezza innitesima, allora la risposta di un sistema lineare stazionario ad
un ingresso x(n) sar la corrispondente sovrapposizione delle risposte relative
A.2 Z-trasformata e funzione di trasferimento 40
ad ogni esponenziale complesso che compare in ingresso; quindi, dato che la
risposta al singolo esponenziale complesso si ottiene moltiplicando questo per
H(), si ha
y(n) =
1
2

H()X()e
in
d (A.22)
Pertanto la trasformata di Fourier delluscita sar
Y () = H()X() (A.23)
Lo stesso risultato potrebbe essere ricavato in modo pi rigoroso calcolando
semplicemente la trasformata di Fourier della somma di convoluzione (A.8), o
della (A.10), ma si voluto evidenziare che la (A.23) una diretta conseguenza
delle particolari propriet dei sistemi lineari stazionari.
A.2 Z-trasformata e funzione di trasferimento
La trasformata di Fourier per segnali e sistemi a tempo discreto, pu essere
generalizzata attraverso la Z-trasformata; questultima gioca un ruolo partico-
larmente importante nellanalisi e nella rappresentazione dei sistemi lineari a
tempo discreto e stazionari.
La Z-trasformata X(Z) di una sequenza x(n) denita come
X(Z) =
+

n=
x(n)Z
n
(A.24)
ove Z una variabile complessa.
Esprimendo la variabile complessa Z in forma polare come Z = e
i
si
pu dare alla (A.24) una interpretazione in termini della trasformata di Fourier.
Specicatamente, con Z espressa in questa forma, la (A.24) diventa
X() =
+

n=
x(n)(e
i
)
n
(A.25)
oppure
X() =
+

n=
x(n)
n
e
in
(A.26)
Pertanto, in accordo con la (A.26), la Z-trasformata di x(n) pu essere inter-
pretata come la trasformata di Fourier di x(n) moltiplicata per una sequenza
esponenziale reale. Per = 1, cio per Z = 1, la Z-trasformata uguale alla
trasformata di Fourier della sequenza x(n).
Si visto nel precedente paragrafo che i sistemi stabili sono caratterizzati
dalla convergenza della serie di potenze associata alla trasformata di Fourier.
Inoltre, abbiamo osservato che la convergenza uniforme della trasformata di
A.2 Z-trasformata e funzione di trasferimento 41
Fourier richiede che la sequenza sia assolutamente sommabile. Se ci si applica
alla (A.26) la condizione diventa
+

n=
|x(n)
n
| < (A.27)
Alla (A.27) legata la denizione di regione di convergenza della Z-trasformata.
A causa della moltiplicazione per lesponenziale reale
n
, infatti, la serie di
potenze della (A.24) converger in una regione anulare del piano complesso,
descritta dalla relazione
R
x
< |Z| < R
x
(A.28)
ove in generale R
x
pu essere piccolo no ad annullarsi ed R
x+
pu essere
grande no allinnito. Dovrebbe essere chiaro dalla (A.27) che possibile che
la Z-trasformata converga anche se non converge la trasformata di Fourier. Ad
esempio, la sequenza
x(n) =

0, n < 0
1, n 0
(A.29)
non assolutamente sommabile e di conseguenza la trasformata di Fou-
rier non converge. Tuttavia x(n)
n
assolutamente sommabile se || < 1, e
pertanto la Z-trasformata di x(n) esiste con regione di convergenza 0 < |Z| < 1.
A.2.1 La convoluzione mediante Z-trasformata
Se w(n) la convoluzione delle due sequenze x(n) e y(n), allora la Z-trasformata
di w(n) il prodotto delle Z-trasformate di x(n) e y(n), cio, se
w(n) =
+

k=
x(k)y(n k) (A.30)
allora W(Z) = X(Z)Y (Z). Per mostrare ci si pu osservare che
W(Z) =
+

n=

k=
x(k)y(n k)

Z
n
(A.31)
Scambiando lordine delle sommatorie si ha
W(Z) =
+

k=
x(k)

n=
y(n k)Z
n

(A.32)
e cambiando lindice della seconda sommatoria da n a m = n k si ottiene
W(Z) =
+

k=
x(k)

m=
y(m)Z
m

Z
k
(A.33)
A.2 Z-trasformata e funzione di trasferimento 42
In denitiva, si avr quanto premesso, ovvero
W(Z) = X(Z)Y (Z) (A.34)
A.2.2 La Z-trasformata come funzione di trasferimento
Si visto che, nellambito dei sistemi lineari e stazionari, la trasformata di
Fourier della risposta allimpulso, corrisponde alla risposta in frequenza del
sistema. Inoltre, nel dominio della frequenza la relazione di ingresso-uscita
corrisponde semplicemente a una moltiplicazione tra la trasformata di Fourier
dellingresso e quella della risposta allimpulso.
E possibile descrivere pi in generale le propriet di linearit e staziona-
riet in termini della Z-trasformata della risposta allimpulso. Se x(n), y(n)
e h(n) indicano rispettivamente lingresso, luscita e la risposta allimpulso, e
X(Z), Y (Z) e H(Z) le loro Z-trasformate, essendo
y(n) = x(n) h(n) (A.35)
in virt della (A.34) si avr
Y (Z) = X(Z)H(Z) (A.36)
Come per la trasformata di Fourier, la relazione di ingresso-uscita per un
sistema stazionario corrisponde ad una moltiplicazione delle Z-trasformate
dellingresso e della risposta allimpulso.
La Z-trasformata della risposta allimpulso viene spesso chiamata funzione
di trasferimento. La funzione di trasferimento calcolata sul circolo unitario
(cio per |Z| = 1) la risposta in frequenza del sistema.
Ricordiamo che condizione necessaria e suciente per la stabilit di un
sistema che la risposta allimpulso h(n) sia assolutamente sommabile. La
regione di convergenza della Z-trasformata denita come quei valori di Z per
cui h(n)Z
n
assolutamente sommabile. Dunque, se la regione di convergenza
della funzione di trasferimento comprende il circolo unitario, il sistema stabile,
e viceversa.
A.3 La trasformata discreta di Fourier 43
A.3 La trasformata discreta di Fourier
La trasformata discreta di Fourier (DFT, Discrete Fourier Transform) una
rappresentazione alternativa alla trasformata (continua) di Fourier e alla Z-
trasformata descritte nei precedenti paragra, applicabile nel caso in cui la
sequenza da rappresentare sia costituita da un numero nito di campioni. La
DFT sar a sua volta una sequenza, di lunghezza nita, anzich una funzione
continua. Tuttavia, per interpretare correttamente il signicato della trasfor-
mata discreta di Fourier, sar dapprima utile analizzare particolari sequenze,
aventi lunghezza innita e periodiche nel tempo.
Si consideri una sequenza x
p
(n) periodica con periodo N, cio tale che sia
x
p
(n)=x
p
(n+kN) per ogni valore intero di k. Tale sequenza non pu essere
rappresentata mediante la sua Z-trasformata, poich non esiste alcun valore
di Z per cui la Z-trasformata converge. E possibile, tuttavia, rappresentare
x
p
(n) per mezzo di una serie di Fourier, cio come somma di sequenze sinusoi-
dali o cosinusoidali o, in modo equivalente, di sequenze esponenziali complesse
con frequenze multipli interi della frequenza fondamentale 2/N associata al-
la sequenza periodica. A dierenza della serie di Fourier valida per funzioni
periodiche continue, esistono soltanto N esponenziali complessi distinti il cui
periodo un sottomultiplo intero del periodo fondamentale N. Ci deriva dal
fatto che lesponenziale complesso
e
k
(n) = e
i
2
N
nk
(A.37)
periodico in k con periodo N. Perci e
0
(n) = e
N
(n), e
1
(n) = e
N+1
(n)
ecc., e quindi linsieme di n esponenziali complessi rappresentati nella (A.37)
con k = 0, 1, ..., N 1 denisce tutti gli esponenziali complessi distinti con
frequenze che sono multipli interi di 2/N. Pertanto, per la rappresentazione
in serie di Fourier di una sequenza periodica x
p
(n), bastano soltanto N di
questi esponenziali complessi e quindi essa pu scriversi nella forma
x
p
(n) =
1
N
N1

k=0
X
p
(k)e
i
2
N
nk
(A.38)
La costante moltiplicativa 1/N stata inserita per convenienza e, naturalmen-
te, non ha alcun eetto importante sulla natura della rappresentazione. Per
ottenere i coecienti X
p
(k) dalla sequenza periodica x
p
(n) usiamo il fatto che
risulta
1
N
N1

n=0
e
i
2
N
nr
=

1, per r = mN, m intero


0, altrove
(A.39)
Perci moltiplicando entrambi i membri della relazione (A.38) per e
i(2/N)nr
A.3 La trasformata discreta di Fourier 44
e sommando da n = 0 a n = N 1, si avr
N1

n=0
x
p
(n)e
i
2
N
nr
=
1
N
N1

n=0
N1

k=0
X
p
(k)e
i
2
N
(kr)n
(A.40)
Oppure, scambiando lordine delle sommatorie al secondo membro della pre-
cedente espressione si pu scrivere
N1

n=0
x
p
(n)e
i
2
N
nr
=
N1

k=0
X
p
(k)

1
N
N1

n=0
e
i
2
N
(kr)n

(A.41)
per cui, usando la (A.39), risulta
N1

n=0
x
p
(n)e
i
2
N
nr
= X
p
(r) (A.42)
Perci i coecienti X
p
(k) nella (A.38) sono dati da
X
p
(k) =
N1

n=0
x
p
(n)e
i
2
N
nk
(A.43)
Notiamo che la sequenza X
p
(k) rappresentata dalla relazione (A.43) periodi-
ca con periodo N, cio X
p
(0) = X(N), X
p
(1) = X(N + 1) ecc. Naturalmente
ci in accordo con il fatto che gli esponenziali complessi rappresentati nelle-
spressione (A.37) sono distinti soltanto per k = 0, 1, ..., N 1, e dunque nella
rappresentazione di una sequenza periodica in serie di Fourier possono esservi
solo N coecienti distinti.
I coecienti della serie di Fourier possono essere considerati come una
sequenza di lunghezza nita, data dalla espressione (A.43) per k = 0, 1, ..., N1
e nulla per valori diversi di k, o come una sequenza periodica denita per
ogni k dalla relazione (A.43). Chiaramente, queste due interpretazioni sono
equivalenti. In generale pi conveniente interpretare i coecienti della serie
di Fourier come una sequenza periodica. In questo modo si stabilisce una
dualit tra i domini del tempo e della frequenza per la rappresentazione di
sequenze periodiche in serie di Fourier. Le relazioni (A.38) e (A.43) vengono
considerate una coppia di trasformate e costituiscono la rappresentazione di
una sequenza periodica in serie di Fourier discreta.
La sequenza periodica e discreta X
p
(n) pu essere vista come una sequenza
di campioni sul circolo unitario (|Z| = 1), equispaziati in angolo, della Z-
trasformata (continua) di un periodo di x
p
(n). Supponiamo infatti che x(n)
rappresenti un periodo di x
p
(n), cio x(n) = x
p
(n) nellintervallo 0 n N1
A.3 La trasformata discreta di Fourier 45
e x(n)=0 per valori diversi di n. Quindi la Z-trasformata di x(n) data da
X(Z) =
N1

n=0
x(n)Z
n
(A.44)
ove si nota che lindice n della sommatoria varia tra 0 ed N 1 in quanto i
valori nulli di x(n), esterni allintervallo, non danno alcun contributo ad X(Z).
Confrontando la (A.43) con la (A.44), si vede che tra X(Z) e X
p
(k) sussiste
la relazione
X
p
(k) = X(Z)|
Z=e
i
2
N
k
(A.45)
La sostituzione Z = e
i(2/N)k
corrisponde quindi a campionare la Z-trasformata
X(Z) in N punti egualmente spaziati in angolo sul circolo unitario, con il primo
campione situato in Z = 1.
La medesima rappresentazione discreta utilizzata per le sequenze periodiche
pu essere applicata a sequenze di durata nita, purch la si interpreti corretta-
mente. Consideriamo dunque una sequenza di durata nita x(n) di lunghezza
N, in modo che x(n) sia nulla eccetto che nellintervallo 0 n N 1. Na-
turalmente, una sequenza di lunghezza M minore di N pu anchessa essere
considerata di lunghezza N con gli ultimi (N M) punti dellintervallo aventi
valore zero. La corrispondente sequenza periodica di periodo N, di cui x(n)
un periodo, sar indicata con x
p
(n) ed data da
x
p
(n) =
+

r=
x(n +rN) (A.46)
Poich x(n) di lunghezza nita N, non vi sovrapposizione tra i termini
x(n + rN) per valori di r dierenti. Dunque la relazione (A.46) pu essere
scritta nella forma alternativa
1
.
x
p
(n) = x(n mod N) (A.47)
La sequenza di durata nita x(n) si ricava da x
p
(n) estraendone un periodo,
cio
x(n) =

x
p
(n), 0 n N 1
0, altrove
(A.48)
Si visto che i coecienti della serie di Fourier discreta X
p
(k) relativi alla
sequenza periodica x
p
(n) sono anchessi una sequenza periodica di periodo N.
Per mantenere la dualit fra i domini del tempo e della frequenza, sceglieremo
i coecienti di Fourier da associare a una sequenza di durata nita come la
sequenza di durata nita costituita da un periodo di X
p
(k). Dunque, indicando
con X(k) i coecienti di Fourier che associamo a x(n), valgono le seguenti
1
Se n espresso come n = n1 +n2N con 0 n1 N 1, n mod N uguale a n1.
A.4 Convoluzione periodica, circolare e lineare 46
relazioni che legano X(k) e X
p
(k)
X
p
(k) = X(k mod N) (A.49)
X(k) =

X
p
(k), 0 k N 1
0, altrove
(A.50)
La relazione tra X
p
(k) e x
p
(n) data dalle equazioni (A.38) e (A.43) e dunque,
in base alle equazioni (A.48) e (A.50), segue che
X(k) =


N1
n=0
x(n)e
i
2
N
kn
, 0 k N 1
0, altrove
(A.51)
x(n) =

1
N

N1
k=0
X(k)e
i
2
N
kn
, 0 n N 1
0, altrove
(A.52)
La coppia di trasformate indicate nelle equazioni (A.51) e (A.52) denita
trasformata discreta di Fourier.
A.4 Convoluzione periodica, circolare e lineare
Siano x
p
(n) e y
p
(n) due sequenze periodiche di periodo N con serie discrete di
Fourier X
p
(k) e Y
p
(k) rispettivamente. Si vuole determinare la sequenza q
p
(n)
la cui serie discreta di Fourier sia X
p
(k)Y
p
(k). Per ottenere questa relazione
notiamo che
X
p
(k) =
N1

m=0
x
p
(m)e
i
2
N
mk
(A.53)
Y
p
(k) =
N1

r=0
y
p
(r)e
i
2
N
rk
(A.54)
per cui
X
p
(k)Y
p
(k) =
N1

m=0
N1

r=0
x
p
(m)y
p
(r)e
i
2
N
k(m+r)
(A.55)
Risulta dunque
q
p
(n) =
1
N
N1

k=0
e
i
2
N
nk
X
p
(k)Y
p
(k) =
N1

m=0
x
p
(m)
N1

r=0
y
p
(r)

1
N
N1

k=0
e
i
2
N
k(nmr)

(A.56)
A.4 Convoluzione periodica, circolare e lineare 47
Consideriamo q
p
(n) per 0 n N 1. Si pu osservare che
1
N
N1

k=0
e
i
2
N
k(nmr)
=

1, per r = (n m) +lN
0, altrove
(A.57)
ove l un intero qualunque. Risulta quindi che
q
p
(n) =
N1

m=0
x
p
(m)y
p
(n m) (A.58)
La (A.58) stabilisce che q
p
(n) si ottiene combinando x
p
(n) e y
p
(n) in un modo
simile ad una convoluzione. E importante notare, tuttavia, che, contrariamen-
te alla convoluzione di sequenze aperiodiche, le sequenze x
p
(m) e y
p
(n m)
della relazione (A.58) sono periodiche in m con periodo N e pertanto lo anche
il loro prodotto. Inoltre la somma eseguita solo su un periodo. Questa forma
di convoluzione comunemente chiamata convoluzione periodica. Cambiando
semplicemente lindice della somma nella relazione (A.58) si pu mostrare che
risulta
q
p
(n) =
N1

m=0
y
p
(m)x
p
(n m) (A.59)
Consideriamo ora le sequenze x(n) e y(n), entrambe nite con durata N,
con relative trasformate discrete di Fourier X(k) e Y (k). Per determinare
la sequenza q(n) i cui coecienti della trasformata discreta di Fourier siano
X(k)Y (k), possibile applicare gli stessi risultati; si tenga infatti presente che
q(n) corrisponde ad un periodo della sequenza q
p
(n), data dalla (3.11). Dun-
que, ricordando anche la relazione tra sequenze periodiche e sequenze nite,
espressa dalla (A.47), si ha
q(n) =

N1

m=0
x(m mod N)y((n m) mod N)

u
N
(n) (A.60)
ove u
N
(n) una sequenza rettangolare denita come
u
N
(n) =

1, 0 n N 1
0, altrove
(A.61)
La convoluzione espressa dalla (A.60) viene spesso indicata con il nome di
convoluzione circolare.
Ma quale relazione intercorre tra la convoluzione circolare appena illustrata
e la convoluzione lineare caratterizzata dalla equazione (A.30), per sequenze di
lunghezza nita?
Si considerino ancora le due sequenze x(n) e y(n) di durata nita, nulle
per n < 0 e per n N. In base alla (A.30), la convoluzione lineare di x(n) e
A.4 Convoluzione periodica, circolare e lineare 48
y(n) risulta
w(n) =
N1

m=0
x(m)y(n m) (A.62)
dalla quale immediato vericare che w(n) ha lunghezza 2N 1; ovvero essa
pu avere al pi 2N 1 punti diversi da zero. Dal confronto fra la (A.60) e
la (A.30) si pu altres dedurre che la convoluzione circolare q(n), espressa in
funzione della convoluzione lineare w(n), risulta
q(n) =

r=
w(n +rN)

u
N
(n) (A.63)
Nel caso della convoluzione circolare q(n), si nota che essa ha lunghezza N, ed
nulla per n < 0 e per n N. Se ora confrontiamo la (A.62) con la (A.63),
diremo che la seconda equazione aetta da aliasing, ovvero, nellintervallo
0 n N1, dierisce dalla (A.62) a causa della sovrapposizione dei termini
w(n +rN) per r = 0.
In molte applicazioni interessa eseguire la convoluzione lineare fra sequenze
ma, come abbiamo visto, moltiplicare le trasformate discrete di Fourier di due
sequenze corrisponde a calcolare la loro convoluzione circolare. Se quindi siamo
interessati ad ottenere una convoluzione lineare, dobbiamo assicurarci che la
convoluzione circolare produca leetto di una convoluzione lineare, facendo in
modo che tutti i termini della sovrapposizione, per r = 0, risultino nulli e che
q(n) abbia lunghezza 2N 1.
Ci pu essere garantito calcolando le trasformate discrete X(k) e Y (k)
sulla base di 2N 1 punti, cio considerando x(n) ed y(n) come sequenze di
2N 1 punti, aventi N 1 zeri nellintervallo N n 2N 2. Pertanto
deniamo
X(k) =
2N2

n=0
x(n)e
i
2
2N1
nk
(A.64)
Y (k) =
2N2

n=0
y(n)e
i
2
2N1
nk
(A.65)
q(n) =
1
2N 1

2N2

k=0
[X(k)Y (k)] e
i
2
2N1
nk

u
2N1
(n) (A.66)
e ne deduciamo che q(n) sar la convoluzione lineare di x(n) e y(n). Ovvia-
mente otterremmo una convoluzione lineare anche se le trasformate di Fou-
rier discrete fossero calcolate sulla base di pi di 2N 1 punti, ma non la
otterremmo, in generale, operando sulla base di un numero inferiore di punti.
Spesso si richiede il calcolo della convoluzione lineare fra due sequenze
di durata diversa. Se x(n) ha durata N
1
ed y(n) ha durata N
2
, allora la
loro convoluzione sar lunga N
1
+ N
2
1. Dunque in questo caso andranno
A.5 La trasformata rapida di Fourier (FFT) 49
moltiplicate tra loro le trasformate di Fourier discrete calcolate sulla base di
N N
1
+N
2
1 punti.
A.5 La trasformata rapida di Fourier (FFT)
La trasformata di Fourier ricopre un ruolo molto importante nellanalisi, nel
progetto e nella realizzazione di algoritmi e sistemi per lelaborazione dei se-
gnali. Uno dei motivi per cui essa cos importante e di vasto impiego nellela-
borazione numerica dei segnali lesistenza di algoritmi ecienti per il calcolo
della trasformata discreta di Fourier.
Si ricordi dalla equazione (A.51) che la trasformata discreta di Fourier
(DFT) denita come
X(k) =
N1

n=0
x(n)e
i
2
N
kn
, k = 0, 1, ..., N 1 (A.67)
mentre (equazione (A.52)) la trasformata discreta inversa
x(n) =
1
N
N1

k=0
X(k)e
i
2
N
kn
, n = 0, 1, ..., N 1 (A.68)
Nelle equazioni (A.67) e (A.68) sia x(n) che X(k) possono essere complesse.
Le due espressioni dieriscono solo per il segno dellesponente e per un fattore
di scala pari a 1/N. Pertanto la discussione delle procedure di calcolo per la
(A.67) pu essere estesa, con semplici modiche, alla (A.68).
E utile preliminarmente esaminare il caso del calcolo diretto della DFT,
cio delle espressioni (A.67) e (A.68). Poich x(n) in generale complessa si
pu intuire, separando la (A.68) nelle sue parti reale ed immaginaria, che il
calcolo diretto di X(k) richiede 4N moltiplicazioni reali e (4N 2) addizioni
reali per ogni valore di k. Considerando che occorre valutare X(k) per N
valori diversi di k, il calcolo diretto della trasformata discreta di Fourier di una
sequenza x(n) richiede 4N
2
moltiplicazioni reali e N(4N 2) addizioni reali,
oppure N
2
moltiplicazioni complesse e N(N 1) addizioni complesse.
Essendo la quantit dei calcoli approssimativamente proporzionale ad N
2
,
evidente che il numero di operazioni aritmetiche richiesto per calcolare la
DFT con il metodo diretto diventa particolarmente elevato per valori grandi
di N. Per questo motivo risultano di notevole interesse procedure di calcolo
che riducano il numero di moltiplicazioni e addizioni.
Va detto che esistono diverse classi di metodi per il calcolo rapido della tra-
sformata discreta di Fourier, generalmente raggruppate sotto la notazione FF
(Fast Fourier Transform). A titolo di esempio si vuole presentare il principio
della decimazione nel tempo, alla base di uno degli algoritmi FFT pi utiliz-
zati nella elaborazione dei segnali numerici. Il modo migliore di illustrare tale
A.5 La trasformata rapida di Fourier (FFT) 50
principio quello di considerare il caso particolare di sequenze di lunghezza N
pari ad una potenza del due, cio N = 2
v
.
Poich N un intero pari, si pu pensare di calcolare X(k) dividendo x(n)
in due sequenze di N/2 punti ciascuna, costituita luna dai punti che hanno
indice pari in x(n), e laltra da quelli con indice dispari. Se nellespressione
(A.67) scomponiamo x(n) nei suoi punti con indice pari e con indice dispari,
otteniamo
X(k) =

n pari
x(n)e
i
2
N
kn
+

n dispari
x(n)e
i
2
N
kn
, k = 0, 1, ..., N1 (A.69)
ovvero, con la sostituzione di variabili n = 2r per n pari e n = 2r + 1 per n
dispari,
X(k) =
(N/2)1

r=0
x(2r)e
2i
2
N
kr
+e
i
2
N
k
(N/2)1

r=0
x(2r + 1)e
2i
2
N
kr
(A.70)
In base alla relazione e
2i(2/N)
= e
i2/(N/2)
, la (A.70) pu essere riscritta
come
X(k) =
(N/2)1

r=0
x(2r)e
i
2
N/2
kr
+
+e
i
2
N
k
(N/2)1

r=0
x(2r + 1)e
i
2
N/2
kr
=
G(k) +e
i
2
N
k
H(k) (A.71)
Si riconosce facilmente che ciascuna delle due sommatorie della (A.71) una
DFT composta da N/2 punti. Sia G(k) che H(k) sono periodiche in k con
periodo N/2; occorre dunque calcolare ogni somma solo per k compreso tra 0
ed N/2 1. Dopo che sono state calcolate le due DFT corrispondenti alle due
sommatorie della (A.71), esse devono essere combinate per ottenere la DFT su
n punti, cio X(k).
Confrontiamo ora il numero di moltiplicazioni e addizioni richieste per la
determinazione della DFT nel caso in cui si usi la struttura di calcolo indi-
cata dalla (A.71) con il caso, considerato in precedenza, del calcolo diretto.
Per questultimo abbiamo visto che sono necessarie circa N
2
moltiplicazioni
e addizioni complesse. A confronto, la (A.71) richiede il calcolo di due DFT
su N/2 punti, che a loro volta richiedono 2(N/2)
2
moltiplicazioni complesse e
circa 2(N/2)
2
addizioni complesse. Le due DFT devono poi essere combinate,
richiedendo altre n moltiplicazioni complesse, corrispondenti al prodotto della
sommatoria H(k) per il termine e
i2k/(N/2)
, ed altre N addizioni complesse
per sommare tale prodotto con la sommatoria G(k). Pertanto il calcolo della
A.5 La trasformata rapida di Fourier (FFT) 51
(A.71) per tutti i valori di k necessita di N + 2(N/2)
2
, cio N + N
2
/2 mol-
tiplicazioni complesse e addizioni complesse. E facile vericare che per N>2,
N +N
2
/2 minore di N
2
.
Lespressione (A.71) corrisponde a spezzare il calcolo originario su N punti
in due calcoli su N/2 punti. Se N/2 pari, come certamente avviene quando
N una potenza del due, allora il calcolo di ciascuna DFT su N/2 punti
nella (A.71) si pu eettuare mediante il calcolo e la successiva combinazione
di due DFT su N/4 punti. Naturalmente possibile procedere scomponendo
ulteriormente tali trasformate su N/8 punti, e cos di seguito nch non si
rimane con sole trasformate su due punti. Ci richiede v stadi di calcolo, ove
v = log
2
N. Si visto che, con la originaria scomposizione di una trasformata su
N punti in due trasformate su N/2 punti, il numero richiesto di moltiplicazioni
e addizioni complesse N + 2(N/2)
2
. Quando le trasformate su N/2 punti
vengono scomposte in trasformate su N/4 punti, allora il fattore (N/2)
2

sostituito con N/2+2(N/4)


2
, cos che il calcolo complessivo richiede N +N +
4(N/4)
2
moltiplicazioni e addizioni complesse. Se N = 2
v
, ci pu essere fatto
al pi v = log
2
N volte, e se ne conclude che, dopo aver iterato al massimo
la scomposizione, il numero di moltiplicazioni e addizioni complesse diventa
Nlog
2
N.
Oltre al particolare caso presentato, la scomposizione delle trasformate di-
screte di Fourier pu essere legata alla rappresentazione di N come prodotto di
fattori primi; ci implica che N non debba essere necessariamente una potenza
del due per ottenere algoritmi relativamente ecienti per il calcolo della DFT.
Tuttavia gli algoritmi validi nel caso di N potenza del due sono particolarmen-
te semplici da realizzare e orono una maggiore ecienza rispetto agli altri
algoritmi. Chiaramente, per il loro utilizzo, verr richiesto di avere a che fare
con sequenze la cui durata una potenza del due. Ci pu essere fatto in molti
casi semplicemente espandendo un sequenza di durata nita con un numero
adeguato di valori nulli, come prevede la tecnica dello zero-padding.
Bibliograa
[1] E. Bont, Automatic Code Generation: From Process Algebraic Ar-
chitectural Descriptions to Multithreaded Java Program, Ph.D. Thesis,
University of Bologna (Italy), March 2008, pp. 29-33.
[2] Cambridge University Press, Numerical Recipes Software, Numerical
Recipes in C: The art of scientic computing, Second Edition 1992,
http://www.nr.com/.
[3] J. F. Claerbout, Fundamentals of geophisical Data Processing,
Blackwell Scientic Publications, 1985.
[4] Oppenheim A.V., Schafer R.W., Digital Signal Processing, Prentice-
Hall, 1975.
[5] Java Documentation, http://java.sun.com/reference/docs/.
[6] Java Sound Resources, http://www.jsresources.org/.
[7] GNU Octave, http://www.gnu.org/software/octave/.
[8] Wikipedia, The Free Encyclopedia, http://www.wikipedia.org/.
Ringraziamenti
Vorrei ringraziare i miei genitori per avermi sostenuto e incitato in questo lungo
viaggio che si conclude in questo scritto, ma soprattutto per le lezioni di vita che
ogni giorno mi regalano. Anche se non ve lho mai detto, ho sempre apprezzato
molto tutto questo. Un grazie sentito al mio relatore Edoardo Bont per la
pazienza, lassistenza e la professionalit profuse nella preparazione di questa
tesi. Un ringraziamento speciale va al Maestro Eugenio Giordani, docente di
Musica Elettronica al Conservatorio G. Rossini di Pesaro. Probabilmente
senza esserne a conoscenza, mi ha fatto trovare le motivazioni per arrivare,
oggi, a scrivere la parola FINE.

Potrebbero piacerti anche