Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Estimate formants using LPC in Python

I'm new to signal processing (and numpy, scipy, and matlab for that matter). I'm trying to estimate vowel formants with LPC in Python by adapting this matlab code:

http://www.mathworks.com/help/signal/ug/formant-estimation-with-lpc-coefficients.html

Here is my code so far:

#!/usr/bin/env python
import sys
import numpy
import wave
import math
from scipy.signal import lfilter, hamming
from scikits.talkbox import lpc

"""
Estimate formants using LPC.
"""

def get_formants(file_path):

    # Read from file.
    spf = wave.open(file_path, 'r') # http://www.linguistics.ucla.edu/people/hayes/103/Charts/VChart/ae.wav

    # Get file as numpy array.
    x = spf.readframes(-1)
    x = numpy.fromstring(x, 'Int16')

    # Get Hamming window.
    N = len(x)
    w = numpy.hamming(N)

    # Apply window and high pass filter.
    x1 = x * w
    x1 = lfilter([1., -0.63], 1, x1)

    # Get LPC.
    A, e, k = lpc(x1, 8)

    # Get roots.
    rts = numpy.roots(A)
    rts = [r for r in rts if numpy.imag(r) >= 0]

    # Get angles.
    angz = numpy.arctan2(numpy.imag(rts), numpy.real(rts))

    # Get frequencies.
    Fs = spf.getframerate()
    frqs = sorted(angz * (Fs / (2 * math.pi)))

    return frqs

print get_formants(sys.argv[1])

Using this file as input, my script returns this list:

[682.18960189917243, 1886.3054773107765, 3518.8326108511073, 6524.8112723782951]

I didn't even get to the last steps where they filter the frequencies by bandwidth because the frequencies in the list aren't right. According to Praat, I should get something like this (this is the formant listing for the middle of the vowel):

Time_s     F1_Hz        F2_Hz         F3_Hz         F4_Hz
0.164969   731.914588   1737.980346   2115.510104   3191.775838 

What am I doing wrong?

Thanks very much

UPDATE:

I changed this

x1 = lfilter([1., -0.63], 1, x1)

to

x1 = lfilter([1], [1., 0.63], x1)

as per Warren Weckesser's suggestion and am now getting

[631.44354635609318, 1815.8629524985781, 3421.8288991389031, 6667.5030877036006]

I feel like I'm missing something since F3 is very off.

UPDATE 2:

I realized that the order being passed to scikits.talkbox.lpc was off due to a difference in sampling frequency. Changed it to:

Fs = spf.getframerate()
ncoeff = 2 + Fs / 1000
A, e, k = lpc(x1, ncoeff)

Now I'm getting:

[257.86573127888488, 774.59006835496086, 1769.4624576002402, 2386.7093679399809, 3282.387975973973, 4413.0428174593926, 6060.8150432549655, 6503.3090645887842, 7266.5069407315023]

Much closer to Praat's estimation!

like image 225
pcaisse Avatar asked Aug 03 '14 18:08

pcaisse


3 Answers

The problem had to do with the order being passed to the lpc function. 2 + fs / 1000 where fs is the sampling frequency is the rule of thumb according to:

http://www.phon.ucl.ac.uk/courses/spsci/matlab/lect10.html

like image 97
pcaisse Avatar answered Nov 06 '22 03:11

pcaisse


I have not been able to get the results you expect, but I do notice two things which might cause some differences:

  1. Your code uses [1, -0.63] where the MATLAB code from the link you provided has [1 0.63].
  2. Your processing is being applied to the entire x vector at once instead of smaller segments of it (see where the MATLAB code does this: x = mtlb(I0:Iend); ).

Hope that helps.

like image 45
Lukeclh Avatar answered Nov 06 '22 02:11

Lukeclh


There are at least two problems:

  • According to the link, the "pre-emphasis filter is a highpass all-pole (AR(1)) filter". The signs of the coefficients given there are correct: [1, 0.63]. If you use [1, -0.63], you get a lowpass filter.

  • You have the first two arguments to scipy.signal.lfilter reversed.

So, try changing this:

x1 = lfilter([1., -0.63], 1, x1)

to this:

x1 = lfilter([1.], [1., 0.63], x1)

I haven't tried running your code yet, so I don't know if those are the only problems.

like image 1
Warren Weckesser Avatar answered Nov 06 '22 01:11

Warren Weckesser