Published
Edited
Jul 11, 2020
1 star
Insert cell
Insert cell
Insert cell
Insert cell
// viewof balance = slider({
// min: 0,
// max: 1,
// value: 0.,
// step: 0.01,
// description: "Force balance (Noise / radial)"
// })
Insert cell
// viewof noiseScale = slider({
// min: 0,
// max: 100,
// value: 0.5,
// step: 0.001,
// description: "Noise scale"
// })
Insert cell
Insert cell
regl = {
var canvas = document.createElement('canvas');
canvas.classList.add('regl')
var pixelRatio = Math.min(devicePixelRatio, 1);
var w = Math.min(640, width);
var h = w;
canvas.style.width = `${w}px`;
canvas.style.height = `${h}px`;
canvas.width = w * pixelRatio;
canvas.height = h * pixelRatio;
let regl = createREGL({
extensions: [],
optionalExtensions: ['OES_texture_float'],
canvas,
pixelRatio: pixelRatio,
attributes: {
antialias: false,
preserveDrawingBuffer: true
}
});
return regl;
}
Insert cell
updateSprites = regl({
// vert just draws a big triange, which covers all the texture
vert: `
precision mediump float;
attribute vec2 position;
void main () {
gl_Position = vec4(position, 0, 1);
}
`,

frag: `
precision highp float;
uniform sampler2D state;
uniform float shapeX, shapeY, deltaT, gravity;
uniform float tick;
uniform float friction;
uniform float noiseScale;
uniform float balance;
uniform float spectrumBin0;
uniform float spectrumBin1;
uniform float spectrumBin2;
uniform float mouseX;
uniform float mouseY;

vec2 random2(vec2 st){
st = vec2( dot(st,vec2(127.1,311.7)),
dot(st,vec2(269.5,183.3)) );
return -1.0 + 2.0*fract(sin(st)*43758.5453123);
}

float WaveletNoise(vec2 p, float z, float k) {
float d=0.,s=1.,m=0., a;
for(float i=0.; i<4.; i++) {
vec2 q = p*s, g=fract(floor(q)*vec2(123.34,233.53));
g += dot(g, g+23.234);
a = fract(g.x*g.y)*1e3;// +z*(mod(g.x+g.y, 2.)-1.); // add vorticity
q = (fract(q)-.5)*mat2(cos(a),-sin(a),sin(a),cos(a));
d += sin(q.x*10.+z)*smoothstep(.25, .0, dot(q,q))/s;
p = p*mat2(.54,-.84, .84, .54)+i;
m += 1./s;
s *= k;
}
return d/m;
}

void main () {
vec2 shape = vec2(shapeX, shapeY);
vec4 prevState = texture2D(state,
gl_FragCoord.xy / shape);
vec2 position = prevState.xy;
vec2 velocity = prevState.zw;
position += 0.5 * velocity * deltaT;
// if (position.x < -1.0 || position.x > 1.0) {
// velocity.x *= -1.0;
// }
// if (position.y < -1.0 || position.y > 1.0) {
// velocity.y *= -1.0;
// }
if (abs(position.x) > 2. || abs(position.y) > 2.) {
position = random2(position) * 1.5;
}
position += 0.5 * velocity * deltaT;

vec2 force_noise = vec2(
// noise(position * noiseScale),
// noise(position * noiseScale)
// noise(vec2(0.), 1., 1.),
// noise(vec2(0.), 1., 1.)
WaveletNoise(position * fract(spectrumBin2 + 0.5) * 20. + tick / 100., 1., 0.5),
WaveletNoise(position + 1000. * fract(spectrumBin2 + 0.5) * 20. + tick / 100., 1., 0.5)
);

vec2 force_radial = -1. * vec2(vec2(mouseX, mouseY) - position);

// velocity.y = velocity.y + gravity * deltaT;
velocity *= (1. - friction);
vec2 force = mix(force_noise, force_radial, fract(spectrumBin1));
velocity = force * (spectrumBin0 - 1.) / 50.;

gl_FragColor = vec4(position, velocity);
}
`,

depth: {enable: false},

// buffer for next state rendering
// ?? can the frag shader have several buffers?
framebuffer: ({tick}) => vars.SPRITES[(tick + 1) % 2],

uniforms: {
// prev state
state: ({tick}) => vars.SPRITES[(tick) % 2],
// is it size of current buffer?
shapeX: regl.context('viewportWidth'),
shapeY: regl.context('viewportHeight'),
deltaT: 0.0005,
gravity: -0.05,
tick: regl.context('tick'),
friction,
// noiseScale,
// balance,
spectrumBin0: regl.prop('spectrumBin0'),
spectrumBin1: regl.prop('spectrumBin1'),
spectrumBin2: regl.prop('spectrumBin2'),
mouseX: regl.prop('mouseX'),
mouseY: regl.prop('mouseY'),
},

attributes: {
position: [
0, -4,
4, 4,
-4, 4
]
},
primitive: 'triangles',
elements: null,
offset: 0,
count: 3
})
Insert cell
drawSprites = regl({
vert: `
precision highp float;
attribute vec2 sprite;
uniform sampler2D state;
varying vec2 rg;
uniform float pointSize;
void main () {
vec2 position = texture2D(state, sprite).xy;
gl_PointSize = pointSize;
rg = sprite;
gl_Position = vec4(position, 0, 1);
}
`,

frag: `
precision highp float;
uniform float val1;
varying vec2 rg;
void main () {
gl_FragColor = vec4(1.);
}
`,

// array of coordinates of all pixels of FBO
attributes: {
sprite: Array(N * N).fill().map(function (_, i) {
const x = i % N
// same as Math.floor()
const y = (i / N) | 0
return [(x / N), (y / N)]
}).reverse()
},

uniforms: {
state: ({tick}) => vars.SPRITES[tick % 2],
// val1: val1,
pointSize,
},

primitive: 'points',
offset: (context, {count}) => N * N - count,
elements: null,
count: regl.prop('count')
})
Insert cell
vars = {
let mic = new p5.AudioIn()
mic.start()
let fft = new p5.FFT()
fft.setInput(mic)

return {
mic,
fft,
// total amount of particles to display.
count: 0,
// a couple of sprites, which swap every frame
SPRITES: Array(2).fill().map(() =>
regl.framebuffer({
radius: N,
colorType: 'float',
depthStencil: false
})),
canvasElement: document.querySelector('canvas.regl'),

}
}
Insert cell
drawLoop = {
let frame = regl.frame(({tick, drawingBufferWidth, drawingBufferHeight, pixelRatio}) => {

let mouseX
let mouseY
document.onmousemove = function(e) {
let rect = e.target.getBoundingClientRect()
mouseX = e.clientX - rect.left
mouseY = e.clientY - rect.top
console.log(e)
}


for (let i = 0; i < BLOCK_SIZE; ++i)
{
BLOCK.data[4 * i] = (Math.random() * 2 - 1)
BLOCK.data[4 * i + 1] = (Math.random() * 2 - 1)
BLOCK.data[4 * i + 2] = 0.25 * (Math.random() - 0.5)
BLOCK.data[4 * i + 3] = Math.random()
}
vars.SPRITES[(tick) % 2].color[0].subimage(BLOCK, vars.count % N, ((vars.count / N) | 0) % N)
// 😬 subimage? what is it for?
vars.count += BLOCK_SIZE

let spectrumBins = binSpectrum((vars.fft.analyze()), 3).map(d => d / 10)

updateSprites({
spectrumBin0: spectrumBins[0],
spectrumBin1: spectrumBins[1],
spectrumBin2: spectrumBins[2],
mouseX: 0,//mouseX,
mouseY: 0,//mouseY,
})

regl.clear({
color: [0, 0, 0, 1],
depth: 1
})

drawSprites({
count: Math.min(vars.count, N * N),
})
})

// Stop the current animation when we re-evaluate this cell
invalidation.then(frame.cancel);

return frame;
}
Insert cell
// toScreen = (x, size, pixelRatio) => {
// return Math.min(Math.max(2.0 * pixelRatio * x / size - 1.0, -0.999), 0.999)
// }
Insert cell
// It seems to be a fragment of vars.SPRITES, which describes positions and velocities of block of particles
// (block contains BLOCK_SIZE of particles)
BLOCK = { return {
// these 4 elements will become x, y, z and w of every point
data: new Float32Array(4 * BLOCK_SIZE),
width: BLOCK_SIZE,
height: 1
}}
Insert cell
// N × N is maximum amount of particles
N = 1024
Insert cell
BLOCK_SIZE = 1024
Insert cell
createREGL = require('regl')
Insert cell
mat4 = require('https://bundle.run/gl-mat4@1.2.0')
Insert cell
mouse = require('https://bundle.run/mouse-change@1.4.0')
Insert cell
hsv2rgb = require('https://bundle.run/hsv2rgb@1.1.0')
Insert cell
import {checkbox, slider} from "@jashkenas/inputs"
Insert cell
window
Insert cell
// split spectrum into bitNumber of bins and sum them up
binSpectrum = (arr, binNumber = 2) => {
let binSize = Math.floor(arr.length / binNumber)
let bins = new Array(binNumber).fill(0)
bins = bins.map((bin, binIndex) => {
for (let i = 0; i < binSize; i++) {
bin += arr[binIndex * binSize + i]
}
return bin
})
return bins
}
Insert cell
p5 = await require("p5")
Insert cell
p5_sound = {
await require('p5@0.7.2/lib/addons/p5.sound.min.js');
return "load p5.sound.js";
}
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more