relazione - Gabriele Buzzi

Transcript

relazione - Gabriele Buzzi
LOCALIZZAZIONE DI SORGENTI BASATA SU
PRESTIMA ADATTATIVA “BLIND” DEL CANALE
ASSOCIATO ALLA COPPIA MICROFONICA”
Tesina del corso di “Metodi Numerici per l'Acustica”
Docente:
Raffaele Parisi
Dottorandi:
Albenzio Cirillo
Gabriele Bunkheila
1
Studente:
Gabriele Buzzi
We all are brothers, but it's hard understanding who is Abel and who is Cain.
Buzzi Gabriele
[email protected]
cell. 3403755237
2
Filtri adattativi
I filtri adattativi sono usati per segnali e ambienti non stazionari, o in applicazioni adattative
campione per campione o in applicazioni in cui è richiesto un basso utilizzo delle risorse per
l'elaborazione dei segnali. Applicazioni di filtri adattativi includono riduzione del rumore su sistemi
multicanale, elaborazione di segnali provenienti da radar e sonar, equalizzazione dei segnali per
cellulari mobili, cancellazione d'eco e codifica della voce con basso ritardo. Un filtro adattativo è
un filtro che autoregola la sua funzione di trasferimento in accordo ad un algoritmo di ricerca. Tali
filtri regolano i propri coefficienti in base al segnale in ingresso. Lo scenario di riferimento è
illustrato nella figura sottostante, dove si vuole ottenere una versione il più fedele possibile del
segnale y[n] partendo dal segnale filtrato x[n].
fig.1
Facendo riferimento a segnali x[n] e y[n] aventi potenza finita, l'accuratezza del segnale stimato
y [ n]= x∗ f [n ] è costituita dalla potenza del segnale d'errore e [ n]= y [n]− y [n] .
P e ={∣e [n]∣2 }={∣y [ n]− x∗ f [n ]∣2 } (1).
Nel nostro caso il filtro da stimare è di tipo FIR per cui l'uscita è definita dalla seguente relazione:
L−1
y [n]=∑k=0 f [k ] x [n−k ] (2).
I coefficienti ottimi del filtro FIR sono ricavati minimizzando l'errore quadratico medio:
f [ n]=argmin {∣y [n ]−∗x [n ]∣2 } (3).
[n ]
Tale problema di minimizzazione si risolve tramite le equazioni normali:
L−1
∑k =0
f [k ] R x [n−k ]=R xy [n ] (4).
In forma compatta:
R x f =r xy (5).
Per cui i coefficienti ottimi del filtro sono dati dalla seguente relazione:
f =R−1
x r xy (6).
I filtri cosi ottenuti sono detti di Wiener. Il filtro di Wiener non dipende dalle particolari forme
d'onda x[n] e y[n], ma solo dalle funzioni di autocorrelazione R x [n] e di crosscorrelazione
r xy [n] . Quindi fissata la funzione di autocorrelazione R x [n] , esso risolve il problema della
stima del minimo errore quadratico mediante filtraggio dell'intera classe di segnali costituita da tutte
le coppie x[n] e y[n] aventi funzione di crosscorrelazione r xy [n] .
Nei filtri adattativi si utilizzano dei metodi iterativi per risolvere il problema di ricerca dei
coefficienti. Nel caso dei filtri LMS(last mean square) si utilizza un algoritmo basato sul gradiente
deterministico. Altri metodi come l'RLS(recursive least squares) cercano di calcolare in maniera
3
ricorsiva l'inverso della matrice di autocorrelazione. Questi algoritmi presentano delle velocità di
convergenza maggiori rispetto all'LMS(least mean squares) ma risultano pesanti dal punto di vista
computazionale. In certe applicazioni è richiesta una lunghezza notevole del filtro da adattare per
far fronte alla risposta impulsiva coinvolta. Lunghezze notevoli del filtro significano richieste di
memoria considerevoli e l’adattamento in frequenza può fornire un valido aiuto per far fronte a tale
problema.
4
Filtri adattativi Least Mean Squares, LMS
Un filtro adattativo si compone di due parti: un filtro digitale che compie il filtraggio del segnale e
un algoritmo di ricerca dei coefficienti. Una tipica struttura di tale applicazione è mostrato in figura,
dove d(n) è il segnale desiderato, y(n) l'uscita del filtro, x(n) il segnale d'ingresso e e(n) l'errore.
fig.2 Esempio di schema a blocchi di un filtro adattativo LMS
L'algoritmo di ricerca serve a trovare i pesi del filtro in modo da minimizzare, passo dopo passo, la
media quadratica di e(n). In fase di progettazione bisogna scegliere tra 2 tipologie di filtri, i FIR e i
IIR.
Il filtri FIR sono sempre stabili e possono restituire una risposta a fase lineare.
I filtri IIR, invece, non sempre sono stabili, infatti se un polo capita al di fuori del cerchio unitario
durante la fase di ricerca la risposta diverge e il sistema diventa instabile. Siccome nei filtri
adattativi la stabilità è molto sentita si preferisce l'utilizzo della prima tipologia rispetto alla
seconda. I filtri FIR utilizzati sono caratterizzati da una struttura composta da un set di L
coefficienti, w l n , l=0,1 ,... , L−1 e una sequenza di valori x  n , x n−1 , ... , x  n− L1 ,
di conseguenza l'uscita è determinata dall'espressione (1).
L−1
y n=∑l =0 w l  n x  n−l  (1)
Definendo il vettore degli ingressi all'istante n:
x n=[ x n , x  n−1 , ... , x  n− L1]T (2)
e il vettore dei coefficienti all'istante n:
T
w n=[w 0 n , w1 n ,... , wl n]
(3)
L'uscita può essere rappresenta come il prodotto scalare dei due vettori precedentemente definiti:
y n=w nT x n=x nT w n (4)
L'algoritmo LMS (least mean squares) è un criterio di ricerca dei coefficienti che ha come
obbiettivo la minimizzazione dell'errore quadratico medio di e(n) in base al metodo dei passi
decrescenti. Il metodo dei passi decrescenti è un algoritmo iterativo ed è descritto dalla seguente
equazione:
w n1=w n−

∇  n (5)
2
Dove n è la media quadratica dell'errore. Nell'LMS si utilizza una stima di n che è la
seguente:
2

n=e
 n (6)
Il gradiente della stima è:
5
 n=2 [∇ e  n]e  n (7)
∇ 
Sapendo che e n=d  n−w nT x n e ∇ e n=−x n il gradiente di n è:
∇  n=−2 x  ne  n (8)
Sostituendo l'ultimo risultato ottenuto all'equazione dei passi decrescenti (5) si ottiene il classico
algoritmo LMS:
w n1=w n x  ne  n (9)
Come si può ben vedere, tale procedura è molto semplice e non prevede l'esecuzione di operazioni
troppo pesanti che rallenterebbero il calcolatore. L'algoritmo può essere elencato nei seguenti passi.
1. Si determina L ,  , w 0 , dove L è l'ordine del filtro,  è un valore numerico da
assegnare e w 0 è il valore iniziale dei pesi all'istante n=0;
2. Si calcola l'uscita:
L−1
y n=∑l =0 w l  n x  n−l  (10)
3. Viene determinato l'errore
e n=d  n− y n (11)
4. Si aggiornano i coefficienti del filtro e si ritorna al passo 2
w n1=w n x n e n (12)
Stabilità
Come mostrato nella figura d'introduzione il filtro adattativo con algoritmo LMS presenta un ramo
di feedback, ciò può portare a problemi di stabilità. Dall'equazione (5) si può osservare come 
controlli l'entità dell'incremento di valore da apportare ai pesi del filtro. La condizione necessaria
perché l'algoritmo converga ad una soluzione ottima è la seguenta:
0
2
 max (13)
Dove max è il massimo autovalore della matrice di autocorrelazione cosi R cosi definita:
[
r xx 0 
r xx 1
r xx 0
R≡E [ x n  x  nT ]= r xx 1
⋮
...
r xx  L−1 r xx  L−2
... r xx  L−1
... r xx  L−2
⋱
⋮
...
r xx 0
]
(14)
In molte applicazioni pratiche è conveniente stimare max utilizzando dei semplici metodi. Dalla
(14) si ottiene:
L−1
tr [ R]= L r xx 0=∑l =0 l (15)
Dove tr[R] denota la traccia della matrice R.
Andando avanti si ha:
L−1
max ≤∑l =0 L r xx 0=L P x (16)
P x rappresenta la potenza istantanea del segnale come descritto dalle precedenti equazioni.
6
Di conseguenza si assume che:
0
2
(17)
L Px
Velocità di convergenza
Nelle sezioni precedenti, si è studiato come w(n) converge ad un valore ottimale w' dato un valore di
 che soddisfa la condizione di convergenza. La convergenza di w(n) da w(0) a w' corrisponde
alla convergenza dell'errore quadratico medio MSE da 0 a  min . La convergenza delle
MSE verso il minimo valore è utilizzata come misura delle prestazioni del sistema adattativo per la
sua semplicità. Se si plotta l'evoluzione delle MSE nel tempo su di un grafico si ottengono una serie
di curve di apprendimento che possono essere utilizzate per descrivere il comportamento transitorio
del sistema adattativo.
Ogni modo adattativo ha la propria costante di tempo, il quale è determinata, in generale, dalla
costante  e l'autovalore l associato al modo. E' facile immaginare che il tempo di
convergenza è determinato dal più piccolo autovalore come descritto dall'equazione seguente:
≃
1
(18)
 min
Dove min è il minimo autovalore della matrice di autocorrelazione R. La massima costante di
tempo =1 / min è una stima conservativa delle prestazioni del filtro e soltanto con
autovalori molto grandi si possono ottenere dei tempi di convergenza significativi. Sfortunatamente
max è molto grande e il campo di valori di  è limitato dalle condizioni di stabilità.
Assegnando ad esempio a  il valore 1/max si ottiene il seguente risultato:
≤
max
(19)
min
Per ingressi stazionari e  sufficientemente piccoli, il tempo di convergenza dipende dal rapporto
degli autovalori. Come menzionato in precedenza è difficoltoso ricavare max e min ,
comunque un efficiente modo per calcolare tale rapporto deriva da un'analisi dello spettro del
segnale.
 max max∣X ∣2
≤
(20)
 min min∣X ∣2
Dove X  è la DFT di x(n). Da questa relazione si evince che un segnale a spettro piatto, come
una sequenza MLS(maximum length sequence) o il rumore bianco, riducono il tempo di
convergenza del filtro adattativo.
7
Filtri adattativi Recursive Least Squares, RLS
I filtri RLS hanno un rapporto di convergenza relativamente veloce verso i coefficienti ottimi.
Nell'algoritmo RLS, l'adattamento inizia con uno stato iniziale del filtro, e successivi campioni del
segnale in ingresso sono usati per aggiornare i coefficienti.
fig.3
L'immagine rappresenta la struttura di un filtro RLS. Dove la stima del segnale desiderato è:
x m=w t m y m , (1)
mentre l'errore di stima utilizzato dall'algoritmo di aggiornamento vale:
  m=x  m−w t m ym . (2)
em =x m− x
L'algoritmo è basato sulla minimizzazione dell'errore medio quadratico cosi definito:
2
t
E {e  m}=E xm−w m y  m=
E {x 2 m}−2 w t mE {y m x m}w t mE {y m y t m}w m= (3)
r xx 0−2 w t mr yx w t m R yy w m.
Il filtro di Wiener è ottenuto minimizzando l'errore quadratico medio rispetto ai coefficienti del
filtro. Per segnali stazionari il risultato di questa minimizzazione è:
−1
w=Ryy r yx . (4)
Dove R yy è la matrice di autocorrelazione del segnale in ingresso e r yx è il vettore di
crosscorrelazione tra il segnale d'ingresso e il segnale desiderato.
Per un vettore di N componenti la matrici di autocorrelazione può essere scritta cosi:
N−1
R yy =Y T Y = ∑ y m y t m (5)
m=0
Tale equazione può essere riscritta in modo ricorsivo come:
t
R yy m=R yy m−1y m y m (6)
Per introdurre una adattabilità alle variazioni statistiche del segnale, la stima dell'autocorrelazone
(6) può essere finestrata per mezzo di un esponenziale decrescente.
R yy m= R yy m−1y m y t m. (7)
Dove  è chiamato fattore di dimenticanza, il suo valore varia tra 0 e 1. Similmente, il vettore di
cross-correlazione è dato da
N−1
r yx = ∑ y m xm. (8)
m=0
Allo stesso modo il vettore di cross-correlazione può essere trovato in modo ricorsivo come segue
8
r yx m=r yx m−1y m x m. (9)
Per trovare una soluzione tramite un metodo ricorsivo, abbiamo bisogno di trovare una formula di
aggiornamento anche per l'inverso della matrice di autocorrelazione nella forma:
−1
−1
R yy m=Ryy m−1update m . (10)
Tale problema è risolto facendo affidamento al seguente lemma.
Lemma dell'inversione di una matrice Dati A e B essere due matrici PxP definite positive unite
dalla seguente relazione
A=B−1C D−1 C T , (11)
dove D è una matrice NxN definita positiva e C è una matrice PxN. Il lemma dell'inversione ci dice
che l'inversa della matrice A può essere espressa in questo modo:
−1
T
−1 T
A =B−B C DC BC  C B. (12)
−1
Il lemma dell'inversione può essere usato per calcolare R yy m .
Posti:
R yy m= A , (13)
−1 −1
 R yy m−1=B , (14)
y m=C , (15)
D=matrice identità. (16)
Sostituendo la (13) e la (14) alla (12) otteniamo:
−2 −1
T
−1
 R yy m−1 y m y mR yy m−1
−1
−1 −1
R yy m= R yy m−1−
. (17)
−1
T
−1
1 y mR yy m−1 y m
Adesso definiamo le variabili  m e k m come:
 yy m=R−1
yy m (18)
−1 −1
 Ryy m−1 y m
k m=
(19)
−1
T
−1
1 y mR yy m−1 y m
oppure
−1  yy m−1 y m
k m=
. (20)
1−1 y T m yy m−1 y  m
Usando le equazioni (20) e (18), l'equazione ricorsiva (17) per il calcolo della matrice inversa può
essere riscritta come
−1
−1
T
 yy m=  yy m−1− k m y m yy m−1. (21)
Dall'equazione (20) e (21) , abbiamo
k m=[−1  yy m−1−−1 k m y T m  yy m−1] y m. (22)
Adesso la (21) e la (22) possono essere usate per derivare l'algoritmo di aggiornamento RLS.
Aggiornamento ricorsivo dei coefficienti del filtro
I coefficienti sono cosi definiti
w m=R−1 m r yx m
(23)
=  yy m r yx m.
Sostituendo la forma ricorsiva di calcolo del vettore di correlazione nell'equazione (23)
w m= yy m[ r yx m−1y m xm]
(24)
=  yy m r yx  m−1 yy m y m x m .
Adesso sostituendo la forma ricorsiva della matrice  yy m dall'equazione (21) e
k m= m y m dall'equazione (22) nel secondo membro dell'equazione (24) otteniamo
−1
−1
T
w m=[  yy m−1− k m y m  yy m−1] r yx  m−1k m x m (25)
o
9
w m= yy m−1r yx m−1−k m y T m yy m−1 r yx m−1k m x m. (26)
Sostituendo w m−1= yy m−1 r yx m−1 nell'equazione 26 ci restituisce il seguente
risultato
w m=w m−1−k m[ x m−y t mw m−1] (27)
che può essere riscritta in quest'altra forma
w m=w m−1−k m em . (28)
L'equazione (28) rappresenta il risultato finale e cioè la formula che descrive l'aggiornamento dei
coefficienti del filtro.
Algoritmo di aggiornamento RLS(Ricorsive Least Squares)
y m e x m rappresentano i segnali d'ingresso del sistema.
Si inizializzano i seguenti valori
 yy m=I
w 0=w I.
Per m=1,2,.. si calcola
Il vettore di guadagno
−1  yy m−1 y m
k m=
.
1−1 y T m yy m−1 y  m
L'errore
em=x  m−w T m−1 y m.
Si aggiornano i coefficienti
w m=w m−1−k m em .
Si aggiorna l'inverso della matrice di correlazione
−1
−1
T
 yy m=  yy m−1− k m y m  yy m−1.
