This commit is contained in:
Benjamin Bädorf 2019-06-14 16:33:22 +02:00
parent 5389f22ff2
commit add2c28363
8 changed files with 1289 additions and 93 deletions

1083
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -11,9 +11,15 @@
"author": "",
"license": "ISC",
"dependencies": {
"@babel/runtime": "^7.4.5",
"p5": "^0.8.0"
},
"devDependencies": {
"@babel/core": "^7.4.5",
"@babel/plugin-proposal-class-properties": "^7.4.4",
"@babel/plugin-transform-runtime": "^7.4.4",
"@babel/preset-env": "^7.4.5",
"babel-loader": "^8.0.6",
"css-loader": "^2.1.1",
"html-webpack-plugin": "^3.2.0",
"node-sass": "^4.12.0",

View file

@ -1,19 +1,19 @@
import Worker from './sound.worker.js';
import { getRMS, getPitch } from './util.js';
import { getSpectrumData } from './util.js';
export default class HorizontalLines {
export default class HorizontalLinesAnimation {
VOLUME_THRESHOLD = 20;
LINE_MARGIN = 20;
MAX_LINES = window.innerWidth / LINE_MARGIN;
FFT_SIZE = 256;
BASE_WIDTH = 2;
BASE_WIDTH = 4;
spectrumPointHeight = 20;
maxLines = 10;
line;
fresh;
maxPitch;
minPitch;
fresh = false;
averageSpectrum;
worker;
analyser;
@ -22,20 +22,28 @@ export default class HorizontalLines {
constructor(audioCtx) {
this.worker = new Worker();
this.analyser = new AnalyserNode(audioCtx);
this.analyser.fftSize = FFT_SIZE;
this.analyser.fftSize = this.FFT_SIZE;
this.audioProcessor = audioCtx.createScriptProcessor(this.FFT_SIZE * 2, 1, 1);
audioProcessor.onaudioprocess = this.audioProcess;
this.audioProcessor.onaudioprocess = () => this.audioProcess();
this.analyser.connect(this.audioProcessor);
this.worker.onmessage = e => this.getAverageData(e.data);
}
setup(sketch) {
sketch.colorMode(sketch.HSL, 255);
sketch.colorMode(sketch.RGB, 255);
sketch.background(0);
this.resize();
}
resize() {
this.spectrumPointHeight = window.innerHeight / this.analyser.frequencyBinCount;
this.maxLines = window.innerWidth / this.LINE_MARGIN;
}
getAverageData(data) {
this.averageSpectrum = data.averageSpectrum;
console.log('got average');
console.log(this.averageSpectrum);
}
audioProcess() {
@ -44,21 +52,9 @@ export default class HorizontalLines {
spectrum.reverse();
const volume = getRMS(spectrum);
const pitch = getPitch(spectrum, volume);
this.line = {
spectrum,
volume,
pitch,
};
if (pitch > this.maxPitch) {
this.maxPitch = pitch;
}
if (pitch < this.minPitch) {
this.minPitch = pitch;
}
const data = getSpectrumData(spectrum);
this.worker.postMessage(data);
this.line = data;
this.fresh = true;
}
@ -76,21 +72,26 @@ export default class HorizontalLines {
sketch.fill(0);
sketch.rect(0, 0, this.LINE_MARGIN, window.innerHeight);
const lineBaseWidth = this.BASE_WIDTH;
sketch.fill(`rgba(255, 255, 255, 0.2)`);
sketch.stroke(((this.line.pitch - this.minPitch) / (this.maxPitch - this.minPitch)) * 255);
sketch.stroke(255, 255, 255);
sketch.strokeWeight(1);
sketch.beginShape();
sketch.curveVertex(lineBaseWidth, -10);
sketch.curveVertex(lineBaseWidth, -10);
sketch.curveVertex(lineBaseWidth, 0);
sketch.curveVertex(this.BASE_WIDTH, -10);
sketch.curveVertex(this.BASE_WIDTH, -10);
sketch.curveVertex(this.BASE_WIDTH, 0);
for (let i = 1; i < this.line.spectrum.length - 1; i++) {
const point = this.line.spectrum[i];
sketch.curveVertex(lineBaseWidth + point, i * this.spectrumPointHeight);
let point;
if (this.averageSpectrum) {
point = Math.max(this.line.spectrum[i] - (this.averageSpectrum[i] * 0.8), 0);
// point = 5 * (this.line.spectrum[i] / this.averageSpectrum[i]);
} else {
point = this.line.spectrum[i];
}
sketch.curveVertex(lineBaseWidth, window.innerHeight);
sketch.curveVertex(lineBaseWidth, window.innerHeight + 10);
sketch.curveVertex(lineBaseWidth, window.innerHeight + 10);
sketch.curveVertex(this.BASE_WIDTH + point, i * this.spectrumPointHeight);
}
sketch.curveVertex(this.BASE_WIDTH, window.innerHeight);
sketch.curveVertex(this.BASE_WIDTH, window.innerHeight + 10);
sketch.curveVertex(this.BASE_WIDTH, window.innerHeight + 10);
sketch.endShape();
}
}

View file

@ -1,22 +1,34 @@
import p5 from 'p5';
import './index.scss';
import HorizontalLines from './horizontal-lines.js';
import HorizontalLinesAnimation from './horizontal-lines.js';
import SquaresAnimation from './squares.js';
async function main() {
const audioCtx = new AudioContext();
const microphone = await navigator.mediaDevices.getUserMedia({ audio: true });
const input = audioCtx.createMediaStreamSource(microphone);
const instance = new p5(( sketch ) => {
let activeAnimation;
const instance = new p5(( sketch ) => {
window.onresize = () => {
sketch.resizeCanvas(window.innerWidth, window.innerHeight);
activeAnimation.resize();
};
function activate(animation) {
activeAnimation = animation;
input.connect(animation.analyser);
animation.setup(sketch);
}
sketch.setup = () => {
sketch.createCanvas(window.innerWidth, window.innerHeight);
const horizontalLines = new HorizontalLines(analyser, sketch);
activeAnimation = horizontalLines;
input.connect(activeAnimation.analyser);
const horizontalLines = new HorizontalLinesAnimation(audioCtx);
const squares = new SquaresAnimation(audioCtx);
activate(horizontalLines);
};
sketch.draw = () => {
@ -24,7 +36,7 @@ async function main() {
};
setTimeout(() => {
activactiveAnimation =
// activactiveAnimation =
}, 1 * 60 * 1000);
});
}

View file

@ -1,26 +1,33 @@
const PERIOD = 60 * 1000;
const PERIOD = 20 * 1000;
const data = [];
let firstRun = true;
function onDataPoint(event) {
console.log(event);
if (!firstRun) {
data.shift();
}
data.push(event.data);
}
self.addEventListener('message', onDataPoint);
self.onmessage = onDataPoint;
function calculateAndSend() {
firstRun = false;
if (!data.length) {
return;
}
let averagePitch = 0;
let averageVolume = 0;
let averageSpectrum = [];
let averageSpectrum = (new Array(data[0].spectrum.length)).fill(0);
let minPitch = data[0].spectrum.length;
let maxPitch = 0;
let minPitch = spectrum.length;
let maxVolume = 0;
let minVolume = 500;
let maxVolume = 0;
for (const { volume, pitch, spectrum } in data) {
for (const { volume, pitch, spectrum } of data) {
if (pitch > maxPitch) {
maxPitch = pitch;
} else if (pitch < minPitch) {
@ -43,7 +50,7 @@ function calculateAndSend() {
averageVolume /= data.length;
averagePitch /= data.length;
averageSpectrum = averageSpectrum.map(p => p / spectrum.length);
averageSpectrum = averageSpectrum.map(p => p / data.length);
self.postMessage({
minVolume,

View file

@ -1,37 +1,101 @@
const VOLUME_THRESHOLD = 20;
const GRID_SIZE = 20;
const MAX_WIDTH = 5;
const MAX_HEIGHT = 8;
const FFT_SIZE = 1024;
import Worker from './sound.worker.js';
import { getSpectrumData, getRandomBetween } from './util.js';
export function setup(sketch, analyser) {
sketch.background('rgb(20, 30, 30)');
sketch.colorMode(sketch.HSB);
analyser.fftSize = FFT_SIZE;
export default class SquaresAnimation {
VOLUME_THRESHOLD = 20;
GRID_SIZE = 20;
MAX_WIDTH = 5;
MAX_HEIGHT = 8;
FFT_SIZE = 1024;
worker;
analyser;
audioProcessor;
minVolume = 360;
maxVolume = 0;
minPitch = 360;
maxPitch = 0;
circle;
fresh = false;
constructor(audioCtx) {
this.worker = new Worker();
this.analyser = new AnalyserNode(audioCtx);
this.analyser.fftSize = this.FFT_SIZE;
this.audioProcessor = audioCtx.createScriptProcessor(this.FFT_SIZE * 2, 1, 1);
this.audioProcessor.onaudioprocess = () => this.audioProcess();
this.analyser.connect(this.audioProcessor);
this.worker.onmessage = e => this.getAverageData(e.data);
}
export function draw(sketch) {
setup(sketch) {
sketch.background('rgb(20, 30, 30)');
sketch.colorMode(sketch.HSB);
this.resize();
}
resize() {}
getAverageData(data) {
this.minVolume = data.minVolume;
this.maxVolume = data.maxVolume;
this.minPitch = data.minPitch;
this.maxPitch = data.maxPitch;
}
audioProcess() {
const spectrum = new Uint8Array(this.analyser.frequencyBinCount);
this.analyser.getByteFrequencyData(spectrum);
spectrum.reverse();
const data = getSpectrumData(spectrum);
const { volume, pitch } = data;
this.worker.postMessage(data);
if (volume > this.maxVolume) {
this.maxVolume = volume;
}
if (volume !== 0 && volume < this.minVolume) {
this.minVolume = volume;
}
if (pitch > this.maxPitch) {
this.maxPitch = pitch;
}
if (pitch < this.minPitch) {
this.minPitch = pitch;
}
this.circle = data;
this.fresh = true;
}
draw(sketch) {
sketch.background('rgba(20, 30, 30, 0.01)');
if (!circle) {
if (!this.fresh) {
return;
}
const color = ((circle.pitch - minPitch) / (maxPitch - minPitch)) * 360;
const brightness = ((circle.volume - minVolume) / (maxVolume - minVolume));
const color = ((this.circle.pitch - this.minPitch) / (this.maxPitch - this.minPitch)) * 360;
const brightness = ((this.circle.volume - this.minVolume) / (this.maxVolume - this.minVolume));
sketch.stroke(color, 100, 100, brightness);
for (let i = 0; i < circle.spectrum.length + 2; i++) {
const p = circle.spectrum[i - 2] * i * i * i * i / 2;
const x = getRandomBetween(0, Math.floor(window.innerWidth / GRID_SIZE)) * GRID_SIZE;
const y = getRandomBetween(0, Math.floor(window.innerHeight / GRID_SIZE)) * GRID_SIZE;
// const width = getRandomBetween(1, MAX_WIDTH) * GRID_SIZE;
// const height = getRandomBetween(1, MAX_HEIGHT) * GRID_SIZE;
for (let i = 0; i < spectrum.length + 2; i++) {
const x = getRandomBetween(0, Math.floor(window.innerWidth / this.GRID_SIZE)) * this.GRID_SIZE;
const y = getRandomBetween(0, Math.floor(window.innerHeight / this.GRID_SIZE)) * this.GRID_SIZE;
sketch.fill(color, 100, 100, brightness * 0.5);
sketch.beginShape();
sketch.vertex(x, y);
sketch.vertex(x, y + GRID_SIZE);
sketch.vertex(x + GRID_SIZE, y + GRID_SIZE);
sketch.vertex(x + GRID_SIZE, y);
sketch.vertex(x, y + this.GRID_SIZE);
sketch.vertex(x + this.GRID_SIZE, y + this.GRID_SIZE);
sketch.vertex(x + this.GRID_SIZE, y);
sketch.vertex(x, y);
sketch.endShape();
}
}
}

View file

@ -1,24 +1,29 @@
export function getRMS(spectrum) {
export function getSpectrumData(spectrum) {
let highestPitch = 0;
let rms = 0;
for (let i = 0; i < spectrum.length; i++) {
rms += spectrum[i] * spectrum[i];
}
rms /= spectrum.length;
rms = Math.sqrt(rms);
return rms;
}
export function getPitch(spectrum) {
let cg = 0;
let weight = 0;
for (let i = 0; i < spectrum.length; i++) {
rms += spectrum[i] * spectrum[i];
cg += spectrum[i] * i;
weight += spectrum[i];
if (spectrum[i] > highestPitch) {
highestPitch = spectrum[i];
}
return Math.floor(cg / weight);
}
rms /= spectrum.length;
rms = Math.sqrt(rms);
const pitch = Math.floor(cg / weight);
return {
spectrum,
volume: rms,
pitch,
highestPitch,
};
}
function getRandomBetween(min, max) {
export function getRandomBetween(min, max) {
return Math.floor(Math.random() * (max - min)) + min;
}

View file

@ -5,7 +5,8 @@ module.exports = {
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.js'
filename: 'bundle.js',
globalObject: `typeof self !== 'undefined' ? self : this`,
},
module: {
rules: [
@ -17,6 +18,23 @@ module.exports = {
{ loader: "sass-loader" },
],
},
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env'],
plugins: [
'@babel/plugin-transform-runtime',
[
'@babel/plugin-proposal-class-properties',
{ loose: true },
],
],
},
},
},
{
test: /\.worker\.js$/,
use: { loader: 'worker-loader' },