xxxxxxxxxx
521
let songs = []; // array to hold songs
let currentSongIndex = -1; // no song for -1
let fft;
let particles = []; // array for particles
let serialInitialized = false;
let highPass, lowPass; // song filters with ultrasonic distance sensor
let SunBurstActive = false; // sunburst effect
let rotateActive = false; // rotating effect
let linesActive = false; // toggle between particles and lines
let lines = [];
let neonColors = [];
let lineSpawnRate = 35; // frames between spawning new lines
const maxLines = 500; // maximum number of lines
let pitch = 1.0; // pitch speed control
function preload() {
songs[0] = loadSound('wake_me_up.mp3');
songs[1] = loadSound('viva_la_vida.mp3');
songs[2] = loadSound('sky_full_of_stars.mp3');
}
function setup() {
createCanvas(windowWidth, windowHeight);
angleMode(DEGREES);
fft = new p5.FFT();
// colors from the past assignment for the lines
neonColors = [
color(173, 216, 230),
color(255, 192, 203),
color(255, 140, 0),
color(255, 255, 255),
color(75, 0, 130),
color(148, 0, 211),
color(0, 255, 255),
color(116, 255, 116)
];
// filters
highPass = new p5.HighPass();
lowPass = new p5.LowPass();
// switching songs
for (let song of songs) {
song.disconnect();
song.connect();
}
}
function draw() {
frameRate(120);
if (fullscreen) {
resizeCanvas(windowWidth, windowHeight); // resizing to fit either p5.js window or fullscreen
} else {
resizeCanvas(windowWidth, windowHeight);
}
background(0);
stroke(255);
strokeWeight(3);
noFill();
translate(width / 2, height / 2); // center
if (rotateActive) {
rotate(frameCount * 0.5); // rotating effect, can be adjusted
}
if (serialActive) {
fft.analyze();
let amp = fft.getEnergy(20, 200); // change to increase/decrease sensitivity of the visualization
let wave = fft.waveform();
// RIGHT HALF OF CIRCLE
beginShape();
for (let i = 0; i <= 180; i += 3) {
let index = floor(map(i, 0, 180, 0, wave.length - 1));
let radius = map(wave[index], -1, 1, 150, 350);
let x = radius * sin(i);
let y = radius * cos(i);
vertex(x, y);
}
endShape();
// LEFT HALF OF CIRCLE
beginShape();
for (let i = 0; i <= 180; i += 3) {
let index = floor(map(i, 0, 180, 0, wave.length - 1));
let radius = map(wave[index], -1, 1, 150, 350);
let x = radius * -sin(i);
let y = radius * cos(i);
vertex(x, y);
}
endShape();
if (linesActive) {
// spawn new lines until the count will reach the maximum limit
if (frameCount % lineSpawnRate === 0 && lines.length < maxLines) {
lines.push(new MovingLine("horizontal"));
lines.push(new MovingLine("vertical"));
}
// draw lines
for (let i = 0; i < lines.length; i++) {
let line = lines[i];
line.update();
line.drawSelf();
// drawing circles like in assignment, but this time they are drawn all the time for better effects
if (line.circle_trigger && line.circle_alpha > 0) {
fill(
red(line.color),
green(line.color),
blue(line.color),
line.circle_alpha
);
noStroke();
circle(line.endX, line.endY, line.circle_size);
line.circle_size += 5;
line.circle_alpha -= 4; // fading away
}
}
} else {
// create particles if lines are not active
let p = new Particle();
particles.push(p);
for (let i = particles.length - 1; i >= 0; i--) {
if (!particles[i].edges()) {
particles[i].update(amp > 200);
particles[i].show();
} else {
particles.splice(i, 1); // if particles are out of the canvas, delete to not overload the memory :D
}
}
}
// sunburst
if (SunBurstActive) {
drawSunBurst(amp);
}
} else {
console.log('ARDUINO IS NOT CONNECTED');
}
}
function keyPressed() {
if (key === ' ') { // connecting arduino
setUpSerial();
}
if (key === 'f') { // fullscreen
let fs = fullscreen();
fullscreen(!fs);
}
}
// reading serial data
function readSerial(data) {
let data_list = data.split(":"); // splitting button, potentiometer and distance sensor values to make it easier to read them
let buttonState = data_list[0]; // reading which button was pressed
let distance = parseFloat(data_list[1]); // distance value
let potValue = parseInt(data_list[2]); // potentiometer value
console.log(data_list);
// mapping potentiometer value to volume
if (!isNaN(potValue)) {
let volume = map(potValue, 0, 1023, 0.0, 1.0);
volume = constrain(volume, 0.0, 1.0); // constraining the volume to stay in adequate range
if (currentSongIndex !== -1) {
songs[currentSongIndex].setVolume(volume);
}
console.log(`Volume set to: ${volume}`);
}
// button values
if (buttonState === "2") {
playSong(0); // song 1
} else if (buttonState === "3") {
playSong(1); // song 2
} else if (buttonState === "11") {
playSong(2); // song 3
} else if (buttonState === "5") {
// increasing speed
pitch += 0.1;
pitch = constrain(pitch, 0.5, 2.0); // setting limits
if (currentSongIndex !== -1) {
songs[currentSongIndex].rate(pitch);
}
console.log(`Pitch increased: ${pitch}`);
} else if (buttonState === "6") {
// decreasing pitch
pitch -= 0.1;
pitch = constrain(pitch, 0.5, 2.0); // limits
if (currentSongIndex !== -1) {
songs[currentSongIndex].rate(pitch);
}
console.log(`Pitch decreased: ${pitch}`);
} else if (buttonState === "7") {
rotateActive = !rotateActive; // rotation effect
} else if (buttonState === "8") {
linesActive = !linesActive; // particles/lines
} else if (buttonState === "9") {
SunBurstActive = !SunBurstActive; // sunburst effect
console.log(`SunBurst effect: ${SunBurstActive}`);
} else if (buttonState === "10") {
togglePlayPause(); // pause/play
}
// distance tracking and highpass / lowpass filters. values of distance can be adjusted
if (!isNaN(distance)) {
if (distance < 20 && distance != -1) {
songs[currentSongIndex]?.disconnect();
songs[currentSongIndex]?.connect(lowPass);
lowPass.freq(200);
} else if (distance >= 20 && distance <= 200) {
songs[currentSongIndex]?.disconnect();
songs[currentSongIndex]?.connect(highPass);
highPass.freq(500);
} else {
songs[currentSongIndex]?.disconnect();
songs[currentSongIndex]?.connect();
}
}
}
// playing the selected song
function playSong(index) {
if (index >= 0 && index < songs.length) {
if (currentSongIndex !== -1 && songs[currentSongIndex].isPlaying()) {
songs[currentSongIndex].stop(); // current song stops
}
currentSongIndex = index;
songs[currentSongIndex].play(); // new song plays
songs[currentSongIndex].rate(1.0); // resetting the speed to 1 when the next song starts playing
console.log(`Playing song: ${currentSongIndex}`);
}
}
// play/pause
function togglePlayPause() {
if (currentSongIndex !== -1) {
if (songs[currentSongIndex].isPlaying()) {
songs[currentSongIndex].pause();
console.log(`Paused song: ${currentSongIndex}`);
} else {
songs[currentSongIndex].play();
console.log(`Resumed song: ${currentSongIndex}`);
}
}
}
// sunburst
function drawSunBurst(amp) {
let numRays = 100; // number of rays can be adjusted
let maxLength = map(amp, 50, 255, 50, 400); // mapping to the length of rays, also can be adjusted
stroke(random(200, 255), random(200, 255), random(200, 255));
for (let i = 0; i < 360; i += 360 / numRays) {
let x1 = 0;
let y1 = 0;
let x2 = maxLength * cos(i);
let y2 = maxLength * sin(i);
line(x1, y1, x2, y2);
}
}
// creating particles as per the example from the tutorial
class Particle {
constructor() {
this.pos = p5.Vector.random2D().mult(250); // putting particle on a random position on the circle radius
this.vel = createVector(0, 0);
this.acc = this.pos.copy().mult(random(0.0001, 0.00001)); // creating small acceleration in the same direction
this.size = random(3.5, 5); // can adjust the size
this.color = [random(200, 255), random(200, 255), random(200, 255)];
}
show() {
noStroke();
fill(this.color);
ellipse(this.pos.x, this.pos.y, this.size);
}
update(cond) {
this.vel.add(this.acc);
this.pos.add(this.vel); // normal acceleration if beat is not exceeding the threshold
if (cond) { // add triple acceleration if certain beat is detected, can be adjusted
this.pos.add(this.vel);
this.pos.add(this.vel);
this.pos.add(this.vel);
}
}
edges() { // for edges adding or subtracting 300 to improve the rotating effect to not let particles disappear earlier than they leave the screen
if (
this.pos.x < -width / 2 - 300 || this.pos.x > width / 2 + 300 ||
this.pos.y < -height / 2 - 300 || this.pos.y > height / 2 + 300
) {
return true;
} else {
return false;
}
}
}
// creating lines same logic like in the past assignment
class MovingLine {
constructor(direction) {
this.direction = direction;
this.color = random(neonColors);
this.alpha = 255;
this.fadingSpeed = random(0.5, 2);
this.lifespan = 0;
this.speed = random(0.01, 0.03);
this.change_dir_angle = random(-150, 150);
this.circle_trigger = false;
this.circle_size = 10;
this.circle_alpha = 255;
if (this.direction === "horizontal") {
this.y = random(-height / 2, height / 2);
this.x1 = -width / 2;
this.x2 = width / 2;
} else {
this.x = random(-width / 2, width / 2);
this.y1 = -height / 2;
this.y2 = height / 2;
}
this.endX = 0;
this.endY = 0;
}
update() {
if (!this.circle_trigger) {
if (this.lifespan < 1) {
this.lifespan += this.speed;
} else {
this.circle_trigger = true;
}
}
if (!mouseIsPressed && this.alpha > 0) {
this.alpha -= this.fadingSpeed;
}
}
drawSelf() {
if (this.alpha > 0) {
stroke(
red(this.color),
green(this.color),
blue(this.color),
this.alpha
);
strokeWeight(2);
if (this.direction === "horizontal") {
let xMovement = lerp(this.x1, this.x2, this.lifespan);
if (xMovement < 0) {
line(this.x1, this.y, xMovement, this.y);
} else {
let curveY = this.y + map(xMovement, 0, this.x2, 0, this.change_dir_angle);
line(this.x1, this.y, 0, this.y);
line(0, this.y, xMovement, curveY);
this.endX = xMovement;
this.endY = curveY;
}
if (xMovement >= this.x2) {
this.endX = this.x2;
this.circle_trigger = true;
}
} else {
let yMovement = lerp(this.y1, this.y2, this.lifespan);
if (yMovement < 0) {
line(this.x, this.y1, this.x, yMovement);
} else {
let curveX = this.x + map(yMovement, 0, this.y2, 0, this.change_dir_angle);
line(this.x, this.y1, this.x, 0);
line(this.x, 0, curveX, yMovement);
this.endX = curveX;
this.endY = yMovement;
}
if (yMovement >= this.y2) {
this.endY = this.y2;
this.circle_trigger = true;
}
}
}
}
}
/* ARDUINO CODE
// first song
const int SONG_1_PIN = 2;
// second song
const int SONG_2_PIN = 3;
// third song
const int SONG_3_PIN = 11;
// increase pitch speed button
const int INCREASE_PITCH_BUTTON_PIN = 5;
// decrease pitch speed button
const int DECREASE_PITCH_BUTTON_PIN = 6;
// rotating effect button
const int ROTATE_BUTTON_PIN = 7;
// toggling lines button
const int LINES_BUTTON_PIN = 8;
// sunburst effect button
const int SUNBURST_EFFECT_BUTTON_PIN = 9;
// play/pause button
const int PLAYPAUSE_BUTTON_PIN = 10;
// ultrasonic distance sensor
const int trigPin = 12;
const int echoPin = 13;
// potentiometer
const int POT_PIN = A1;
void setup() {
Serial.begin(9600); // start serial
pinMode(SONG_1_PIN, INPUT_PULLUP);
pinMode(SONG_2_PIN, INPUT_PULLUP);
pinMode(SONG_3_PIN, INPUT_PULLUP);
pinMode(ROTATE_BUTTON_PIN, INPUT_PULLUP);
pinMode(INCREASE_PITCH_BUTTON_PIN, INPUT_PULLUP);
pinMode(DECREASE_PITCH_BUTTON_PIN, INPUT_PULLUP);
pinMode(SUNBURST_EFFECT_BUTTON_PIN, INPUT_PULLUP);
pinMode(LINES_BUTTON_PIN, INPUT_PULLUP);
pinMode(PLAYPAUSE_BUTTON_PIN, INPUT_PULLUP);
pinMode(trigPin, OUTPUT); // ultrasonic trigger
pinMode(echoPin, INPUT); // ultrasonic echo
}
void loop() {
// reading values
int song1ButtonState = digitalRead(SONG_1_PIN);
int song2ButtonState = digitalRead(SONG_2_PIN);
int song3ButtonState = digitalRead(SONG_3_PIN);
int rotateButtonState = digitalRead(ROTATE_BUTTON_PIN);
int increasePitchState = digitalRead(INCREASE_PITCH_BUTTON_PIN);
int decreasePitchState = digitalRead(DECREASE_PITCH_BUTTON_PIN);
int sunburstEffectState = digitalRead(SUNBURST_EFFECT_BUTTON_PIN);
int playpauseSongState = digitalRead(PLAYPAUSE_BUTTON_PIN);
int linesButtonState = digitalRead(LINES_BUTTON_PIN);
// analog read for potentiometer
int potValue = analogRead(POT_PIN);
// distance sensor as per tutorial
long duration, cm;
digitalWrite(trigPin, LOW);
delayMicroseconds(2);
digitalWrite(trigPin, HIGH);
delayMicroseconds(10);
digitalWrite(trigPin, LOW);
duration = pulseIn(echoPin, HIGH, 30000); // measuring echo
cm = duration > 0 ? microsecondsToCentimeters(duration) : -1; // converting to cm
// conditions to handle the buttons pressed
if (song1ButtonState == LOW) {
Serial.print("2");
} else if (song2ButtonState == LOW) {
Serial.print("3");
} else if (song3ButtonState == LOW) {
Serial.print("11"); /
} else if (increasePitchState == LOW) {
Serial.print("5");
} else if (decreasePitchState == LOW) {
Serial.print("6");
} else if (rotateButtonState == LOW) {
Serial.print("7");
} else if (linesButtonState == LOW) {
Serial.print("8");
} else if (sunburstEffectState == LOW) {
Serial.print("9");
} else if (playpauseSongState == LOW) {
Serial.print("10");
} else {
Serial.print("0"); // gives 0 when no button is pressed
}
// sending following data from distance sensor and potentiometer divided by : to make it easier for p5.js to read it
Serial.print(":");
Serial.print(cm); // distance converted into cm
Serial.print(":");
Serial.println(potValue);
delay(100);
}
long microsecondsToCentimeters(long microseconds) {
// function converting to cm as per tutorial
return microseconds / 29 / 2;
}
*/