Sound and FFT
Importiamo una serie di "utilities" che si useranno nel seguito:
In [8]:
import scipy
import numpy as np
from matplotlib import pyplot as plt from scipy.fftpack import fft, fftfreq
Definiamo le variabili che caratterizzano il nostro esercizio:
SAMPLE_RATE determines how many data points the signal uses to represent the sine wave per second. So if the signal had a sample rate of 10 Hz and was a five-second sine wave, then it would have 10 * 5 = 50 data points.
DURATION is the length of the generated sample.
In [33]:
SAMPLE_RATE = 44100 # Hertz DURATION = 5 # Seconds
Next, you define a function to generate a sine wave since you’ll use it multiple times later on. The function takes a frequency, freq, and then returns the x and y values that you’ll use to plot the wave.
In [34]:
# definisco la funzione che genera il segnale sinusoidale:
def generate_sine_wave(freq, sample_rate, duration):
x = np.linspace(0, duration, sample_rate * duration, endpoint=False) frequencies = x * freq
# 2pi because np.sin takes radians y = np.sin((2 * np.pi) * frequencies) return x, y
The x-coordinates of the sine wave are evenly spaced between 0 and DURATION, so the code uses NumPy’s linspace() to generate them. It takes a start value, an end value, and the number of samples to generate. Setting endpoint=False is important for the Fourier transform to work properly because it assumes a signal is periodic.
np.sin() calculates the values of the sine function at each of the x-coordinates. The result is multiplied by the frequency to make the sine wave oscillate at that frequency, and the product is multiplied by 2π to convert the input values to radians.
After you define the function, you use it to generate a two-hertz sine wave that lasts five seconds and plot it using Matplotlib. Your sine wave plot should look something like this:
In [44]:
# Generate a 2 hertz sine wave that lasts for 5 seconds x, y = generate_sine_wave(2, SAMPLE_RATE, DURATION) plt.plot(x, y)
plt.xlabel('time(s)') plt.show()
The x-axis represents time in seconds, and since there are two peaks for each second of time, you can see that the sine wave oscillates twice per second. This sine wave is too low a frequency to be audible, so in the next section, you’ll generate some higher-frequency sine waves, and you’ll see how to mix them.
Mixing Audio Signals in just two steps:
Adding the signals together Normalizing the result
Before you can mix the signals together, you need to generate them:
(Nota: che "," iniziale serve per mettere da parte il risultato del calcolo precedente scaricandolo nella
variabile "" (come avviene in un registro di memoria di calcolatrice). In questo caso viene usato per togliere di mezzo qualsiasi interferenza proveniente da valori precedentemente ottenuti.)
In [47]:
_, nice_tone = generate_sine_wave(400, SAMPLE_RATE, DURATION) _, noise_tone = generate_sine_wave(4000, SAMPLE_RATE, DURATION) _, noise_2=generate_sine_wave(7000, SAMPLE_RATE, DURATION)
noise_tone = noise_tone * 0.3 # regola l'ampiezza del noise noise_2 = noise_2*0.5
mixed_tone = nice_tone + noise_tone +noise_2 # somma segnale e rumore
noise_tone avra' invece una frequenza di 4000 Hz;
noise_2 avra' invece una frequenza di 7000 Hz;
mixed_tone conterra' ovviamente la somma dei due segnali precedenti.
Si noti che noise_tone e noise_2 vengono attenuati di un fattore rispettivamente di 0.3 e 0.5 prima di essere mescolati a produrre il negnale mixed_tone.
Il prossimo passo e' la normalizzazione, o scaling, del segnale. P.es. se lo vogliamo codificare in un formato a 16 bit avra' un range da -32768 a 32767:
In [55]:
normalized_tone = np.int16((mixed_tone / mixed_tone.max()) * 32767) plt.plot(normalized_tone[:1000])
plt.show()
In [51]:
len(normalized_tone)
Se volessimo ascoltare questo segnale audio dovremmo registrarlo in uno dei formati che un audio player puo' leggere. Dato che l'uso di interi a 16 bit e' lo standard per il formato .wav, useremo il metodo
wavfile.write di SciPy per registrarlo in un file:
In [56]:
from scipy.io.wavfile import write
# Remember SAMPLE_RATE = 44100 Hz is our playback rate write("mysinewave.wav", SAMPLE_RATE, normalized_tone)
Ora il prossimo passo che ci proponiamo e' di distinguere le tre componenti del suono ed eliminare la parte di rumore per ottenere un suono piu' pulito. A questo scopo useremo la trasformata di Fourier:
Out[51]:
220500
In [58]:
# Number of samples in normalized_tone N = SAMPLE_RATE * DURATION
yf = fft(normalized_tone) # calcolo la TdF del segnale
xf = fftfreq(N, 1 / SAMPLE_RATE) # frequenze centrali dei bin in cui la TdF # e' stata prima calcolata
plt.plot(xf, np.abs(yf)) # np.abs valuta sqrt(re^2+im^2) della trasforma ta
# complessa (che e' poi il Power Spectrum) plt.xlim([0,5000]) # per limitare l'estensione dell'asse x
plt.xlabel('Hz') plt.ylabel('Power')
#plt.xlim([0generate_sine_wave,5000]) # per limitare il range dell'asse X plt.show()
Lo spettro di potenza appare con due picchi simmetrici rispetto alla frequenza 0 (lo spettro di potenza della TdF e' sempre una funzione pari, ma le frequenze negative non hanno senso fisico). Le frequenze positive corrispondenti cadono esattamente ai valori delle frequenze con cui abbiamo costruito il segnale audio di prova: 400 Hz e 4000 Hz.
Notare che la linea blu del plot si estende sull'asse X fino alla frequenza ~22000 Hz (per l'esattezza 22050 Hz) che corrisponde esattamente alla meta' del SAMPLE_RATE=44100 Hz che abbiamo usato per generare il segnale. Questa e' la frequenza di Nyquist, cioe' la massima frequenza rivelabile da un campionamento a 44100 Hz.
Inversione
Passiamo ora a sfruttare l'invertibilita' della trasformata di Fourier. Questa proprieta' permette di ritornare dallo spazio delle frequenze a quello del segnale. Se pero' nello spazio di Fourier eliminiamo una data frequenza, anche nella ricostruzione del segnale non la ritroveremo. Siccome il rumore l'abbiamo aggiunto (e visto nello spettro di potenza) a 4000 Hz, andremo ora ad eliminare quella componente dallo spettro di Fourier.
1) individuiamo a quali indici del vettore delle ascisse corrisponde la frequenza di rumore:
# cerco gli indici dei punti in cui il vettore abs(yf) (spettro di potenza)
# supera un valore dato. Dallo spettro di potenza si vede che
# la frequenza di rumore corrisponde ai punti in cui np.abs(yf) > 0.5E9.
# Individuiamo gli indici in cui il vettore abs(yf) supera il valore dato iii=[i for i,v in enumerate(np.abs(yf)) if v > .5e9]
print('indici a cui la potenza > 0.5e9: ',iii) print('corrispondenti alle frequenze: ',xf[iii])
2) individuati gli indici corrispondenti alla frequenza di -4000 Hz e +4000 Hz (sono iii[1] ed iii[2]),
andiamo ad azzerare la trasformata di Fourier in quei punti che corrispondono alla frequenza di 4000 Hz che abbiamo aggiunto per simulare un rumore:
In [78]:
yf[iii[1]-2:iii[1]+3]=0
#yf[iii[2]-2:iii[2]+3]=0
#yf[iii[3]-2:iii[3]+3]=0 yf[iii[4]-2:iii[4]+3]=0 print(np.abs(yf[iii]))
In [79]:
plt.plot(xf, np.abs(yf))
#plt.xlim([0,5000]) plt.show()
3) adesso, dopo aver azzerato la frequenza del rumore, usiamo la trasformazione inversa di Fourier per ottenere il segnale "pulito":
indici a cui la potenza > 0.5e9: [2000, 20000, 35000, 185500, 20050 0, 218500]
corrispondenti alle frequenze: [ 400. 4000. 7000. -7000. -4000.
-400.]
[2.10041698e+09 0.00000000e+00 1.05021919e+09 1.05021919e+09 0.00000000e+00 2.10041698e+09]
In [80]:
from scipy.fftpack import ifft # importiamo la trasformata inversa new_sig = ifft(yf).real # separiamo la parte reale da plottare plt.plot(new_sig[:400]) # plot dei primio 400 punti
plt.show()
Warning:
La procedura illustrata qui e' utile per illustrare le potenzialita' della trasformata di Fourier, ma non e' quella piu' appropriata da usare nei casi reali. Infatti, i segnali reali non sono mai perfettamente monocromatici come in questo esempio e si devono utilizzare metodi piu' raffinati per tagliare opportunamente lo spettro di Fourier prima di antitrasformare. In questo campo gioca un ruolo essenziale l'utilizzo di filtri che, per essere efficaci, devono rispondere a precisi requisiti che qui non trattiamo.
In [ ]: