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": "", "author": "",
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"@babel/runtime": "^7.4.5",
"p5": "^0.8.0" "p5": "^0.8.0"
}, },
"devDependencies": { "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", "css-loader": "^2.1.1",
"html-webpack-plugin": "^3.2.0", "html-webpack-plugin": "^3.2.0",
"node-sass": "^4.12.0", "node-sass": "^4.12.0",

View file

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

View file

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

View file

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

View file

@ -1,37 +1,101 @@
const VOLUME_THRESHOLD = 20; import Worker from './sound.worker.js';
const GRID_SIZE = 20; import { getSpectrumData, getRandomBetween } from './util.js';
const MAX_WIDTH = 5;
const MAX_HEIGHT = 8;
const FFT_SIZE = 1024;
export function setup(sketch, analyser) { export default class SquaresAnimation {
sketch.background('rgb(20, 30, 30)'); VOLUME_THRESHOLD = 20;
sketch.colorMode(sketch.HSB); GRID_SIZE = 20;
analyser.fftSize = FFT_SIZE; MAX_WIDTH = 5;
} MAX_HEIGHT = 8;
FFT_SIZE = 1024;
worker;
analyser;
audioProcessor;
export function draw(sketch) { minVolume = 360;
sketch.background('rgba(20, 30, 30, 0.01)'); maxVolume = 0;
if (!circle) { minPitch = 360;
return; 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);
} }
const color = ((circle.pitch - minPitch) / (maxPitch - minPitch)) * 360; setup(sketch) {
const brightness = ((circle.volume - minVolume) / (maxVolume - minVolume)); sketch.background('rgb(20, 30, 30)');
sketch.stroke(color, 100, 100, brightness); sketch.colorMode(sketch.HSB);
for (let i = 0; i < circle.spectrum.length + 2; i++) { this.resize();
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; resize() {}
// const width = getRandomBetween(1, MAX_WIDTH) * GRID_SIZE;
// const height = getRandomBetween(1, MAX_HEIGHT) * GRID_SIZE; 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 (!this.fresh) {
return;
}
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 < 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.fill(color, 100, 100, brightness * 0.5);
sketch.beginShape(); sketch.beginShape();
sketch.vertex(x, y); sketch.vertex(x, y);
sketch.vertex(x, y + GRID_SIZE); sketch.vertex(x, y + this.GRID_SIZE);
sketch.vertex(x + GRID_SIZE, y + GRID_SIZE); sketch.vertex(x + this.GRID_SIZE, y + this.GRID_SIZE);
sketch.vertex(x + GRID_SIZE, y); sketch.vertex(x + this.GRID_SIZE, y);
sketch.vertex(x, y); sketch.vertex(x, y);
sketch.endShape(); sketch.endShape();
}
} }
} }

View file

@ -1,24 +1,29 @@
export function getRMS(spectrum) { export function getSpectrumData(spectrum) {
let highestPitch = 0;
let rms = 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 cg = 0;
let weight = 0; let weight = 0;
for (let i = 0; i < spectrum.length; i++) { for (let i = 0; i < spectrum.length; i++) {
rms += spectrum[i] * spectrum[i];
cg += spectrum[i] * i; cg += spectrum[i] * i;
weight += spectrum[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; return Math.floor(Math.random() * (max - min)) + min;
} }

View file

@ -5,7 +5,8 @@ module.exports = {
entry: './src/index.js', entry: './src/index.js',
output: { output: {
path: path.resolve(__dirname, 'dist'), path: path.resolve(__dirname, 'dist'),
filename: 'bundle.js' filename: 'bundle.js',
globalObject: `typeof self !== 'undefined' ? self : this`,
}, },
module: { module: {
rules: [ rules: [
@ -17,6 +18,23 @@ module.exports = {
{ loader: "sass-loader" }, { 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$/, test: /\.worker\.js$/,
use: { loader: 'worker-loader' }, use: { loader: 'worker-loader' },