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− L1 , 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− L1]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 nT x n=x nT 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 n1=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 nT x n e ∇ e n=−x n il gradiente di n è: ∇ n=−2 x ne n (8) Sostituendo l'ultimo risultato ottenuto all'equazione dei passi decrescenti (5) si ottiene il classico algoritmo LMS: w n1=w n x ne 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 n1=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 nT ]= 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 ym . (2) em =x m− x L'algoritmo è basato sulla minimizzazione dell'errore medio quadratico cosi definito: 2 t E {e m}=E xm−w m y m= E {x 2 m}−2 w t mE {y m x m}w t mE {y m y t m}w m= (3) r xx 0−2 w t mr 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−1y 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−1y 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 xm. (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−1y 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−1update 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−1C 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 DC 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 mR yy m−1 −1 −1 −1 R yy m= R yy m−1− . (17) −1 T −1 1 y mR 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 mR 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−1y m xm] (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−1k m x m (25) o 9 w m= yy m−1r yx m−1−k m y T m yy m−1 r yx m−1k 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 mw m−1] (27) che può essere riscritta in quest'altra forma w m=w m−1−k m em . (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 em=x m−w T m−1 y m. Si aggiornano i coefficienti w m=w m−1−k m em . 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: i1=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 / 2Nk 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 i2 /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 xk 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=hd 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∣= E2 E 2 = DT −W T XT H T H D −W X = DT −W T XT 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 { DTk H X k }2 W Tk { XTk 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 XT H D −H X W =W 2 XT 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 xk 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à: XTk E k W k 1=W k − (23) tr [ XTk 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− ibi 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∗snbi 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 nu= 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 nu=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 è en= T n x n u , (4) ∥u n∥ e l'algoritmo LMS può essere espresso cosi n−en ∇ en , (5) un1= u dove , il passo di convergenza, è una costante positiva ∇ en= [ ] 1 un xn−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: un 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 un1 ad ogni passo di aggiornamento. Finalmente l'equazione di aggiornamento è data da n−en ∇ e n u un1= . (8) n−en ∇ 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 en=u t n xn , (9) u n−en xn un1= . (10) ∥u n−en xn∥ 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 un (contenente una stima di g2 ). Un effetto “specchio” nella seconda meta di un (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. WT1 W 1W 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. XT1 m E m W 1 m1=W 1 m− (3) tr [ XT1 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 m1= W 1 m1 (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. XT1 m E m W 1 m1=W 1 m− (8) tr [ XT1 m X 1 m] Si ricava il nuovo q. q= WT1 m1W 1 m1W T2 m1W 2 m1 (9) 2N Si normalizzano i vettori. W 1 m1= W 1 m1 (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