Skip to content

Commit

Permalink
added gaussian smoothing
Browse files Browse the repository at this point in the history
  • Loading branch information
dribnet committed Sep 10, 2019
1 parent 2fb2140 commit fb596b7
Show file tree
Hide file tree
Showing 4 changed files with 191 additions and 9 deletions.
3 changes: 2 additions & 1 deletion index.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@
<script language="javascript" type="text/javascript" src="lib/p5.js"></script>
<script language="javascript" type="text/javascript" src="lib/p5.dom.js"></script>
<script language="javascript" type="text/javascript" src="lib/p5.sound.js"></script>
<script language="javascript" type="text/javascript" src="lib/taira.js"></script>
<script language="javascript" type="text/javascript" src="z_purview_helper.js"></script>
<script language="javascript" type="text/javascript" src="music_settings.js"></script>
<script language="javascript" type="text/javascript" src="history.js"></script>
<script language="javascript" type="text/javascript" src="music.js"></script>
<script language="javascript" type="text/javascript" src="music_runner.js"></script>

<style>
Expand Down
148 changes: 148 additions & 0 deletions lib/taira.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
/**
* taira - simple smoothing of one dimensional arrays
*/
class Taira extends Array {
/**
* Smoothen 1D-Array using selected algorithm
* @param {*} options Takes one of the supported algorithms (defaults to AVERAGE) and its parameters
*/
smoothen (...options) {
return Taira.smoothen(this, ...options)
}

/**
* Smoothen 1D-Array using selected algorithm
* @param {*} array The input data array
* @param {Taira.ALGORITHMS} algorithm Takes one of the supported algorithms (defaults to AVERAGE)
* @param {*} options Parameters for the algorithm
* @returns {Taira} New smooth array
* @throws Will throw an error if 2*size+1>=array.length for AVERAGE,MEDIAN and GAUSSIAN algorithm
*/
static smoothen (array, algorithm, ...options) {
let [option1, option2, option3, ...other] = options
array = array || []
switch (algorithm || 0) {
case Taira.ALGORITHMS.MEDIAN:
return Taira._median(array, option1 || 2, option2 || 1, option3, ...other)
case Taira.ALGORITHMS.GAUSSIAN:
return Taira._gaussian(array, option1 || 2, option2 || 2, option3, ...other)
default:
return Taira._average(array, option1 || 2, option2 || 1, option3, ...other)
}
}

/**
* Taira.ALGORITHMS.AVERAGE (do not use directly)
* @param {*} array The input data array
* @param {integer} size The number of neighbor elements to take, results in 2*size+1
* @param {integer} pass How many times to go over the array
* @param {boolean} circular Joins beginning and end of array, to make the array circular
* @returns {*} Array calculated with Taira.ALGORITHMS.AVERAGE
*/
static _average (array, size, pass, circular) {
if (array.length <= 2 * size + 1) throw new Error('Array needs to be longer than the box size (2*size+1).')
let out = new Taira()
array.forEach((_, index) => {
if ((index - size < 0 || index + size >= array.length) && !circular) {
out.push(array[index])
} else {
let segmentstart = (index - size < 0) ? (index - size) + array.length : index - size
let sum = 0
for (let a = segmentstart;
(index + size + 1) % array.length !== a; a = a % array.length) {
sum += array[a]
a++
}
out.push(sum / ((size * 2) + 1))
}
})
if (pass > 1) {
return Taira._average(array, size, --pass)
} else {
return out
}
}

/**
* Taira.ALGORITHMS.MEDIAN (do not use directly)
* @param {*} array The input data array
* @param {integer} size The number of neighbor elements to take, results in 2*size+1
* @param {integer} pass How many times to go over the array
* @param {boolean} circular Joins beginning and end of array, to make the array circular
* @returns {*} Array calculated with Taira.ALGORITHMS.MEDIAN
*/
static _median (array, size, pass, circular) {
if (array.length <= 2 * size + 1) throw new Error('Array needs to be longer than the box size (2*size+1).')
let out = new Taira()
array.forEach((_, index) => {
if ((index - size < 0 || index + size >= array.length) && !circular) {
out.push(array[index])
} else {
let segmentstart = (index - size < 0) ? (index - size) + array.length : index - size
let median = []
for (let a = segmentstart;
(index + size + 1) % array.length !== a; a = a % array.length) {
median.push(array[a])
a++
}
median = median.sort((a, b) => {
if (a < b) {
return -1
}
if (a > b) {
return 1
}
return 0
})
out.push(median[Math.trunc(((size * 2) + 1) / 2)])
}
})
if (pass > 1) {
return Taira._median(array, size, --pass)
} else {
return out
}
}

/**
* Taira.ALGORITHMS.GAUSSIAN (do not use directly)
* @param {*} array The input data array
* @param {integer} kernel Size of the kernel array is e.g. 2*kernel+1
* @param {*} radius The blur radius (sigma from the gaussian function)
* @param {boolean} circular Joins beginning and end of array, to make the array circular
* @returns {*} Array calculated with Taira.ALGORITHMS.GAUSSIAN
*/
static _gaussian (array, kernel, radius, circular) {
if (array.length <= 2 * kernel + 1) throw new Error('Array needs to be longer than the kernel size (2*size+1).')
let out = new Taira()
let filter = new Float64Array(2 * kernel + 1)
let denominator1 = radius * Math.sqrt(2 * Math.PI)
let denominator2 = Math.pow(radius, 2) * 2
filter = filter.map((_, index) => Math.exp(-(Math.pow(index - kernel, 2)) / denominator2) / denominator1)
let normalizer = filter.reduce((acc, val) => acc + val)
let normfilter = filter.map((value) => value / normalizer)
array.forEach((_, index) => {
if ((index - kernel < 0 || index + kernel >= array.length) && !circular) {
out.push(array[index])
} else {
let segmentstart = (index - kernel < 0) ? (index - kernel) + array.length : index - kernel
let sum = 0
let c = 0
for (let a = segmentstart;
(index + kernel + 1) % array.length !== a; a = a % array.length) {
sum += array[a++] * normfilter[c++]
}
out.push(sum)
}
})
return out
}
}

