42

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?

user1802143
  • 14,662
  • 17
  • 46
  • 55

2 Answers2

82

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:
signal

Here is the spectrum 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.

imbr
  • 6,226
  • 4
  • 53
  • 65
yshk
  • 921
  • 7
  • 8
  • 5
    Good answer! You may want to remove the `>>>` so the OP and others can copy and paste. Also I've found it helps the answer if you include a picture if your code makes a plot. – Hooked Apr 30 '14 at 02:54
  • Thanks. I have update the thread with prompt removed and new pictures. – yshk Apr 30 '14 at 03:53
  • 1
    How would you concatenate multiple wav files? I have a lot of small wav files. – user1802143 Apr 30 '14 at 06:38
  • I have on the order of a few hundred small wave files. So I need an efficient way to do it. – user1802143 Apr 30 '14 at 06:44
  • I have updated the code so that it should be able to handle multiple files with minimum extra code. – yshk Apr 30 '14 at 17:44
  • It seems like this code plots each wav file in a different plot. Is there a way to concatenate all the data and then plot it in a single plot? – user1802143 May 04 '14 at 01:14
  • Thanks for the answer. I got very stuck when you cited sfft and found that this helped `import scipy.fftpack as sfft.` – Adrienne Oct 23 '15 at 14:10
  • 4
    It `a = data.T[0]` should be it `a = data.T[0:data.size]` ? – Darleison Rodrigues Jun 08 '16 at 18:07
  • why do "you only need half of the fft list"? – theonlygusti Nov 24 '17 at 14:50
  • @theonlygusti 1. By definition, the FFT is symmetric across data[0] to data[len-1] 2. The unit is hz on the xlabel. – yshk Dec 03 '17 at 12:27
  • My wav file seems to have a different amount of bits that 8. How can I read the bit depth ? – Johannes Lemonde May 24 '19 at 14:49
  • The link to the wav file takes you to the chrome store to install some plugin – AlBeebe Jun 07 '19 at 16:40
  • 1
    d = len(c)/2 is a float which throws: TypeError: slice indices must be integers or None or have an __index__ method. Shouldn't you cast it to an int? – Boris May 04 '21 at 10:22
  • 2
    @Boris the pythonic way to do is `d = len(c) // 2` which performs an integer division straight away and avoids any need to cast the result. The answer is probably written for python2 considering its age, and back then integer division was the standard if both operands were integers, in python3 it defaults to floating point division instead. – alkanen May 14 '21 at 10:25
16

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()
bunkus
  • 975
  • 11
  • 19
  • 11
    It works nicely. However, you need to fix the division operators; change N/2 to N//2 because that operation creates a float – pbgnz May 17 '18 at 02:36
  • 2
    @pbgnz Only for Python 3, or if you used `from __future__ import division` in Python 2 – supergra Jun 05 '19 at 17:58