xxxxxxxxxx
559
// require https://cdn.jsdelivr.net/npm/tweakpane@3.0.7/dist/tweakpane.min.js
// require https://cdn.jsdelivr.net/npm/p5@latest/lib/p5.min.js
// require https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.1/p5.min.js
const pane = new Tweakpane.Pane();
const SHOULD_SAVE = false;
// constants
const FR = SHOULD_SAVE ? 10 : 15;
const CANVAS_WIDTH = 960;
const CANVAS_HEIGHT = 540;
let sphereID = 0;
const PARAMS = {
isDev: false,
// colors
bgColor: "#FFF",
lineColor: "#FF7878",
deviceLineColor: "#6668",
deviceFillColor: "#FFF6",
objectLineColor: "#A3E000",
// shape
diameter: 100,
margin: 4,
angle: 30,
circlePointsMax: 20,
circlePointsMin: 5,
strokeWeight: 0.3,
// anim
speed: 0.3,
seed: 315,
wobbleMoveSpeed: 3,
// diam noise
useDiameterNoise: true,
dNoiseAmplitude: 3.5,
dNoiseFrequency: 0.009,
// shape noise
useShapeNoise: true,
sNoiseAmplitude: 0.4,
sNoiseFrequency: 0.08,
// audio
audioThresholdMin: 0.4,
audioThresholdMax: 1,
framePauseBetween: 10,
audioSpeed: 0.1,
// room
roomHeight: 0.7,
roomMargin: 6,
// device
deviceWidth: 200,
deviceHeight: 300,
deviceX: 0.2,
deviceY: 100,
deviceAura: 70,
};
const aura = {
left: 0,
right: 0,
top: 0,
bottom: 0,
};
let generatorNoise = 0;
const audioWaves = [];
let currentAudioMax = 0;
let isWave = false;
let framesSinceLastWave = 0;
let currentMaxVol = 0;
let isLooping = true;
let strokeColor1 = null;
let strokeColor2 = null;
let cnv = null;
function setup() {
cnv = createCanvas(CANVAS_WIDTH, CANVAS_HEIGHT);
noFill();
frameRate(FR);
// add parameters
pane.addInput(PARAMS, "isDev");
const f0 = pane.addFolder({
title: "Colors",
expanded: false,
});
f0.addInput(PARAMS, "bgColor", {
view: "color",
});
f0.addInput(PARAMS, "lineColor", {
view: "color",
});
f0.addInput(PARAMS, "deviceLineColor", {
view: "color",
});
f0.addInput(PARAMS, "deviceFillColor", {
view: "color",
});
f0.addInput(PARAMS, "objectLineColor", {
view: "color",
});
const f1 = pane.addFolder({
title: "Base Shape",
expanded: false,
});
f1.addInput(PARAMS, "diameter", {
min: 0,
max: CANVAS_WIDTH,
});
f1.addInput(PARAMS, "margin", {
min: 2,
max: 100,
});
f1.addInput(PARAMS, "angle", {
min: 5,
max: 90,
});
f1.addInput(PARAMS, "circlePointsMax", {
step: 1,
min: 0,
max: 100,
});
f1.addInput(PARAMS, "circlePointsMin", {
step: 1,
min: 0,
max: 100,
});
f1.addInput(PARAMS, "strokeWeight", {
min: 0,
max: 2,
});
const f2 = pane.addFolder({
title: "Animation",
expanded: false,
});
f2.addInput(PARAMS, "speed", {
min: 0,
max: 3,
});
f2.addInput(PARAMS, "seed", {
step: 1,
min: 0,
max: 999,
});
const f3 = pane.addFolder({
title: "Diameter Noise",
expanded: false,
});
f3.addInput(PARAMS, "useDiameterNoise");
f3.addInput(PARAMS, "dNoiseAmplitude", {
min: 0,
max: 10,
});
f3.addInput(PARAMS, "dNoiseFrequency", {
step: 0.001,
min: 0,
max: 0.05,
});
const f4 = pane.addFolder({
title: "Shape Noise",
expanded: false,
});
f4.addInput(PARAMS, "useShapeNoise");
f4.addInput(PARAMS, "sNoiseAmplitude", {
min: 0,
max: 1,
});
f4.addInput(PARAMS, "sNoiseFrequency", {
step: 0.001,
min: 0,
max: 0.1,
});
const f5 = pane.addFolder({
title: "Audio Parameters",
expanded: false,
});
f5.addInput(PARAMS, "audioSpeed", {
min: 0.01,
max: 1,
});
f5.addInput(PARAMS, "audioThresholdMin", {
min: 0,
max: 1,
});
f5.addInput(PARAMS, "audioThresholdMax", {
min: 0,
max: 1,
});
f5.addInput(PARAMS, "framePauseBetween", {
step: 1,
min: 10,
max: 200,
});
const f6 = pane.addFolder({
title: "Room",
expanded: false,
});
f6.addInput(PARAMS, "roomHeight", {
min: 0,
max: 1,
});
f6.addInput(PARAMS, "roomMargin", {
min: 2,
max: 50,
});
const f7 = pane.addFolder({
title: "Device",
expanded: false,
});
f7.addInput(PARAMS, "deviceWidth", {
min: 1,
max: 500,
});
f7.addInput(PARAMS, "deviceHeight", {
min: 1,
max: 500,
});
f7.addInput(PARAMS, "deviceX", {
min: 0,
max: 1,
});
f7.addInput(PARAMS, "deviceY", {
min: -200,
max: 200,
});
f7.addInput(PARAMS, "deviceAura", {
min: 0,
max: 200,
});
adjustValues();
pane.on("change", adjustValues);
audioWaves.push(new WobbleSphere());
}
function adjustValues() {
strokeColor1 = color(PARAMS.lineColor);
strokeColor2 = color(PARAMS.objectLineColor);
strokeWeight(PARAMS.strokeWeight);
noiseSeed(PARAMS.seed);
updateAura();
}
function draw() {
background(PARAMS.bgColor);
stroke(PARAMS.lineColor);
generatorNoise = noise(frameCount * PARAMS.audioSpeed);
if (generatorNoise > PARAMS.audioThresholdMin) {
framesSinceLastWave++;
isWave = true;
if (generatorNoise > currentMaxVol) {
currentMaxVol = generatorNoise;
}
} else if (isWave) {
audioWaves.push(new WobbleSphere(currentMaxVol, framesSinceLastWave));
framesSinceLastWave = 0;
isWave = false;
currentMaxVol = 0;
if (PARAMS.isDev) {
push();
fill(0);
circle( 100, 100, 50 );
pop();
}
}
// draw objects
drawRoom();
drawDevice();
audioWaves.forEach((w) => w.draw());
if (PARAMS.isDev) {
drawAura();
drawGraph();
}
SHOULD_SAVE && save(cnv, `Scene_01-${frameCount}.jpg`);
}
class WobbleSphere {
constructor(d = 0.1, length = 0) {
// console.log(length)
this.cutCount = length;
this.width = length * PARAMS.margin;
this.diameter =
map(d, PARAMS.audioThresholdMin, PARAMS.audioThresholdMax, 0, 1, true) *
PARAMS.diameter;
// global variables that are needed
// this.cutCount = int(this.width / PARAMS.margin);
this.sphere_counter = 0;
this.moveSpeed = PARAMS.wobbleMoveSpeed;
this.posX = 0;
this.posY = random();
this.noiseShift = random();
this.ID = sphereID;
sphereID++;
}
draw() {
push();
translate(width - this.posX, 0);
const tX = this.sphere_counter * PARAMS.speed;
const p_now = tX / PARAMS.margin;
if (tX > PARAMS.margin) {
this.sphere_counter = 0;
}
translate(0, height * this.posY);
const shapeRadius = this.diameter / PARAMS.margin / 2;
for (let i = 0; i < this.cutCount; i++) {
translate(PARAMS.margin, 0);
const p = lerp(i / this.cutCount, (i + 1) / this.cutCount, p_now);
const sliceCut = lerp(0, this.width, p);
const radius = sqrt(this.width * sliceCut - pow(sliceCut, 2));
// make nice round shape
let useThisDiameter = this.diameter;
if (i < shapeRadius) {
const part = i / shapeRadius;
useThisDiameter = this.diameter * sin(part * HALF_PI);
} else if (i > this.cutCount - shapeRadius) {
const part = (this.cutCount - i) / shapeRadius;
useThisDiameter = this.diameter * sin(part * HALF_PI);
}
const h = useThisDiameter; //radius * 2;
const d = useThisDiameter * cos(radians(90 - PARAMS.angle));
const dn = PARAMS.useDiameterNoise
? noise(this.noiseShift + PARAMS.dNoiseFrequency * frameCount, p) *
PARAMS.dNoiseAmplitude
: 1;
const cutX = width - this.posX + PARAMS.margin * i;
// 0 == not in transition
// 1 == fully transformed
let transitionVal = 0;
if (cutX < aura.right && cutX > aura.left) {
if (cutX > aura.right - PARAMS.deviceAura) {
transitionVal = map(
cutX,
aura.right,
aura.right - PARAMS.deviceAura,
0,
1,
true
);
} else if (cutX < aura.left + PARAMS.deviceAura) {
transitionVal = map(
cutX,
aura.left,
aura.left + PARAMS.deviceAura,
0,
1,
true
);
} else {
transitionVal = 1;
}
}
if( this.posY * height < aura.top ){
transitionVal = 0;
}
const thisCirclePoints = lerp(
PARAMS.circlePointsMax,
PARAMS.circlePointsMin,
transitionVal
);
const actualColor = lerpColor(strokeColor1, strokeColor2, transitionVal);
stroke(actualColor);
// draw circle
beginShape();
for (let c = 0; c <= thisCirclePoints; c++) {
let x = ((sin((c / thisCirclePoints) * TWO_PI) * d) / 2) * dn;
let y = ((cos((c / thisCirclePoints) * TWO_PI) * h) / 2) * dn;
if (PARAMS.useShapeNoise) {
const n2 =
sin(p * PI) *
PARAMS.sNoiseAmplitude *
(noise(frameCount * 0.01, PARAMS.sNoiseFrequency * c) - 0.5);
x += n2 * d;
y += n2 * h;
}
x += tX;
// const actualX = cutX + x
// if( actualX < aura.right && actualX > aura.left ){
if (transitionVal > 0.4) {
vertex(x, y);
} else {
curveVertex(x, y);
}
}
endShape(CLOSE);
}
this.sphere_counter += 1;
this.posX += this.moveSpeed;
pop();
// delete sphere if out of frame
if (this.posX > width + this.width * 2) {
this.destroy();
}
}
destroy() {
const arrayIndex = audioWaves.findIndex((w) => w.ID === this.ID);
delete audioWaves[arrayIndex];
audioWaves.splice(arrayIndex, 1);
}
}
function drawRoom() {
const horizon = height * PARAMS.roomHeight;
const floorLineShift = ((height - horizon) * -1) / sin(radians(PARAMS.angle));
const roomLines = (width + abs(floorLineShift)) / PARAMS.roomMargin;
push();
// vertical
for (let i = 0; i < roomLines; i++) {
translate(PARAMS.roomMargin, 0);
if (i * PARAMS.roomMargin < width) {
line(0, 0, 0, horizon);
}
line(0, horizon, floorLineShift, height);
}
pop();
}
function drawDevice() {
const objW = PARAMS.deviceWidth;
const objH = PARAMS.deviceHeight;
const midPointH = 30;
const midPoint = objW / 2 + -1 / sin(radians(PARAMS.angle));
const objH_rad = objH - objW / 2;
const devicePosY = height * PARAMS.roomHeight + PARAMS.deviceY;
const devicePosX = PARAMS.deviceX * width;
push();
translate(devicePosX, devicePosY);
stroke(PARAMS.deviceLineColor);
fill(PARAMS.deviceFillColor);
beginShape();
vertex(0, 0); // bottom left
bezierVertex(0, midPointH, objW / 2, midPointH, midPoint, midPointH); // bottom mid
bezierVertex(objW, midPointH, objW, 0, objW, 0); // bottom right
vertex(objW, -1 * objH_rad); // top right
bezierVertex(objW, -1 * objH, objW / 2, -1 * objH, objW / 2, -1 * objH); // top mid
bezierVertex(objW / 2, -1 * objH, 0, -1 * objH, 0, -1 * objH_rad);
// vertex( 0, -1 * objH_rad );
endShape(CLOSE);
if (PARAMS.isDev) {
stroke("#00f");
line(objW / 2, 0, midPoint, midPointH);
}
pop();
}
function updateAura() {
const devicePosY = height * PARAMS.roomHeight + PARAMS.deviceY;
const devicePosX = PARAMS.deviceX * width;
aura.top = devicePosY - PARAMS.deviceHeight - PARAMS.deviceAura;
aura.bottom = devicePosY + PARAMS.deviceAura;
aura.left = devicePosX - PARAMS.deviceAura;
aura.right = devicePosX + PARAMS.deviceWidth + PARAMS.deviceAura;
}
function drawAura() {
push();
stroke("#00f");
line(aura.left, 0, aura.left, height);
line(aura.right, 0, aura.right, height);
line(aura.left, aura.bottom, aura.right, aura.bottom);
line(aura.left, aura.top, aura.right, aura.top);
pop();
}
function drawGraph() {
if (generatorNoise > currentAudioMax) {
currentAudioMax = generatorNoise;
}
push();
if (generatorNoise > PARAMS.audioThresholdMin) {
fill(PARAMS.lineColor);
} else {
fill(100);
}
rect(0, 0, 2, generatorNoise * height);
text(nfc(generatorNoise, 2), 8, 12);
push();
fill(PARAMS.lineColor);
rect(0, PARAMS.audioThresholdMin * height, 6, 1);
text(nfc(PARAMS.audioThresholdMin, 2), 8, PARAMS.audioThresholdMin * height);
rect(0, PARAMS.audioThresholdMax * height, 6, 1);
text(nfc(PARAMS.audioThresholdMax, 2), 8, PARAMS.audioThresholdMax * height);
pop();
fill(0);
rect(0, currentAudioMax * height, 6, 1);
text(nfc(currentAudioMax, 2), 8, currentAudioMax * height);
pop();
}
function keyPressed() {
if (key === "p" || key === "P") {
isLooping ? noLoop() : loop();
isLooping = !isLooping;
}
}