xxxxxxxxxx
465
let notes = ['C5', 'Bb4', 'G4', 'F4', 'Db4', 'C4'];
let numRows = 16;
let grid = Array(numRows).fill().map(() => Array(6).fill(false));
let playheadY = 0;
let isPlaying = false;
let lastBeatTime = 0;
let tempo = 520;
let synth;
let gridOpacity = 0;
let lastClickTime = 0;
let currentBeat = 0;
let horizontalStrings = [];
let verticalStrings = [];
let circles = [];
let divisions = [];
let verticalSquares = [];
let squares = [];
class VerticalSquare {
constructor(lifespan = 500) {
this.size = random(50, 200);
this.isTopToBottom = random() > 0.5;
this.y = this.isTopToBottom ? -this.size : height;
this.x = random(width - this.size);
this.speed = random(3, 8);
this.lifespan = lifespan;
this.startTime = millis();
this.isAlive = true;
}
update() {
if (millis() - this.startTime >= this.lifespan) {
this.isAlive = false;
return;
}
if (this.isTopToBottom) {
this.y += this.speed;
if (this.y > height) this.isAlive = false;
} else {
this.y -= this.speed;
if (this.y + this.size < 0) this.isAlive = false;
if (i === 5) { // C4 note
let c4Count = 0;
for (let row = 0; row <= currentBeat; row++) {
if (grid[row][5]) c4Count++;
}
for (let j = 0; j < c4Count; j++) {
verticalSquares.push(new VerticalSquare(1500));
}
}
}
}
draw() {
blendMode(DIFFERENCE);
const alpha = map(millis() - this.startTime, this.lifespan * 0.7, this.lifespan, 255, 0, true);
noStroke();
fill(255, alpha);
rect(this.x, this.y, this.size, this.size);
blendMode(BLEND);
}
}
class MovingSquare {
constructor(lifespan = 500) {
this.size = random(50, 200);
this.isLeftToRight = random() > 0.5;
this.x = this.isLeftToRight ? -this.size : width;
this.y = random(height - this.size);
this.speed = random(3, 8);
this.lifespan = lifespan;
this.startTime = millis();
this.isAlive = true;
}
update() {
if (millis() - this.startTime >= this.lifespan) {
this.isAlive = false;
return;
}
if (this.isLeftToRight) {
this.x += this.speed;
if (this.x > width) this.isAlive = false;
} else {
this.x -= this.speed;
if (this.x + this.size < 0) this.isAlive = false;
}
}
draw() {
blendMode(DIFFERENCE);
const alpha = map(millis() - this.startTime, this.lifespan * 0.7, this.lifespan, 255, 0, true);
noStroke();
fill(255, alpha);
rect(this.x, this.y, this.size, this.size);
blendMode(BLEND);
}
}
class Circle {
constructor(lifespan = 500) {
this.x = random(width);
this.y = random(height);
this.size = random(40, 80);
this.lifespan = lifespan;
this.startTime = millis();
this.isAlive = true;
}
update() {
if (millis() - this.startTime >= this.lifespan) {
this.isAlive = false;
}
}
draw() {
const alpha = map(millis() - this.startTime, 0, this.lifespan, 255, 0, true);
noStroke();
fill(255, alpha);
circle(this.x, this.y, this.size);
}
}
class DivisionEffect {
constructor(lifespan = 500) {
this.divisions = 1;
this.isVertical = random() > 0.5;
this.lifespan = lifespan;
this.startTime = millis();
this.isAlive = true;
}
update() {
if (millis() - this.startTime >= this.lifespan) {
this.isAlive = false;
}
}
draw() {
blendMode(DIFFERENCE);
const alpha = map(millis() - this.startTime, this.lifespan * 0.7, this.lifespan, 255, 0, true);
noStroke();
for (let i = 0; i < this.divisions + 1; i++) {
if (i % 2 === 0) {
fill(255, alpha);
} else {
fill(0, alpha);
}
if (this.isVertical) {
let x = map(i, 0, this.divisions, 0, width);
let w = width / this.divisions;
rect(x, 0, w, height);
} else {
let y = map(i, 0, this.divisions, 0, height);
let h = height / this.divisions;
rect(0, y, width, h);
}
}
blendMode(BLEND);
}
}
class HorizontalString {
constructor(y, lifespan = 500) {
this.y = y;
this.points = [];
this.lifespan = lifespan;
this.startTime = millis();
this.isAlive = true;
this.dampening = 0.99;
this.initPoints();
}
initPoints() {
const numPoints = 40;
for (let i = 0; i < numPoints; i++) {
let x = map(i, 0, numPoints - 1, 0, width);
this.points.push({
x,
y: this.y,
velocity: random(-5, 5),
targetY: this.y
});
}
}
update() {
const currentTime = millis();
const age = currentTime - this.startTime;
const lifeRatio = age / this.lifespan;
if (lifeRatio >= 1) {
this.isAlive = false;
return;
}
for (let p of this.points) {
let force = (p.targetY - p.y) * 0.2;
p.velocity += force;
p.velocity *= this.dampening;
p.y += p.velocity;
}
}
draw() {
blendMode(DIFFERENCE);
const age = millis() - this.startTime;
const alpha = map(age, this.lifespan * 0.7, this.lifespan, 255, 0, true);
stroke(255, alpha);
strokeWeight(2);
noFill();
beginShape();
for (let p of this.points) {
vertex(p.x, p.y);
}
endShape();
blendMode(BLEND);
}
}
class VerticalString {
constructor(x, lifespan = 500) {
this.x = x;
this.points = [];
this.lifespan = lifespan;
this.startTime = millis();
this.isAlive = true;
this.dampening = 0.99;
this.initPoints();
}
initPoints() {
const numPoints = 40;
for (let i = 0; i < numPoints; i++) {
let y = map(i, 0, numPoints - 1, 0, height);
this.points.push({
y,
x: this.x,
velocity: random(-5, 5),
targetX: this.x
});
}
}
update() {
const currentTime = millis();
const age = currentTime - this.startTime;
const lifeRatio = age / this.lifespan;
if (lifeRatio >= 1) {
this.isAlive = false;
return;
}
for (let p of this.points) {
let force = (p.targetX - p.x) * 0.2;
p.velocity += force;
p.velocity *= this.dampening;
p.x += p.velocity;
}
}
draw() {
blendMode(DIFFERENCE);
const age = millis() - this.startTime;
const alpha = map(age, this.lifespan * 0.7, this.lifespan, 255, 0, true);
stroke(255, alpha);
strokeWeight(2);
noFill();
beginShape();
for (let p of this.points) {
vertex(p.x, p.y);
}
endShape();
blendMode(BLEND);
}
}
function setup() {
createCanvas(405, 720);
synth = new p5.PolySynth();
}
function draw() {
blendMode(BLEND);
background(0);
// Update and draw all effects
for (let i = divisions.length - 1; i >= 0; i--) {
divisions[i].update();
divisions[i].draw();
if (!divisions[i].isAlive) {
divisions.splice(i, 1);
}
}
for (let i = squares.length - 1; i >= 0; i--) {
squares[i].update();
squares[i].draw();
if (!squares[i].isAlive) {
squares.splice(i, 1);
}
}
for (let i = circles.length - 1; i >= 0; i--) {
circles[i].update();
circles[i].draw();
if (!circles[i].isAlive) {
circles.splice(i, 1);
}
}
let cellWidth = width / 6;
let cellHeight = height / numRows;
if (millis() - lastClickTime < 2000) {
gridOpacity = map(millis() - lastClickTime, 0, 2000, 255, 0);
}
for (let row = 0; row < numRows; row++) {
for (let col = 0; col < 6; col++) {
if (grid[row][col]) {
fill(255, gridOpacity);
} else {
fill(30, gridOpacity);
}
stroke(68, gridOpacity);
strokeWeight(0.5);
rect(col * cellWidth, row * cellHeight, cellWidth, cellHeight);
}
}
for (let i = horizontalStrings.length - 1; i >= 0; i--) {
horizontalStrings[i].update();
horizontalStrings[i].draw();
if (!horizontalStrings[i].isAlive) {
horizontalStrings.splice(i, 1);
}
}
for (let i = verticalStrings.length - 1; i >= 0; i--) {
verticalStrings[i].update();
verticalStrings[i].draw();
if (!verticalStrings[i].isAlive) {
verticalStrings.splice(i, 1);
}
}
if (isPlaying) {
let beatInterval = (60 / tempo) * 1000;
let progress = (millis() - lastBeatTime) / beatInterval;
playheadY = (currentBeat * cellHeight + progress * cellHeight) % height;
if (playheadY < 0) playheadY = height + playheadY;
stroke('#FFD700');
strokeWeight(2);
line(0, playheadY, width, playheadY);
if (millis() - lastBeatTime >= beatInterval) {
playBeat();
currentBeat = (currentBeat + 1) % numRows;
lastBeatTime = millis();
}
}
if (keyIsPressed && (keyCode === UP_ARROW || keyCode === DOWN_ARROW)) {
fill(255);
noStroke();
textSize(24);
textAlign(RIGHT, TOP);
text(tempo + ' BPM', width - 20, 20);
}
}
function mousePressed() {
let cellWidth = width / 6;
let cellHeight = height / numRows;
let col = floor(mouseX / cellWidth);
let row = floor(mouseY / cellHeight);
if (col >= 0 && col < 6 && row >= 0 && row < numRows) {
grid[row][col] = !grid[row][col];
lastClickTime = millis();
}
}
function keyPressed() {
if (key === ' ') {
isPlaying = !isPlaying;
if (isPlaying) {
lastBeatTime = millis();
playheadY = currentBeat * (height / numRows);
}
}
if (keyCode === UP_ARROW) {
tempo = min(tempo + 5, 200);
}
if (keyCode === DOWN_ARROW) {
tempo = max(tempo - 5, 60);
}
}
function playBeat() {
for (let i = 0; i < notes.length; i++) {
if (grid[currentBeat][i]) {
synth.play(notes[i], 0.3, 0, 0.2);
if (i === 0) {
let c5Count = 0;
for (let row = 0; row <= currentBeat; row++) {
if (grid[row][0]) c5Count++;
}
for (let j = 0; j < c5Count; j++) {
let y = random(0, height);
let x = random(0, width);
horizontalStrings.push(new HorizontalString(y, 1000));
}
}
if (i === 1) {
let bb4Count = 0;
for (let row = 0; row <= currentBeat; row++) {
if (grid[row][1]) bb4Count++;
}
for (let j = 0; j < bb4Count; j++) {
let x = random(0, width);
let y = random(0, height);
verticalStrings.push(new VerticalString(x, 1000));
}
}
if (i === 2) { // G4 note
let g4Count = 0;
for (let row = 0; row <= currentBeat; row++) {
if (grid[row][2]) g4Count++;
}
for (let j = 0; j < g4Count; j++) {
circles.push(new Circle(1000));
}
}
if (i === 3) { // F4 note
let f4Count = 0;
for (let row = 0; row <= currentBeat; row++) {
if (grid[row][3]) f4Count++;
}
let newDivision = new DivisionEffect(1000);
newDivision.divisions = f4Count + 1;
divisions.push(newDivision);
}
if (i === 4) { // Db4 note
let db4Count = 0;
for (let row = 0; row <= currentBeat; row++) {
if (grid[row][4]) db4Count++;
}
for (let j = 0; j < db4Count; j++) {
squares.push(new MovingSquare(1500));
}
}
}
}
}