Skip to content

Commit

Permalink
Merge pull request #11 from hannahilea/hr/p5-flock
Browse files Browse the repository at this point in the history
Add sketch of flock chorus
  • Loading branch information
hannahilea authored May 9, 2024
2 parents c70cc9b + 3968c51 commit 0b8cdfa
Show file tree
Hide file tree
Showing 3 changed files with 327 additions and 0 deletions.
55 changes: 55 additions & 0 deletions projects/flock-chorus/index.html
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>
267 changes: 267 additions & 0 deletions projects/flock-chorus/sketch.js
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);
}
}
5 changes: 5 additions & 0 deletions projects/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,11 @@ <h2 id="virtual-gallery">Virtual Gallery</h2>
One-off creative coding projects, often small and interactive.
<ul>
<!-- Add new project here -->
<!-- <li><a href="./flock-chorus">
Flock chorus
</a>
<p>TODO-description</p>
</li> -->
<li><a href="https://github.com/hannahilea/cat-friend-chrome-extension">
Chrome extension: Cat Friend!
</a>
Expand Down

0 comments on commit 0b8cdfa

Please sign in to comment.