Ldf

Da Tesine Linguaggi e Traduttori.

Jump to: navigation, search

Contents

Descrizione

L'obiettivo è definire un formato di file testuale per la rappresentazione di filtri digitali di tipo FIR e IIR per applicazioni audio. Un filtro viene completamente descritto attraverso:

  • le risposte all'impulso, nel dominio del tempo, tra coppie di canali di input e output;
  • i parametri del filtro che l'utente può impostare.

La sintassi dovrà avere le seguenti caratteristiche:

  • essere relativamente semplice (poco verbosa) ed intuitiva;
  • permettere l'estrapolazione di informazioni sul filtro in maniera semplice (ordine del filtro, latenza “algoritmica”, ecc.);
  • permettere l'utilizzo di algoritmi di convoluzione temporale relativamente semplici senza compromettere troppo la performance;
  • essere predisposta per ulteriori sviluppi futuri.

Rappresentazione logica

Per ogni coppia di canali (x, y), dove x è un canale di input e y è un canale di output, il sistema logicamente simulato è illustrato nella seguente figura:

Immagine:Schema1.jpg


In particolare:

  • h_1, h_2, …, h_n sono le componenti della risposta all'impulso ad anello aperto;
  • r_1, r_2, …, r_n sono le componenti della risposta all'impulso in retroazione “composta” con la risposta all'impulso ad anello aperto;
  • LAT è la latenza algoritmica causata dall'eventuale non causalità della risposta all'impulso ad anello aperto.

Le componenti r_x per t < 1 devono essere nulle per evitare che la latenza possa essere virtualmente infinita. La scelta di una simile rappresentazione è stata effettuata per semplificare l'implementazione di algoritmi efficienti di convoluzione nei casi in cui le varie parti della risposta all'impulso siano abbastanza distanti temporalmente tra loro (echi, riverberi, ecc.)

Sintassi

Un file in formato LDF si divide in 5 sezioni, separate tra loro dalla sequenza %% posta ad inizio rigo e seguita da un carattere di newline. Inoltre, ad eccezione dei primi caratteri del file e delle linee di separazione tra le sezioni, le seguenti regole sono sempre valide:

  • è possibile porre dei commenti nel file – si considerano commenti le sequenze di caratteri che iniziano con il carattere # e terminano con un carattere di newline;
  • i caratteri di tabulazione, spaziatura e ritorno a capo sono sempre ignorati (li si può utilizzare liberamente).

Inoltre, alcune regole e costrutti sono riutilizzati in più sezioni per rendere la sintassi uniforme, pertanto vengono analizzate di seguito.

Valori numerici

Sono definiti due tipi di valori numerici: interi e a virgola mobile. Qualsiasi valore numerico è sempre espresso in base dieci ed eventualmente preceduto da al più un segno (+ o -). I valori interi sono composti da sequenze di cifre di cui la prima è diversa da 0, oppure il valore è semplicemente 0. I valori in virgola mobile sono espressi in maniera del tutto analoga al linguaggio C, esclusi i suffissi di tipo, e sono sempre valutati con doppia precisione. Dato che un valore intero è anche un valore in virgola mobile valido, l'interprete avrà l'onere di utilizzarlo coerentemente al contesto in cui si trova.

Identificatori

Un'identificatore è una sequenza di caratteri in cui il primo carattere è una lettera maiuscola o minuscola “semplice” (ASCII 7) o il carattere di underscore, e i caratteri seguenti sono lettere maiuscole o minuscole semplici, il carattere di underscore o cifre numeriche. Esistono una serie di sequenze di caratteri che, secondo la definizione precedente, sarebbero identificatori validi, ma che sono invece parole chiave riservate; queste sono: in, out, mono, stereo, left, right, time, h, r e sample_rate. In particolare, la parola chiave sample_rate indica il valore della frequenza di campionamento (in Hz) alla quale lavora il filtro.

Espressioni matematiche

Il linguaggio permette di specificare espressioni matematiche con sintassi analoga al linguaggio C. Gli operatori riconosciuti sono: + (addizione), - (sottrazione), * (moltiplicazione), / (divisione), % (resto della divisione), ^ (elevamento a potenza) e ! (fattoriale). Tutti hanno associatività a sinistra, ad eccezione degli ultimi due che hanno associatività a destra. La priorità degli operatori è (dalla priorità maggiore a quella minore): !, ^, - unario (segno) e + unario (segno), * e /, %, + e -. Nell'operazione op1 % op2, op2 deve essere positivo e non nullo, e il risultato vale

op1 - op1/op2 * op2

(quindi l'operazione è valida anche per op2 non intero). Inoltre, nell'operazione op1 ^ op2, op2 non può essere negativo, e in op1! l'argomento deve essere intero (logicamente – in realtà può essere un valore in virgola mobile, ma tale valore deve avere parte decimale pari a 0) e non negativo. Ancora, le espressioni matematiche possono contenere identificatori di variabili e chiamate a funzioni (vedi sezione 4). Ad eccezione delle definizioni delle funzioni, gli identificatori di variabili devono riferirsi necessariamente ai parametri del filtro (sezione 3), mentre le chiamate a funzioni hanno sintassi:

nomefunzione ( espressione_1 , espressione_2 , espressione_3 , … , espressione_n )

dove espressione_x sono anch'esse espressioni matematiche, il cui valore viene utilizzato come argomento per la chiamata a funzione. Infine, sono messe a disposizione dell'utente le parentesi tonde ( e ) per poter gestire la priorità delle operazioni.

Sezione 1: Sequenza “magica” di riconoscimento

La prima sezione è dedicata esclusivamente all'individuazione del tipo di file (utile sia per le implmentazioni che per utility esterne). Il file deve necessariamente iniziare con la sequenza LDF01T seguita da un carattere di newline. Tale sequenza serve a individuare il tipo di file (LDF), la versione del formato (01 – 0.1) e la modalità di lettura per il file (T – testuale).

Sezione 2: Informazioni e proprietà del filtro

In questa sezione vengono specificate alcune informazioni e proprietà del filtro, secondo lo schema:

proprietà : valore ;

Per la versione attuale (0.1) del formato, le proprietà riconosciute sono:

ProprietàDescrizioneValore
inSetup dei canali in ingressomono, stereo
outSetup dei canali in uscitamono, stereo


Entrambe queste proprietà devono essere obbligatoriamente specificate.

Sezione 3: Parametri

Questa sezione serve a rendere noti i parametri del filtro. La lista di tali parametri è semplicemente specificata come:

parametro_1 , parametro_2 , parametro_3 , … , parametro_n ;

dove ogni parametro_x è un identificatore non riservato. Se il filtro non ha parametri settabili dall'utente, la lista non deve comparire (l'utilizzo del solo carattere ; non è valido).

Sezione 4: Funzioni

Al fine di rendere la sintassi più compatta, è possibile definire delle funzioni matematiche in questa sezione. La sintassi è la seguente:

nomefunzione ( arg_1 , arg_2 , arg_3 , … , arg_n ) = espressione ;

dove nomefunzione è un identificatore non riservato (può anche essere uguale al nome di un parametro già esistente, dato che non vi è ambiguità nella sintassi), argx sono gli argomenti della funzione, specificati come identificatori non riservati, ed espressione è l'espressione matematica corrispondente alla funzione. La risoluzione delle variabili nell'espressione avviene nel seguente modo:

  • se l'identificatore è uguale al nome di uno degli argomenti, la variabile fa riferimento al valore dell'argomento della funzione; altrimenti
  • l'identificatore deve necessariamente fare riferimento ad un parametro del filtro.

Ogni forma di ricorsione è proibita: una funzione non può richiamare sé stessa, né direttamente né attraverso altre funzioni (questa misura è necessaria, in quanto non si hanno a disposizione costrutti condizionali e/o iterativi).

Nota: la sintassi delle funzioni e delle espressioni matematiche si è rivelata sufficientemente potente da permettere l'approssimazione di varie funzioni matematiche; ad esempio le funzioni trigonometriche basilari (seno, coseno, tangente) possono essere approssimate come segue:

pi() = 3.14159265358979;
_cos(x) = 1 -x^2/2 +x^4/4! -x^6/6! +x^8/8! -x^10/10! +x^12/12! -x^14/14! +x^16/16!;
cos(x) = -_cos(x%(2*pi())-pi());
sin(x) = cos(x-pi()/2);
tan(x) = sin(x)/cos(x);

Sezione 5: Componenti della risposta all'impulso

Questa sezione si compone di un numero variabile (anche nessuna) di componenti della risposta all'impulso del filtro. La sintassi per ognuna componente è:

canalein -> canaleout : time
{
statement_1
statement_2
statement_3
...
statement_n
}

canalein e canaleout indicano, rispettivamente, il canale di ingresso e quello di uscita per la componente della risposta all'impulso, e possono essere mono se la sorgente o la destinazione corrispondente è mono, left o right se stereo. Gli statement sono presenti in numero variabile (anche nessuno) per ogni componente ed hanno la seguente sintassi:

tipo [ indice ] = espressione ;

dove tipo è h (anello aperto) o r (retroazione), indice specifica uno o più campioni della risposta all'impulso a cui si applica l'espressione seguente, ed espressione è l'espressione matematica corrispondente. La sintassi di indice è:

n oppure da : a oppure da : step : a

n, da, a e step sono interi; nel primo caso (n) viene indicato un solo valore, nel secondo caso (da : a) si indica un intervallo di valori (estremi inclusi), nel terzo caso (da : step : a) si indica un gruppo di valori equispaziati, a distanza step tra loro, all'interno dell'intervallo [da, a], a partire dal minore degli estremi. Ogni componente logicamente descrive, al più, un blocco di tipo h e un blocco di tipo r tra una coppia di canali (uno di ingresso e uno di uscita), tramite la risposta all'impulso (che si assume nulla ove non specificata). Quindi è possibile determinare la latenza algoritmica del filtro (LAT) come l'indice minimo di h tra tutte le componenti a cui corrisponde un campione non nullo, se tale indice è minore di 0, altrimenti la latenza sarà nulla; mentre l'ordine del filtro sarà il massimo tra il massimo indice di r tra tutte le componenti e la massima differenza tra gli indici di h tra tutte le componenti.

Esempi

  • Filtro “silenziatore” mono
LDF01T
%%
in:  mono;
out: mono;
%%
%%
%%

Questo filtro produce semplicemente silenzio all'uscita, qualsiasi sia l'ingresso.

  • Filtro "identità" mono
LDF01T
%%
in:  mono;
out: mono;
%%
%%
%%
mono->mono: time
{
	h[0] = 1;
}

Questo filtro riproduce in uscita lo stesso segnale che viene dato in ingresso.

  • Filtro amplificatore mono
LDF01T
%%
in:  mono;
out: mono;
%%
gain;
%%
%%
mono->mono: time
{
	h[0] = gain;
}

Questo filtro manda in uscita il segnale in ingresso moltiplicato per la variabile gain, quindi con volume generalmente diverso. Dando a gain il valore 0 si ottiene il filtro silenziatore, con 1 si ottiene il filtro identità, con valori compresi tra 0 e 1 si ha un'attenuazione del volume, mentre per valori superiori a 1 il volume viene aumentato.

  • Filtro “stereo to mono”
LDF01T
%%
in:  stereo;
out: mono;
%%
%%
%%
left->mono: time
{
	h[0] = 0.5;
}
right->mono: time
{
	h[0] = 0.5;
}

Questo filtro converte un segnale in ingresso stereo in un segnale in uscita mono, miscelando i canali destro e sinitro in maniera equa e presentando in uscita la media del volume dei due canali.

  • Filtro “mono to stereo”
LDF01T
%%
in:  mono;
out: stereo;
%%
%%
%%
mono->left: time
{
	h[0] = 1;
}
mono->right: time
{
	h[0] = 1;
}

Questo filtro converte un segnale in ingresso mono in un segnale in uscita stereo, copiando il segnale in ingresso sui due canali di uscita.

  • Filtro eco mono
LDF01T
%%
in:  mono;
out: mono;
%%
attenuation;
%%
%%
mono->mono: time
{
	h[0] = 1;
	r[20000] = 1 – attenuation;
}

Questo filtro applica un effetto eco infinito all'ingresso, dove il segnale in ingresso viene ripetuto a distanza di 20000 campioni (circa mezzo secondo a frequenza di campionamento di 44.1 kHz) con intensità pari a 1 - attenuation volte rispetto alla ripetizione precedente. Per ottenere un'eco che suoni abbastanza “naturale” all'orecchio si possono scegliere valori di attenuation compresi tra 0.9 e 1.

  • Simulatore di filtro passa-basso RC mono
LDF01T
%%
in:  mono;
out: mono;
%%
cut_frequency;
%%
atan(x) = x -x^3/3 +x^5/5 -x^7/7 +x^9/9 -x^11/11 +x^13/13 -x^15/15 +x^17/17 -x^19/19; 
rfreq() = cut_frequency/sample_rate;
awfreq() = 2*atan(rfreq()/2); # Anti frequency-warp
%%
mono->mono: time
{
	h[0:1] = pi()*awfreq()/(pi()*awfreq()+1);
	r[1] = (1-pi()*awfreq())/((pi()*awfreq()+1)^2);
}

Questo filtro simula il comportamento in un circuito passa-basso RC con frequenza di taglio pari a cut_frequency (in Hz). Per ottenere una buona simulazione la frequenza di campionamento dell'ingresso dovrebbe essere almeno 4 volte superiore a cut_frequency, mentre l'effetto diventa molto evidente all'orecchio per valori di cut_frequency inferiori a 2000.

  • Simulatore di filtro passa-alto RC mono
LDF01T
%%
in:  mono;
out: mono;
%%
cut_frequency;
%%
atan(x) = x -x^3/3 +x^5/5 -x^7/7 +x^9/9 -x^11/11 +x^13/13 -x^15/15 +x^17/17 -x^19/19;
rfreq() = cut_frequency/sample_rate;
awfreq() = 2*atan(rfreq()/2); # Anti frequency-warp
%%
mono->mono: time
{
	h[0] = 1/(pi()*awfreq()+1);
	h[1] = -1/(pi()*awfreq()+1);
	r[1] = (1-pi()*awfreq())/((pi()*awfreq()+1)^2);
}

Questo filtro simula il comportamento in un circuito passa-alto RC con frequenza di taglio pari a cut_frequency (in Hz). Per ottenere una buona simulazione la frequenza di campionamento dell'ingresso dovrebbe essere almeno 4 volte superiore a cut_frequency, mentre l'effetto diventa molto evidente all'orecchio per valori di cut_frequency superiori a 1000.

Implementazione

Il programma realizzato prende in ingresso un file audio in formato RIFF/WAVE (.wav) e un file LDF che descrive il filtro, chiede all'utente i valori da assegnare ai parametri del filtro e produce un file RIFF/WAVE applicando il filtro al file audio di ingresso. Esso inoltre mostra a schermo alcune proprietà del file in ingresso e del filtro, nel produrre il file di output compensa la latenza algoritmica del filtro e, se il filtro non ha componenti r, “allunga” il file di uscita di un numero di campioni pari all'ordine del filtro. Insieme ai sorgenti e ai file necessari per la compilazione, vengono forniti anche filtri d'esempio.

Compilazione

Per compilare il programma si necessita degli header di sviluppo, oltre che della libreria C e della libreria matematica, della libreria libsndfile. Una volta assicuratisi che tali header siano presenti nel path di inclusione del compilatore, basta utilizzare il Makefile distribuito insieme ai sorgenti. Verrà creato un eseguibile con nome ldfapply. Per ulteriori informazioni e/o in caso di problemi consultare il Makefile.

Utilizzo

Il programma va invocato come segue:

ldfapply file_audio_input file_audio_output file_filtro

Se il filtro presenta parametri settabili dall'utente, questi verrano richiesti dal programma sullo standard input.

Limitazioni

Il programma è in grado di verificare che le definizioni di funzioni non abbiano ricorsività diretta (una funzione chiama sé stessa), ma non è in grado di verificare che non abbia ricorsività indiretta (con una serie di chiamate una funzione richiama sé stessa).

Donwload

Sorgenti : http://www.skenz.it/traduttori/tesine/materiale/2009/ldfapply.tar.gz

(contiene anche gli esempi descritti in precedenza)

Personal tools