Taira.ALGORITHMS = {
AVERAGE: 0,
MEDIAN: 1,
GAUSSIAN: 2
}

// module.exports = Taira
45 changes: 38 additions & 7 deletions music_runner.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ function preload() {
table = loadTable('volumes.csv', 'csv');
}

let volumes = [];
let volume_length = 0;

function setup() {
main_canvas = createCanvas(canvasWidth, canvasHeight);
main_canvas.parent('canvasContainer');
Expand All @@ -58,6 +61,33 @@ function setup() {
songButton.mousePressed(switchRunMode);
songButton.parent('button1Container');
songButton.elt.disabled = true;

vol1 = [];
vol2 = [];
vol3 = [];
vol4 = [];
volumes = [vol1, vol2, vol3, vol4];
volume_table_length = table.getRowCount();
for(let i=0; i< volume_table_length;i++) {
let row = table["rows"][i].arr;
vol1.push(float(row[1]));
vol2.push(float(row[2]));
vol3.push(float(row[3]));
vol4.push(float(row[4]));
}
/*
for(let i=0; i<4; i++) {
let radius = map(i, 0, 3, 0, 3);
volumes[i] = Taira.smoothen(vol1, Taira.ALGORITHMS.GAUSSIAN, 10, radius, true)
}
volumes[0] = vol1;
*/
if(smoothing != 0) {
let radius = map(smoothing, 0, 100, 0, 3);
for(let i=0; i<4; i++) {
volumes[i] = Taira.smoothen(volumes[i], Taira.ALGORITHMS.GAUSSIAN, 10, radius, true)
}
}
}

function switchRunMode() {
Expand Down Expand Up @@ -141,16 +171,17 @@ function draw() {
let curMillis = millis();
let timeOffset = curMillis - songEpoch;
let curSlice = int(60 * timeOffset / 1000.0);
if (curSlice < table.getRowCount()) {
if (curSlice < volume_table_length) {
// print("Processing " + curSlice + " of " + table.getRowCount())
let row = table["rows"][curSlice].arr
// let row = table["rows"][curSlice].arr
// draw_one_frame(row);
// print(row);
slider1.value(row[1]);
slider2.value(row[2]);
slider3.value(row[3]);
slider4.value(row[4]);
draw_one_frame(row[1], row[2], row[3], row[4], curSlice);
let row = [volumes[0][curSlice], volumes[1][curSlice], volumes[2][curSlice], volumes[3][curSlice]]
slider1.value(row[0]);
slider2.value(row[1]);
slider3.value(row[2]);
slider4.value(row[3]);
draw_one_frame(row[0], row[1], row[2], row[3], curSlice);
}
}
}
Expand Down
4 changes: 3 additions & 1 deletion music_settings.js
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
const smoothing = 0;
// smoothing is applied to the audio file to smooth the signal
// expected values are from 0 (no smoothing) to 100 (lots of smoothing)
const smoothing = 25;

0 comments on commit fb596b7

Please sign in to comment.