robinnnnn - 6 months ago 32

Javascript Question

If I had an array of numbers such as [3, 5, 0, 8, 4, 2, 6], is there a way to “smooth out” the values so they’re closer to each other and display less variance?

I’ve looked into windowing the data using something called the Gaussian function for a **1-dimensional case**, which is my array, but am having trouble implementing it. This thread seems to solve exactly what I need but I don’t understand how user naschilling (second post) came up with the Gaussian matrix values.

**Context:** I’m working on a music waveform generator (borrowing from SoundCloud’s design) that maps the amplitude of the song at time *t* to a corresponding bar height. Unfortunately there’s a lot of noise, and it looks particularly ugly when the program maps a tiny amplitude which results in a sudden decrease in height. I basically want to smooth out the bar heights so they aren’t so varied.

The language I'm using is Javascript.

**EDIT**: Sorry, let me be more specific about "smoothing out" the values. According to the thread linked above, a user took an array

`[10.00, 13.00, 7.00, 11.00, 12.00, 9.00, 6.00, 5.00]`

and used a Gaussian function to map it to

`[ 8.35, 9.35, 8.59, 8.98, 9.63, 7.94, 5.78, 7.32]`

Notice how the numbers are much closer to each other.

`var array = [10, 13, 7, 11, 12, 9, 6, 5];`

function smooth(values, alpha) {

var weighted = average(values) * alpha;

var smoothed = [];

for (var i in values) {

var curr = values[i];

var prev = smoothed[i - 1] || values[values.length - 1];

var next = curr || values[0];

var improved = Number(this.average([weighted, prev, curr, next]).toFixed(2));

smoothed.push(improved);

}

return smoothed;

}

function average(data) {

var sum = data.reduce(function(sum, value) {

return sum + value;

}, 0);

var avg = sum / data.length;

return avg;

}

smooth(array, 0.85);

Answer

Interesting question!

The algorithm to smooth out the values obviously could vary a *lot*, but here is my take:

```
"use strict";
var array = [10, 13, 7, 11, 12, 9, 6, 5];
function avg (v) {
return v.reduce((a,b) => a+b, 0)/v.length;
}
function smoothOut (vector, variance) {
var t_avg = avg(vector)*variance;
var ret = Array(vector.length);
for (var i = 0; i < vector.length; i++) {
(function () {
var prev = i>0 ? ret[i-1] : vector[i];
var next = i<vector.length ? vector[i] : vector[i-1];
ret[i] = avg([t_avg, avg([prev, vector[i], next])]);
})();
}
return ret;
}
function display (x, y) {
console.clear();
console.assert(x.length === y.length);
x.forEach((el, i) => console.log(`${el}\t\t${y[i]}`));
}
display(array, smoothOut(array, 0.85));
```

NOTE: It uses some ES6 features like fat-arrow functions and template strings. Firefox 35+ and Chrome 45+ should work fine. Please use the babel repl otherwise.

My method basically computes the average of all the elements in the array in advance, and uses that as a *major* factor to compute the new value along with the current element value, the one prior to it, and the one after it. I am also using the prior value as the one *newly* computed and not the one from the original array. Feel free to experiment and modify according to your needs. You can also pass in a "variance" parameter to control the difference between the elements. Lowering it will bring the elements much closer to each other since it decreases the value of the average.

A slight variation to loosen out the smoothing would be this:

```
"use strict";
var array = [10, 13, 7, 11, 12, 9, 6, 5];
function avg (v) {
return v.reduce((a,b) => a+b, 0)/v.length;
}
function smoothOut (vector, variance) {
var t_avg = avg(vector)*variance;
var ret = Array(vector.length);
for (var i = 0; i < vector.length; i++) {
(function () {
var prev = i>0 ? ret[i-1] : vector[i];
var next = i<vector.length ? vector[i] : vector[i-1];
ret[i] = avg([t_avg, prev, vector[i], next]);
})();
}
return ret;
}
function display (x, y) {
console.clear();
console.assert(x.length === y.length);
x.forEach((el, i) => console.log(`${el}\t\t${y[i]}`));
}
display(array, smoothOut(array, 0.85));
```

which doesn't take the averaged value as a major factor.

Feel free to experiment, hope that helps!