//Lan Wei
//lanw@andrew.cmu.edu
//Section // D
//Final Project-Musical Universe
var snd = []; //this array will hold the sounds
var amplitude = [];
var ampTotal; //Total amplitude to change background color
var mouseXArray = []; //the array of X values
var mouseYArray = []; //the array of y values
var balls = []; //the array of ball objects
var np = 26; //the number of background lines
var pArray = []; //the array of particles
//preload the sounds
function preload() {
var PREFIX = "https://courses.ideate.cmu.edu/15-104/f2018/wp-content/uploads/2018/12/";
for (var i = 1; i < 10; i++){
var sound = loadSound(PREFIX + "sound" + i + ".wav"); //load sounds
sound.setVolume(0.5);
snd.push(sound); //push the sounds in snd. var sound will never be used again
}
}
//make ball objects
function makeBalls(bx, by, br){
var ball = {x: bx,
y: by,
r: br, //radius of the ball
update: updateBall, //update the size of the balls
sound: whichSound, //which amplitude is the ball related to
draw: drawBall,
play: playSound};
return ball; //return the new object
}
function drawBall(){
stroke(255);
//ellipse(this.x, this.y, this.r, this.r);
var total = 20; //control the density of the points on the balls
for (var i = 0; i < total; i++){
var longtitude = map(i, 0, total, -PI, PI);
for (var j = 0; j < total; j++){
var latitude = map(j, 0, total, -HALF_PI, HALF_PI);
//fold the longtitude-latitude panel into a sphere
var ptX = this.r * sin(longtitude) * cos(latitude);
var ptY = this.r * sin(longtitude) * sin(latitude);
var ptZ = this.r * cos(longtitude);
push();
stroke(255);
translate(ptX, ptY, ptZ);
sphere(0.1);
pop();
}
}
}
function whichSound(){
if (zone(this.x, this.y) == 1){
return snd[0];
}
if (zone(this.x, this.y) == 2){
return snd[1];
}
if (zone(this.x, this.y) == 3){
return snd[2];
}
if (zone(this.x, this.y) == 4){
return snd[3];
}
if (zone(this.x, this.y) == 5){
return snd[4];
}
if (zone(this.x, this.y) == 6){
return snd[5];
}
if (zone(this.x, this.y) == 7){
return snd[6];
}
if (zone(this.x, this.y) == 8){
return snd[7];
}
if (zone(this.x, this.y) == 9){
return snd[8];
}
}
function playSound(){
var soundLocal = this.sound();
soundLocal.play();
}
function updateBall(){
var amp = amplitude[zone(this.x, this.y) - 1];
var level = amp.getLevel(); //get the level
this.r = 30 + 300 * level; //the size of balls
}
//particles to make background lines
function makeParticle(px, py, pdx, pdy){
p = {x: px,
y: py,
dx: pdx,
dy: pdy,
update: pUpdate,
draw: pDraw}
return p;
}
function pUpdate(){
this.x += this.dx;
this.y += this.dy;
//limit the lines in a certain area of 500 * 500 using 'bouncing'
if (this.x < -250){ //left boundary
this.x = -this.x - 250;
this.dx = -this.dx - 250;
}
else if (this.x > 250){ //right boundary
this.x = 300 - this.x;
this.dx = 300 - this.dx;
}
if (this.y < -250){ //downward boundary
this.y = -this.y - 250;
this.dy = -this.dy - 250;
}
else if (this.y > 250){ //upward boundary
this.y = 300 - this.y;
this.dy = 300 - this.dy;
}
}
function pDraw(){
ellipse(this.x, this.y, 5, 5);
}
/////////////////////////////////////////////////////////////////////////////////////
function setup() {
createCanvas(450, 450, WEBGL); // 3D mode. (0, 0)locates in the center of the canvas
background(0);
stroke(255);
perspective(); //perspective view
for (var i = 0; i < 9; i++){
amplitude[i] = new p5.Amplitude();
amplitude[i].setInput(snd[i]); //get independent amplitude
}
ampTotal = new p5.Amplitude(); //total amplitude
for (var i = 0; i < np; i++){
//the boundary of particles is a little bigger than the canvas
var p = makeParticle(random(-300, 300), random(-300, 300), random(-2, 2), random(-2, 2));
pArray.push(p);
}
}
function draw() {
var levelTotal = ampTotal.getLevel();
var col = map(levelTotal, 0, 1, 0, 100);//background color
background(col, 0, 2 * col);
//draw background lines
for (var i = 0; i < np; i++){
pArray[i].update();
pArray[i].draw();
//lines between particles
for (var j = 0; j < np/2; j++){
stroke(random(0, 150));
strokeWeight(0.2);
line(pArray[j].x, pArray[j].y, pArray[j + np/2].x, pArray[j + np/2].y);
}
}
//the canvas is divided by a 3*3 grid
strokeWeight(1);
stroke(random(20, 70));
line(-75, -225, -75, 225);
line(75, -225, 75, 225);
line(-225, -75, 225, -75);
line(-225, 75, 225, 75);
if (mouseXArray.length != 0){ //after the 1st mouse press
stroke(255);
fill(255);
// draw all the balls
for (i = 0; i < balls.length; i ++){
balls[i].update(); //update the radius of the balls
push();
translate(balls[i].x, balls[i].y, 0);
//rotate with randomness
rotateX(frameCount * 0.05 + i);
rotateY(frameCount * 0.05 + i * 5);
rotateZ(frameCount * 0.05 + i * 5);
balls[i].draw(); //draw the balls
pop();
}
}
}
//To identify which zone is the ball in
//translate the coordinate to normal mode
function zone(x, y){
if ((y > -225 )& (y < height/3 - 225)){
if ((x > -225) && (x < width/3 - 225)){ //position 1
return 1;
}
if ((x > width/3 - 225) & (x < 2 * width/3 - 225)){ //position 2
return 2;
}
if ((x > 2 * width/3 - 225) & (x < width - 225)){ //position 3
return 3;
}
}
if ((y > height/3 - 225) & (y < 2 * height/3 - 225)){
if ((x > -225) && (x < width/3 - 225)){ //position 4
return 4;
}
if ((x > width/3 - 225) & (x < 2 * width/3 - 225)){ //position 5
return 5;
}
if ((x > 2 * width/3 - 225) & (x < width - 225)){ //position 6
return 6;
}
}
if ((y > 2 * height/3 - 225) & (y < height - 225)){
if ((x > -225) && (x < width/3 - 225)){ //position 7
return 7;
}
if ((x > width/3 - 225) & (x < 2 * width/3 - 225)){ //position 8
return 8;
}
if ((x > 2 * width/3 - 225) & (x < width - 225)){ //position 9
return 9;
}
}
}
//when mouse is clicked, a sound will be played and a ball will be drawn
function mouseClicked(){
var clickOn; //to check whether click on an existing ball
var newBalls = [];
var newMouseXArray = [];
var newMouseYArray = [];
if (mouseXArray.length == 0){ //when there is no existing balls
clickOn = 0;
newBalls.push(balls[0]);
newMouseXArray.push(mouseXArray[0]);
newMouseYArray.push(mouseYArray[0]);
}
// a ball will be removed when clicked again
else{ //after the 1st click
for (i = 0; i < balls.length; i++){
var distance = dist(mouseX - 225, mouseY - 225, balls[i].x, balls[i].y);
//is clicked
if (distance <= 20){ //minimum distance
var soundBall = balls[i].sound();
soundBall.setVolume(0); //stop the sound of the ball that's clicked
// balls.splice(i, 1); //remove the ball
// mouseXArray.splice(i, 1);
// mouseYArray.splice(i, 1);
clickOn = 1;
}
//is not clicked
else{
clickOn = 0;
newBalls.push(balls[i]);
newMouseXArray.push(mouseXArray[i]);
newMouseYArray.push(mouseYArray[i]);
}
}
balls = newBalls;
mouseXArray = newMouseXArray;
mouseYArray = newMouseYArray;
}
if (clickOn == 0){
mouseXArray.push(mouseX - 225);//translate to WEBGL coordinates
mouseYArray.push(mouseY - 225);
// initial radius: 30
//make ball objects
var newBall = makeBalls(mouseX - 225, mouseY - 225, 30);
balls.push(newBall);
}
//play sound using the object
for (i = 0; i < balls.length; i++){
var soundLocal = balls[i].sound();
balls[i].play();
soundLocal.loop();
}
}
//YOU'VE GOT A MUSICAL UNIVERSE!!!
[How to play]: Click on different positions of the canvas to get different mixtures of ‘UNIVERSE MUSIC’,click the balls again to remove them as well as the sound (in the opposite order you created the balls). Play slowly! (Sometimes it doesn’t work well if played fast, maybe it’s my browser’s problem.)
My initial idea of the project is to create a simple instrument that enables users to mix several pieces of music. In the proposal stage, I have imagined many sound effects and interaction methods, but have only achieved part of them. The actual coding process is much harder than I thought, and the final result becomes more visual than auditory. But actually, I’m very happy about the project because it is really a reviewing as well as a learning process. I’m not very good at using objects and particles so I tried to include these contents in my project. And I also watched some online tutorials to create the 3D effect of the balls. The final result is very satisfying for me, especially the 3D ball effect!
Have Fun!