var angle = 0; //tree branch angle
var mic; //internal microphone
var forest = []; //holds trees
var iInc; //branch incriment
var ns = 10; //stars
var p; //moon curve variable
var q; //moon curve variable
var curveSize; //moon curve constraint
function setup() {
createCanvas(480, 280);
background(51);
mic = new p5.AudioIn(); //create mic
mic.start(); //activate mic
//push 3 trees into array
var tree1 = new Branch(87,10);
forest.push(tree1);
var tree2 = new Branch(70,70);
forest.push(tree2);
var tree3 = new Branch(40, 300);
forest.push(tree3);
flock = new Flock();
//creating and pushing stars into an array
for (var i = 0; i < ns; i++) {
var p = makeStar(-10, -10,random(-50, 50), random(-50, 50));
stars.push(p);
}
frameRate(10);
}
function draw() {
background(51, 7.5);
stroke(255,5);
//get volume from mic (trees, stars, moon)
var micLevel = mic.getLevel()*200;
angle = micLevel/40;
//call the function
flock.run();
//get volume from mic (flowy energy)
var vol = mic.getLevel()*15;
//if the volume is above a certain threshold, add a new flock of boids to the scene
if (vol > 2) {
flock.addBoid(new Boid(0, height/2));
}
pop();
background(51,51,51,255*.2); //redrawing the background to give ghostly layering effect
//Trees
for(var i = 0; i < forest.length; i++){ //create multiple trees
push();
var xPos = forest[i].x;
translate(xPos, height);
forest[i].display();
pop();
}
//Stars
//if the volume exceeds a certain threshold, new stars are drawn
if (micLevel > 10) {
var newS = makeStar(random(10, width), random(10, height/3*2), random(-0.5, 0.5), random(-0.5, 0.5));
stars.push(newS);
}
//store newly created stars
newStars = [];
for (var i = 0; i < stars.length; i++) {
var p = stars[i];
p.draw();
if (p.age < 200) {
newStars.push(p);
}
}
stars = newStars;
//Moon
//map volume threshold to moon curve formula
p = map (micLevel, 0, 100, 0, 100);
q = map (micLevel, 0, 100, 0, 130);
curveSize = map (micLevel, 0, width, 20, 20);
//curve formula
var k = p/q;
push();
translate (380, 70);
iInc = map (micLevel, 0, height, 0.05, 1.5);
for (var i = 0; i < TWO_PI * q ; i+= iInc*1.2) {
var r = curveSize * cos (k*i);
var x = r * cos(i);
var y = r * sin(i);
fill(189, 207 , 253, 10);
strokeWeight(iInc*5);
point (x, y);
fill(255);
}
}
//Completing the tree objects
function Branch(len, xTrans){
this.originalTall = len;
this.x = xTrans;
this.display = function(){
push();
translate(50);
branching(this.originalTall);
pop();
}
}
//Making the recursive tree
function branching(len){
line(0,0,0,-len);
translate(0,-len);
stroke(255);
if(len > 4) {
push();
rotate(angle);
branching(len * 0.67);
pop();
push();
rotate(-angle);
branching(len * 0.67);
pop();
}
}
//Making the stars
function starDraw() {
fill(255);
point(this.x + random (0.1, -0.1), this.y + random(-0.1, 0.1));
}
//create the star
function makeStar(sx, sy) {
p = {x: sx +random(-0.1, 0.1), y: sy + (-0.1, 0.1),
age: 0,
draw: starDraw
}
return p;
}
var stars = [];
// Starting here:
// Flocking Behavior algorithm sampled from Daniel Shiffman, "The Nature of Code"
// http://natureofcode.com
function Flock() {
// An array for all the boids
this.boids = []; // Initialize boids array
}
Flock.prototype.run = function() {
for (var i = 0; i < this.boids.length; i++) {
this.boids[i].run(this.boids); // Passing the entire list of boids to each boid individually
}
}
Flock.prototype.addBoid = function(b) {
this.boids.push(b); //
}
// Separation, Cohesion, and Alignment forces added
//defining the boid object
function Boid(x,y) {
this.acceleration = createVector(0,0);
this.velocity = createVector(random(-1,1),random(-1,1));
this.position = createVector(x,y);
this.r = 3.0; //radius of boid
this.maxspeed = 2; // Maximum speed
this.maxforce = 0.05; // Maximum steering force
// Starting here:
// Our own original code
var rr = random(70, 90);
var gg = random(70, 90);
var bb = random(80, 100);
this.rcolor = rr;
this.gcolor = gg;
this.bcolor = bb;
// Keep track of old x y coordinates of boid
this.history = [];
}
// Our own original code
// Ending here:
// Continuing here:
// Flocking Behavior algorithm sampled from Daniel Shiffman, "The Nature of Code"
// http://natureofcode.com
Boid.prototype.run = function(boids) {
this.flock(boids);
this.update();
this.borders();
this.render();
}
Boid.prototype.applyForce = function(force) {
this.acceleration.add(force);
}
// We accumulate a new acceleration each time based on three rules
Boid.prototype.flock = function(boids) {
var sep = this.separate(boids); // Separation
var ali = this.align(boids); // Alignment
var coh = this.cohesion(boids); // Cohesion
// Arbitrarily weight these forces
sep.mult(1.5);
ali.mult(1.0);
coh.mult(1.0);
// Add the force vectors to acceleration
this.applyForce(sep);
this.applyForce(ali);
this.applyForce(coh);
}
// Method to update location
Boid.prototype.update = function() {
// Update velocity
this.velocity.add(this.acceleration);
// Limit speed
this.velocity.limit(this.maxspeed);
this.position.add(this.velocity);
// update the property that stores old positions
var v = [this.position.x, this.position.y]; //AG
this.history.push(v); //AG
// Reset accelertion to 0 each cycle
this.acceleration.mult(0);
}
// A method that calculates and applies a steering force towards a target
// STEER = DESIRED MINUS VELOCITY
Boid.prototype.seek = function(target) {
var desired = p5.Vector.sub(target,this.position); // A vector pointing from the location to the target
// Normalize desired and scale to maximum speed
desired.normalize();
desired.mult(this.maxspeed);
// Steering = Desired minus Velocity
var steer = p5.Vector.sub(desired,this.velocity);
steer.limit(this.maxforce); // Limit to maximum steering force
return steer;
}
//Starting Here:
//Heavily modified perameters by us
Boid.prototype.render = function() {
// Draw a triangle rotated in the direction of velocity
var theta = this.velocity.heading() + radians(90);
noFill();
noStroke();
push();
translate(this.position.x,this.position.y);
rotate(theta);
beginShape();
vertex(0, -this.r);
vertex(-this.r, this.r*6); //boid size
vertex(this.r, this.r*6); //boid size
endShape(CLOSE);
pop();
// Starting here:
// Our own original code
// Displays path of old locations
for (var i = 0; i < this.history.length; i++) {
var posx = this.history[i][0];
var posy = this.history[i][1];
fill(this.rcolor, this.gcolor, this.bcolor, 90);
ellipse(posx, posy, 5, 5);
}
}
// Our own original code
// Ending here:
// Starting here:
// Separation, Alignment and Cohesion force algorithms sampled from Daniel Shiffman, "The Nature of Code"
// http://natureofcode.com
// Border Constraints
Boid.prototype.borders = function() {
if (this.position.x < 0) {
this.velocity.x = -this.velocity.x;
}
if (this.position.y > height) {
this.velocity.y = -this.velocity.y;
}
if (this.history.length > 10) {
this.history.shift();
}
}
// Separation
// Method checks for nearby boids and steers away
Boid.prototype.separate = function(boids) {
var desiredseparation = 25.0;
var steer = createVector(0,0);
var count = 0;
// For every boid in the system, check if it's too close
for (var i = 0; i < boids.length; i++) {
var d = p5.Vector.dist(this.position,boids[i].position);
// If the distance is greater than 0 and less than an arbitrary amount (0 when you are yourself)
if ((d > 0) & (d < desiredseparation)) {
// Calculate vector pointing away from neighbor
var diff = p5.Vector.sub(this.position,boids[i].position);
diff.normalize();
diff.div(d); // Weight by distance
steer.add(diff);
count++; // Keep track of how many
}
}
// Average -- divide by how many
if (count > 0) {
steer.div(count);
}
// As long as the vector is greater than 0
if (steer.mag() > 0) {
// Implement Reynolds: Steering = Desired - Velocity
steer.normalize();
steer.mult(this.maxspeed);
steer.sub(this.velocity);
steer.limit(this.maxforce);
}
return steer;
}
// Alignment
// For every nearby boid in the system, calculate the average velocity
Boid.prototype.align = function(boids) {
var neighbordist = 50;
var sum = createVector(0,0);
var count = 0;
for (var i = 0; i < boids.length; i++) {
var d = p5.Vector.dist(this.position,boids[i].position);
if ((d > 0) & (d < neighbordist)) {
sum.add(boids[i].velocity);
count++;
}
}
if (count > 0) {
sum.div(count);
sum.normalize();
sum.mult(this.maxspeed);
var steer = p5.Vector.sub(sum,this.velocity);
steer.limit(this.maxforce);
return steer;
} else {
return createVector(0,0);
}
}
// Cohesion
// For the average location (i.e. center) of all nearby boids, calculate steering vector towards that location
Boid.prototype.cohesion = function(boids) {
var neighbordist = 50;
var sum = createVector(0,0); // Start with empty vector to accumulate all locations
var count = 0;
for (var i = 0; i < boids.length; i++) {
var d = p5.Vector.dist(this.position,boids[i].position);
if ((d > 0) & (d < neighbordist)) {
sum.add(boids[i].position); // Add location
count++;
}
}
if (count > 0) {
sum.div(count);
return this.seek(sum); // Steer towards the location
} else {
return createVector(0,0);
}
}
Anna Gusman and I created a therapeutic garden landscape that grows when it’s sung to. We chose to collaborate on this project because we were both super eager to explore p5’s comprehensive and lauded sound library. We thought that sonic interactions and audiovisual expressions would be great domains to explore by utilizing this tool.
Initially, we brainstormed ways by which people could see themselves “reflected” in a dynamic digital environment. By activating and scaling properties of the internal mic, we could poise the voice of an audience as the actuator of a captivating and therapeutic experience. The scene we designed contains four dynamic elements: recursion, flocking behavior, algorithmic curves and a particle system. Respectively, these would inform the emergence of natural objects like trees, wind, the moon and stars. Each of these dynamic elements are initiated by a singing voice and continue to grow as the viewer continues to sing! While some elements with more exposure to sound multiply, others express continued metamorphosis within themselves.