Summary
If you want a frame rate-independent low-pass filter (an infinite impulse response, or IIR) to apply to a stream of data, use this formula:
filteredValue = oldValue + (newValue - oldValue) / (smoothing / timeSinceLastUpdate)
Background
Imagine that you have a stream of data coming in that has some noise in it. This could be the frame rate of a game, the mouse motion of the user drawing a line, the voltage measured across a resistor…whatever.
You want to graph this value, but you want to hide the noise. One solution is a "low-pass filter".
A Simple Low-Pass Filter
Similar to an audio graphic equalizer with the treble dialed down, a low-pass filter muffles high-frequency (fast) changes to the signal. Unlike a moving average, it requires only three numbers to cover the entire spectrum of effect, from no-change-to-the-input to unyielding-snail-paced-graph.
Here's an example of using a low-pass filter to change values of an array, using JavaScript:
// values: an array of numbers that will be modified in place
// smoothing: the strength of the smoothing filter; 1=no change, larger values smoothes more
function smoothArray( values, smoothing ){
var value = values[0]; // start with the first input
for (var i=1, len=values.length; i<len; ++i){
var currentValue = values[i];
value += (currentValue - value) / smoothing;
values[i] = value;
}
}
All the power of the filter is in the line value += (currentValue - value) / smoothing
. This finds the difference between the new value and the current (smoothed) value, shrinks it based on the strength of the filter, and then adds it to the smoothed value. You can see that if smoothing
is set to 1
then the smoothed value always becomes the next value. If the smoothing
is set to 2
then the smoothed value moves halfway to each new point on each new frame. The larger the smoothing
value, the less the smoothed line is perturbed by new changes.
Try it yourself. The following green line is the smoothed value of our blue noise. Adjust the smoothing
below and see the effects on the line. Find a value that you think looks smooth and nice, while still giving a good representation of what's going on in the blue.
smoothing =
The Impact of Lower Frame Rates
If you're like me, you might have chosen a smoothing
value somewhere between 20
and 60
. The higher values smooth out the noise nicely, at the cost of a slight delay when representing the two instantaneous, sharp signal jumps.
However, we're not in a good place (yet). If our frame rate changes—the speed at which we're sampling the signal—the effect of our carefully-chosen smoothing can have drastically different effects.
The following graph shows the effects of 2x lowered frame rate (magenta), 5x lowered (red) and varying sample rate (yellow). A smoothing
that works nicely at a high sampling rate is far too strong when the rate drops. (If you cannot see the differences between the lines, try turning up the smoothing
above.)
What we need is a formula that accounts for changes to the frame rate.
Achieving Frame Rate Independence
You may have noticed that there is a similar effect between increasing the smoothing
value and lowering the sampling rate. In fact, it turns out that they are more than similar, they are equivalent. A doubling of the smoothing
is the same as a halving of the rate. As such, only a minor tweak is needed to our smoothing formula:
value += (currentValue - value) / (smoothing / timeSinceLastSample);
Following are the various sampling rates with smoothing adjusted based on the rate. Although they vary slightly from one another (based on which of the horrendously noisy samples they latch onto) you can see that they generally match, regardless of the smoothing
setting above:
Finally, here is a code block similar to what I use:
var smoothed = 0; // or some likely initial value
var smoothing = 10; // or whatever is desired
var lastUpdate = new Date;
function smoothedValue( newValue ){
var now = new Date;
var elapsedTime = now - lastUpdate;
smoothed += elapsedTime * ( newValue - smoothed ) / smoothing;
lastUpdate = now;
return smoothed;
}