Monday 11 April 2011

Sine window function

Hi!

Today I'm going to explain how to implement a simple windowing function. Window functions are used to control the shape of a graph: typically to shape the ends to zero.

Today's problem:

I'm writing an audio visualization plugin for Autodesk Maya and a central part of that involves heavy use of the Fast Fourier Transform. In order to get accurate output data from the FFT and avoid spectral leakage it is important that the input discrete samples are periodic. This means that the split second of audio I'm performing calculations on at any time must loop.

Today's solution:

By implementing a window function I can gradually bring the ends of the signal to zero, essentially making it periodic.

There are a plethora of different window functions (many are listed here: http://en.wikipedia.org/wiki/Window_function) but I am going to use the sine wave to shape my data for a number of reasons:
  • I don't need to play with line functions (much).
  • Trig is available in pretty much every language.
  • The first 180 degrees of 1 period starts at 0, rises to 1 and then falls to 0 again.
  • The transition is gradual, my data is going to be uniformly modified.

You might think "Why a sine wave? The cosine wave is centred and reflects in the Y axis!". Well, in this situation my data is in a list and starts at an index of 0. If I multiply that by sin(0) I will get zero. Which is what I want!

The most tricky thing here is I need to scale the sine wave so the half-period ends at the same value as the length of my array of data. If I have 1000 samples, I want sin(PI) to land on X = 1000.

The line function for a sine wave is:
y = a sin [ b( x - hpi) ] + k
  
Where A is the Y scale, B is the X scale, H is the X shift and K is the Y shift.

I want the function to oscillate between 1 and -1 so I can leave A and K alone. I also want to start at X = 0 so I can leave H alone. This leaves me with B.

Once I have my scale value, all I need to do is feed in my X (the index of the sample) and then multiply the 0-1 result with the original sample. Performing this across an entire array will window the entire dataset!

The following is my function in Python. Sample is the current, single value I'm working with. I is the index of the value. Width is the number of values in the full array.

def window_sample(sample, i, width):
 
 width *= 2 #i only want the first half of the sine period
 y = math.sin((math.pi * i)/width) #value at sine(index)
 
 #modify sample
 sample *= y
 
 return sample

You'll notice I'm dividing by the scalar B rather than multiplying. This is because it scales inversely. Here are 2 examples of how this function will shape a set of samples:



As you can see (particularly with the second example) the signal is very much a strong representation of its former self despite being windowed, however it is now periodic.