I have a handful of wav files. I'd like to use SciPy FFT to plot the frequency spectrum of these wav files. How would I go about doing this?
Python provides several api to do this fairly quickly. I download the sheep-bleats wav file from this link. You can save it on the desktop and cd there within terminal. These lines in the python prompt should be enough: (omit >>>)
import matplotlib.pyplot as plt
from scipy.fftpack import fft
from scipy.io import wavfile # get the api
fs, data = wavfile.read('test.wav') # load the data
a = data.T[0] # this is a two channel soundtrack, I get the first track
b=[(ele/2**8.)*2-1 for ele in a] # this is 8-bit track, b is now normalized on [-1,1)
c = fft(b) # calculate fourier transform (complex numbers list)
d = len(c)/2 # you only need half of the fft list (real signal symmetry)
plt.plot(abs(c[:(d-1)]),'r')
plt.show()
Here is a plot for the input signal:
Here is the spectrum
For the correct output, you will have to convert the xlabelto the frequency for the spectrum plot.
k = arange(len(data))
T = len(data)/fs # where fs is the sampling frequency
frqLabel = k/T
If you are have to deal with a bunch of files, you can implement this as a function:
put these lines in the test2.py:
import matplotlib.pyplot as plt
from scipy.io import wavfile # get the api
from scipy.fftpack import fft
from pylab import *
def f(filename):
fs, data = wavfile.read(filename) # load the data
a = data.T[0] # this is a two channel soundtrack, I get the first track
b=[(ele/2**8.)*2-1 for ele in a] # this is 8-bit track, b is now normalized on [-1,1)
c = fft(b) # create a list of complex number
d = len(c)/2 # you only need half of the fft list
plt.plot(abs(c[:(d-1)]),'r')
savefig(filename+'.png',bbox_inches='tight')
Say, I have test.wav and test2.wav in the current working dir, the following command in python prompt interface is sufficient:
import test2
map(test2.f, ['test.wav','test2.wav'])
Assuming you have 100 such files and you do not want to type their names individually, you need the glob package:
import glob
import test2
files = glob.glob('./*.wav')
for ele in files:
f(ele)
quit()
You will need to add getparams in the test2.f if your .wav files are not of the same bit.
You could use the following code to do the transform:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import print_function
import scipy.io.wavfile as wavfile
import scipy
import scipy.fftpack
import numpy as np
from matplotlib import pyplot as plt
fs_rate, signal = wavfile.read("output.wav")
print ("Frequency sampling", fs_rate)
l_audio = len(signal.shape)
print ("Channels", l_audio)
if l_audio == 2:
signal = signal.sum(axis=1) / 2
N = signal.shape[0]
print ("Complete Samplings N", N)
secs = N / float(fs_rate)
print ("secs", secs)
Ts = 1.0/fs_rate # sampling interval in time
print ("Timestep between samples Ts", Ts)
t = scipy.arange(0, secs, Ts) # time vector as scipy arange field / numpy.ndarray
FFT = abs(scipy.fft(signal))
FFT_side = FFT[range(N/2)] # one side FFT range
freqs = scipy.fftpack.fftfreq(signal.size, t[1]-t[0])
fft_freqs = np.array(freqs)
freqs_side = freqs[range(N/2)] # one side frequency range
fft_freqs_side = np.array(freqs_side)
plt.subplot(311)
p1 = plt.plot(t, signal, "g") # plotting the signal
plt.xlabel('Time')
plt.ylabel('Amplitude')
plt.subplot(312)
p2 = plt.plot(freqs, FFT, "r") # plotting the complete fft spectrum
plt.xlabel('Frequency (Hz)')
plt.ylabel('Count dbl-sided')
plt.subplot(313)
p3 = plt.plot(freqs_side, abs(FFT_side), "b") # plotting the positive fft spectrum
plt.xlabel('Frequency (Hz)')
plt.ylabel('Count single-sided')
plt.show()
Related
I'm trying to plot the frequencies that make up the first 1 second of a voice recording.
My approach was to:
Read the .wav file as a numpy array containing time series data
Slice the array from [0:sample_rate-1], given that the sample rate has units of [samples/1 second], which implies that sample_rate [samples/seconds] * 1 [seconds] = sample_rate [samples]
Perform a fast fourier transform (fft) on the time series array in order to get the frequencies that make up that time-series sample.
Plot the the frequencies on the x-axis, and amplitude on the y-axis. The frequency domain would range from 0:(sample_rate/2) since the Nyquist Sampling Theorem tells us that the recording captured frequencies of at least two times the maximum frequency, i.e 2*max(frequency). I'll also slice the frequency output array in half since the output frequency data is symmetrical
Here is my implementation
import matplotlib.pyplot as plt
import numpy as np
from scipy.fftpack import fft
from scipy.io import wavfile
sample_rate, audio_time_series = wavfile.read(audio_path)
single_sample_data = audio_time_series[:sample_rate]
def fft_plot(audio, sample_rate):
N = len(audio) # Number of samples
T = 1/sample_rate # Period
y_freq = fft(audio)
domain = len(y_freq) // 2
x_freq = np.linspace(0, sample_rate//2, N//2)
plt.plot(x_freq, abs(y_freq[:domain]))
plt.xlabel("Frequency [Hz]")
plt.ylabel("Frequency Amplitude |X(t)|")
return plt.show()
fft_plot(single_sample_data, sample_rate)
This is the plot that it generated
However, this is incorrect, my spectrogram tells me I should have frequency peaks below the 5kHz range:
In fact, what this plot is actually showing, is the first second of my time series data:
Which I was able to debug by removing the absolute value function from y_freq when I plot it, and entering the entire audio signal into my fft_plot function:
...
sample_rate, audio_time_series = wavfile.read(audio_path)
single_sample_data = audio_time_series[:sample_rate]
def fft_plot(audio, sample_rate):
N = len(audio) # Number of samples
y_freq = fft(audio)
domain = len(y_freq) // 2
x_freq = np.linspace(0, sample_rate//2, N//2)
# Changed from abs(y_freq[:domain]) -> y_freq[:domain]
plt.plot(x_freq, y_freq[:domain])
plt.xlabel("Frequency [Hz]")
plt.ylabel("Frequency Amplitude |X(t)|")
return plt.show()
# Changed from single_sample_data -> audio_time_series
fft_plot(audio_time_series, sample_rate)
The code sample above produced, this plot:
Therefore, I think one of two things is going on:
The fft() function is not actually performing an fft on the time series data it is being given
The .wav file does not contain time series data to begin with
What could be the issue? Has anyone else experienced this?
I have replicated, essentially replicated, the code in the question and I don't see the problem the OP has described.
In [172]: %reset -f
...: import matplotlib.pyplot as plt
...: import numpy as np
...: from scipy.fftpack import fft
...: from scipy.io import wavfile
...:
...: sr, data = wavfile.read('sample.wav')
...: print(data.shape, sr)
...: signal = data[:sr,0]
...: Signal = fft(signal)
...: fig, (axt, axf) = plt.subplots(2, 1,
...: constrained_layout=1,
...: figsize=(11.8,3))
...: axt.plot(signal, lw=0.15) ; axt.grid(1)
...: axf.plot(np.abs(Signal[:sr//2]), lw=0.15) ; axf.grid(1)
...: plt.show()
sr, data = wavfile.read('sample.wav')
(268237, 2) 8000
Hence, I'm voting for closing the question because it is "Not reproducible or was caused by a typo".
Please feel free to point out any errors/improvements in the existing code
So this is a very basic question and I only have a beginner level understanding of signal processing. I have a 1.02 second accelerometer data sampled at 32000 Hz. I am looking to extract the following frequency domain features after having performed FFT in python -
Mean Freq, Median Freq, Power Spectrum Deformation, Spectrum energy, Spectral Kurtosis, Spectral Skewness, Spectral Entropy, RMSF (Root Mean Square Freq.), RVF (Root Variance Frequency), Power Cepstrum.
More specifically, I am looking for plots of these features as a final output.
The csv file containing data has four columns: Time, X Axis Value, Y Axis Value, Z Axis Value (The accelerometer is a triaxial one). So far on python, I have been able to visualize the time domain data, apply convolution filter to it, applied FFT and generated a Spectogram that shows an interesting shock
To Visualize Data
#Importing pandas and plotting modules
import numpy as np
from datetime import datetime
import pandas as pd
import matplotlib.pyplot as plt
#Reading Data
data = pd.read_csv('HelicalStage_Aug1.csv', index_col=0)
data = data[['X Value', 'Y Value', 'Z Value']]
date_rng = pd.date_range(start='1/8/2018', end='11/20/2018', freq='s')
#Plot the entire time series data and show gridlines
data.grid=True
data.plot()
enter image description here
Denoising
# Applying Convolution Filter
mylist = [1, 2, 3, 4, 5, 6, 7]
N = 3
cumsum, moving_aves = [0], []
for i, x in enumerate(mylist, 1):
cumsum.append(cumsum[i-1] + x)
if i>=N:
moving_ave = (cumsum[i] - cumsum[i-N])/N
#can do stuff with moving_ave here
moving_aves.append(moving_ave)
np.convolve(x, np.ones((N,))/N, mode='valid')
result_X = np.convolve(data[["X Value"]].values[:,0], np.ones((20001,))/20001, mode='valid')
result_Y = np.convolve(data[["Y Value"]].values[:,0], np.ones((20001,))/20001, mode='valid')
result_Z = np.convolve(data[["Z Value"]].values[:,0],
np.ones((20001,))/20001, mode='valid')
plt.plot(result_X-np.mean(result_X))
plt.plot(result_Y-np.mean(result_Y))
plt.plot(result_Z-np.mean(result_Z))
enter image description here
FFT and Spectogram
import numpy as np
import scipy as sp
import scipy.fftpack
import pandas as pd
import matplotlib.pyplot as plt
%matplotlib inline
df = pd.read_csv('HelicalStage_Aug1.csv')
df = df.drop(columns="Time")
df.plot()
plt.title('Sensor Data as Time Series')
signal = df[['Y Value']]
signal = np.squeeze(signal)
Y = np.fft.fftshift(np.abs(np.fft.fft(signal)))
Y = Y[int(len(Y)/2):]
Y = Y[10:]
plt.figure()
plt.plot(Y)
enter image description here
plt.figure()
powerSpectrum, freqenciesFound, time, imageAxis = plt.specgram(signal, Fs= 32000)
plt.show()
enter image description here
If my code is correct and the generated FFT and spectrogram are good, then how can I graphically compute the previously mentioned frequency domain features?
I have tried doing the following for MFCC -
import numpy as np
import matplotlib.pyplot as plt
import scipy as sp
from scipy.io import wavfile
from python_speech_features import mfcc
from python_speech_features import logfbank
# Extract MFCC and Filter bank features
mfcc_features = mfcc(signal, Fs)
filterbank_features = logfbank(signal, Fs)
# Printing parameters to see how many windows were generated
print('\nMFCC:\nNumber of windows =', mfcc_features.shape[0])
print('Length of each feature =', mfcc_features.shape[1])
print('\nFilter bank:\nNumber of windows =', filterbank_features.shape[0])
print('Length of each feature =', filterbank_features.shape[1])
Visualizing filter bank features
#Matrix needs to be transformed in order to have horizontal time domain
mfcc_features = mfcc_features.T
plt.matshow(mfcc_features)
plt.title('MFCC')
enter image description here
enter image description here
I think your fft taking procedure is not correct, fft output is usually peak and when you are taking abs it should be one peak, as , probably you should change it to Y = np.fft.fftshift(np.abs(np.fft.fft(signal))) to Y=np.abs(np.fft.fftshift(signal)
I am trying to use the NumPy library for Python to do some frequency analysis. I have two .wav files that both contain a 440 Hz sine wave. One of them I generated with the NumPy sine function, and the other I generated in Audacity. The FFT works on the Python-generated one, but does nothing on the Audacity one.
Here are links to the two files:
The non-working file: 440_audacity.wav
The working file: 440_gen.wav
This is the code I am using to do the Fourier transform:
import numpy as np
import matplotlib.pyplot as plt
import scipy.io.wavfile as wave
infile = "440_gen.wav"
rate, data = wave.read(infile)
data = np.array(data)
data_fft = np.fft.fft(data)
frequencies = np.abs(data_fft)
plt.subplot(2,1,1)
plt.plot(data[:800])
plt.title("Original wave: " + infile)
plt.subplot(2,1,2)
plt.plot(frequencies)
plt.title("Fourier transform results")
plt.xlim(0, 1000)
plt.tight_layout()
plt.show()
I have two 16-bit PCM .wav files, one from Audacity and one created with the NumPy sine function. The NumPy-generated one gives the following (correct) result, with the spike at 440Hz:
The one I created with Audacity, although the waveform appears identical, does not give any result on the Fourier transform:
I admit I am at a loss here. The two files should contain in effect the same data. They are encoded the same way, and the wave forms appear identical on the upper graph.
Here is the code used to generate the working file:
import numpy as np
import wave
import struct
import matplotlib.pyplot as plt
from operator import add
freq_one = 440.0
num_samples = 44100
sample_rate = 44100.0
amplitude = 12800
file = "440_gen.wav"
s1 = [np.sin(2 * np.pi * freq_one * x/sample_rate) * amplitude for x in range(num_samples)]
sine_one = np.array(s1)
nframes = num_samples
comptype = "NONE"
compname="not compressed"
nchannels = 1
sampwidth = 2
wav_file = wave.open(file, 'w')
wav_file.setparams((nchannels, sampwidth, int(sample_rate), nframes, comptype, compname))
for s in sine_one:
wav_file.writeframes(struct.pack('h', int(s)))
Let me explain why your code doesn't work. And why it works with [:44100].
First of all, you have different files:
440_gen.wav = 1 sec and 44100 samples (counts)
440_audacity.wav = 5 sec and 220500 samples (counts)
Since for 440_gen.wav in FFT you use the number of reference points N=44100 and the sample rate 44100, your frequency resolution is 1 Hz (bins are followed in 1 Hz increments).
Therefore, on the graph, each FFT sample corresponds to a delta equal to 1 Hz.
plt.xlim(0, 1000) just corresponds to the range 0-1000 Hz.
However, for 440_audacity.wav in FFT, you use the number of reference points N=220500 and the sample rate 44100. Your frequency resolution is 0.2 Hz (bins follow in 0.2 Hz increments) - on the graph, each FFT sample corresponds to a frequency in 0.2 Hz increments (min-max = +(-) 22500 Hz).
plt.xlim(0, 1000) just corresponds to the range 1000x0.2 = 0-200 Hz.
That is why the result is not visible - it does not fall within this range.
plt.xlim (0, 5000) will correct your situation and extend the range to 0-1000 Hz.
The solution [:44100] that jwalton brought in really only forces the FFT to use N = 44100. And this repeats the situation with the calculation for 440_gen.wav
A more correct solution to your problem is to use the N (Windows Size) parameter in the code and the np.fft.fftfreq() function.
Sample code below.
I also recommend an excellent article https://realpython.com/python-scipy-fft/
import numpy as np
import matplotlib.pyplot as plt
import scipy.io.wavfile as wave
N = 44100 # added
infile = "440_audacity.wav"
rate, data = wave.read(infile)
data = np.array(data)
data_fft = np.fft.fft(data, N) # added N
frequencies = np.abs(data_fft)
x_freq = np.fft.fftfreq(N, 1/44100) # added
plt.subplot(2,1,1)
plt.plot(data[:800])
plt.title("Original wave: " + infile)
plt.subplot(2,1,2)
plt.plot(x_freq, frequencies) # added x_freq
plt.title("Fourier transform results")
plt.xlim(0, 1000)
plt.tight_layout()
plt.show()
Since answering this question #Konyukh Fyodorov was able to provide a better and properly justified solution (below).
The following worked for me and produced the plots as expected. Unfortunately I cannot piece together quite why this works, but I'm sharing this solution in the hope it may assist someone else to make that leap.
import numpy as np
import matplotlib.pyplot as plt
import scipy.io.wavfile as wave
infile = "440_gen.wav"
rate, data = wave.read(infile)
data = np.array(data)
# Use first 44100 datapoints in transform
data_fft = np.fft.fft(data[:44100])
frequencies = np.abs(data_fft)
plt.subplot(2,1,1)
plt.plot(data[:800])
plt.title("Original wave: " + infile)
plt.subplot(2,1,2)
plt.plot(frequencies)
plt.title("Fourier transform results")
plt.xlim(0, 1000)
plt.tight_layout()
plt.show()
I have used this code how to extract frequency associated with fft values in python and added a firwin filter to detect a short high frequency signal. The signal is 1 second long and occurs at random in a wav audio file of x-seconds. My code looks as follows:
from scipy import signal
from scipy.io import wavfile
from scipy.fftpack import fft, ifft,fftfreq
import matplotlib.pyplot as plt
import wave
import numpy as np
import sys
import struct
frate,data = wavfile.read('SoundPeep.wav')
#print(frate)
b = signal.firwin(101, cutoff=900, fs= frate, pass_zero=False)
data = signal.lfilter(b, [1.0], data)
w = np.fft.fft(data)
freqs = np.fft.fftfreq(len(w))
#print(len(w)/frate)
#print(w.min(), w.max())
# (-0.5, 0.499975)
# Find the peak in the coefficients
idx = np.argmax(np.abs(w))
winner = np.argwhere(np.abs(w) == np.amax(np.abs(w)))
freq = freqs[idx]
freq_in_hertz = abs(freq * frate)
print("HZ")
print(freq_in_hertz)
occurence = idx/frate
print(occurence)
The code works well at detecting the peak HZ frequency. My problem is that I want to calculate where the high frequency signal begins in the audio-file. I thought it could be done simply by dividing the index(idx) by the framerate of the recording but this does not seem to work.
you could make a sonogram by using a short time fourier transform to see how frequency changes over time. the stft returns 3 values: timestamp, amplitude, and frequency. If the amplitude isnt relevant, you can just use peak detection from there.
I generate the an audio file SingleTone.wav using the following sox command.
sox -n SingleTone.wav synth 10 sin 525
I then execute the following program to perform an fft on a sample of this tone.
import matplotlib.pyplot as plt
from scipy.fftpack import fft, rfft
from scipy.io import wavfile
from sys import argv
# FFT sample count
N = 8192
# Sampling frequency
samples_per_second = 44100
# Frequency resolution
freq_resolution = samples_per_second / N
fs, data = wavfile.read(argv[1])
a = data.T
b=[(ele/2**8.)*2-1 for ele in a]
c = fft(b, N)
d = len(c)/2 - 1
frequencies = [x*freq_resolution for x in xrange(d)]
print '\n'.join(",".join([str(f),str(x)]) for f, x in zip(frequencies, abs(c[:d])))
plt.plot(frequencies, abs(c[:d]),'r')
plt.show()
The output I get is a frequency spike between 440 Hz and 455 Hz, rather than at 525 Hz as I had expected.
What is the reason for the disparity?
Valid answers would point at a misunderstanding of FFT, a bug in the code, or anything incorrect about the setup.
Try this: use the sampling rate from the wav file, and, for convenience and fewer bugs, use the frequency list provided by fft library.
Here is the code per the above,
#!/usr/bin/python
import matplotlib.pyplot as plt
from scipy.fftpack import fft, rfft, fftfreq, rfftfreq
from scipy.io import wavfile
from sys import argv
samples_per_second, data = wavfile.read(argv[1])
# FFT sample count
N = 8192
a = data.T
b=[(ele/2**8.)*2-1 for ele in a]
c = fft(b, N)
d = len(c)/2 - 1
frequencies = fftfreq(N,1./samples_per_second)
#print '\n'.join(",".join([str(f),str(x)]) for f, x in zip(frequencies[:d], abs(c[:d])))
plt.plot(frequencies[:d], abs(c[:d]),'r')
plt.show()