Screenshot 2017 03 11 14 23 25

I have been imagining a sort of outer space audio physics interactive art environment and have tried to create it for this final project. I was inspired by an outer-space spray paint art project I did growing up at camp.

The environment does not work in the safari browser because of audio input issues. It works well in Chrome. You can try it out and see a video of the environment below.

[su_button url=”http://danielsilber-baker.com/noc/” target=”blank” background=”#d65459″ size=”6″ wide=”yes” center=”yes” radius=”10″ icon=”icon: tencent-weibo” text_shadow=”0px 0px 0px #000000″]Try The Thing[/su_button]

Nature of Code 2017 Final Documentation p5 Interactive Audio Constellation Environment from Daniel Silber-Baker on Vimeo.

A description of the moving parts

Here is a legend diagramming all the interactive elements of the environment

Nocfinal legend

[su_lightbox type=”image” src=”http://www.danielsilber-baker.com/wp-content/uploads/2017/03/nocfinal_legend.png”][su_button] Enlarge Image[/su_button][/su_lightbox]

The outer space physics interactive art environment is made up of

speakerBox: reading in the amplitude of the song being played and is mapping it to properties of an ellipse element

Orbiters: system of boids following physics set in the program. New ones can be generated by the user by pressing the button in the top left hand corner

Particles: created using particles js. Their behavior is set to be onClick = create new particles and onHover = repel.

Celestial Mist: The project posted on the web is currently using the fft input of the song being played. In the version seen in the videos the mist is reading in the ftt from the computers microphone and mapping it to a set of ellipses which are set to have low opacity values using RGBA and colors being mapped to the input as well.

The program is set up to be dynamic and able to create different paintings each time based on user interaction and randomization programmed into the code. Also trying to create an interaction where improvisation is possible between computer and human. The program changes the velocity of the objects moving through space over time, which gives the environment the ability to continue changing over time.

Here is a video from the beginning stages of the environments, and this one highlights the particles, which you can interact with. See the mouse interaction at the bottom of the screen.

Particles Demo

begCycle particleInteraction from Daniel Silber-Baker on Vimeo.

Here is another video of the environment as it first begins to run. This video highlights the orbiters, which users can make more of by pressing the button in the top left corner

Orbiters Demo

begCycle oribters from Daniel Silber-Baker on Vimeo.

Here is a video of the environment after letting it run for a couple minutes

endCycle from Daniel Silber-Baker on Vimeo.

And here it is after a few more minutes running

lettingItRun from Daniel Silber-Baker on Vimeo.

Some Alternate Universes

In the ‘final’ version of this project I gave the p5 canvas no background color which reveals all of the particles in the background, and creates the color streaks left by the elements paths. There is however an alternate universe in which their is a background to the canvas, and that one is fun to explore as well

nature of code p5 constellation with canvas background from Daniel Silber-Baker on Vimeo.

* In some of the videos you may notice a little trick with the button in the left hand corner. I have made the button element content editable in the html so that I can live edit what the button text is. This has a couple interesting possibilities, one of them that I am trying here, is to add a layer of improvisational documentation, by typing what the button is doing while pressing the button. Very meta.

Breakdown of the code

//variables for the orbiters
var orbiters = [];
var attractor;

// velocity times acceleration = something important in all of this
// variables to control the constant circle bar ---anything else?
var angle;
var aVelocity = 0;
var aAcceleration = 0.00001;

//variables to play the song and read in the values
var song;
var analyzer;

//sound
var mic;
var fft;

// variables for canvas and window width and height to place the canvas correctly
var c;
var window_width;
var window_height;
var x;
var y;

//function to center the p5 canvas, with window resized function at bottom which refreshes the value every time the window size is adjusted to make canvas responsive
function centerCanvas() {
var x = (windowWidth - width) / 2;
var y = (windowHeight - height) / 2;
c.position(x, y);
}

function setup() {
// variables to hold the window width and height
window_height = windowHeight;
window_width = windowWidth;
//create canvas
c=  createCanvas(window_width, window_height-200);
//center the canvas
centerCanvas()
//add id to canvas to make its position absolute
c.id('phyCanvas');

/*
AUDIO SET UP
*/

//load song
song = loadSound('gta.mp3');

// select play button created on index.html page to toggle playing song
// var circleGrabber = select("#playButton");
var playButton = createButton('PLAY SONG')
playButton.position(400,0)
playButton.addClass('playButton')
//run the play song function on button press
playButton.mousePressed(playDatMusic);
//function to play song
function playDatMusic () {
if ( song.isPlaying() ) {
song.stop();
} else {
//anything else I want to happen when song is playing
song.play();
}
}

//set up computer mic to read in audio input
// mic = new p5.AudioIn();
// mic.start();

//instantiate p5 fft reader to receive analysis of individual audio frequencies within a waveform
fft = new p5.FFT();
//set the input to be the mic--I could also change this to read in the value of the song playing using fft.setInput(song). This would solve the https issue I'm having accessing computer mic non locally
fft.setInput(song);
//instantiate p5 amplitude to measure volume
analyzer = new p5.Amplitude();
//set the analyzer to read in the amplitude of the song playing
analyzer.setInput(song);

/*
End AUDIO SET UP
*/


//set initial angle for the circle bars
angle = PI;

//push starting orbiters into the orbiters array which will be used to draw orbiters to canvas below in the draw functon. This is calling from the file orbiters.js
//Right now set to begin with just one. But other values are enjoyable as well and can be easily adjusted by changing the value that is currently 1 in the for loop section below: ' i<1'; e.g. 'i<20'
for (var i = 0; i < 1; i++) {
//create the possibility for improvisation by setting a random range for the new orbiters to begin
orbiters.push(new Orbiter(random(0.1, 0.2), random(width), random(height)));
}

//create button to generate more orbiters
var button = createButton('press me')
button.position(0,0);
button.addClass('playButton')
//press button to call newOne function which is below and will instantiate a new orbiter object as
button.mousePressed(newOne)

// instantiate the attractor which is set in the attractor.js file. This will be used in calculating the force applied to the physics of the orbiters
attractor = new Attractor();
}


function draw() {
//set the initial stroke and fill for the canvas
stroke(1)

// using RGBA to alter the opacity values of both stroke and fill are the key to layered coloring that happens in the sketch
stroke('rgba(12, 41, 76, .01)')
fill('rgba(103, 146, 189, 0.005))');

//different backround options. Currently set to no background color for canvas to let the particles come through
// background('rgba(60, 11, 11, .00001)');
// background('rgba(21, 28, 54, .001)');
// background('#151C36')

//took out vertex graph of fft input but it could invoked from vertex.js in the folder structure

//draw the orbiters and their physics to the canvas
attractor.display();

//draw all of the orbiters in the orbiters array on to the canvas with their physics
for (var i = 0; i < orbiters.length; i++) {
var force = attractor.calculateAttraction(orbiters[i]);
orbiters[i].applyForce(force);
orbiters[i].update();
orbiters[i].display();
}

// call a function to draw the circles connected by a line and their orbiting circles
drawBar();

//end setup
}

//function to make a new orbiter every time the button is pressed
function newOne(){

orbiters.push(new Orbiter(random(0.1, 0.2), random(width), random(height)));
}

//function to draw the circle bars
function drawBar(){
//translate to the middle
translate(width/2, height/2);
rotate(angle);
//set stroke and fill for circles
strokeWeight(2);
stroke('rgba(200, 91, 70, .4)');
fill('#97354D');
// draw 2 inner circles connected by a line and to outer circles
line(-60, 0, 60, 0);
ellipse(60, 0, 16, 16);
ellipse(-60, 0, 16, 16);
ellipse(-160, 0, 38, 38);
ellipse(160, 0, 38, 38);
//fft analyzer to draw the cosmic mist based on the waveforms of the song
var spectrum = fft.analyze(song);
//set stroke and fill for the mist. Again using rgba to lower opacity
stroke('rgba(12, 41, 76, .01)')
fill('rgba(103, 146, 189, .005)');
//begin drawing mist using p5 shape method
beginShape();
for (i = 0; i<spectrum.length; i++) {
//additional 'edge' to the brush using the fft mapped to a vertex
vertex(i, map(spectrum[i], 0, 255, height, 0) );
//drawing the mist using an ellipse and the fft as a brush
ellipse(i*PI, map(spectrum[i], 0, 455, height, -100), 18, 18);
}
endShape();
// the angle changes by velocity, which starts at 0 and goes up by .0001 via the aAcceleration each time the program runs
angle += aVelocity;
aVelocity += aAcceleration;
}

var m;
var e;
var Attractor = function() {
  this.position = createVector(width/2, height/2);
  this.mass = 20;
  this.G = 1;

  this.calculateAttraction = function(m) {
    // Calculate direction of force
    var force = p5.Vector.sub(this.position, m.position);
    // Distance between objects
    var distance = force.mag();
    // Limiting the distance to eliminate "extreme" results for very close or very far objects
    distance = constrain(distance, 5, 25);
    // Normalize vector (distance doesn't matter here, we just want this vector for direction)
    force.normalize();
    // Calculate gravitional force magnitude
    var strength = (this.G * this.mass * m.mass) / (distance * distance);
    // Get force vector --> magnitude * direction
    force.mult(strength);
    return force;
  };

  this.display = function() {
    ellipseMode(CENTER);
    strokeWeight(2);
    stroke("rgba(0, 160, 176, 1)");
       fill('rgba(27, 176, 206, .04)');

   for (var i = 0; i< 10; i++){
   var rms = analyzer.getLevel();
  // Draw an ellipse with size based on volume
  ellipse(this.position.x, this.position.y, 10+rms*400, 10+rms*400);
  }
  };
};
//Based on
// The Nature of Code
// Daniel Shiffman
// http://natureofcode.com
var Orbiter = function(m, x, y) {
  // create an initial vector for the Orbiter to begin calculations and position apparently
  //3 arguments. m,x,y where x,y is the location of the initial this.position and m -= this.mass
  // PART ONE-SETTING UP THE VARIABLES
  this.position = createVector(x, y);
  this.mass = m;
// creating a Orbiter object using the spinning baton idea
// overall logic follows these two apparently magic formulas
// angle = angle + angular velocity
// angular velocity = angular velocity + angular acceleration
  this.angle = 0;
  this.aVelocity = 0;
  this.aAcceleration = 0;
// ! creation opp
//randomizing a bit for improvisation
  this.velocity = createVector(random(-1, 1), random(-1, 1));
    // this.velocity = createVector(0, 0);
    // this.acceleration = createVector(0, 0);
    this.acceleration = createVector(random(-1, 1), random(-1, 1));
  this.applyForce = function(force) {
    var f = p5.Vector.div(force, this.mass);
    this.acceleration.add(f);
  };
  this.update = function () {
    this.velocity.add(this.acceleration);
    this.position.add(this.velocity);
    this.aAcceleration = this.acceleration.x / 10.0;
    this.aVelocity += this.aAcceleration;
    this.aVelocity = constrain(this.aVelocity, -0.1, 0.1);
    this.angle += this.aVelocity;
    this.acceleration.mult(0);
  };
  this.display = function () {
    stroke(.1);
    stroke('#97354D')
      fill('#269288')
ellipseMode(CORNER);
  push();
    translate(this.position.x, this.position.y);
    rotate(this.angle);
     ellipse(0,0, this.mass*6, this.mass*6);
     ellipse(0,0, this.mass*7, this.mass*7);
     pop();
  };
};

//function to recenter the canvas everytime the window is resized to make responsive on desktop
function windowResized() {
centerCanvas();
// window_height = windowHeight;
// window_width = windowWidth;
}