10
Filtri LMS nel dominio della frequenza
Unconstrained Frequency-Domain Least Mean
Squares
UFLMS
Introduzione
L'algoritmo qui presentato è basato sulla tecnica di “overlap 'n save” usata per il filtraggio nel
dominio della frequenza. Nel dominio del tempo tali filtri sono realizzati tramite FIR, la stima dei
parametri è ottenuta invece per mezzo di algoritmi adattativi come l'LMS, il “gradient lattice” o
“least squares lattice algorithms”. La complessità di questi metodi aumenta linearmente con la
lunghezza dei filtri. Un primo algoritmo nel dominio della frequenza è stato presentato da “Ferrara”
ed offre delle buone prestazioni, in termini di complessità, per filtri composti da numerosi elementi.
Comunque, il problema principale dell'algoritmo FLMS(Frequency-Domain Least Mean Squares) è
nella propria lentezza di convergenza per segnali in ingresso fortemente correlati, come nel caso del
“time domain LMS”. L'FLMS proposto da Ferrara richiede 5 DFT o IDFT per ogni iterazione e due
di loro sono necessarie per impostare un vincolo nel dominio del tempo, siccome gli ultimi N
coefficienti della risposta impulsiva stimata devono essere posti a valore nullo, in quanto si sta
utilizzando la tecnica “overlap 'n save”. Successivamente grazie al lavoro svolto da “David
Mansourand” e “Augustine H. Gray” si è potuto ridurre il numero di DFT e IDFT per ciclo,
passando da 5 a 3. L'algoritmo proposto converge più velocemente alla soluzione ottima di Wiener
senza alcun bisogno di vincoli e riduce di molto la complessità rispetto alla precedente soluzione.
Filtri adattativi nel dominio della frequenza
Per semplificare l'introduzione dell'algoritmo nel dominio della frequenza sarà presentata un breve
ripasso dell'algoritmo nel dominio del tempo.
I coefficienti del filtro sono aggiornati ad ogni iterazione in accordo alla seguente relazione:
 i1=i 2 e i x i. (1)
Dove x i è il vettore dei campioni in ingressi del filtro. L'errore ad ogni iterazione è definito come:
e i=d i− y i. (2)
Dove:
t
y i= x i  i . (3)
E d i è la risposta desiderata. Nel dominio del tempo e i e y i sono dei scalari. Nel dominio
della frequenza invece sono dei vettori. Le lettere maiuscole indicheranno le variabili nel dominio
della frequenza, le minuscole nel dominio del tempo e quelle in grassetto i vettori o matrici.
L'algoritmo UFLMS è basato sulla convoluzione rapida detta “overlap 'n save”. Per chiarificare
questo algoritmo si introdurrà tale tecnica di convoluzione usando un'annotazione matriciale.
Assumiamo che l'ordine del filtro numerico sia pari a N. Si definisce allora w vettore della
risposta impulsiva composto da 2N elementi nel seguente modo:
11
w i=w i
i=0,1 , ... , N
(4)
w i=0
i= N 1,... ,2 N −1
Il flusso dei dati in ingresso x(n) è segmentato in un vettore di 2N elementi con N elementi di
overlap come segue:
x n= x k N n  n=0,1 , ... , N −1
(5)
k =0,1 , ... ,∞
Usando l'annotazione matriciale della convoluzione circolare e omettendo per semplicità k il vettore
dell'uscita y k sarà:
(6)
Possiamo vedere per ispezione che i primi N punti del vettore w sono tutti nulli e gli ultimi N punti
del vettore in uscita y k sono il risultato della convoluzione lineare. Nell'overlap 'n save tali
operazioni sono eseguite tramite l'ausilio dell'FFT. Il quale migliora di molto l'efficenza
computazionale. Per ogni iterazione abbiamo 2N dati in ingresso e N dati in uscita. Per questa
ragione i dati in ingresso sono sovrapposti ogni N punti. F È una matrice simmetrica 2Nx2N i cui
elementi sono uguali a F kj =exp −i 2 / 2Nk j k , j=0,1 ,... ,2 N −1. Quando viene eseguito
il prodotto tra un vettore di dimensione 2N e F il risultato non è altro che la DFT del vettore. I
coefficienti dell'inverso di F sono pari a F kj =1/2N exp i2 /2N k j k , j=0,1 , ...,2 N −1.
x k è la matrice circolare definita per mezzo della 5 e della 6 per ogni iterazione.
Adesso definiamo:
X k =F x k F
−1
(7)
X k è una matrice diagonale i quali elementi sono la DFT della prima riga della matrice circolare
x k . Si ha inoltre che X k =F xk F −1 (8).
h è una matrice di finestratura di dimensione 2Nx2N dove soltanto il blocco in basso a destra NxN
è pari alla matrice identità mentre gli altri sono tutti nulli.
h=0 0 (10)
0 I
Si definisce H come il seguente prodotto:
H =F h F−1 (11)
H è una matrice circolare dove la prima riga è la DFT del vettore ( 0 0 ...0 1 1 ... 1).
 è una matrice diagonale 2Nx2N e i suoi elementi rappresentano la costante di convergenza per
ogni componente in frequenza nell'algoritmo UFLMS.
d k , w k , e k , y k Sono rispettivamente i vettori di dimensione 2N della risposta desiderata, dei
coefficienti del filtro FIR, dell'errore e dell'uscita. Con D k ,W k , E k , Y k rappresentano le DFT dei
vettori precedentemente introdotti.
L'uscita del filtro risulta essere per ciò:
12
Y k= X k W k (12)
e nel dominio del tempo:
y k =F
−1
X k W k (13)
L'errore nel dominio del tempo prima della finestratura è:
e ' k =d k − y k (14)
In accordo con il metodo dell'overlap 'n save soltanto gli ultimi N punti del vettore y k sono
l'uscita del filtro ottenuti tramite la convoluzione lineare. Allora l'errore deve essere finestrato per h
e k=hd k − y k =h d k −F −1 X k W k . (15)
E' possibile ottenere la DFT dell'errore moltiplicando entrambi i membri per F.
E k =F e k =F h d k −F
−1
X k W k
−1
E k =F e k F =F h F d k −F F
−1
X k W k  (16)
E k =H  D k− X k W k 
Per derivare l'algoritmo adattativo si seguiranno i stessi passi fatti per ottenere l'algoritmo LMS nel
dominio del tempo. Si assume che l'ingresso sia stazionario e che il sistema da stimare sia tempo
invariante. L'errore quadratico per ogni iterazione è:
∣E 2∣= E2 E 2 = DT −W T XT  H T H  D −W X = DT −W T XT  H  D −W X  (17)
k
k
k
k
k
k
k
k
k
k
k
k
Si assume al momento che il vettore dei pesi w k indipendente da
medio sarà allora:
2
T
T
T
T
{∣E ∣}={ D H D }−2 { D H X }W W  { X H X }W (18)
k
k
k
k
k
k
k
k
k
k
k
k
x k . L'errore quadratico
k
Il vettore ottimale W ' è ottenuto ponendo il gradiente rispetto a W k a zero:
∇ W Tk −2 { DTk H X k }2 W Tk { XTk H X k }=0 (19)
L'UFLMS usa il metodo del gradiente per risolvere la (18). In accordo a questo metodo il
successivo vettore dei pesi W k 1 è uguale all'attuale vettore dei pesi W k più un certo
incremento proporzionale al valore negativo del gradiente:
W k 1=W k − ∇ W k (20)
Nell'algoritmo UFLMS, il vettore del gradiente è stimato per mezzo del suo valore istantaneo ad
ogni iterazione k. Come nell'algoritmo LMS, questo gradiente istantaneo è usato per approssimare il
gradiente reale. Dalla (18), l'equazione che aggiorna i pesi del filtro sarà:
W =W 2  XT  H D −H X W =W 2 XT E (21)
k 1
k
k
k
k
k
k
k
k
Dove la matrice diagonale  è la costante di convergenza come nell'algoritmo LMS. Come si
potrà vedere si potrà scegliere differenti coefficienti di convergenza per differenti frequenze.
L'immagine in basso rappresenta la struttura del filtro:
13
fig.4 schema di un filtro UFLMS
Versione Normalizzata dell'algoritmo UFLMS
Unconstrained Frequency-Domain Normalize Least Mean Squares
Avvolte risulta difficoltoso assegnare dei opportuni valori alla matrice diagonale  , in quanto
bisogna conoscere le caratteristiche statistiche del segnale in ingresso. Per questo per
l'implementazione del progetto si è scelto di implementare una versione normalizzata di tale
algoritmo. In questo caso invece di utilizzare una matrice dei coefficienti di convergenza si usa uno
scalare e si applica in seguito una normalizzazione in base al segnale in ingresso in accordo
all'algoritmo NLMS(normalize least mean squares) nel dominio nel tempo.
Nel caso NLMS l'aggiornamento dei pesi avviene secondo la seguente relazione:
w k 1=w k 
ek xk
(22)
t
xk x k
Perciò nel dominio della frequenza si applicherà la stessa normalizzazione in base all'energia della
sequenza in ingresso. Tale soluzione risulta essere non ottimale in quanto ogni componente spettrale
è moltiplicata per lo stesso passo di convergenza, ma permette di ottenere una buona stabilità del
filtro. Si ricorda che X k è una matrice diagonale i quali elementi sono la DFT della prima riga
della matrice circolare x k .
Quindi nel dominio della frequenza si otterrà:
XTk E k
W k 1=W k −
(23)
tr [ XTk X k ]
14
Modello per il problema della stima del ritardo
Time Dalay Estimation TDE
Modello ideale in campo libero
Per una data sorgente s(n) che si propaga attraverso un generico spazio libero in presenza di rumore,
il segnale acquisito all'i-esimo microfono può essere espresso come:
x i n=i s n− ibi n , (1)
dove  i è fattore di attenuazione che rappresenta le perdite di propagazione nel mezzo, i è il
ritardo di propagazione della sorgente al microfono ed infine bi rappresenta il rumore additivo.
In questo modello si assume che s n , b1 n e b2 n siano a media nulla, incorrelati,
stazionari e a distribuzione normale. Il relativo ritardo tra i due segnali dei microfoni 1 e 2 è definito
come:
12=1−2. (2)
Modello per ambienti riverberanti
Sfortunatamente, in un ambiente acustico reale dobbiamo tenere in considerazione gli effetti dei
riverberi della stanza. Allora un modello più complesso dei segnali microfonici può essere espresso
come segue:
x i n=g i∗snbi n , (3)
Dove g i rappresenta la risposta impulsiva tra la sorgente e il microfono i-esimo. Per di più,
b1 n e b2 n potrebbero essere correlate, questo è il caso in cui il rumore è direzionale come
ad esempio una ventola di un computer.
15
Metodo adattativo
Principio
Si assume che la stanza, in cui si effettuano le misure, sia un sistema lineare e tempo invariante.
Osservando il modello riverberante e dal fatto che x 1∗g 2=s∗g 1∗g 2=x 2∗g1 , in assenza di
rumore additivo, si ha la seguente relazione:
T
T
T
x nu= x 1 n g2− x 2 n g1=0 (1)
dove:
T
T
x n=[ x 1 n , X 2 n] (2)
T
T
u=[g2, −g1 ] (3)
Dalla (1) può essere ricavato che R nu=0 , in cui R n=E {x n x T n} è la matrice di
covarianza del segnale microfonico x T n . Questo implica che il vettore u (contenente le due
risposte impulsive) è l'autovettore della matrice di covarianza R n corrispondente all'autovalore
nullo. Inoltre le due risposte impulsive g1 e g2 non hanno zeri in commune, la matrice di
autocorrelazione del segnale di sorgente s n è a rango pieno e la matrice di covarianza R n
ha uno ed un solo autovalore uguale a 0.
Algoritmo adattativo
In ordine ad una efficiente stima dell'autovettore u corrispondente al minimo autovalore di
R n , l'algoritmo LMS è spesso usato. Il segnale d'errore è
en=
 T n x n
u
, (4)
∥u n∥
e l'algoritmo LMS può essere espresso cosi

 n−en ∇ en , (5)
un1=
u
dove  , il passo di convergenza, è una costante positiva
∇ en=
[
]

1
un
xn−e n
. (6)

 n∥
∥u∥
∥u
Sostituendo la (4) e la (6) nella (5) per n ∞ la convergenza ci restituisce:
R
u ∞
u ∞
=E {e2 n}
, (7)

∥u ∞∥
∥u∞∥

Il quale è il risultato desiderato: un
converge in media all'autovettore di R corrispondente al
2
più piccolo autovalore pari a
E {e n} . Per evitare la propagazione di errori di

approssimazione, la normalizzazione è imposta sul vettore un1
ad ogni passo di
aggiornamento. Finalmente l'equazione di aggiornamento è data da
 n−en ∇ e n
u

un1=
. (8)
 n−en ∇ e n∥
∥u
Se il più piccolo autovalore è pari a zero, il quale è il nostro caso, allora l'algoritmo può essere
semplificato come segue:
16
en=u t n xn , (9)
u n−en xn

un1=
. (10)
∥u n−en xn∥
Il nostro obbiettivo non è stimare accuratamente le 2 risposte impulsive ma il tempo di ritardo,
solamente i due percorsi diretti sono d'interesse. Inoltre per prendere in considerazione ritardi
 M /2 0=1 , dove M è la lunghezza della risposta
relativi positivi e negativi, si inizializza u
 M/ 2 0=1 sarà considerata come una stima del percorso diretto g2 e durante
impulsiva. u
l'adattamento dei coefficienti esso sarà dominante rispetto agli altri M-1 coefficienti della prima

meta di un
(contenente una stima di g2 ). Un effetto “specchio” nella seconda meta di

un
(contenente una stima di −g1 ): un picco negativo sarà dominante il quale è una stima
del percorso diretto di −g1 . Cosi la stima del ritardo non sarà altro che la differenza tra gli indici
di questi due picchi.
17
Metodo adattativo tramite filtri RLS
L'algoritmo
Nel caso del filtro RLS si seguiranno i stessi passi fatti nel caso LMS e verranno riportate in questa
sezione soltanto le modifiche apportate all'algoritmo classico.
Con x 1 e x 2 indicano i due vettori contenenti i campioni del segnale in ingresso, con w 1 e
w 2 i vettori contenenti i pesi dei 2 filtri RLS. Tutti i vettori sono di dimensione N. A questo punto
si definisce il vettore w di dimensione 2N come la concatenazione di w 1 e w 2 . Allo stesso
modo si definisce il vettore x come la concatenazione di x 1 e x 2 . Perciò la matricie di
correlazione avrà una dimensione pari a 2Nx2N.
Il segnale d'errore è calcolato allo stesso modo del caso LMS:
e  m=
w T m−1 x  m
(1)
∥w m−1∥
Una volta calcolati i nuovi pesi del filtro tramite l'algoritmo RLS si applica ad essi la
normalizzazione per evitare il propagarsi di errori di approssimazione.
w m=
w  m
(2)
∥w  m∥
Algoritmo di aggiornamento RLS
Si inizializzano i seguenti valori
 xx  m= I
(3)
w  N /2=1.
Per m=1,2,.. si calcola
Il vettore di guadagno
−1  xx m−1 x m
k m=
. (4)
1−1 x T m xx m−1 x  x 
L'errore
w T m−1 x  m
e  m=
(5)
∥w m−1∥
Si aggiornano i coefficienti
w m=w  m−1−k  m e m. (6)
Si aggiorna l'inverso della matrice di correlazione
−1
−1
T
 xx m=  xx m−1− k m x m xx m−1. (7)
