From db822f35c080470971383ebac9a81a1f949b42b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20B=C3=A4dorf?= Date: Fri, 17 Sep 2021 20:43:42 +0200 Subject: [PATCH] add shapes animation --- .envrc | 1 + .gitignore | 1 + shell.nix | 8 +++ src/birds.js | 61 ++++++++++++++++++++++ src/birds.worker.js | 47 +++++++++++++++++ src/circles.js | 8 +-- src/index.js | 16 ++++-- src/shapes.js | 121 ++++++++++++++++++++++++++++++++++++++++++++ src/squares.js | 4 +- 9 files changed, 257 insertions(+), 10 deletions(-) create mode 100644 .envrc create mode 100644 shell.nix create mode 100644 src/birds.js create mode 100644 src/birds.worker.js create mode 100644 src/shapes.js diff --git a/.envrc b/.envrc new file mode 100644 index 0000000..051d09d --- /dev/null +++ b/.envrc @@ -0,0 +1 @@ +eval "$(lorri direnv)" diff --git a/.gitignore b/.gitignore index 2ccbe46..3daca07 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ /node_modules/ +tags diff --git a/shell.nix b/shell.nix new file mode 100644 index 0000000..50255ce --- /dev/null +++ b/shell.nix @@ -0,0 +1,8 @@ +{ pkgs ? import {} }: + +pkgs.mkShell { + buildInputs = [ + pkgs.python3 + pkgs.nodejs + ]; +} diff --git a/src/birds.js b/src/birds.js new file mode 100644 index 0000000..cd11300 --- /dev/null +++ b/src/birds.js @@ -0,0 +1,61 @@ +import { getSpectrumData, getRandomBetween } from './util.js'; + +const BIRD_NUM = 300; + +export default class BirdsAnimation { + maxX; + maxY; + + birds = []; + + constructor() { } + + start(sketch) { + this.resize(); + + this.worker = new Worker(); + this.worker.onmessage = e => this.onBirdPositions(e.data); + this.worker.postMessage({ + maxX: this.maxX, + maxY: this.maxY, + }); + + sketch.background(0); + sketch.colorMode(sketch.HSB); + } + + onBirdPositions(data) { + this.birds = data.birds; + } + + resize() { + this.maxX = window.innerWidth; + this.maxY = window.innerHeight; + } + + stop() { + this.worker.terminate(); + } + + draw(sketch) { + sketch.background('rgba(0, 0, 0, 0.20)'); + + const brightness = ((this.data.volume - this.minVolume) / (this.maxVolume - this.minVolume)) * 80; + const color = ((this.data.pitch - this.minPitch) / (this.maxPitch - this.minPitch)) * 360; + for (let i = 0; i < brightness; i++) { + const p = this.data.spectrum[i]; + sketch.stroke(color, 100, 100); + + sketch.fill(color, 100, brightness); + const x = getRandomBetween(0, this.maxX) * this.GRID_SIZE; + const y = getRandomBetween(0, this.maxY) * this.GRID_SIZE; + sketch.beginShape(); + sketch.vertex(x, 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(); + } + } +} diff --git a/src/birds.worker.js b/src/birds.worker.js new file mode 100644 index 0000000..fd08683 --- /dev/null +++ b/src/birds.worker.js @@ -0,0 +1,47 @@ +import Worker from './birds.worker.js'; + +const STEP_TIME = 10; +const +const data = {}; + +function onInitialData(event) { + data.maxX = event.maxX; + data.maxY = event.maxY; + + for (let i = 0; i < BIRD_NUM; i += 1) { + data.birds.push({ + x: getRandomBetween(0, data.maxX), + y: getRandomBetween(0, data.maxY), + r: getRandomBetween(0, 360), + v: getRandomBetween(1, 10), + }); + } +} + +self.onmessage = onInitialData; + +function calculateAndSend() { + const { + birds, + maxX, + maxY, + } = data; + + for (const bird of birds) { + + } + + self.postMessage({ + minVolume, + averageVolume, + maxVolume, + + minPitch, + averagePitch, + maxPitch, + + averageSpectrum, + }); +} + +setInterval(calculateAndSend, STEP_TIME); diff --git a/src/circles.js b/src/circles.js index e0c05c8..0db52a6 100644 --- a/src/circles.js +++ b/src/circles.js @@ -3,10 +3,10 @@ import { getSpectrumData } from './util.js'; export default class CirclesAnimation { VOLUME_THRESHOLD = 20; - CIRCLE_RADIUS = 100; + CIRCLE_RADIUS = 30; CIRCLE_POINTS = 256; FFT_SIZE = 256; - MAX_CIRCLES = 20; + MAX_CIRCLES = 50; baseHeight; baseWidth; @@ -15,7 +15,7 @@ export default class CirclesAnimation { minPitch = 0; circles = []; - averageSpectrum; + averageSpectrum = []; constructor(audioCtx) { this.analyser = new AnalyserNode(audioCtx); @@ -83,11 +83,11 @@ export default class CirclesAnimation { sketch.background(30); sketch.noFill(); sketch.strokeWeight(2); - sketch.stroke(230); for (let c = 0; c < this.circles.length; c++) { const circle = this.circles[c]; sketch.beginShape(); + sketch.stroke((c / this.circles.length) * 200 + 50); for (let i = 0; i < circle.spectrum.length; i++) { const p = Math.max(circle.spectrum[i] - (this.averageSpectrum[i] * 0.6), 0); sketch.curveVertex( diff --git a/src/index.js b/src/index.js index 280580c..3f07adb 100644 --- a/src/index.js +++ b/src/index.js @@ -4,6 +4,8 @@ import './index.scss'; import HorizontalLinesAnimation from './horizontal-lines.js'; import SquaresAnimation from './squares.js'; import CirclesAnimation from './circles.js'; +import BirdsAnimation from './birds.js'; +import ShapesAnimation from './shapes.js'; // const ANIMATION_TIME = 10 * 60 * 1000; const ANIMATION_TIME = 20 * 1000; @@ -16,11 +18,15 @@ async function main() { const horizontalLines = new HorizontalLinesAnimation(audioCtx); const squares = new SquaresAnimation(audioCtx); const circles = new CirclesAnimation(audioCtx); + const birds = new BirdsAnimation(audioCtx); + const shapes = new ShapesAnimation(audioCtx); const animations = [ - squares, - horizontalLines, - circles, + // birds, + shapes, + // circles, + // squares, + // horizontalLines, ]; let animationNum = 0; @@ -39,7 +45,9 @@ async function main() { } activeAnimation = animation; input.disconnect(); - input.connect(activeAnimation.analyser); + if (activeAnimation.analyser) { + input.connect(activeAnimation.analyser); + } animation.start(sketch); } diff --git a/src/shapes.js b/src/shapes.js new file mode 100644 index 0000000..c32c23e --- /dev/null +++ b/src/shapes.js @@ -0,0 +1,121 @@ +import p5 from 'p5'; +import { getSpectrumData, getRandomBetween } from './util.js'; + +const MIN_SIDES = 3; +const MAX_SIDES = 6; +const SHAPE_NUM = 12; + +export default class ShapesAnimation { + FFT_SIZE = 1024; + + maxX; + maxY; + + minVolume = 360; + maxVolume = 0; + + shapes = []; + + data; + fresh = false; + + analyser; + audioProcessor + + constructor(audioCtx) { + this.analyser = new AnalyserNode(audioCtx); + this.analyser.fftSize = this.FFT_SIZE; + this.analyser.timeSmoothingConstant = 0.2; + this.audioProcessor = audioCtx.createScriptProcessor(this.FFT_SIZE * 2, 1, 1); + this.audioProcessor.onaudioprocess = () => this.audioProcess(); + } + + start(sketch) { + this.resize(); + this.analyser.connect(this.audioProcessor); + + this.shapes = (new Array(SHAPE_NUM)) + .fill(null) + .map(() => { + const baseX = getRandomBetween(0, this.maxX); + const baseY = getRandomBetween(0, this.maxY); + const baseV = sketch.createVector( + getRandomBetween(-5, 5), + getRandomBetween(-5, 5), + getRandomBetween(-5, 5), + ); + return (new Array(getRandomBetween(MIN_SIDES, MAX_SIDES))) + .fill(null) + .map(() => ({ + x: getRandomBetween(baseX - 50, baseX + 50), + y: getRandomBetween(baseY - 50, baseY + 50), + v: sketch.createVector( + getRandomBetween(-10, 10) / 3, + getRandomBetween(-10, 10) / 3, + getRandomBetween(-10, 10) / 3, + ).add(baseV), + })); + }); + + sketch.background(0); + sketch.colorMode(sketch.HSB); + } + + stop() { + this.analyser.disconnect(); + } + + resize() { + this.maxX = window.innerWidth; + this.maxY = window.innerHeight; + } + + getAverageData(data) { + this.minVolume = data.minVolume; + this.maxVolume = data.maxVolume; + + this.minPitch = data.minPitch; + this.maxPitch = data.maxPitch; + + this.averageSpectrum = data.averageSpectrum; + } + + audioProcess() { + const spectrum = new Uint8Array(this.analyser.frequencyBinCount); + this.analyser.getByteFrequencyData(spectrum); + + spectrum.reverse(); + + const data = getSpectrumData(spectrum); + const { volume, pitch } = data; + + if (volume > this.maxVolume) { + this.maxVolume = volume; + } + + if (volume !== 0 && volume < this.minVolume) { + this.minVolume = volume; + } + + this.data = data; + this.fresh = true; + } + + draw(sketch) { + sketch.background(0, 0, 10); + + sketch.stroke(0, 0, 255); + sketch.fill(0, 0, 255); + + for (const shape of this.shapes) { + sketch.beginShape(); + for (const point of shape) { + sketch.vertex(point.x, point.y); + point.x = point.x + point.v.x; + point.y = point.y + point.v.y; + point.z = point.z + point.v.z; + } + sketch.endShape(); + } + } +} diff --git a/src/squares.js b/src/squares.js index 24f93e6..c950aaf 100644 --- a/src/squares.js +++ b/src/squares.js @@ -96,7 +96,7 @@ export default class SquaresAnimation { } draw(sketch) { - sketch.background('rgba(0, 0, 0, 0.02)'); + sketch.background('rgba(0, 0, 0, 0.20)'); if (!this.fresh) { return; } @@ -107,7 +107,7 @@ export default class SquaresAnimation { const p = this.data.spectrum[i]; sketch.stroke(color, 100, 100); - sketch.fill(color, 100, brightness); + sketch.fill(color, 100, 100); const x = getRandomBetween(0, this.maxX) * this.GRID_SIZE; const y = getRandomBetween(0, this.maxY) * this.GRID_SIZE; sketch.beginShape();