4
$\begingroup$

I'm new to DSP and I have a wav file with a number of different frequencies playing at different volumes. I'd like to plot the power of the 350Hz frequency signal at each point in time. The source is rotating so should see the power rising and falling periodically. To start with I broke the signal up into windows and then implemented Geortzel's algorithm (from Wikipedia). This seems to work: https://www.dropbox.com/s/aq1aigdx8dq306q/Screenshot%202015-03-18%2017.38.20.png?dl=0 but for different window length and window overlaps I get different plots.

I'd love some guidance on what the correct way to choose the window length and the amount by which each window overlap is? window_length and window_skip in the code.

Also should the windows be processed (i.e. convolved with a window function like Hanning) I've read about this but I'm not clear on what this would do to the output, nor how to choose the length of the window function?

Here's the audio file: https://www.dropbox.com/s/knvwqwg7s3mcz56/example.wav?dl=0

Here's my code, comments welcome:

import pylab
import scipy.io.wavfile
import numpy as np
import math

def goetrzel(data, target_frequency):
        s_prev = 0
        s_prev2 = 0
        normalized_frequency = 2.0 * np.pi * target_frequency / len(data)
        coeff = 2.0 * np.cos(normalized_frequency)
        for sample in data:
                s = sample + coeff * s_prev - s_prev2
                s_prev2 = s_prev
                s_prev = s
                power = s_prev2 * s_prev2 + s_prev * s_prev - coeff * s_prev * s_prev2
        return power

def sliding_window(data, length, skip=None):
        if skip is None:
                skip = length
        n = len(data)
        for i in range(0, n - int(length), int(skip)):
                yield data[i:i+length]

detect_frequency = 350.0
#detect_frequency = 485.0
#detect_frequency = 633.0
#detect_frequency = 796.0

rate, data = scipy.io.wavfile.read('example.wav')

window_length = 2 * detect_frequency # How should I pick this?
window_skip = window_length # How should I pick this?

power = [goetrzel(window, detect_frequency) for window in sliding_window(data, window_length, window_skip)]
pylab.figure()
pylab.plot(power)
pylab.show()
$\endgroup$
8
$\begingroup$

Using overlapping rectangular windows has the potential to give an extra 'weighting' to certain sections of the audio unless you choose the overlap carefully. For example, with an overlap of 25% you're accounting for half of the data twice and the other half just once. This is not necessarily an issue depending on the application, but is something to bear in mind. Choosing overlaps of 1/2, 2/3, 3/4... solves this problem (assuming a signal of reasonable length).

For a given overlap, the choice of window length affects two things really - temporal resolution and coefficient quality.

The first is slightly easier to explain and visualise. If the window increases in length then you have fewer windows spread across your audio and therefore a greater time interval between each - i.e. lower temporal resolution.

The coefficient quality, however, increases with window length. If you perform a Fourier transform over a longer sample you get a higher frequency resolution and each coefficient is therefore more 'representative' of its frequency. Think of a coefficient as a frequency bin rather than a single frequency value. Shorter windows leads to wider frequency bins and smoothing/smearing of the coefficients, whereas longer windows leads to narrower, more 'distinct' bins. A quick way of working out the bin size is to do 1 / (length of window in seconds). If this comes out to be 20 Hz and you want to be able to compare the coefficients at 330 Hz with those at 340 Hz then you might consider upping the window length.

You can probably see the tradeoff in window length here - longer windows give less temporal resolution and better coefficient quality; shorter windows give higher temporal resolution and lower coefficient quality.

Currently you're using not using a window function, i.e. your windows are rectangular. Window functions are useful in that they can make your window of data appear more periodic than it actually is. Remember that the Fourier transform assumes periodicity. Any significant differences in the start and end of your window of data become a sharp step change under that assumption, producing a lot of extra coefficients/noise. Multiplying the data by a window function can help mitigate that problem to some extent by reducing the magnitude down to zero or near-zero at the edges of your window. Common window functions used for audio are the Hann and Hamming windows.

What this all means for you:

  • I'd recommend using a window function to reduce noise if your windows of data are non-periodic, which is likely. Start out with a Hann window.
  • Id also recommend an overlap of 50%. When used with a Hann window this value has the advantage that 50% overlapping Hann windows sum together to a constant magnitude of unity. An overlap of more than this results in better coefficient quality at the expense of extra processing, but 50% is a good starting point.
  • With the overlap chosen your decision about window length should be based on the tradeoff mentioned earlier and depends on your application. If you want a high temporal resolution then you're going to have to sacrifice quality. If you want to be able to compare tightly spaced frequencies over time then you'll have to sacrifice temporal resolution.
$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.