-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #11 from hannahilea/hr/p5-flock
Add sketch of flock chorus
- Loading branch information
Showing
3 changed files
with
327 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
<!DOCTYPE html> | ||
<html lang="en"> | ||
|
||
<head> | ||
<title>Sketch: Flock chorus</title> | ||
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.7.0/p5.js"></script> | ||
<script src="https://cdn.jsdelivr.net/npm/[email protected]"></script> | ||
<script src="https://unpkg.com/tone"></script> | ||
<script> | ||
var GUI = lil.GUI; | ||
</script> | ||
<link rel="stylesheet" type="text/css" href="../../css/main.css" /> | ||
<style> | ||
canvas { | ||
display: block; | ||
position: absolute; | ||
left: 0; | ||
top: 10; | ||
} | ||
|
||
</style> | ||
<script> | ||
function playNote() { | ||
// create a synth | ||
const synth = new Tone.Synth().toDestination(); | ||
synth.volume.value = -12; | ||
// const panner = new Tone.Panner(-1).toDestination(); | ||
// const osc = new Tone.Oscillator(100).connect(panner).start(); | ||
// play a note from that synth | ||
// synth.triggerAttackRelease("C4", "8n"); //.chain(panner); | ||
synth.triggerAttack("C4"); | ||
synth.setNote(440); | ||
} | ||
</script> | ||
</head> | ||
|
||
<body> | ||
<nav class="navbar" role="navigation"></nav> | ||
<div class="underline"> | ||
<h4><a href="../..">@hannahilea</a> > <a href="../../projects">projects</a> > Sketch: Flock chorus | ||
</h4> | ||
</div> | ||
</nav> | ||
<details> | ||
<summary>Details</summary> | ||
<p>Built on top of the <a href="https://p5js.org/examples/simulate-flocking.html">p5 flocking demo</a>.</p> | ||
<p><i>Usage:</i> Click on screen to spawn a bird. Make sure your computer sound is on. May not yet work on mobile.</p> | ||
|
||
</details> | ||
<button onclick="playNote()">Test sound!</button> | ||
<script src="./sketch.js"></script> | ||
|
||
</body> | ||
|
||
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,267 @@ | ||
// Built off of p5 flocking demo sketch https://p5js.org/examples/simulate-flocking.html | ||
console.log("Access to tone module?", Tone) | ||
let flock; | ||
|
||
const params = { | ||
worldWraps: false, | ||
maxspeed: .5, //2, | ||
maxforce: 0.05, // Maximum steering force | ||
radius: 3, | ||
targetFreqHz: 440, | ||
maxStartOffsetHz: 100, | ||
}; | ||
|
||
const gui = new GUI(); | ||
gui.add(params, 'worldWraps').name("Wrap world"); | ||
const guiFolder = gui.addFolder( 'Settings for spawned boids' ); | ||
guiFolder.add(params, 'targetFreqHz', 27, 2350, 5).name("Target freq (Hz)"); | ||
guiFolder.add(params, 'maxStartOffsetHz', 0, 100, 1); | ||
guiFolder.add(params, 'maxspeed', 0, 8, .5).name("Max speed"); | ||
guiFolder.add(params, 'maxforce', 0, .1, .01).name("Max steering force"); | ||
guiFolder.add(params, 'radius', .1, 10, .3).name("Size"); | ||
|
||
function setup() { | ||
createCanvas(windowWidth, windowHeight) | ||
Tone.start(); | ||
|
||
flock = new Flock(); | ||
// Add an initial set of boids into the system | ||
// for (let i = 0; i < 100; i++) { | ||
// let b = new Boid(width / 2, height / 2); | ||
// flock.addBoid(b); | ||
// } | ||
} | ||
|
||
function windowResized() { | ||
resizeCanvas(windowWidth, windowHeight); | ||
} | ||
|
||
function draw() { | ||
// https://stackoverflow.com/questions/55026293/google-chrome-javascript-issue-in-getting-user-audio-the-audiocontext-was-not | ||
// getAudioContext().resume(); | ||
|
||
background(51); | ||
flock.run(); | ||
} | ||
|
||
// Add a new boid into the System | ||
function mouseClicked() { | ||
flock.addBoid(new Boid(mouseX, mouseY)); | ||
} | ||
|
||
// The Nature of Code | ||
// Daniel Shiffman | ||
// http://natureofcode.com | ||
|
||
// Flock object | ||
// Does very little, simply manages the array of all the boids | ||
|
||
function Flock() { | ||
// An array for all the boids | ||
this.boids = []; // Initialize the array | ||
} | ||
|
||
Flock.prototype.run = function () { | ||
for (let i = 0; i < this.boids.length; i++) { | ||
this.boids[i].run(this.boids); // Passing the entire list of boids to each boid individually | ||
} | ||
if (!params.worldWraps) { | ||
// If the world doesn't wrap, remove all boids that have flown off screen | ||
this.boids = this.boids.filter((b) => { | ||
return b.position.x <= windowWidth && b.position.x >= 0 && b.position.y <= windowHeight && b.position.y >= 0; | ||
}); | ||
} | ||
} | ||
|
||
Flock.prototype.addBoid = function (b) { | ||
this.boids.push(b); | ||
} | ||
|
||
// The Nature of Code | ||
// Daniel Shiffman | ||
// http://natureofcode.com | ||
|
||
// Boid class | ||
// Methods for Separation, Cohesion, Alignment added | ||
|
||
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 = params.radius; | ||
this.maxspeed = params.maxspeed; // Maximum speed | ||
this.maxforce = params.maxforce; // Maximum steering force | ||
this.targetFreqHz = params.targetFreqHz; | ||
this.synth = new Tone.PolySynth().toDestination(); | ||
this.synth.volume.value = -12 | ||
this.currentFreqHz = this.targetFreqHz + params.maxStartOffsetHz; //TODO: allow negative, randomize idff | ||
this.synth.triggerAttack(this.currentFreqHz) | ||
} | ||
|
||
Boid.prototype.run = function (boids) { | ||
this.flock(boids); | ||
this.update(); | ||
this.borders(); | ||
this.render(); | ||
} | ||
|
||
Boid.prototype.applyForce = function (force) { | ||
// We could add mass here if we want A = F / M | ||
this.acceleration.add(force); | ||
} | ||
|
||
// We accumulate a new acceleration each time based on three rules | ||
Boid.prototype.flock = function (boids) { | ||
let sep = this.separate(boids); // Separation | ||
let ali = this.align(boids); // Alignment | ||
let 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); | ||
// 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) { | ||
let 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 | ||
let steer = p5.Vector.sub(desired, this.velocity); | ||
steer.limit(this.maxforce); // Limit to maximum steering force | ||
return steer; | ||
} | ||
|
||
Boid.prototype.render = function () { | ||
// Draw a triangle rotated in the direction of velocity | ||
let theta = this.velocity.heading() + radians(90); | ||
fill(127); | ||
stroke(200); | ||
push(); | ||
translate(this.position.x, this.position.y); | ||
rotate(theta); | ||
beginShape(); | ||
vertex(0, -this.r * 2); | ||
vertex(-this.r, this.r * 2); | ||
vertex(this.r, this.r * 2); | ||
endShape(CLOSE); | ||
pop(); | ||
} | ||
|
||
// Wraparound | ||
Boid.prototype.borders = function () { | ||
if (this.position.x < -this.r) this.position.x = width + this.r; | ||
if (this.position.y < -this.r) this.position.y = height + this.r; | ||
if (this.position.x > width + this.r) this.position.x = -this.r; | ||
if (this.position.y > height + this.r) this.position.y = -this.r; | ||
} | ||
|
||
// Separation | ||
// Method checks for nearby boids and steers away | ||
Boid.prototype.separate = function (boids) { | ||
let desiredseparation = 25.0; | ||
let steer = createVector(0, 0); | ||
let count = 0; | ||
// For every boid in the system, check if it's too close | ||
for (let i = 0; i < boids.length; i++) { | ||
let 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 | ||
let 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); | ||
// console.log(count,this.currentFreqHz, this.targetFreqHz) | ||
if (this.currentFreqHz > this.targetFreqHz) { | ||
this.currentFreqHz = this.currentFreqHz - 1; | ||
// this.synth.triggerRelease() | ||
this.synth.triggerAttack(this.currentFreqHz, Tone.now()) | ||
this.synth.releaseAll() | ||
console.log("OK") | ||
} | ||
} | ||
|
||
// 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); | ||
|
||
// Play collision sound! | ||
// console.log(this, "ping", steer.mag()) | ||
} | ||
|
||
return steer; | ||
} | ||
|
||
// Alignment | ||
// For every nearby boid in the system, calculate the average velocity | ||
Boid.prototype.align = function (boids) { | ||
let neighbordist = 50; | ||
let sum = createVector(0, 0); | ||
let count = 0; | ||
for (let i = 0; i < boids.length; i++) { | ||
let 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); | ||
let 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) { | ||
let neighbordist = 50; | ||
let sum = createVector(0, 0); // Start with empty vector to accumulate all locations | ||
let count = 0; | ||
for (let i = 0; i < boids.length; i++) { | ||
let 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); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters