KULT Underground

una della più "antiche" e-zine italiane – attiva dal 1994

Tecniche digitali di sintesi

4 min read

Tecniche digitali di sintesi

Il modo più semplice per sintetizzare digitalmente un suono (a parte quello basato su suoni già campionati) è quello di simulare il funzionamento di un sintetizzatore analogico. Bisogna cioè creare algoritmi che simulino i blocchi elementari di un synth (oscillatori, filtri, ecc…). Naturalmente la potenza del digitale va ben oltre alla semplice simulazione dell’analogico ed infatti vedremo alcune tecniche che non possono essere realizzare con l’elettronica analogica, ma richiedono un sistema digitale.
Prima di analizzare questi algoritmi è però necessaria una precisazione: questo articolo è a carattere divulgativo per cui la trattazione matematica sarà limitata a ciò che è strettamente indispensabile per comprendere l’argomento. Verranno presentati alcuni listati scritti in C, vista l’universalità di tale linguaggio.

Filtri

Le principali tecniche per filtrare frequenze da un segnale sono due:
* filtraggio con la trasformata di Fourier
* uso di equazioni alle differenze finite
Il primo metodo è molto complesso ed ha problemi di prestazioni: infatti, a meno che non simuli filtri analogici particolarmente complessi, richiede molti più calcoli rispetto al secondo tipo di algoritmo. In questo articolo analizzeremo quindi solo il secondo caso.

Un filtro numerico è un algoritmo che agisce su uno stream di campioni e ne produce un altro che identifica la forma d’onda del suono filtrato. Può essere visto come una scatola in cui da una parte entra la sequenza di campioni del suono originale e dall’altra esce la sequenza di campioni del suono filtrato.
Diamo ora un po’ di notazione: chiamiamo X il vettore che contiene i campioni in ingresso e Y il vettore dei campioni in uscita. Sia X(n), l’n-esimo campione nel vettore, il campione attualmente in ingresso al filtro; X(n-1) sarà il campione precedente, X(n-M) risiederà M posizioni prima rispetto a quello attuale. Analogamente Y(n) è il campione in uscita dal filtro, Y(n-1) il campione in uscita nel passo precedente e così via.
Un generico filtro numerico è una equazione alle differenze finite in cui è espresso un legame fra l’ingresso, l’uscita, i valori precedenti in ingresso e i valori precedenti in uscita dal filtro.
La sua espressione matematica è:

Y0 = b0*X(n) + b1*X(n – 1) + … + bN * X(n – N)

– a1*Y(n – 1) – … – aM * Y(n – M)

dove a0, a1, …, aM e b0, b1, …, bN sono i coefficienti del filtro.
Esistono due classi di filtri numerici: i filtri FIR (Finite Impulsive
Response) e IIR (Infinite Impulsive Response)
I primi hanno tutti i coefficienti a0 = a1 = … = aM = 0 e sono definiti con risposta finita all’impulso perchè un volta che l’ingresso è stato sollecitato con un impulso l’uscita si annulla dopo un numero finito di campioni. Questi filtri sono anche chiamati moving average dato che la loro uscita è semplicemente una media pesata dei valori in ingresso. I filtri FIR presentano una elevata qualità (non danno distorsione di fase e la risposta in ampiezza è molto regolare) ed inoltre sono di facile realizzazione e dimensionamento. Presentano però il grosso svantaggio di approssimare perfettamente il filtro ideale soltanto per valori di N molto grandi, tendenti all’infinito.
Questo significa che hanno un costo computazionale molto elevato perchè per ogni campione in ingresso devono essere calcolate N moltiplicazioni e altrettante adizioni. Infatti N assume dei valori compresi generalmente fra 50 e 100; questo significa che per ogni campione sono necessarie fra le 100 e le 200 operazioni e ciò rende inaccettabile l’uso dei filtri FIR in applicazioni real-time.
Nei filtri IIR, oltre che dai valori in ingresso, l’uscita dipende anche dai precedenti valori in uscita. Quando all’ingresso vengono sollecitati con un impulso, l’uscita può continuare ad oscillare indefinitamente. Questi tipi di filtri hanno il notevole vantaggio di presentare un costo computazionale veramente ridotto ma hanno il grosso difetto di essere difficili da realizzare in quanto durante la fase di dimensionamento è necessario compiere un’attenta analisi della loro stabilità. Inoltre hanno una qualità inferiore ai filtri FIR perchè non presentano linearità di fase e la curva di risposta delle ampiezze non è ben delineata in prossimità della frequenza di taglio.
Ho calcolato per voi i coefficienti di tre filtri (passa-basso, passa-alto e passa-banda) del II ordine secondo l’approssimazione di
Butterworth. Di seguito presenterò quattro funzioni scritte in C: le prime tre servono per il calcolo dei coefficienti, mentre la quarta esegue il filtraggio di un campione in base ai coefficienti dimensionati dalle altre tre funzioni.

#define SAMPFREQ 44100.0 float a0, a1, a2, b1, b2;
void IIRLowPass(long fCut)
{ float c;

c = (float)(1.0 / tan(M_PI * (double)fCut / SAMPFREQ));
a0 = 1.0 / (1.0 + c * SQRT2 + c * c);
a1 = 2.0 * a0;
a2 = a0;
b1 = 2.0 * a0 * (1.0 – c * c);
b2 = a0 * (1.0 – SQRT2 * c + c * c);
}
void IIRHighPass(long fCut)
{ float c;

c = (float)tan(M_PI * (double)fCut / SAMPFREQ);
a0 = 1.0 / (1.0 + c * SQRT2 + c * c);
a1 = -2.0 * a0;
a2 = a0;
b1 = 2.0 * a0 * (c * c – 1.0);
b2 = a0 * (1.0 – SQRT2 * c + c * c);
}
void IIRBandPass(long fCut, long bWidth)
{ float c, d;

c = (float)(1.0 / tan(M_PI * (double)bWidth / SAMPFREQ));
d = (float)(2.0 * cos(2.0 * M_PI * (double)fCut / SAMPFREQ));
a0 = 1.0 / (1.0 + c);
a1 = 0.0;
a2 = -a0;
b1 = -c * d * a0;
b2 = a0 * (c – 1.0);
}

float GenericIIR(float x0)
{ static float x1, x2, y0, y1, y2;

y0 = a0*x0 + a1*x1 + a2*x2 – b1*y1 – b2*y2;
*Camp++ = (BYTE)((char)y0);
x2 = x1;
x1 = x0;
y2 = y1;
y1 = y0;

return(y0);
}

———————————————————————-

Il filtraggio è una delle operazioni più complesse nel dominio digitale, ma necessaria per poter realizzare altre componenti. Nel prossimo articolo vedremo come realizzare oscillatori e inviluppi e vedremo anche come sfruttare le linee di ritardo per ottenere effetti impossibili con l’elettronica analogica.

Thomas Serafini

Commenta