Si normalizzano i pesi dei filtri
w  m
w m=
(8)
∥w  m∥
Implementazione in matlab
18
Per prima cosa si descriverà il significato di ogni vettore,matrice e variabile istanziati.
-”x1” e “x2” sono i due vettori relativi all'ingresso dei due filtri RLS;
-”x” è il vettore ottenuto per concatenazione dei primi due;
-”w”=[w1,w2] è il vettore dei pesi dei due filtri di dimensione 2N. Il coefficiente di posizione N/2 è
inizializzato a 1;
-”PHI” è l'inverso della matrice di correlazione ;
-”lamba” è l'inverso del fattore di dimenticanza;
-”e” è l'errore;
-”df” è la variabile
-”norma” è il valore numerico utilizzato per effettuare la normalizzazione e vale sqrt(w'*w) tale
variabile è inizializzata a uno per evitare errori nel primo ciclo;
-dim è un valore numerico utilizzato per impostare la dimensione dei vettori e delle matrici. Esso
vale 2N.
Ad ogni ciclo viene eseguito il seguente codice:
x1=[ingresso;x1(1:(dim_mezzi-1))];
x2=[ingresso;x2(1:(dim_mezzi-1))];
x=[x1;x2];
e=(w'*x)/norma;
df=1/(lambda + x'*PHI*x);
k=PHI*x*df;
PHI=(PHI-PHI*x*df*x'*PHI);
w=w-k*e(n);
norma=sqrt(w'*w);
w=w/norma;
Come si può notare il codice è molto semplice e intuitivo. Tuttavia questo algoritmo è molto
pesante dal punto di vista computazionale e già con filtri composti da 128 elementi si è potuto
notare un notevole dispendio delle risorse del calcolatore. Comunque può essere un buono
strumento per valutare le prestazioni dell'implementazione tramite filtri UFLMS normalizzati.
19
Metodo adattativo tramite filtri UFLMS normalizzati
Struttura del filtro per il calcolo del ritardo nel dominio della frequenza
L'immagine sottostante rappresenta la struttura del filtro utilizzato nell'algoritmo per la stima del
ritardo.
fig.5 schema del localizzatore
Dal grafico si può notare come si utilizzino due filtri in parallelo e non uno solo come nei casi
precedenti. Ciò è stato fatto perché è impensabile utilizzare un unico vettore dei pesi W che
contenga le due risposte impulsive, altrimenti l'FFT o l'IFFT restituirà dei valori sfalsati. Un'altra
motivazione sta nel fatto che l'errore nel caso del domino della frequenza è un vettore e non più uno
scalare.
La variabile q, presente nel grafico, è utilizzata per eseguire la normalizzazione richiesta
dall'algoritmo ed è inizializzata ad 1 per evitare errori nel primo ciclo di esecuzione dell'algoritmo.

WT1 W 1W T2 W 2 (1)
q=
2N
Dove W 1 e W 2 sono l'FFT di w 1 e w 2 .
Una volta calcolate le uscite dei filtri tramite la convoluzione circolare l'errore è definito come la
somma dei due vettori d'uscita normalizzati per il valore di q. Siccome si sta applicando l'overlap 'n
save soltanto gli ultimi campioni del vettore d'errore sono utili all'aggiornamento dei pesi.
e= y 1 y 2/ q (2)
Dopo la finestratura si esegue il calcolo dell'FFT dell'errore che sarà usata in seguito per aggiornare
i coefficienti del filtro.
XT1 m E  m
W 1 m1=W 1 m−
(3)
tr [ XT1  m X 1  m]
In questo caso come spiegato in precedenza  è uno scalare e non un vettore.
20
Una volta finito di aggiornare i coefficienti del filtro si aggiorna il valore di q tramite la (1) dopo di
che si effettua la normalizzazione dei coefficienti del filtro nel dominio della frequenza.
W 1 m1=
W 1  m1
(4)
q
L'algoritmo finora esposto presenta ancora delle problematiche, la quale la prima è che in presenza
di rumore in ingresso esso diverge a valori non desiderati dei pesi dei due filtri. Per risolvere questo
problema è stato sufficiente inserire un livello di soglia sul valore rms della sequenza in ingresso. In
questo modo si possono discriminare i frame dove non vi è una presenza di segnale e annullare cosi
ogni operazione di aggiornamento. La seconda problematica riscontrata riguarda l'incapacità del
filtro di inseguire le sorgenti in movimento. Di fatto il filtro riesce a stimare correttamente il ritardo
grazie alla condizione iniziale imposta ai due vettori w 1 e w 2 , se noi spostiamo la sorgente
sonora dopo che i filtri hanno stimato una determinata risposta, tale condizione non è più soddisfatta
e l'algoritmo impiegherà molto più tempo a trovare una nuova soluzione. Per risolvere questo
problema si possono utilizzare due metodi. Il primo consiste nel resettare lo stato dei filtri dopo un
periodo di tempo prefissato. L'altro metodo invece forza i pesi dei filtri a tornare allo stato iniziale
aggiornando i coefficienti nel seguente modo:
w1 [i]=w1 [i]−w 1 [i]/k i=0,1 ,... ,2 N −1
w 2 [i]=w 2 [i]−w2 [i]/ k i=0,1 , ...,2 N −1 e i≠N /2 (5)
w 2 [ N /2]=w 2 [ N /2]− w2 [ N / 2]−1/k
“k” in questo caso è un valore scalare scelto a piacere, l'importante è che non sia troppo basso da
impedire all'algoritmo di convergere o troppo alto da vanificare l'utilizzo di tale soluzione.
Quest'ultimo metodo presentato e quello implementato successivamente nel software scritto in c++
e dai test fatti sembra dare degli ottimi risultati.
Algoritmo implementato
Si calcola l'uscita nel dominio della frequenza.
Y 1 m=W 1  m X 1  m
(6)
Y 2  m=W 2  m X 2 m
Si calcola l'errore nel dominio del tempo per cui bisogna eseguire l'IFFT delle uscite trovate.
e  m=
h  y 1 m y 2 m
(7)
q
Una volta calcolato l'errore si aggiornano i coefficienti del filtro.
XT1 m E  m
W 1 m1=W 1 m−
(8)
tr [ XT1  m X 1  m]
Si ricava il nuovo q.
q=

WT1 m1W 1  m1W T2 m1W 2  m1 (9)
2N
Si normalizzano i vettori.
W 1 m1=
W 1  m1
(10)
q
Si applica la soluzione trovata in precedenza per forzare i pesi a tornare allo stato iniziale.
21
w1 [i]=w1 [i]−w 1 [i]/k i=0,1 ,... ,2 N −1
w 2 [i]=w 2 [i]−w2 [i]/ k i=0,1 , ...,2 N −1 e i≠N /2 (11)
w 2 [ N /2]=w 2 [ N /2]− w2 [ N / 2]−1/k
Implementazione in matlab
In questa implementazione dell'algoritmo in matlab è stato omesso l'ultimo passo dell'algoritmo che
invece è presente nel progetto finale.
Di seguito è riportata la lista di tutti i vettori e variabili utilizzati nell'implementazione. Le variabili
o vettori indicati con lettere maiuscole sono nel dominio della frequenza.
-x1,x2,X1 e X2 sono rispettivamente i vettori dei segnali in ingresso nel dominio del tempo e nel
dominio della frequenza.
-y1,y2,Y1 e Y2 sono i vettori del segnale in uscita ottenuto tramite il metodo dell'overlap 'n save.
-w1,w2,W1 e W2 sono i vettori dei pesi del filtro.
-e,E sono i vettori d'errore.
-q è la variabile utilizzata per la normalizzazione dei pesi del filtro.
Adesso viene riportato il codice eseguito ad ogni ciclo dell'algoritmo.
X1=fft(x1);
X2=fft(x2);
Y1=X1.*W1;
y1=ifft(Y1);
Y2=X2.*W2;
y2=ifft(Y2);
e=(y1+y2)/q;
e=[zeros(N,1);e((N+1):(2*N))];
E=fft(e);
W1=W1-(mu*conj(X1).*E)/((X1'*X1)+1);
W2=W2-(mu*conj(X2).*E)/((X2'*X2)+1);
q=sqrt(([W1;W2]'*[W1;W2])/(2*N));
W1=W1/q;
W2=W2/q;
22
Risultati
In questa parte verrano mostrati alcuni risultati sulla convergenza dell'algoritmo proposto
confrontandolo con l'analogo RLS. Nel test i due filtri FIR hanno una dimensione pari a 32, per
l'algoritmo RLS si è scelto un fattore di dimenticanza pari a 5, mentre per l'algoritmo UFLMS si è
scelto un  pari a 30. Come segnale d'ingresso si è usato una sequenza di rumore bianco e infine
sono state fatte diverse prove con differenti valori di ritardo.
La figura sottostante mostra l'andamento dell'orrore quadratico medio per un ritardo nullo. In verde
è riportata la risposta del filtro UFLMS, mentre in blu l'RLS. In questo caso i due filtri convergono
con la stessa velocità. Nella figura si nota come d'improvviso l'andamento dell'errore quadratico
medio rimane fisso ad un valore costante, ciò è dovuto alla massima risoluzione che raggiunge il
tipo di dato utilizzato, in questo caso si sono utilizzate variabili di tipo double. Qui tale valore
costante è da attribuire ad errori di approssimazione. Nella figura nell'asse delle ordinate è riportato
il valore quadratico medio dell'errore e nelle ascisse l'intervallo di campionamento. Nella prova è
stato generato un rumore bianco come segnale di test e delle risposte impulsive pari a un campione
unitario. Visto la pesantezza computazionale dei filtri RLS si è scelto una dimensione del filtro pari
a 32 campioni.
La fig.6 rappresenta l'andamento dell'errore quadratico medio in presenza di ritardo nullo.
fig.6
In presenza di un ritardo pari a un campione. In questo caso in presenza di un lieve ritardo il filtro
UFLMS ha un andamento quasi uguale all'RLS per un primo tratto.
23
fig.7
Le due figure (fig.8 e fig.9) sottostanti descrivono l'andamento dell'errore per ritardi pari a 3 e 6
campioni. In questo caso le prestazione per ritardi medi sembrano le stesse.
fig.8
24
fig.9
In presenza di un forte ritardo di 14 campioni (fig.10) il filtro UFLMS sembra avere un andamento
leggermente migliore rispetto all'RLS.
fig.10
Un altro test è stato effettuato tramite 2 risposte impulsive simulate di una sala, utilizzando filtri
FIR di dimensione 512 e  pari a 230. In questo caso si è evitato di fare la simulazione anche per
filtri di tipo RLS visto la pesantezza computazionale che caratterizza tale algoritmo.
25
Le due figure sottostanti mostrano le due risposte impulsive ottenute tramite Roomsim.
fig.11
fig.12
La figura sottostante mostra i coefficienti dei due filtri al termine della stima. Si può notare come in
presenza di riverberi vi siano diversi picchi nella stima.
26
fig.13
Andamento dell'errore. Tale livello nell'errore quadratico medio è anche dovuto al fatto che la
lunghezza del filtro è inferiore rispetto alla lunghezza in campioni delle due risposte impulsive
simulate e alla presenza dei riverberi. Si può notare come in un caso più vicino alla realtà l'errore
quadratico medio converge ad un livello più alto, in questo caso 50dB.
fig.14
27
Implementazione dell'algoritmo in C++
Introduzione
L'intero progetto è costituito da 7 classi di cui soltanto una implementa l'algoritmo finora esposto, le
altre sono usate per realizzare il form, gestire l'aggiornamento del display, gestione della webcam e
della scheda audio. Nella stesura del codice sono state usate diverse librerie ed esse sono le ipp per
l'elaborazione numerica dei segnali in ingresso e la portaudio per la gestione del dispositivo audio.
Come detto in precedenza soltanto una classe implementa l'algoritmo vero è proprio ed è
“DSP.cpp”. Essa è costituita da due metodi principali che eseguono un primo filtraggio del segnare
in ingresso per eliminare possibili componenti continue e rumori a bassa e alta frequenza ed un
metodo che ci restituisce il risultato della stima.
Per avere una maggiore portabilità del software sono state utilizzate le librerie IPP per processori
generici a 32bit i cui file .lib e .dll sono indicati dal suffisso “px” nel nome.
Compilatore
Per lo sviluppo del progetto è stato utilizzato il compilatore della Borland il c++ builder 2006. Tale
compilatore è risultato essere abbastanza intuitivo nell'utilizzo, soprattutto nella realizzazione delle
form e nella gestione dei relativi eventi. Anche la realizzazione e la gestione dei threads è risultata
abbastanza agevolata, visto che la programmazione multithreading in windows è sempre un
argomento ostico. In appendice è riportata una breve guida alla programmazione multithreading in
C++ Builder.
PortAudio
Portaudio è una libreria audio libera e gratuita. Essa fornisce delle semplici API per la registrazione
o riproduzione dell'audio usando una semplice CallBack. Nel mondo dell'informatica esistono
moltissime API per la gestione dell'audio come ad esempio DirectSound™ su Microsoft
Windows™, il Macintosh™ Sound Manager, e l'OSS su Linux.
fig.15
In poche parole portaudio fa da wrapper alle API native redendo molto più semplice la
programmazione.
L'architettura di portaudio prevede due astrazioni: Audio Devices e Audio Streams. Gli audio
devices sono i dispositivi di input e output. Portaudio offre delle funzione per enumerare e
interrogare i dispositivi presenti nel calcolatore. L'audio stream gestisce le attivita di input e output
tra un dispositivo d'ingresso e uno d'uscita e processa l'audio da un'applicazione client per mezzo di
28
una callback che è associata allo stream quando questo viene aperto.
Prima di utilizzare le librerie bisogna chiamare il metodo Pa_Initialize() e quando le librerie non
sono più richieste si deve richiamare Pa_Terminate().
Pa_CountDevices() ritorna il numero di dispositivi installati e Pa_GetDeviceInfo(id) ritorna un
puntatore ad un dato strutturato il quale contiene delle informazioni riguardo il dispositivo
utilizzato. Esso include il nome del dispositivo, il massimo numero di ingressi e uscite, la massima
frequenza di campionamento e i tipi di dato supportati.
Lo stream
puo essere aperto con Pa_OpenStream()
o
Pa_OpenDefaultStream().
Pa_OpenDefaultStream() apre lo stream sul dispositivo di default e richiede un numero di parametri
inferiore rispetto a Pa_OpenStream(). Nel progetto è stato usato Pa_OpenStream() permettendo cosi
di scegliere il dispositivo.
Quando lo stream è stato avviato, le attività di I/O hanno inizio e la funzione di callback è
richiamata ripetutamente con un puntatore al buffer di uscita e uno al buffer d'ingresso. Viene anche
passato un time-stamp che rappresenta il numero di frame generati e può essere usato per
sincronizzare l'audio con il video o con eventi MIDI. La callback definita dall'utonto ha il seguente
prototipo che deve essere seguito:
typedef int (PortAudioCallback){
void *inputBuffer,
void *outputBuffer,
unsigned long framesPerBuffer,
PaTimestamp outTime,
void *userData };
La callback ritorna ad ogni ciclo il valore 0. Per terminare lo stream bisogna chiamare la funzione
Pa_CloseStream().
Integrated Performance Primitives
Come detto in precedenza le IPP sono delle API per la multimedialità e coprono diversi campi di
applicazione, come l'audio, l'elaborazione delle immagini, la crittografia, ecc...
Queste librerie non possono essere utilizzate direttamente in un compilatore della Borland ma
bisogna per prima cosa rigenerare tutti i file “.lib” partendo dalle DLL. Per compiere tale
operazione bisogna usare da terminale il comando implib che genera le nuove “.lib”. Il file
implib.exe è presente all'interno della directory del compilatore e non è un comando di windows. Le
DLL di interesse sono quelle con il suffisso px(il suffisso px indica che sono per processori generici
a 32bit) nella cartella d'installazione delle IPP. Fatta questa operazione si copiano le DLL nella
cartella del nuovo progetto creato dal compilatore insieme alla DLL “ippcore.dll”. All'interno del
progetto invece bisogna importare tutti i file “.lib” generati, il file “ipp.h” e il file “ipp_px.h”.
Seguendo questi passi c'è un'alta probabilità che il tutto funzioni.
Per la maggior parte delle primitive delle ipp, vi sono funzioni di inizializzazione per allocare le
risorse necessarie e funzioni per deallocare. Per questo fatto in un primo impatto sembrano essere
un poco ostiche al programmatore.
29
Form
Dal form principale è possibile impostare tutti i parametri della stima e il dispositivo di acquisizione
tramite i vari campi. Quando si modificano i valori nei campi non si fa altro che richiamare i metodi
di set delle classi SchedaAudio e DSP. La classe Form crea anche un oggetto di tipo
DisplayManager, il cui riferimento è passato anche all'unica istanza di SchedaAudio. Tale oggetto
lancia un thread che permette la gestione dei due display e dei campi di testo che mostrano il ritardo
e l'angolo stimato, con un refresh di circa 40 mS. Altra istanza creata dalla Form è una seconda
finestra che mostra il video catturato dalla webcam con una freccia che indica la provenienza del
segnale audio catturato dal dispositivo.
fig.16
DisplayManager e PrintDisplay
DispleyManager è la classe che permette di gestire i due display e i campi di testo che mostrano
l'angolo e il ritardo stimato. Dopo la sua creazione l'oggetto della classe Form richiama alcuni
metodi di set per passare i riferimenti agli oggetti gestiti da DisplayManager. Quando si da avvio
all'acquisizione del segnale audio si lancia un thread che ogni 40ms aggiorna i risultati sullo
schermo. I valori sono da aggiornare sono salvati in un buffer gestito come sessione critica.
Prima di avviare l'acquisizione viene sempre impostato il buffer tramite il metodo setFrameSize,
dopodiché si lancia il thread PrintDisplay. La sezione critica è necessaria per evitare errori di lettura
da parte del thread, perchè è l'istanza di DisplayManager che prende i dati da scrivere sul buffer
tramite il metodo “aggiorna”.
Quando si chiude lo stream audio per evitare un inutile spreco di risorse il thread viene terminato.
SchedaAudio
SchedaAudio gestisce il dispositivo tramite l'ausilio di portaudio. Quando si utilizza questo tipo di
librerie bisogna definire un dato strutturato da passare come parametro ad un metodo statico.
Questo metodo statico, nominato CallbackRecord nel codice, viene richiamato ogni volta che il
dispositivo audio ha acquisito un certo numero di campioni.
30
All'interno della classe vi sono diversi metodi di set che impostano i parametri dell'algoritmo, questi
metodi a sua richiamano i corrispettivi metodi di set della classe DSP, visto che è quest'ultima che
esegue la stima del ritardo.
Vi sono poi due metodi nominati “record” e “stop”. Record inizializza il display e l'istanza di DSP,
mentre Stop termina lo stream in ingresso e blocca l'esecuzione dell'algoritmo e l'aggiornamento del
display.
Come detto in precedenza il metodo statico CallbackRecord viene chiamato ogni volta che sono
stati acquisiti un certo numero di campioni e tramite il dato strutturato “data” e possibile richiamare
i metodi di DSP e DisplayManager a ogni ciclo per la stima e l'aggiornamento.
DSP
DSP è la classe che implementa l'algoritmo vero e proprio ed è composta da due metodi principali e
altrettanti per il settaggio.
Il primo metodo esegue il filtraggio del segnale richiamando le primitive delle ipp. Il filtraggio è
eseguito tramite due filtri IIR, un passa basso e un passa alto di Butterworth del 3' ordine in modo
da limitare la banda del segnale eliminando così la presenza di componenti continue e di rumore a
bassa e alta frequenza. Prima del filtraggio sono richiamati dei metodi che allocano la memoria
necessaria per eseguire il filtraggio e calcolano i coefficienti dei filtri. Un'altro metodo viene
richiamato al termine per liberare la memoria allocata dal metodo di setup precedentemente
introdotto.
Il secondo metodo esegue la stima del ritardo secondo l'algoritmo descritto nella relazione. Tale
metodo prende come parametro in ingresso il risultato del filtraggio. Anche per questo metodo
esisto altri di setup utilizzati per allocare la memoria necessaria per eseguire le FFT, IFFT, per i vari
parametri dei filtri adattativi e cosi via.
Di seguito è riportato il codice del metodo “estimation” della classe DSP.
float* DSP::estimation(float *in){
“in” è il vettore in ingresso e contiene i campioni del segnale interlacciati dopo il filtraggio. Su
questo vettore viene calcolato il valore rms per poter discriminare frame rumorosi.
rms=0;
for (int i = 0; i < 2*frameSize; i++) {
rms+=(in[i]*in[i]);
}
rms=sqrt(rms/(2*frameSize));
Da qui in poi viene eseguita la convoluzione circolare tramite l'overlap 'n save. In minuscolo sono
indicate le variabili nel dominio del tempo e in maiuscolo nel dominio della frequenza. Il numero
indica il canale e la “i” come suffisso indica i valori immaginari.
for (int i = 0; i < frameSize; i++) {
in1[i] = in1[frameSize+i];
in2[i] = in2[frameSize+i];
in1[frameSize+i] = in[i*2];
31
in2[frameSize+i] = in[i*2+1];
}
status = ippsFFTFwd_CToC_32f(in1,in1i,IN1,IN1i,spec,NULL);
status = ippsFFTFwd_CToC_32f(in2,in2i,IN2,IN2i,spec,NULL);
for (int i = 0; i < (2*frameSize); i++) {
OUT1[i] = IN1[i]*W1[i]-IN1i[i]*W1i[i];
OUT1i[i] = IN1[i]*W1i[i]+IN1[i]*W1i[i];
OUT2[i] = IN2[i]*W2[i]-IN2i[i]*W2i[i];
OUT2i[i] = IN2[i]*W2i[i]+IN2[i]*W2i[i];
}
Si calcola l'IFFT dei vettori d'uscita per ottenere il vettore d'errore tramite la loro somma
status = ippsFFTInv_CToC_32f(OUT1,OUT1i,out1,out1i,spec,NULL);
status = ippsFFTInv_CToC_32f(OUT2,OUT2i,out2,out2i,spec,NULL);
Del vettore d'errore si aggiornano soltanto gli elementi dell'ultima metà. In questo modo i primi
coefficienti rimangono sempre a valore nullo, evitando cosi di effettuare la finestatura del segnale.
for (int i = 0; i < frameSize; i++) {
e[frameSize+i]=(out1[frameSize+i]+out2[frameSize+i])/q;
}
status = ippsFFTFwd_CToC_32f(e,ei,E,Ei,spec,NULL);
Se il valore rms è superiore al valore di soglia si può procedere con l'aggiornamento dei pesi
if(rms>threshold){
Le variabili supporto1 e supporto2 non fanno altro che salvare il valore della norma quadra dei
vettori che contengono l'FFT dei segnali d'ingresso richiesti per il calcolo della versione
normalizzata dell'UFLMS.
supporto1=0;
supporto2=0;
for (int i = 0; i < (frameSize*2); i++) {
supporto1+=(IN1[i]*IN1[i]+IN1i[i]*IN1i[i]);
supporto2+=(IN2[i]*IN2[i]+IN2i[i]*IN2i[i]);
}
Si aggiornano i pesi dei filtri nel dominio della frequenza
for (int i = 0; i < (frameSize*2); i++) {
W1[i] = W1[i] - mu*(IN1[i]*E[i]+IN1i[i]*Ei[i])/supporto1;
W1i[i]= W1i[i] - mu*(IN1[i]*Ei[i]-IN1i[i]*E[i])/supporto1;
W2[i] = W2[i] - mu*(IN2[i]*E[i]+IN2i[i]*Ei[i])/supporto2;
W2i[i]= W2i[i] - mu*(IN2[i]*Ei[i]-IN2i[i]*E[i])/supporto2;
32
}
Si calcola il nuovo valore di q utilizzato per normalizzare i pesi dei filtri.
q=0;
for (int i = 0; i < (frameSize*2); i++) {
q+=W1[i]*W1[i]+W1i[i]*W1i[i]+W2[i]*W2[i]+W2i[i]*W2i[i];
}
q=sqrt(q/(frameSize*2));
for (int i = 0; i < (frameSize*2); i++) {
W1[i] = W1[i]/q;
W1i[i]= W1i[i]/q;
W2[i] = W2[i]/q;
W2i[i]= W2i[i]/q;
}
}
In questa parte di codice si esegue L'IFFT dei di W1 e W2 per poter forzare i filtri a tornare allo
stato iniziale.
status = ippsFFTInv_CToC_32f(W1,W1i,w1,w1i,spec,NULL);
status = ippsFFTInv_CToC_32f(W2,W2i,w2,w2i,spec,NULL);
for (int i = 0; i < (frameSize*2); i++) {
w1[i]=w1[i]-w1[i]/k;
w1i[i]=w1i[i]-w1i[i]/k;
w2i[i]=w2i[i]-w2i[i]/k;
if(i==(frameSize/2)){
w2[i]=w2[i]-(w2[i]-1)/k;
}else{
w2[i]=w2[i]-w2[i]/k;
}
}
status = ippsFFTFwd_CToC_32f(w1,w1i,W1,W1i,spec,NULL);
status = ippsFFTFwd_CToC_32f(w2,w2i,W2,W2i,spec,NULL);
for (int i=0; i < frameSize; i++){
out_st[i*2]=w1[i]*0.5;
out_st[i*2+1]=w2[i]*0.5;
}
A questo punto si cerca l'indice del minimo in w1. Tramite min si può stimare il ritardo e l'angolo di
provenienza della sorgente sonora.
33
min=0;
for(int i = 0; i < frameSize; i++) {
if(w1[i]<w1[min])min=i;
}
delay=(min-(frameSize/2))/samplesPerSec;
angle=atan((330*delay)/micDistance);
return out_st;
}
c_cap e unit1
“c_cap” permette l'uso della webcam tramite le winapi e mostra il video catturato su di una form.
Sotto al video viene visualizzato un indicatore che ci mostra la provenienza della sorgente.
Conclusioni
La tesina ha avuto come obiettivo la realizzazione di un sistema per la valutazione dei ritardi tramite
stima cieca delle due risposte impulsive tra i due microfoni e la sorgente.
Nelle prove eseguite il software realizzato sembra dare dei buoni risultati e riesce a inseguire
sorgenti in movimento, anche se in presenza di ambienti fortemente riverberanti può dare qualche
volta delle stime errate.
Delle semplici migliorie possono essere ottenute ad esempio inserendo dei blocchi per il
riconoscimento dell'attività vocale per scartare sequenze non d'interesse oppure confrontare più
stime tra di loro per diminuire il numero di errori, basta pensare che con finestre in acquisizione di
256 campioni e una frequenza di campionamento di 96KHz si eseguono 375 stime al secondo.
La figura mostra il programma di localizzazione in esecuzione. In rosso sono rappresentati i
coefficienti del filtro di riferimento mentre in blu l'altro. La linea verde rappresenta il punto di
minimo globale e perciò il valore di ritardo stimato. A destra è rappresentata la form che mostra
l'immagine cattura dalla webcam.
Futuri sviluppi del lavoro svolto in questa tesina riguarderanno la stesura di codice per la gestione
di più coppie microfoniche.
34
fig.17
35
Appendici
Programmazione Multithreading
Introduzione
Nella programmazione orientata agli oggetti i threads sono considerati dei veri e propri oggetti, con
opportune proprietà e metodi. Un esempio di proprietà può essere il livello di priorità, impostato al
momento della creazione dell’oggetto.
Nella definizione di un thread bisogna per prima cosa ereditare da una opportuna classe che
definisce metodi e proprietà per la gestione dell’oggetto in questione e successivamente
implementare altri metodi in cui vi è descritto il comportamento del thread in esecuzione.
In generale queste considerazione valgono per quasi tutti i linguaggi ad oggetti, come java, c++
,c# ,etc...
fig.1
Per tale scopo il compilatore C++ Builder della borland offre la classe TThread che permette
un’agevole definizione dei thread.
Altro punto cruciale della programmazione multithreading è la sincronizzazione, infatti è
impossibile che più threads in esecuzione contemporaneamente non condividono delle risorse fra di
loro. Per risorse si intende qualsiasi cosa file, buffer in memoria, scheda audio, etc...
La sincronizzazione è necessaria per regolare gli accessi in lettura o in scrittura, su di un file ad
esempio, evitando errori indesiderati oppure per regolare l’avvio dell’esecuzione di un thread. Si
può infatti far partire un thread quando un altro termina oppure creare un thread che si mette in
attesa di particolari richieste provenienti dall’esterno come un server multithread.
Le azioni di lettura e scrittura vanno a comporre la cosiddetta sessione critica del nostro programma
che deve essere regolata da opportuni strumenti detti semafori. Infatti è impensabile che un thread
scriva dati su di una risorsa mentre contemporaneamente un altro legge. Per questo i semafori
permettono l’accesso alla sessione critica ad un solo processo alla volta.
Ed infine è necessario anche instaurare una sorta di sistema di comunicazione tra threads, a tale
scopo ci viene incontro i messaggi.
I messaggi non sono altro che un'area di memoria comune in cui vengono salvati e ordinati i
messaggi scambiati. I messaggi offrono anche metodi di sincronizzazione ad esempio un processo si
può mettere in attesa della ricezione di un messaggio oppure no.
Tutto ciò descritto sopra compongono gli strumenti principali della programmazione multithreading
e presenti in qualsiasi linguaggio anche se in forme diverse.
36
La classe TThread
La classe TThread permette la realizzazione di threads in esecuzione in una applicazione. Qui di
seguito è riportata una lista dei metodi e proprietà più importanti e delle loro funzioni:
Metodo
Descrizione
DoTerminate
Genera un evento che può essere catturato dall’esterno quando il thread
termina la propria esecuzione.
E’ un metodo astratto che deve essere implementato e contiene il codice che
il thread esegue quando viene lanciato.
Dopo che il thread è stato creato questo metodo avvia l’esecuzione.
Sospende l’esecuzione del thread.
Termina l’esecuzione del thread.
Attende la fine dell’esecuzione del thread.
Execute
Resume
Suspend
Terminate
WaitFor
Proprietà
Descrizione
Handle
Priority
ReturnValue
Suspended
Terminated
ThreadID
Indica l’handle del thread creato.
Indica la priorità del thread in esecuzione .
Il valore numerico restituito al momento della terminazione del thread.
Indica se il thread è stato sospeso.
Indica se il thread a terminato la propria esecuzione.
Identificativo del thread.
Per creare un nuovo thread, all’interno di un progetto già esistente, in c++ builder è molto semplice.
E’ sufficiente andare su file, new, other(Fig 2) e si aprira la finestra di dialogo new item box. A
questo punto si seleziona C++Builder File nel menu Item Categories e Thread Object(Fig 3). A
finire il programma vi chiederà il nome da assegnare alla nuova classe(fig 4) e poi creerà
automaticamente il costrutto del file ‘.cpp’ e ‘.h’.
Figura 2
37
Figura 3
Figura 4
Dopo aver creato il nuovo file si consiglia di salvare subito quest’ultimo con lo stesso nome
assegnato alla classe. Qui di sotto si riportano i listati dei due file .cpp e .h:
MyThread.h
//$$---- Thread HDR ---//----------------------------------------------------------------------#ifndef MyThreadH
#define MyThreadH
//----------------------------------------------------------------------#include <Classes.hpp>
//----------------------------------------------------------------------class MyThread : public TThread
{
private:
protected:
void __fastcall Execute();
public:
__fastcall MyThread(bool CreateSuspended);
};
//----------------------------------------------------------------------#endif
MyThread.cpp
//$$---- Thread CPP ---//----------------------------------------------------------------------#include <vcl.h>
#pragma hdrstop
#include "MyThread.h"
38
#pragma package(smart_init)
//----------------------------------------------------------------------//
Important: Methods and properties of objects in VCL can only be
//
used in a method called using Synchronize, for example:
//
//
Synchronize(&UpdateCaption);
//
//
where UpdateCaption could look like:
//
//
void __fastcall MyThread::UpdateCaption()
//
{
//
//
Form1->Caption = "Updated in a thread";
}
//-----------------------------------------------------------------------
__fastcall MyThread::MyThread(bool CreateSuspended)
: TThread(CreateSuspended)
{
}
//----------------------------------------------------------------------void __fastcall MyThread::Execute()
{
//---- Place thread code here ---}
//-----------------------------------------------------------------------
Il thread adesso è pronto per essere usato nel programma. Il codice che deve essere eseguito va
scritto nel metodo astratto Execute() ereditato da TThread. E’ possibile aggiungere anche dei metodi
in più e ciò è utile per realizzare funzioni di setup.
Adesso analizzeremo come creare e avviare un thread.
1. Per prima cosa bisogna includere il file .h nel listato del programma:
#include <MyThread.h>
2. Creare un puntatore a oggetti di tipo MyThread nell’header del file:
MyThread *myThread;
3. Creare un nuovo oggetto di tipo MyThread nel programma:
39
myThread = new MyThread(true);
Il costruttore del thread prende come parametro in ingresso un valore booleano. Se tale
valore è posto a false allora il thread viene eseguito subito, se invece tale valore è true per
avviare il thread bisogna chiamare il metodo ‘resume’.
myThread.Resume();
4. Ultima accortezza che bisogna avere è quella di distruggere il thread al termine della sua
esecuzione, nel caso ad esempio vi siano dei cicli per cui si richiama più volte la ‘new’,
etc...
delete myThread;
La classe TcriticalSection
Questa classe permette di creare i semafori all’interno del nostro programma per gestire gli accessi
alla sessione critica. Essa è composta da due metodi acquire e release.
Ora vediamo come utilizzare questo strumento.
1. Per prima cosa bisogna includere il file syncobjs.h nel listato del programma
#include <syncobjs.h>
2. Creare un nuovo puntatore a oggetti di tipo TCriticalSection nel programma:
TCriticalSection *semaforo;
3. Creare un nuovo oggetto di tipo TCriticalSection;
semaforo = new TcriticalSection();
4. Adesso il semaforo è pronto per essere utilizzato, ma bisogna passare il suo riferimento a
tutti i thread. Perciò bisogna passare il puntatore al semaro o nel costruttore del thread
oppure creare un nuovo metodo di setup che prendi come parametro il puntatore. Nel
secondo caso quando si crea il thread richiamando il costruttore bisogna passare come
parametro booleano true, altrimenti il thread andrà immediatamente in esecuzione.
5. Per realizzare la sessione critica bisogna seguire sempre il seguente costrutto standard
sem->Acquire();
try{
//sessione critica
}__finally{
sem->Release();
}
Nell’uso dei semafori si usa il costrutto try perchè se un programma lancia una eccezione
può rilasciare il semaforo senza bloccare altri processi in attesa di entrare in sessione critica.
40
Codice
progetto.cpp
#include <vcl.h>
#pragma hdrstop
USEFORM("Form.cpp", Form1);
USEFORM("Unit1.cpp", WebCamForm);
WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
{
//carica i due form del progetto, quello principale Form1 e quello relativo alla
//webcam WebCamForm
try
{
Application->Initialize();
Application->CreateForm(__classid(TForm1), &Form1);
Application->CreateForm(__classid(TWebCamForm), &WebCamForm);
Application->Run();
}
catch (Exception &exception)
{
Application->ShowException(&exception);
}
catch (...)
{
try
{
throw Exception("");
}
catch (Exception &exception)
{
Application->ShowException(&exception);
}
}
return 0;
}
Form.h
#ifndef FormH
#define FormH
41
#include <Classes.hpp>
#include <Controls.hpp>
#include <StdCtrls.hpp>
#include <Forms.hpp>
#include <ExtCtrls.hpp>
#include <Buttons.hpp>
#include <DisplayManager.h>
#include <SchedaAudio.h>
#include <Menus.hpp>
#include <Unit1.h>
class TForm1 : public TForm
{
__published:
//display1 e display2 sono i due grafi che mostrano l'andamento dello stato
//dei due filtri, il secondo è quello relativo al filtro di riferimento il cui
//coefficiente in posizione N/2 è pari ad uno
TImage *display1;
TImage *display2;
//start è il riferimento al pulsante che richiama l'evento che da avvio
//all'acquisizione del segnale audio
TBitBtn *start;
//stop è il riferimento al pulsante che richiama l'evento che termina
//l'acquisizione del segnale audio
TBitBtn *stop;
//ver utilizzato solamente per mostrare la versione dell'ipp utilizzata
TButton *ver;
//filterOn è il riferimento alla check box utilizzata per abilitare o
//disabilitare il filtraggio del segnale in ingresso
TCheckBox *filterOn;
TLabel *Label1;
TLabel *Label2;
//samplePerSecTxt è il riferimento alla combo box che regola la frequenza di
//campionamento della scheda audio
TComboBox *samplePerSecTxt;
//frameSizeTxt è il riferimento alla combo box che regola la dimensione dei
//frame in ingresso
TComboBox *frameSizeTxt;
//muTxt è il riferimento al text box che regola il valore di mu.
TEdit *muTxt;
TLabel *Label3;
42
//thresholdTxt è il riferimento alla text box che regola il valore di soglia
//utilizzata per discriminare i frame rumorosi da quelli in cui vi è presenza di
//un segnale utile
TEdit *thresholdTxt;
TLabel *Label4;
//ResetVelocityTxt è il riferimento alla text box che regola il valore
//utilizzato per forzare i coefficienti dei 2 filtri a tornare allo stato
//iniziale
TEdit *ResetVelocityTxt;
TLabel *Label5;
//fmaxTxt e fminTxt sono due text box che regolano le frequenze di taglio dei
//filtri iir che eseguono il filtraggio del segnale audio
TEdit *fmaxTxt;
TEdit *fminTxt;
TLabel *fmax;
TLabel *fmin;
//IIRGainTxt è il riferimento alla text box che regola il guadagno dei filtri
//iir
TEdit *IIRGainTxt;
TLabel *Label6;
//webCamBtn è il riferimento al pulsante che apre il form che mostra il video
//catturato dalla webcam
TButton *webCamBtn;
// micDistanceTxt è il riferimento alla textbox per impostare la distanza
//intermicrofonica all'interno del programma
TEdit *micDistanceTxt;
TLabel *Label7;
//device è il riferimento alla combo box che mostra la lista dei dispositivi
//audio presenti nel computer
TComboBox *device;
TLabel *Label8;
TLabel *Label9;
//delayTxt è il riferimento al campo di testo utilizzato per mostrare il ritardo
//stimato
TEdit *delayTxt;
TLabel *Label10;
//angleTxt è il riferimento al campo di testo utilizzato per mostrare l'angolo
//stimato
TEdit *angleTxt;
//questa è la lista di tutti gli eventi chiamati all'interno del form
43
void __fastcall startClick(TObject *Sender);
void __fastcall stopClick(TObject *Sender);
void __fastcall verClick(TObject *Sender);
void __fastcall filterOnClick(TObject *Sender);
void __fastcall frameSizeTxtExit(TObject *Sender);
void __fastcall samplePerSecTxtExit(TObject *Sender);
void __fastcall muTxtExit(TObject *Sender);
void __fastcall thresholdTxtExit(TObject *Sender);
void __fastcall ResetVelocityTxtExit(TObject *Sender);
void __fastcall fmaxTxtExit(TObject *Sender);
void __fastcall fminTxtExit(TObject *Sender);
void __fastcall IIRGainTxtExit(TObject *Sender);
void __fastcall webCamBtnClick(TObject *Sender);
void __fastcall micDistanceTxtExit(TObject *Sender);
void __fastcall deviceChange(TObject *Sender);
void __fastcall FormClose(TObject *Sender, TCloseAction &Action);
void __fastcall display1Click(TObject *Sender);
private:
//DisplayManager è il puntatore all'oggetto che gestisce il due display che
//mostrano le risposte impulsive, il ritardo e l'angolo stimato.
DisplayManager *dm;
//SchedaAudio è il puntatore all'oggetto che gestisce il dispositivo audio
SchedaAudio *sa;
//webCam è il puntatore al form che mostra il video catturato
TWebCamForm *webCam;
public:
//costruttore
__fastcall TForm1(TComponent* Owner);
};
extern PACKAGE TForm1 *Form1;
#endif
Form.cpp
#include <vcl.h>
#pragma hdrstop
#include "Form.h"
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
//costruttore della form
__fastcall TForm1::TForm1(TComponent* Owner)
44
: TForm(Owner)
{
//crea un oggetto di tipo TWebCamForm
webCam = new TwebCamForm(Form1);
//crea un oggetto di tipo DisplayManager e tramite i metodi sotto riportati
//vengono passati i riferimenti agli oggetti che gestisce
dm = new DisplayManager();
dm->setCanaleDestro(display1);
dm->setCanaleSinistro(display2);
dm->setAngleTxt(angleTxt);
dm->setDelayTxt(delayTxt);
dm->setTrackBar(webCam->TrackBar1);
//viene creato un oggetto di tipo SchedaAudio e gli viene passato il puntatore
//all'oggetto DisplayManager in questo modo l'oggetto di tipo SchedaAudio può
//richiamare dei metodi per aggiornare i display. Una soluzione migliore
//consiste nel realizzare un'area di memoria comune che separi completamente
//SchedaAudio da DisplayManager, in modo da evitare da passare il puntare a
//DisplayManager.
sa = new SchedaAudio();
sa->setDisplay(dm);
//questo ciclo set gli items della combo box
int i=0;
char *name;
do{
sa->setDevice(i);
name = sa->getName();
device->Items->Add(name);
delete name;
i++;
}while(i< sa->getNumDevices());
sa->setDevice(0);
device->ItemIndex=0;
delete name;
}
//questo è l'evento richiamato quando si da avvio alla cattura del segnale audio
//il form non fa altro che richiamare il relativo metodo di dell'oggetto puntato
//da sa.
void __fastcall TForm1::startClick(TObject *Sender)
{
sa->record();
45
}
//evento chiamato quando si preme il pulsante per l'interruzione della cattura
//del segnale audio
void __fastcall TForm1::stopClick(TObject *Sender)
{
sa->stop();
}
//questo è l'evento richiamato quando si preme il pulsante ver e restituisce la
//versione dell'ipp usata
void __fastcall TForm1::verClick(TObject *Sender)
{
sa->getVersione();
}
//questo è l'evento richiamato quando si preme sulla checkbox per impostare o
//togliere il filtraggio del segnale prima della stima
void __fastcall TForm1::filterOnClick(TObject *Sender)
{
sa->setFilterOn(filterOn->Checked);
}
//Questo è l'evento richiamato quando si varia la dimensione del frame in
//ingresso. In questo caso si controlla l'indece dell'item selezionato e poi si
//richiama il relativo metodo di SchedaAudio. La dimensione dei frame devono
//essere potenze di 2
void __fastcall TForm1::frameSizeTxtExit(TObject *Sender)
{
if(frameSizeTxt->ItemIndex==0){
sa->setFrameSize(256);
}
if(frameSizeTxt->ItemIndex==1){
sa->setFrameSize(512);
}
if(frameSizeTxt->ItemIndex==2){
sa->setFrameSize(1024);
}
if(frameSizeTxt->ItemIndex==3){
sa->setFrameSize(2048);
}
}
//questo è l'evento richiamato quando si cambia la frequenza di campionamento
void __fastcall TForm1::samplePerSecTxtExit(TObject *Sender)
46
{
if(samplePerSecTxt->ItemIndex==0){
sa->setSamplesPerSec(96000);
}
if(samplePerSecTxt->ItemIndex==1){
sa->setSamplesPerSec(48000);
}
if(samplePerSecTxt->ItemIndex==2){
sa->setSamplesPerSec(44100);
}
if(samplePerSecTxt->ItemIndex==3){
sa->setSamplesPerSec(22050);
}
if(samplePerSecTxt->ItemIndex==4){
sa->setSamplesPerSec(11025);
}
if(samplePerSecTxt->ItemIndex==5){
sa->setSamplesPerSec(8000);
}
}
//questo è l'evento richiamato quando s'imposta il valore di mu. In questo caso
//per ottenere il valore numerico dalla text box si è usato il metodo ToDouble
void __fastcall TForm1::muTxtExit(TObject *Sender)
{
sa->setMu((float)(muTxt->Text.ToDouble()));
}
//Questo è l'evento richiamato per impostare il livello di soglia utilizzato
//per discriminare i frame rumorosi da quelli utili
void __fastcall TForm1::thresholdTxtExit(TObject *Sender)
{
sa->setThreshold((float)(thresholdTxt->Text.ToDouble()));
}
//Questo è il metodo richiamato per impostare il valore usato per forzare i
//coefficienti del filtro a tornare allo stato iniziale
void __fastcall TForm1::ResetVelocityTxtExit(TObject *Sender)
{
sa->setResetVelocity((float)(ResetVelocityTxt->Text.ToDouble()));
}
//Questi sono i due eventi richiamati per impostare le frequenze di taglio dei
//filtri iir in ingresso
47
void __fastcall TForm1::fmaxTxtExit(TObject *Sender)
{
sa->setFmax((float)(fmaxTxt->Text.ToDouble()));
}
void __fastcall TForm1::fminTxtExit(TObject *Sender)
{
sa->setFmin((float)(fminTxt->Text.ToDouble()));
}
//Questo è il metodo usato per impostare il guadagno dei filtri iir
void __fastcall TForm1::IIRGainTxtExit(TObject *Sender)
{
sa->setIIRGain((float)(IIRGainTxt->Text.ToDouble()));
}
//questo è l'evento richiamato quando si preme il pulsante per mostrare il video
//catturato dalla webcam
void __fastcall TForm1::webCamBtnClick(TObject *Sender)
{
webCam->Show();
}
//questo è l'evento chiamato per settare la distanza intermicrofonica
void __fastcall TForm1::micDistanceTxtExit(TObject *Sender)
{
sa->setMicDistance((float)(micDistanceTxt->Text.ToDouble()));
}
//questo è l'evento richiamato quando si sceglie il dispositivo nella combo box
void __fastcall TForm1::deviceChange(TObject *Sender)
{
sa->setDevice( device->ItemIndex );
}
//questo è l'evento richiamato quando si chiude il programma
void __fastcall TForm1::FormClose(TObject *Sender, TCloseAction &Action)
{
sa->termina();
}
DisplayManager.h
#include <vcl.h>
#include <ComCtrls.hpp>
#include <syncobjs.hpp>
#include <PrintDisplay.h>
#include <stdlib.h>
48
#ifndef DisplayManagerH
#define DisplayManagerH
class DisplayManager{
private:
//frameSize è la dimesione del buffer del display che mostra la risposta
//impulsiva stimata
int frameSize;
//canaleD è un puntatore a Display1 della classe Form
TImage *canaleD;
//stessa cosa del precedente
TImage *canaleS;
float delay;
float angle;
// delayTxt è un puntatore a delayTxt della classe Form
TEdit *delayTxt;
// angleTxt è un puntatore a angleTxt della classe Form
TEdit *angleTxt;
// TrackBar1 è un puntatore a TrackBar1 della classe Form
TTrackBar *TrackBar1;
int deltayD;
int divisore;
//termina è un flag utilizzato per far terminare il thread che viene avviato per
//aggiornare i display e i campi di testo
bool termina;
//sono i puntatori ai buffer
float *punti;
float *punti2;
//è il semaforo che gestisce la sessione critica con il thread. Regola l'accesso
//al buffer
TCriticalSection *sem;
//è il thread che aggiorna i display e i campi di testo
PrintDisplay *display;
public:
//costruttore
DisplayManager();
//questi sono una serie di metodi usati per passare i riferimenti degli oggetti
//d'interesse
void setCanaleDestro(TImage *cd);
void setCanaleSinistro(TImage *cs);
void setDelayTxt(TEdit *d);
void setAngleTxt(TEdit *a);
49
void setTrackBar(TTrackBar *tb);
//alloca i buffer
void setFremeSize(int n);
//puliscie i display
void reset();
//è il metodo usato per aggiornare i dati nei buffer
del);
void aggiorna(int numChannels,int dimBuffer,float *buffer,float ang, float
//avvia il thread che aggiorna i display
void start();
//termina l'esecuzione del thread che aggiorna i display settando a true la
//variabile booleana termina
void stop();
};
#endif
DisplayManager.cpp
#pragma hdrstop
#include "DisplayManager.h"
//costruttore
DisplayManager::DisplayManager(){
//crea il semaforo
sem = new TcriticalSection();
//imposta il valore di default di frameSize
frameSize=1024;
//Alloca i buffer
punti=(float *)malloc((4*frameSize));
punti2=(float *)malloc((4*frameSize));
for(int i=0;i<frameSize;i++){
punti[i]=0;
punti2[i]=0;
}
termina=false;
}
//imposta il nuovo valore di framesize
void DisplayManager::setFremeSize(int n){
frameSize=n;
//libera lo spazio precedentemente allocato e alloca della nuova memoria per i
//buffer
free(punti);
free(punti2);
50
punti=(float *)malloc((4*frameSize));
punti2=(float *)malloc((4*frameSize));
for(int i=0;i<frameSize;i++){
punti[i]=0;
punti2[i]=0;
}
}
//salva il riferimento all'oggetto display per poterlo gestire. Anche i metodi
//seguenti hanno lo stesso scopo
void DisplayManager::setCanaleDestro(TImage *cd){
canaleD=cd;
}
void DisplayManager::setCanaleSinistro(TImage *cs){
canaleS=cs;
}
void DisplayManager::setDelayTxt(TEdit *d){
delayTxt=d;
}
void DisplayManager::setAngleTxt(TEdit *a){
angleTxt=a;
}
void DisplayManager::setTrackBar(TTrackBar *tb){
TrackBar1=tb;
}
//ripulisce i diplay
void DisplayManager::reset(){
//imposta il colore del penello
canaleD->Canvas->Brush->Color=clBlack;
//ridisegna i riquadri
canaleD->Canvas->FillRect(Rect(0,0,canaleD->Width,canaleD->Height));
canaleD->Canvas->Pen->Color=clBlue;
canaleD->Canvas->MoveTo(0,(canaleD->Height)/2);
canaleD->Canvas->LineTo(canaleD->Width,(canaleD->Height)/2);
canaleS->Canvas->Brush->Color=clBlack;
canaleS->Canvas->FillRect(Rect(0,0,canaleS->Width,canaleS->Height));
//imposta il colore della penna
canaleS->Canvas->Pen->Color=clRed;
//disegna le linee di riferimento
51
canaleS->Canvas->MoveTo(0,(canaleS->Height)/2);
canaleS->Canvas->LineTo(canaleS->Width,(canaleS->Height)/2);
//ripulisce i buffer
for(int z=0;z<frameSize;z++){
punti[z] =0;
punti2[z]=0;
}
}
//aggiorna i display
void DisplayManager::aggiorna(int numChannels,int dimBuffer,float *buffer, float
ang , float del){
//si mette in attesa di entrare in sessione critica
sem->Acquire();
try{
//aggiorna i valori di ritardo e angolo da visualizzare
delay=del;
angle=ang;
//scrive i dati sui buffer
if(numChannels==1){
for(int z=0;z<frameSize;z++){
punti[z]=(buffer[z]);
punti2[z]=0;
}
}if(numChannels==2){
for(int z=0;z<frameSize;z++){
punti[z]=(buffer[z*2]);
punti2[z]=(buffer[z*2+1]);;
}
}
}__finally{
//rilascia il semaforo
sem->Release();
}
}
//avvia il thread per aggiornare il diplay
void DisplayManager::start(){
//imposta termina a true
termina = true;
//crea il nuovo thread
display = new PrintDisplay(true);
//si richiamano i metodi di setup del thread in modo da passare i riferimenti
52
//agli oggetti d'interesse
display->setCanaleSinistro(canaleS);
display->setCanaleDestro(canaleD);
display->setAngle(&angle);
display->setDelay(&delay);
display->setAngleTxt(angleTxt);
display->setDelayTxt(delayTxt);
display->setTrackBar(TrackBar1);
display->setTermina(&termina);
display->setSemaforo(sem);
display->setFrameSize(frameSize);
display->setBuffer(punti,punti2);
//con il metodo resume il thread entra in esecuzione
display->Resume();
}
//termina l'esecuzione del thread
void DisplayManager::stop(){
//imposta termina a false
termina = false;
//attende che il thread termini
display->WaitFor();
//distrugge l'oggetto liberando la memoria
delete display;
}
#pragma package(smart_init)
PrintDisplay.h
#ifndef PrintDisplayH
#define PrintDisplayH
#include <Classes.hpp>
#include <ComCtrls.hpp>
#include <syncobjs.hpp>
#include <math.h>
class PrintDisplay : public TThread
{
private:
TImage *canaleD;
TImage *canaleS;
float ipotenusa;
int pos;
float *angle;
53
float *delay;
TEdit *delayTxt;
TEdit *angleTxt;
TTrackBar *TrackBar1;
int deltayD;
int deltayD2;
TPoint punti[2048];
TPoint punti2[2048];
float *puntiy;
float *puntiy2;
int min;
int frameSize;
bool *termina;
__int64 *posizione;
TCriticalSection *sem;
protected:
void __fastcall Execute();
public:
__fastcall PrintDisplay(bool CreateSuspended);
void setCanaleDestro(TImage *cd);
void setCanaleSinistro(TImage *cs);
void setAngle(float *n);
void setDelay(float *n);
void setDelayTxt(TEdit *d);
void setAngleTxt(TEdit *a);
void setTrackBar(TTrackBar *tb);
void setSemaforo(TCriticalSection *s);
void setBuffer(float *b,float *c);
void setFrameSize(int n);
void setTermina(bool *b);
void __fastcall aggiornaCanali();
};
#endif
PrintDisplay.cpp
#include <vcl.h>
#pragma hdrstop
#include "PrintDisplay.h"
#pragma package(smart_init)
__fastcall PrintDisplay::PrintDisplay(bool CreateSuspended)
: TThread(CreateSuspended)
54
{
//ipotenusa è il valore necessario per conoscere la posizione della freccia nel
//form della webcam in funzione dell'angolo
ipotenusa=50 / (sin((3.14/180)*25));
pos=0;
}
void __fastcall PrintDisplay::Execute()
{
//quando termina è true il thread continua la sua esecuzione. Quando questa
//variabile cambia si esce dal ciclo while e il thread termina.
while(*termina==true){
//tramite questa sleep il display viene aggiornato come al massimo ogni 40ms
Sleep(40);
sem->Acquire();
try{
//richiama il metodo di aggiornamento tramite Synchronize
Synchronize(&aggiornaCanali);
}__finally{
sem->Release();
}
}
}
//questo metodo setta il riferimento al display del form e specificatamente
//quello del canale destro
void PrintDisplay::setCanaleDestro(TImage *cd){
canaleD=cd;
//pone l'array di punti, usato per disegnare la polilinea, a un valore di
//default
for(int i=0;i<2048;i++){
punti[i].x=canaleD->Width;
punti[i].y=(canaleD->Height)/2;
}
}
//questo metodo fa le stesse cose del precedente ma per il canale sinistro
void PrintDisplay::setCanaleSinistro(TImage *cs){
canaleS=cs;
for(int i=0;i<2048;i++){
punti2[i].x=canaleS->Width;
punti2[i].y=(canaleS->Height)/2;
}
55
}
//tutti questi metodi servono per impostare i riferimenti alle variabili o
//oggetti che deve gestire il thread
void PrintDisplay::setAngle(float *n){
angle=n;
}
void PrintDisplay::setDelay(float *n){
delay=n;
}
void PrintDisplay::setDelayTxt(TEdit *d){
delayTxt=d;
}
void PrintDisplay::setAngleTxt(TEdit *a){
angleTxt=a;
}
void PrintDisplay::setTrackBar(TTrackBar *tb){
TrackBar1=tb;
}
void PrintDisplay::setSemaforo(TCriticalSection *s){
sem = s ;
}
void PrintDisplay::setBuffer(float *b,float *c){
puntiy=b;
puntiy2=c;
}
//imposta la dimensione dei frame da mostrare
void PrintDisplay::setFrameSize(int n){
frameSize=n;
}
void PrintDisplay::setTermina(bool *b){
termina=b;
}
void __fastcall PrintDisplay::aggiornaCanali(){
//sono delle variabili di supporto, per memorizzare l'altezza diviso 2 del
//display
deltayD = (canaleD->Height)/2;
deltayD2 = (canaleS->Height)/2;
min=0;
//cerca il punto di minimo all'interno del buffer per poi mostrare una linea
56
//verde verticale su di esso come marker
for (int i = 0; i < (frameSize); i++) {
if(puntiy[i]<puntiy[min])min=i;
}
//calcola i valori da assegnare alla polilinea in base alle dimensioni del
//display
for (int i=0; i < (frameSize); i++){
if(puntiy[i]<puntiy[min])min=i;
punti2[i].x=punti[i].x=(i*((canaleD->Width)-1))/frameSize;
punti[i].y=deltayD-(int)(puntiy[i]*((float)(deltayD)));
punti2[i].y=deltayD2-(int)(puntiy2[i]*((float)(deltayD2)));
}
//Aggiorna prima il canale destro e poi il sinistro
//per prima cosa ripulisce il display ridisegnando la linea di riferimento a
//meta dell'altezza
canaleD->Canvas->Brush->Color=clBlack;
canaleD->Canvas->FillRect(Rect(0,0,canaleD->Width,canaleD->Height));
canaleD->Canvas->Pen->Color=clBlue;
canaleD->Canvas->MoveTo(0,deltayD);
canaleD->Canvas->LineTo(canaleD->Width,deltayD);
//disegna la polilinea contenente la risposta impulsiva
canaleD->Canvas->Polyline(punti,(frameSize-1));
canaleD->Canvas->Pen->Color=clYellow;
canaleD->Canvas->MoveTo((canaleD->Width)/2,0);
canaleD->Canvas->LineTo((canaleD->Width)/2 ,canaleD->Height );
//disegna una linea verde verticale nel punto di minimo
canaleD->Canvas->Pen->Color=clGreen;
canaleD->Canvas->MoveTo((min*((canaleD->Width)-1))/frameSize,0);
canaleD->Canvas->LineTo((min*((canaleD->Width)-1))/frameSize ,canaleD>Height );
canaleD->Refresh();
//adesso si fanno le stesse operazioni per il canale sinistro
canaleS->Canvas->Brush->Color=clBlack;
canaleS->Canvas->FillRect(Rect(0,0,canaleS->Width,canaleS->Height));
canaleS->Canvas->Pen->Color=clRed;
canaleS->Canvas->MoveTo(0,deltayD2);
canaleS->Canvas->LineTo(canaleS->Width,deltayD2);
canaleS->Canvas->Polyline(punti2,(frameSize-1));
canaleS->Refresh();
//Aggiorna il testo delle due textbox
angleTxt->Text=FloatToStr(*angle);
57
delayTxt->Text=FloatToStr((*delay)*1000);
//Calcola la posizione della freccia nel form della webcam
pos = (int)(50 + ipotenusa*sin(/*(3.14/180)**/(*angle)));
if (pos<0) {
pos=0;
}
if (pos>100) {
pos=100;
}
//aggiorna la posizione del TrackBar
TrackBar1->Position=pos;
}
SchedaAudio.h
#include <portaudio.h>
#include <DisplayManager.h>
#include "DSP.h"
#ifndef SchedaAudioH
#define SchedaAudioH
#define SAMPLE_RATE
(44100)
//questo è il tipo strutturato che viene utilizzato per passare i parametri
//all'interno della funizone statica di CallBack
typedef struct
{
bool rec;//indica che lo il programma sta acquisendo
bool filter;//indica se il filtraggio del segnale è attivo
float input;
int frameSize;//dimensione dei frame in ingresso
int samplesPerSec;//frequenza di campionamento
DSP *dsp;//riferimento al l'oggetto dsp che elabora il segnale
DisplayManager *dm;//riferimento all'oggetto displaymanager
}
paTestData;
class SchedaAudio{
private:
paTestData data;
//variabile che contiene il codice di errore quando un metodo fallisce
PaError err;
PortAudioStream *stream;
//indice del dispositivo usato
int device;
58
public:
SchedaAudio();
//const PaDeviceInfo *info;
char* getName();
void termina();
int getNumDevices();
void setDevice(int n);
void setMicDistance(float n);
void setIIRGain(float n);
void setMu(float n);
void setFmin(float n);
void setFmax(float n);
void setThreshold(float n);
void setResetVelocity(float n);
void stop();
void record();
void setDisplay(DisplayManager *d);
void getVersione();
void setFilterOn(bool x);
void setFrameSize(int n);
void setSamplesPerSec(int n);
static int CallbackRecord( void *inputBuffer, void *outputBuffer,
unsigned long framesPerBuffer,
PaTimestamp outTime, void
*userData );
};
#endif
SchedaAudio.cpp
#pragma hdrstop
#include "SchedaAudio.h"
//costruttore
SchedaAudio::SchedaAudio(){
//si inizializza portaudio
err = Pa_Initialize();
if(err==-1){
Application->MessageBoxA("Errore nell'inizializzazione della scheda
audio!!", "Errore", MB_OK);
}
//pone rec a false perchè il programma quando viene lanciato ovviamente non è
//nello stato di rec
data.rec=false;
59
//crea un oggetto dsp
data.dsp =new DSP();
//setta i valori di dafault
data.filter = true;
data.frameSize=1024;
data.samplesPerSec=44100;
device=0;
}
//questo
metodo restituisce il nome del dispositivo audio attualmente usato
char* SchedaAudio::getName(){
char *name;
int size=0;
const PaDeviceInfo *info = Pa_GetDeviceInfo( device);
//Controlla la dimensione della stringa. In c++ le stringhe terminano con il
//carattere nullo
while((info->name[size])!=0)size++;
if(info!=NULL){
//crea un'area di memoria puntata da name per salvare il nome del dispositivo
name = (char *)malloc(size+1);
for (int i = 0; i < size+1; i++) {
name[i]=0;
}
for (int i = 0; i < size; i++) {
name[i]=info->name[i];
}
}else{
//Se info è Null restituisce un messaggio di errore
name = (char *)malloc(7);
for (int i = 0; i < 7; i++) {
name[i]=0;
}
name[0]='e';
name[1]='r';
name[2]='r';
name[3]='o';
name[4]='r';
name[5]='!';
}
return
}
60
name;
//ritorna il numero di dispositivi presenti nel calcolatore
int SchedaAudio::getNumDevices(){
return Pa_CountDevices();
}
//Setta il dispositivo
void SchedaAudio::setDevice(int n){
device=n;
}
//setta la distanza intermicrofonica. Questo metodo nn fa altro che richiamare
//il corrispettivo in dsp, perchè tale valore è una variabile di dsp.
void SchedaAudio::setMicDistance(float n){
data.dsp->setMicDistance(n);
}
//Setta il guadagno dei filtri. Per lo stesso motivo del precedente metodo, si
//richiama il corrispettivo metodo di dsp
void SchedaAudio::setIIRGain(float n){
data.dsp->setIIRGain(n);
}
//Setta la frequenza di taglio del filtro iir passaalto
void SchedaAudio::setFmin(float n){
data.dsp->setFmin(n);
}
//Setta la frequenza di taglio del filtro iir passabasso
void SchedaAudio::setFmax(float n){
data.dsp->setFmax(n);
}
//setta la il valore di mu del filtro adattativo
void SchedaAudio::setMu(float n){
data.dsp->setMu(n);
}
//Setta il livello di soglia per discriminare i frame
void SchedaAudio::setThreshold(float n){
data.dsp->setThreshold(n);
}
//setta il valore del parametro usato per forzare i coefficienti del filtro
//adattativo a tornare allo stato iniziale
void SchedaAudio::setResetVelocity(float n){
data.dsp->setResetVelocity(n);
}
//Questo metodo termina lo streaming audio in ingresso
61
void SchedaAudio::stop(){
//Se il player è nello stato di rec allora esegue tutte le istruzioni per
//terminare la registrazione
if(data.rec == true){
//E' la procedura di portaudio per chiudere lo stream
err = Pa_StopStream( stream );
if(err!=0)Application->MessageBoxA("Errore nel terminare lo
stream!!", "Errore", MB_OK);
err = Pa_CloseStream( stream );
if(err!=0)Application->MessageBoxA("Errore nella chiusura dello
stream!!", "Errore", MB_OK);
//pone rec a false in modo da eseguire una sola volta tale procedura
data.rec=false;
//Pulisce i display
data.dm->reset();
//Termmina il thread che aggiorna i display
data.dm->stop();
//termina l'elaborazione del segnale e libera la memoria allocata per le
//funzioni delle ipp
data.dsp->freeIIRFilter();
data.dsp->freeEstimation();
}
}
//termina è richiamato dal form principale quando quest'ultimo viene
//chiuso
void SchedaAudio::termina(){
Pa_Terminate();
}
//Questo è il metodo che inizializza la registrazione
void SchedaAudio::record(){
//il metodo esegue le istruzioni solo se il programma non è già in uno stato di
//acquisizione, quindi solo se rec è false.
if(data.rec==false){
//imposta la dimensione dei frame da rappresentare in funzione di frameSize.
//Dove frameSize regola anche la grandezza dei filtri.
data.dm->setFremeSize(data.frameSize);
//Ripulisce i display
data.dm->reset();
data.dm->setFremeSize((data.frameSize));
//lancia il thread che aggiorna i display
data.dm->start();
62
//Inizzializza l'oggetto dsp che deve elaborare il segnale
data.dsp->setFrameSize(data.frameSize);
data.dsp->setIIRFilter();
data.dsp->setEstimation();
err==0;
if(err==-1){
Application->MessageBoxA("Errore nell'inizializzazione della
scheda audio!!", "Errore", MB_OK);
}else{
//inizializza il dispositivo audio
err =Pa_OpenStream( &stream,
(PaDeviceID)device,//dispositivo in ingresso
2,//numero di canali in ingresso
paFloat32,//tipo di dato in ingresso
NULL,
paNoDevice,//dispositivo in uscita
0,//numero di canali in uscita
paFloat32,//tipo di dato in uscita
NULL,
(data.samplesPerSec),//frequenza di campionamento
(data.frameSize),//dimensione dei frame
0,
0,
CallbackRecord,//La funzione che deve richiamare
&data );//l'indirizzo del dato strutturato usato
if(err==-1){
Application->MessageBoxA("Errore nell'inizializzazione
della scheda audio!!", "Errore", MB_OK);
}else{
//Avvia lo streaming
err = Pa_StartStream( stream );
if(err==-1){
Application->MessageBoxA("Errore
nell'inizializzazione della scheda audio!!", "Errore", MB_OK);
}else{
//Se tutto va a buon esito rec viene posto a true
data.rec=true;
}
}
}
}
63
}
void SchedaAudio::setDisplay(DisplayManager *d){
data.dm=d;
data.dm->reset();
}
void SchedaAudio::getVersione(){
data.dsp->getVersione();
}
void SchedaAudio::setFilterOn(bool x){
data.filter=x;
}
void SchedaAudio::setFrameSize(int n){
data.frameSize=n;
}
void SchedaAudio::setSamplesPerSec(int n){
data.samplesPerSec=n;
data.dsp->setSamplesPerSec((float)n);
}
//funzione richiamata in fase di acquisizione
int SchedaAudio::CallbackRecord( void *inputBuffer, void *outputBuffer,
unsigned long framesPerBuffer,
PaTimestamp outTime, void
*userData )
{
//fa il casting del dato. Questa operazione è dovuta
paTestData *data = (paTestData*)userData;
(void) outTime;
(void) outputBuffer;
//Viene aggiornato il display dopo aver eseguito la stima tramite il metodo
//estimation di dsp. Estimetion restituisce il valore dei coefficienti dei 2
//filtri interlacciati tra di loro. In questo caso estimation è chiamato
//direttamente all'interno del metodo aggiorna.
data->dm->aggiorna(2,data->frameSize,data->dsp->estimation(data->dsp>IIRFilter((float *)inputBuffer,data->filter)),(data->dsp->getAngle()),(data>dsp->getDelay()));
return 0;
}
#pragma package(smart_init)
DSP.h
#ifndef DSPH
#define DSPH
64
#include <vcl.h>
#include <stdio.h>
#include <ipp.h>
#include <math.h>
class DSP{
private:
IppsIIRState_32f *ctxlp;
//puntatore all'array contenente il frame in ingresso del filtro iir passabasso
//del canale destro
float *xlp;
//puntatore all'array contenente il frame in uscita del filtro iir passabasso
//del canale destro. Questo array sarà anche l'ingresso del filtro passa alto
float *ylp;
//array contenente i coefficienti del filtro iir passabasso del canale destro
float tapslp[8];
//guadagno del filtro iir passabasso del canale destro
float GAINlp;
IppsIIRState_32f *ctxhp;
//puntatore all'array contenente il frame in uscita del filtro iir passaalto
//del canale destro.
float *yhp;
//array contenente i coefficienti del filtro iir passaalto del canale destro
float tapshp[8];
//guadagno del filtro iir passaalto del canale destro
float GAINhp;
//Come per le variabili precedenti ma in questo caso ci si riferisce al filtro
//del canale sinistro
IppsIIRState_32f *ctxlp2;
float *xlp2;
float *ylp2;
float tapslp2[8];
float GAINlp2;
IppsIIRState_32f *ctxhp2;
float *yhp2;
float tapshp2[8];
float GAINhp2;
//vettore contenente le risposte risposte impulsive interlacciate
float *out;
float *out_st;
//diemnsione dei frame in ingresso
65
int frameSize;
//frequenza di campionamento
double samplesPerSec;
//periodo di campionamento
double tc;
double den;
//frequenza di taglio dei filtri irr passa alto
double fmin;
//frequenza di taglio dei filtri iir passa basso
double fmax;
Ipp64f omega;
Ipp64f omega2;
//Questo sono tutte le variabili che si riferiscono al filtro adattativo che
//esegue la stima del ritardo, i puntatori che hanno come suffisso una i
//rappresentano la parte immaginaria, mentre gli altri quella reale
float *in1;//ingresso del primo filtro nel dominio discreto
float *IN1;//ingresso del primo filtro nel dominio della frequenza
float *in2;//ingresso del secondo filtro nel dominio discreto
float *IN2;//ingresso del secondo filtro nel dominio della frequenza
float *out1;//uscita del primo filtro nel dominio discreto
float *OUT1;//uscita del primo filtro nel dominio della frequenza
float *out2;//uscita del secondo filtro nel dominio discreto
float *OUT2;//uscita del secondo filtro nel dominio della frequenza
float *w1;//coefficienti del primo filtro
float *W1;//coefficienti del primo filtro nel dominio della frequenza
float *w2;//coefficienti del secondo filtro
float *W2;//coefficienti del secondo filtro nel dominio della frequenza
float *e;//errore nel dominio discreto
float *E;//errore nel dominio della frequenza
float *in1i;
float *IN1i;
float *in2i;
float *IN2i;
float *out1i;
float *OUT1i;
float *out2i;
float *OUT2i;
float *w1i;
float *W1i;
float *w2i;
66
float *W2i;
float *ei;
float *Ei;
float mu;//è il mu dell'algoritmo
float q;//è usata per la normalizzazione
float threshold;//livello di soglia per discriminare i frame
float resetV;//è il valore usato per forzare lo stato dei filtri
adattativi
float supporto1;//sono usate per calcolare il denominatore dell'algoritmo
ufnlms
float supporto2;
float powerIn;//livello rms del segnale in ingresso
IppStatus status;
IppsFFTSpec_C_32f* spec;
int orderFFT;//è l'ordine dell'FFT che deve essere eseguita log2(N)
float micDistance;//distanza intermicrofonica
float delay;//ritardo stimato
float angle;//angolo stimato
int min;
public:
DSP();
float getDelay();
float getAngle();
void setMicDistance(float n);
void setSamplesPerSec(float n);
void setMu(float n);
void setIIRGain(float n);
void setFmin(float n);
void setFmax(float n);
void setThreshold(float n);
void setResetVelocity(float n);
void setFrameSize(int n);
void getVersione();
void setIIRFilter();
void freeIIRFilter();
float* IIRFilter(float *in,bool filterOn);
float* estimation(float *in);
void setEstimation();
void freeEstimation();
};
#endif
67
DSP.cpp
#pragma hdrstop
#include "DSP.h"
DSP::DSP(){
//imposta dei valori di default
samplesPerSec=44100;
tc=1/samplesPerSec;
fmin=300;
fmax=6000;
frameSize=1024;
orderFFT=10;
omega=omega2=0;
GAINlp2=GAINlp=1;
GAINhp2=GAINhp=1;
//Alloca la memoria necessaria
xlp=(float *)malloc((4*frameSize));
ylp=(float *)malloc((4*frameSize));
yhp=(float *)malloc((4*frameSize));
xlp2=(float *)malloc((4*frameSize));
ylp2=(float *)malloc((4*frameSize));
yhp2=(float *)malloc((4*frameSize));
out=(float *)malloc((8*frameSize));
out_st=(float *)malloc((8*frameSize));
in1=(float *)malloc((8*frameSize));
IN1=(float *)malloc((8*frameSize));
in2=(float *)malloc((8*frameSize));
IN2=(float *)malloc((8*frameSize));
out1=(float *)malloc((8*frameSize));
OUT1=(float *)malloc((8*frameSize));
out2=(float *)malloc((8*frameSize));
OUT2=(float *)malloc((8*frameSize));
w1=(float *)malloc((8*frameSize));
W1=(float *)malloc((8*frameSize));
w2=(float *)malloc((8*frameSize));
W2=(float *)malloc((8*frameSize));
e=(float *)malloc((8*frameSize));
E=(float *)malloc((8*frameSize));
in1i=(float *)malloc((8*frameSize));
IN1i=(float *)malloc((8*frameSize));
in2i=(float *)malloc((8*frameSize));
68
IN2i=(float *)malloc((8*frameSize));
out1i=(float *)malloc((8*frameSize));
OUT1i=(float *)malloc((8*frameSize));
out2i=(float *)malloc((8*frameSize));
OUT2i=(float *)malloc((8*frameSize));
w1i=(float *)malloc((8*frameSize));
W1i=(float *)malloc((8*frameSize));
w2i=(float *)malloc((8*frameSize));
W2i=(float *)malloc((8*frameSize));
ei=(float *)malloc((8*frameSize));
Ei=(float *)malloc((8*frameSize));
//inizializza gli array
for (int i = 0; i < frameSize; i++) {
xlp[i]=0;
ylp[i]=0;
yhp[i]=0;
xlp2[i]=0;
ylp2[i]=0;
yhp2[i]=0;
}
for (int i = 0; i < (2*frameSize); i++) {
out[i]=0;
out_st[i]=0;
in1[i]=0;
IN1[i]=0;
in2[i]=0;
IN2[i]=0;
out1[i]=0;
OUT1[i]=0;
out2[i]=0;
OUT2[i]=0;
w1[i]=0;
W1[i]=0;
w2[i]=0;
W2[i]=0;
e[i]=0;
E[i]=0;
in1i[i]=0;
IN1i[i]=0;
in2i[i]=0;
69
IN2i[i]=0;
out1i[i]=0;
OUT1i[i]=0;
out2i[i]=0;
OUT2i[i]=0;
w1i[i]=0;
W1i[i]=0;
w2i[i]=0;
W2i[i]=0;
ei[i]=0;
Ei[i]=0;
}
//pone il coefficiente in posizione N/2 del filtro di riferimento a 1
w2[(frameSize/2)]=1;
//Calcola l'ordine dell'fft
orderFFT = (int)(log((double)(frameSize*2))/log(2));
//esegue l'FFT di w2 per trovare i valori nel dominio della frequenza
status = ippsFFTInitAlloc_C_32f(&spec, orderFFT,
IPP_FFT_DIV_INV_BY_N,ippAlgHintNone );
status = ippsFFTFwd_CToC_32f(w2,w2i,W2,W2i,spec,NULL);
ippsFFTFree_C_32f( spec );
mu=5;
threshold=0.0005;
resetV=100;
q=1;
micDistance=0.12f;
min=0;
}
//ritorna il ritardo stimato
float DSP::getDelay(){
return delay;
}
//ritorna l'angolo stimato
float DSP::getAngle(){
return angle;
}
//imposta la distanza tra i due microfoni
void DSP::setMicDistance(float n){
micDistance=n;
}
//setta il guadagno dei filtri iir
70
void DSP::setIIRGain(float n){
GAINlp2=GAINlp=sqrt(n);
GAINhp2=GAINhp=sqrt(n);
}
//imposta la frequenza di campionamento
void DSP::setSamplesPerSec(float n){
samplesPerSec=(double)n;
tc=1/samplesPerSec;
}
//imposta la frequenza di taglio del filtro iir passa alto
void DSP::setFmin(float n){
fmin=(double)n;
}
//imposta la frequenza di taglio del filtro iir passa basso
void DSP::setFmax(float n){
fmax=(double)n;
}
//imposta il valore di mu
void DSP::setMu(float n){
mu=n;
}
//imposta il valore di soglia
void DSP::setThreshold(float n){
threshold = n;
}
//imposta il valore del parametro che forza lo stato dei filtri adattativi
void DSP::setResetVelocity(float n){
resetV=n;
}
//ritorna la versione delle ipp
void DSP::getVersione(){
const IppLibraryVersion *lib = ippsGetLibVersion();
Application->MessageBoxA(lib->Version, "Versione", MB_OK);
}
//cambia la dimensione dei frame e rialloca la memoria
void DSP::setFrameSize(int n){
frameSize=n;
orderFFT = (int)(log((double)(frameSize*2))/log(2));
free(xlp);
free(ylp);
71
free(yhp);
free(xlp2);
free(ylp2);
free(yhp2);
free(out);
free(out_st);
xlp=(float *)malloc((4*frameSize));
ylp=(float *)malloc((4*frameSize));
yhp=(float *)malloc((4*frameSize));
xlp2=(float *)malloc((4*frameSize));
ylp2=(float *)malloc((4*frameSize));
yhp2=(float *)malloc((4*frameSize));
out=(float *)malloc((8*frameSize));
out_st=(float *)malloc((8*frameSize));
free(in1);
free(IN1);
free(in2);
free(IN2);
free(out1);
free(OUT1);
free(out2);
free(OUT2);
free(w1);
free(W1);
free(w2);
free(W2);
free(e);
free(E);
free(in1i);
free(IN1i);
free(in2i);
free(IN2i);
free(out1i);
free(OUT1i);
free(out2i);
free(OUT2i);
free(w1i);
free(W1i);
free(w2i);
free(W2i);
72
free(ei);
free(Ei);
free(h);
in1=(float *)malloc((8*frameSize));
IN1=(float *)malloc((8*frameSize));
in2=(float *)malloc((8*frameSize));
IN2=(float *)malloc((8*frameSize));
out1=(float *)malloc((8*frameSize));
OUT1=(float *)malloc((8*frameSize));
out2=(float *)malloc((8*frameSize));
OUT2=(float *)malloc((8*frameSize));
w1=(float *)malloc((8*frameSize));
W1=(float *)malloc((8*frameSize));
w2=(float *)malloc((8*frameSize));
W2=(float *)malloc((8*frameSize));
e=(float *)malloc((8*frameSize));
E=(float *)malloc((8*frameSize));
in1i=(float *)malloc((8*frameSize));
IN1i=(float *)malloc((8*frameSize));
in2i=(float *)malloc((8*frameSize));
IN2i=(float *)malloc((8*frameSize));
out1i=(float *)malloc((8*frameSize));
OUT1i=(float *)malloc((8*frameSize));
out2i=(float *)malloc((8*frameSize));
OUT2i=(float *)malloc((8*frameSize));
w1i=(float *)malloc((8*frameSize));
W1i=(float *)malloc((8*frameSize));
w2i=(float *)malloc((8*frameSize));
W2i=(float *)malloc((8*frameSize));
ei=(float *)malloc((8*frameSize));
Ei=(float *)malloc((8*frameSize));
h=(float *)malloc((8*frameSize));
for (int i = 0; i < frameSize; i++) {
xlp[i]=0;
ylp[i]=0;
yhp[i]=0;
xlp2[i]=0;
ylp2[i]=0;
yhp2[i]=0;
}
73
for (int i = 0; i < (2*frameSize); i++) {
out[i]=0;
out_st[i]=0;
in1[i]=0;
IN1[i]=0;
in2[i]=0;
IN2[i]=0;
out1[i]=0;
OUT1[i]=0;
out2[i]=0;
OUT2[i]=0;
w1[i]=0;
W1[i]=0;
w2[i]=0;
W2[i]=0;
e[i]=0;
E[i]=0;
in1i[i]=0;
IN1i[i]=0;
in2i[i]=0;
IN2i[i]=0;
out1i[i]=0;
OUT1i[i]=0;
out2i[i]=0;
OUT2i[i]=0;
w1i[i]=0;
W1i[i]=0;
w2i[i]=0;
W2i[i]=0;
ei[i]=0;
Ei[i]=0;
h[i]=0;
}
w2[(frameSize/2)]=1;
orderFFT = (int)(log((double)(frameSize*2))/log(2));
status = ippsFFTInitAlloc_C_32f(&spec, orderFFT,
IPP_FFT_DIV_INV_BY_N,ippAlgHintNone );
status = ippsFFTFwd_CToC_32f(w2,w2i,W2,W2i,spec,NULL);
ippsFFTFree_C_32f( spec );
q=1;
}
74
//calcola i coefficienti dei filtri iir
void DSP::setIIRFilter(){
omega=(Ipp64f)((fmax)/(samplesPerSec));
Ipp64f t[8];
//ritorna i valori dei coefficienti del filtro
ippsIIRGenLowpass_64f(omega,0.1,3,t,ippButterworth);
//siccome ci servono in float32 viene eseguito questo ciclo e il compilatore
//esegue il cast in automatico
for (int i = 0; i < 8; i++) {
tapslp2[i]=tapslp[i]=(float)t[i];
}
omega=(Ipp64f)((fmin)/(samplesPerSec));
//stessa cosa di prima ma per il passa alto
ippsIIRGenHighpass_64f(omega,0.1,3,t,ippButterworth);
for (int i = 0; i < 8; i++) {
tapshp2[i]=tapshp[i]=(float)t[i];
}
//Alloca le risorse necessarie per eseguire il filtraggio tramite iir
ippsIIRInitAlloc_32f( &ctxlp, tapslp, 3, (Ipp32f*)0 );
ippsIIRInitAlloc_32f( &ctxhp, tapshp, 3, (Ipp32f*)0 );
ippsIIRInitAlloc_32f( &ctxlp2, tapslp2, 3, (Ipp32f*)0 );
ippsIIRInitAlloc_32f( &ctxhp2, tapshp2, 3, (Ipp32f*)0 );
}
//libera le risorse allocate in precedenza
void DSP::freeIIRFilter(){
ippsIIRFree_32f( ctxlp );
ippsIIRFree_32f( ctxhp );
ippsIIRFree_32f( ctxlp2 );
ippsIIRFree_32f( ctxhp2 );
}
//esegue il filtraggio del segnale
float* DSP::IIRFilter(float *in,bool filterOn){
if (filterOn == true) {
for (int i = 0; i < frameSize; i++) {
xlp[i]=in[i*2];
xlp2[i]=in[i*2+1];
}
ippsDivC_32f_I( GAINlp, xlp, frameSize);
ippsIIR_32f( xlp, ylp, frameSize, ctxlp );
ippsDivC_32f_I( GAINhp, ylp, frameSize);
75
ippsIIR_32f( ylp, yhp, frameSize, ctxhp );
ippsDivC_32f_I( GAINlp2, xlp2, frameSize);
ippsIIR_32f( xlp2, ylp2, frameSize, ctxlp2 );
ippsDivC_32f_I( GAINhp2, ylp2, frameSize);
ippsIIR_32f( ylp2, yhp2, frameSize, ctxhp2 );
for (int i=0; i < frameSize; i++) {
out[i*2]=yhp[i];
out[i*2+1]=yhp2[i];
}
}else{
for (int i = 0; i < frameSize; i++) {
xlp[i]=in[i*2];
xlp2[i]=in[i*2+1];
}
ippsDivC_32f_I( GAINlp, xlp, frameSize);
ippsIIR_32f( xlp, ylp, frameSize, ctxlp );
ippsDivC_32f_I( GAINhp, ylp, frameSize);
ippsIIR_32f( ylp, yhp, frameSize, ctxhp );
ippsDivC_32f_I( GAINlp2, xlp2, frameSize);
ippsIIR_32f( xlp2, ylp2, frameSize, ctxlp2 );
ippsDivC_32f_I( GAINhp2, ylp2, frameSize);
ippsIIR_32f( ylp2, yhp2, frameSize, ctxhp2 );
for (int i=0; i < (frameSize*2); i++) {
out[i]=in[i];
}
}
return out;
}
float* DSP::estimation(float *in){
//“in” è il vettore in ingresso e contiene i campioni del
//dopo
il filtraggio.
segnale interlacciati
Su questo vettore viene calcolato il valore rms per
//poter discriminare frame rumorosi.
powerIn=0;
for (int i = 0; i < 2*frameSize; i++) {
powerIn+=(in[i]*in[i]);
}
//Da qui in poi viene eseguita la convoluzione circolare tramite l'overlap 'n
//save. In minuscolo sono indicate le variabili nel dominio del tempo e in
//maiuscolo nel dominio della frequenza. Il numero indica il canale e la “i”
//come suffisso indica i valori immaginari.
76
powerIn=sqrt(powerIn/(2*frameSize));
for (int i = 0; i < frameSize; i++) {
in1[i]
= in1[frameSize+i];
in2[i]
= in2[frameSize+i];
in1[frameSize+i] = in[i*2];
in2[frameSize+i] = in[i*2+1];
}
status = ippsFFTFwd_CToC_32f(in1,in1i,IN1,IN1i,spec,NULL);
status = ippsFFTFwd_CToC_32f(in2,in2i,IN2,IN2i,spec,NULL);
for (int i = 0; i < (2*frameSize); i++) {
OUT1[i]
= IN1[i]*W1[i]-IN1i[i]*W1i[i];
OUT1i[i] = IN1[i]*W1i[i]+IN1[i]*W1i[i];
OUT2[i]
= IN2[i]*W2[i]-IN2i[i]*W2i[i];
OUT2i[i] = IN2[i]*W2i[i]+IN2[i]*W2i[i];
}
//Si calcola l'IFFT dei vettori d'uscita per ottenere il vettore d'errore
//tramite la loro somma
status = ippsFFTInv_CToC_32f(OUT1,OUT1i,out1,out1i,spec,NULL);
status = ippsFFTInv_CToC_32f(OUT2,OUT2i,out2,out2i,spec,NULL);
//Del
vettore d'errore si aggiornano soltanto gli elementi
//In questo modo
dell'ultima metà.
i primi coefficienti rimangono sempre a valore nullo, evitando
//cosi di effettuare la finestatura del segnale.
for (int i = 0; i < frameSize; i++) {
e[frameSize+i]=(out1[frameSize+i]+out2[frameSize+i])/q;
}
status = ippsFFTFwd_CToC_32f(e,ei,E,Ei,spec,NULL);
//Se il valore rms è superiore al valore di soglia si può procedere con
//l'aggiornamento dei pesi
if(powerIn>threshold){
//Le variabili
supporto1 e supporto2 non fanno altro che salvare
//della norma quadra dei vettori
//d'ingresso
richiesti
per
che
il
contengono
calcolo
della
l'FFT
supporto1=0;
supporto2=0;
for (int i = 0; i < (frameSize*2); i++) {
supporto1+=(IN1[i]*IN1[i]+IN1i[i]*IN1i[i]);
supporto2+=(IN2[i]*IN2[i]+IN2i[i]*IN2i[i]);
77
valore
segnali
versione normalizzata
//dell'UFLMS.
}
dei
il
//Si aggiornano i pesi dei filtri nel dominio della frequenza
for (int i = 0; i < (frameSize*2); i++) {
W1[i] = W1[i]
- mu*(IN1[i]*E[i]+IN1i[i]*Ei[i])/supporto1;
W1i[i]= W1i[i] - mu*(IN1[i]*Ei[i]-IN1i[i]*E[i])/supporto1;
W2[i] = W2[i]
- mu*(IN2[i]*E[i]+IN2i[i]*Ei[i])/supporto2;
W2i[i]= W2i[i] - mu*(IN2[i]*Ei[i]-IN2i[i]*E[i])/supporto2;
}
//Si calcola il nuovo valore di q utilizzato per normalizzare i pesi dei filtri.
q=0;
for (int i = 0; i < (frameSize*2); i++) {
q+=W1[i]*W1[i]+W1i[i]*W1i[i]+W2[i]*W2[i]+W2i[i]*W2i[i];
}
q=sqrt(q)/(frameSize*2);
for (int i = 0; i < (frameSize*2); i++) {
W1[i] = W1[i]/q;
W1i[i]= W1i[i]/q;
W2[i] = W2[i]/q;
W2i[i]= W2i[i]/q;
}
}
//In questa parte di codice si esegue L'IFFT dei di W1 e W2 per poter forzare i
//filtri a tornare allo stato iniziale.
status = ippsFFTInv_CToC_32f(W1,W1i,w1,w1i,spec,NULL);
status = ippsFFTInv_CToC_32f(W2,W2i,w2,w2i,spec,NULL);
for (int i = 0; i < (frameSize*2); i++) {
w1[i]=w1[i]-w1[i]/resetV;
w1i[i]=w1i[i]-w1i[i]/resetV;
w2i[i]=w2i[i]-w2i[i]/resetV;
if(i==(frameSize/2)){
w2[i]=w2[i]-(w2[i]-1)/resetV;
}else{
w2[i]=w2[i]-w2[i]/resetV;
}
}
status = ippsFFTFwd_CToC_32f(w1,w1i,W1,W1i,spec,NULL);
status = ippsFFTFwd_CToC_32f(w2,w2i,W2,W2i,spec,NULL);
for (int i=0; i < frameSize; i++){
out_st[i*2]=w1[i]*0.5;
out_st[i*2+1]=w2[i]*0.5;
}
78
//A questo punto si cerca l'indice del minimo in w1. Tramite min si può stimare
//il ritardo e l'angolo di provenienza della sorgente sonora.
min=0;
for(int i = 0; i < frameSize; i++) {
if(w1[i]<w1[min])min=i;
}
delay=(min-(frameSize/2))/samplesPerSec;
angle=atan((330*delay)/micDistance);
return out_st;
}
void DSP::setEstimation(){
orderFFT = (int)(log((double)(frameSize*2))/log(2));
status = ippsFFTInitAlloc_C_32f(&spec, orderFFT,
IPP_FFT_DIV_INV_BY_N,ippAlgHintNone );
}
void DSP::freeEstimation(){
ippsFFTFree_C_32f( spec );
}
#pragma package(smart_init)
c_cap.h
#ifndef c_capH
#define c_capH
//--------------------------------------------------------------------------#include <vfw.h> // video for windows library
class TCap
{
private:
protected:
HWND ParentHandle;
HWND hwndVideo;
CAPDRIVERCAPS CapDrvCaps; // driver capabilities
int
SelectedDevice;
public:
TStringList
*pStringCapDrivers;
int EnumCapDrv();
void Connect (int Selected);
79
void Format ();
void Source ();
void CaptureFrame (char *FileName);
__fastcall TCap(HWND Handle);
__fastcall ~TCap();
};
#endif
c_cap.cpp
//--------------------------------------------------------------------------#include <vcl.h>
#pragma hdrstop
#include <stdio.h>
#include "c_cap.h"
//--------------------------------------------------------------------------#pragma package(smart_init)
__fastcall TCap::TCap (HWND Handle)
{
// create video capture window
ParentHandle = Handle;
hwndVideo = capCreateCaptureWindow(
(LPSTR) "My Capture Window",
WS_CHILD | WS_VISIBLE,
0, 0, 300, 200,
(HWND) Handle,
(int) 1);
pStringCapDrivers = new TStringList;
SelectedDevice = -1;
}
__fastcall TCap::~TCap ()
{
80
delete pStringCapDrivers;
capPreview(hwndVideo, FALSE); // end preview
capDriverConnect(hwndVideo, SelectedDevice);
capDriverDisconnect(hwndVideo); // disconnect from driver
}
//--------------------------------------------------------------------------// enumerate the installed capture drivers
//--------------------------------------------------------------------------int TCap::EnumCapDrv ()
{
char szDeviceName[80]; // driver name
char szDeviceVersion[80]; // driver version
char str[161]; // concatinated string
int xcap; // counter
xcap = 0;
pStringCapDrivers->Clear ();
do
{
if (capGetDriverDescription(xcap, szDeviceName, sizeof(szDeviceName),
szDeviceVersion,
sizeof(szDeviceVersion))){
sprintf (str, "%s, %s", szDeviceName, szDeviceVersion);
pStringCapDrivers->AddObject (str, (TObject *)xcap);
}
else {
break;
}
xcap++;
} while (true);
return 0;
}
//--------------------------------------------------------------------------//
connecting to selected device and starts preview
//--------------------------------------------------------------------------void TCap::Connect (int Selected)
{
81
CAPSTATUS CapStatus;
int
hsize;
// capDlgVideoDisplay(hwndVideo);
// connect to the driver
if (SelectedDevice != -1) {
capPreview (hwndVideo, FALSE);
capDriverConnect(hwndVideo, SelectedDevice);
}
if (!capDriverConnect(hwndVideo, Selected)) {
// ---- Unable to connect to driver
return;
}
// update the driver capabilities
capDriverGetCaps (hwndVideo, sizeof(CAPDRIVERCAPS), &CapDrvCaps);
capDlgVideoFormat(ParentHandle);
// Are there new image dimensions
capGetStatus(hwndVideo, &CapStatus, sizeof(CAPSTATUS));
hsize = GetSystemMetrics(SM_CYMENU);
hsize += GetSystemMetrics(SM_CYCAPTION)+30;
// ---- Rescaling the windows
SetWindowPos(hwndVideo, NULL, 0, 0, CapStatus.uiImageWidth,
CapStatus.uiImageHeight, SWP_NOZORDER | SWP_NOMOVE);
SetWindowPos(ParentHandle, NULL, 0, hsize, CapStatus.uiImageWidth,
CapStatus.uiImageHeight+hsize, SWP_NOZORDER | SWP_NOMOVE);
// set preview rate to 33.3 miliseconds, or 30 FPS
capPreviewRate (hwndVideo, 33.3);
// start preview video
capPreview (hwndVideo, TRUE);
// ---- Remember selected device
SelectedDevice = Selected;
82
}
//--------------------------------------------------------------------------//
Get access to the video source format box
//--------------------------------------------------------------------------void TCap::Format ()
{
int
hsize;
CAPSTATUS CapStatus;
capDlgVideoFormat(hwndVideo);
// Are there new image dimensions
capGetStatus(hwndVideo, &CapStatus, sizeof(CAPSTATUS));
hsize = GetSystemMetrics(SM_CYMENU);
hsize += GetSystemMetrics(SM_CYCAPTION);
SetWindowPos(ParentHandle, NULL, 0, hsize, CapStatus.uiImageWidth,
CapStatus.uiImageHeight+hsize, SWP_NOZORDER |
SWP_NOMOVE);
SetWindowPos(hwndVideo, NULL, 0, 0, CapStatus.uiImageWidth,
CapStatus.uiImageHeight, SWP_NOZORDER | SWP_NOMOVE);
}
//--------------------------------------------------------------------------//
Get access to the video source dialog box
//--------------------------------------------------------------------------void TCap::Source ()
{
capDlgVideoSource(hwndVideo);
}
//--------------------------------------------------------------------------//
capture a frame and save it
//--------------------------------------------------------------------------void TCap::CaptureFrame (char *FileName)
{
capFileSaveDIB (hwndVideo, FileName);
}
Unit1.h
83
#ifndef Unit1H
#define Unit1H
//--------------------------------------------------------------------------#include <Classes.hpp>
#include <Controls.hpp>
#include <StdCtrls.hpp>
#include <Forms.hpp>
#include <ExtCtrls.hpp>
#include <Graphics.hpp>
#include <Menus.hpp>
#include <Dialogs.hpp>
#include <ExtDlgs.hpp>
#include <ComCtrls.hpp>
//--------------------------------------------------------------------------class TWebCamForm : public TForm
{
__published:
// IDE-managed Components
TMainMenu *MainMenu1;
TMenuItem *File1;
TMenuItem *Exit1;
TMenuItem *Options1;
TMenuItem *Format1;
TMenuItem *Source1;
TMenuItem *N3;
TTimer *Timer1;
TMenuItem *N1;
TMenuItem *Save1;
TSavePictureDialog *SavePictureDialog1;
TTrackBar *TrackBar1;
void __fastcall Format1Click(TObject *Sender);
void __fastcall Source1Click(TObject *Sender);
void __fastcall Exit1Click(TObject *Sender);
void __fastcall Save1Click(TObject *Sender);
private:
// User declarations
void __fastcall MyMenyClick(TObject *Sender);
public:
// User declarations
__fastcall TWebCamForm(TComponent* Owner);
};
//--------------------------------------------------------------------------extern PACKAGE TWebCamForm *WebCamForm;
84
//--------------------------------------------------------------------------#endif
Unit1.cpp
#include <vcl.h>
#pragma hdrstop
#include "Unit1.h"
#include "c_cap.h"
//--------------------------------------------------------------------------#pragma package(smart_init)
#pragma resource "*.dfm"
TWebCamForm *WebCamForm;
TCap
*Cap;
//--------------------------------------------------------------------------__fastcall TWebCamForm::TWebCamForm(TComponent* Owner)
: TForm(Owner)
{
int ii;
TMenuItem *NewItem;
Timer1->Enabled = false;
Cap = new TCap (Handle);
Cap->EnumCapDrv ();
for (ii=0; ii<Cap->pStringCapDrivers->Count; ii++) {
NewItem = new TMenuItem(Options1);
NewItem->Caption = Cap->pStringCapDrivers->Strings[ii];
NewItem->Tag = ii;
NewItem->Checked = ii==0 ? true:false;
NewItem->OnClick =
MyMenyClick;
Options1->Add (NewItem);
}
if (ii > 0 )
Cap->Connect (0);
}
//--------------------------------------------------------------------------void __fastcall TWebCamForm::MyMenyClick(TObject *Sender)
{
85
int
ii;
TMenuItem *MenuItem;
MenuItem = reinterpret_cast
<TMenuItem *> (Sender);
if (MenuItem) {
// ---- Disable rest og checked menu
for (ii=0; ii<Options1->Count; ii++) {
Options1->Items[ii]->Checked = false;
}
MenuItem->Checked = true;
Cap->Connect (MenuItem->Tag);
}
}
//--------------------------------------------------------------------------void __fastcall TWebCamForm::Format1Click(TObject *Sender)
{
Cap->Format ();
}
//--------------------------------------------------------------------------void __fastcall TWebCamForm::Source1Click(TObject *Sender)
{
Cap->Source ();
}
//--------------------------------------------------------------------------void __fastcall TWebCamForm::Exit1Click(TObject *Sender)
{
Close ();
}
//--------------------------------------------------------------------------void __fastcall TWebCamForm::Save1Click(TObject *Sender)
{
if (SavePictureDialog1->Execute ()) {
Cap->CaptureFrame (SavePictureDialog1->FileName.c_str());
}
}
86
Bibliografia
[1]
Jacob Benesty, Gary Welko
ADAPTIVE EIGENVALUE DECOMPOSITION ALGORITHM FOR REALTIME
ACOUSTIC SOURCE LOCALIZATION SYSTEM
[2]
David mansour and Augustine H. Gray
UNCONSTAINED FREQUENCY-DOMAIN ADAPTIVE FILTER
IEEE transactions on acoustics, speech and signal processing, vol. assp-30, NO. 5 october
1982 pag 726
[3]
G. Scarano
FENOMENI ALEATORI
Dispense del corso di fenomeni aleatori
[4]
Saeed V. Vaseghi
ADVANCED DIGITAL SIGNAL PROCESSING AND NOISE REDUCTION
John Wiley & Sons
[5]
Vinay K. Ingle, John G. Proakis
DIGITAL SIGNAL PROCESSING USING MATLAB
PWS Publishing Company
[6]
Sem M. Kuo, Bob H. Lee
REAL TIME DIGITAL SIGNAL PROCESSING
John Wiley & Sons
[7]
Ross Bencina, Phil Burk
PORTAUDIO-AN OPEN SOURCE CROSS PLATFORM AUDIO API
Articolo presentato al ICMC2001
87
Sitografia
[8]
http://www.portaudio.com/
Librerie di portaudio
[9]
http://www.intel.com/cd/software/products/asmo-na/eng/302910.htm
Integrated Performance Primitives
88
Indice
Pag.
Filtri adattativi
3
Filtri adattativi least mean squeres
5
Stabilità
6
Velocità di convergenza
7
Filtri adattativi recursive least squares
8
Filtri LMS nel dominio della frequenza
11
Versione normalizzata del filtro UFLMS
Modello per il problema della stima del ritardo
Modello per ambienti riverberanti
14
15
15
Metodo adattativo
16
Metodo tramite filtri RLS
18
Metodo adattativo tramite filtri UFLMS normalizzati
20
Implementazione dell'algoritmo in C++
28
Appendici
35
89
Programmazione multithreading
35
Codice commentato
40
Success is in the honesty and the right behaviour, if you can sham them you have won.
90