Ring Ring by Kyle McDonald
visual id : 1167 author id : 838
Source files :
RingRing.pde
ForceDirectedGraph.pde
Musician.pde
Musicians.pde
SortedQueue.pde
Source files retrieved from openProcessing.org on Thursday 02 September 2010, 23:06:55 (CEST)
Code licensed under Creative Commons GNU GPL : http://creativecommons.org/licenses/GPL/2.0/
RingRing.pde
import promidi.*;
int midiPort = 0;
float musicianSize = 20;
int reactionAvg = 250; // global mean
int reactionVar = 100; // individual variance
int reactionMod = 50; // per clap variation
int fadeOut = 200;
int minVelocity = 30;
int maxVelocity = 100;
int[] notes = {
45, 47,
48, 50, 52, 53, 55, 57, 59,
60, 62, 64, 65, 67, 69, 71,
72, 74, 76, 77, 79, 81, 83,
84, 86, 88};
Musicians m;
PFont trebuchet;
MidiIO midiIO;
MidiOut midiOut;
void setup() {
size(640, 360);
colorMode(HSB, 1);
trebuchet = loadFont("TrebuchetMS-9.vlw");
textFont(trebuchet, 9);
m = new Musicians(26, new ForceDirectedGraph());
midiIO = MidiIO.getInstance(this);
midiOut = midiIO.openOutput(midiPort);
}
void draw() {
background(1);
m.update();
if(dragged != null) {
Vector3D xy = m.fdg.remap(mouseX, mouseY);
dragged.particle.moveTo(xy.x(), xy.y(), 0);
}
m.render();
}
Musician dragged, watcher;
void keyPressed() {
if(key == ' ')
m.clap();
else if(keyCode == TAB) {
m.panic();
setup();
}
else if(keyCode == BACKSPACE)
watcher = null;
else if(key >= 'a' && key <= 'z')
connect(m.get(str(char(key))));
}
void connect(Musician selected) {
if(watcher == null) {
watcher = selected;
watcher.highlight();
} else {
if(selected != null)
if(watcher == selected)
watcher.watch(null);
else
watcher.watch(selected);
watcher.highlight();
watcher = null;
}
}
void mousePressed() {
Vector3D xy = m.fdg.remap(mouseX, mouseY);
Musician cur = m.at(xy.x(), xy.y());
if(cur != null) {
if(mouseButton == RIGHT)
cur.forceClap();
else if(watcher != null || (keyPressed && keyCode == CONTROL))
connect(cur);
else
dragged = cur;
}
}
void mouseReleased() {
dragged = null;
}
ForceDirectedGraph.pde
// libraries available from http://www.cs.princeton.edu/~traer/
import traer.physics.*;
import traer.animation.*;
final float EDGE_LENGTH = 15;
final float EDGE_STRENGTH = 0.3;
final float SPACER_STRENGTH = 1000;
class ForceDirectedGraph extends ParticleSystem {
Smoother3D centroid;
ForceDirectedGraph() {
this(0.25, 0.8);
}
ForceDirectedGraph(float friction, float smoothness) {
super(0, friction);
centroid = new Smoother3D(smoothness);
reset();
}
void reset() {
clear();
centroid.setValue(0, 0, 1);
}
void position() {
translate( width/2 , height/2 );
if(centroid.z() != 1)
scale(centroid.z());
translate(-centroid.x(), -centroid.y());
}
Vector3D remap(float x, float y) {
return new Vector3D(
centroid.x() + (x - width/2) / centroid.z(),
centroid.y() + (y - height/2) / centroid.z(),
0);
}
Particle addVertex() {
Particle p = makeParticle();
for(int i = 0; i < numberOfParticles(); i++) {
Particle t = getParticle(i);
if(t != p)
makeAttraction(p, t, -SPACER_STRENGTH, 20);
}
return p;
}
Spring makeEdge(Particle a, Particle b) {
return makeSpring(a, b, EDGE_STRENGTH, EDGE_STRENGTH, EDGE_LENGTH);
}
void updateCentroid() {
float
xMax = Float.NEGATIVE_INFINITY,
xMin = Float.POSITIVE_INFINITY,
yMin = Float.POSITIVE_INFINITY,
yMax = Float.NEGATIVE_INFINITY;
for(int i = 0; i < numberOfParticles(); i++) {
Particle p = getParticle(i);
xMax = max(xMax, p.position().x());
xMin = min(xMin, p.position().x());
yMin = min(yMin, p.position().y());
yMax = max(yMax, p.position().y());
}
float deltaX = xMax - xMin;
float deltaY = yMax - yMin;
if (deltaY > deltaX)
centroid.setTarget(xMin + 0.5 * deltaX, yMin + 0.5 * deltaY, constrain(height/(deltaY+50), 0, 1));
else
centroid.setTarget(xMin + 0.5 * deltaX, yMin + 0.5 * deltaY, constrain(width/(deltaX+50), 0, 1));
}
void time(float tickLength) {
super.tick(tickLength);
if(numberOfParticles() > 1)
updateCentroid();
centroid.tick();
}
}
Musician.pde
class Musician {
SortedQueue heard;
Musician watching;
ForceDirectedGraph fdg;
Particle particle;
Spring watchingSpring;
boolean highlighted;
String name;
boolean clapping;
int lastClap;
int pitch;
int reactionTime;
Note lastNote;
Musician(String name, int pitch, ForceDirectedGraph fdg) {
this.name = name;
this.heard = new SortedQueue();
this.lastClap = -fadeOut;
this.highlighted = false;
this.pitch = pitch;
this.reactionTime = reactionAvg + (int) random(-reactionVar, reactionVar);
this.fdg = fdg;
this.particle = fdg.addVertex();
particle.makeFixed();
}
void watch(Musician watch) {
if(watchingSpring != null)
watchingSpring.turnOff();
if(watch != null) {
watchingSpring = fdg.makeEdge(particle, watch.particle);
particle.makeFree();
} else
particle.makeFixed();
watching = watch;
}
void clap() {
if(!heard.isEmpty() && heard.peekInt() < millis()) {
clapping = true;
lastClap = heard.pop();
sendClap();
}
else
clapping = false;
}
void forceClap() {
heard.push(millis());
}
void sendClap() {
stopLastClap();
lastNote = new Note(0, pitch, (int) random(minVelocity, maxVelocity));
midiOut.sendNoteOn(lastNote);
}
void stopLastClap() {
if(lastNote != null) {
midiOut.sendNoteOff(lastNote);
lastNote = null;
}
}
void watch() {
if(follower() && watching.clapping)
heard.push(millis() + reactionTime + (int) random(-reactionMod, reactionMod));
}
void highlight() {
highlighted = highlighted ? false : true;
}
void drawArrow() {
if(follower()) {
stroke(0);
fill(1);
Vector3D
axy = particle.position(),
bxy = watching.particle.position();
arrow(axy.x(), axy.y(), bxy.x(), bxy.y(), musicianSize);
}
}
void drawNode() {
fill(1);
float clapFade = millis() - lastClap;
if(clapFade < fadeOut) {
fill(0, 1 - clapFade/fadeOut, 1);
} else
stopLastClap();
if(highlighted)
fill(.15, 1, 1);
stroke(0);
Vector3D xy = particle.position();
if(follower())
ellipse(xy.x(), xy.y(), musicianSize, musicianSize);
else
rect(xy.x()-musicianSize/2, xy.y()-musicianSize/2, musicianSize, musicianSize);
fill(0);
text(name, xy.x() - 2, xy.y() + 3);
}
boolean follower() {
return watching != null;
}
}
void arrow(float x1, float y1, float x2, float y2, float base) {
float angle = atan2(y2 - y1, x2 - x1);
float r = base / 2;
float ax = x1 + cos(angle + HALF_PI) * r;
float ay = y1 + sin(angle + HALF_PI) * r;
float bx = x1 + cos(angle - HALF_PI) * r;
float by = y1 + sin(angle - HALF_PI) * r;
triangle(ax, ay, x2, y2, bx, by);
}
Musicians.pde
class Musicians extends Vector{
ForceDirectedGraph fdg;
Musician leader;
Musicians(int total, ForceDirectedGraph fdg) {
this.fdg = fdg;
leader = new Musician("a", notes[0], fdg);
add(leader);
for(int i = 1; i < total; i++) {
Musician cur = new Musician(str(char(97 + i)), notes[i], fdg);
Musician watched = getRandom();
cur.watch(watched);
Vector3D base = watched.particle.position();
cur.particle.moveTo(
base.x() + random(-1, 1),
base.y() + random(-1, 1),
0);
add(cur);
}
}
Musician get(int i) {
return (Musician) super.get(i);
}
Musician get(String name) {
for(int i = 0; i < size(); i++)
if(get(i).name.equals(name))
return get(i);
return null;
}
Musician getRandom() {
return get((int) random(size()));
}
void add(Musician m) {
super.add(m);
}
void update() {
fdg.time(1);
for(int i = 0; i < size(); i++) get(i).clap();
for(int i = 0; i < size(); i++) get(i).watch();
}
void render() {
fdg.position();
for(int i = 0; i < size(); i++) get(i).drawArrow();
for(int i = 0; i < size(); i++) get(i).drawNode();
}
Musician at(float x, float y) {
for(int i = 0 ; i < size(); i++) {
Vector3D xy = get(i).particle.position();
float d = dist(x, y, xy.x(), xy.y());
if(d < musicianSize/2)
return get(i);
}
return null;
}
void clap() {
leader.forceClap();
}
void panic() {
for(int i = 0; i < size(); i++)
get(i).stopLastClap();
}
void connect(String a, String b) {
if(a.equals(b))
get(a).watch(null);
else
get(a).watch(get(b));
}
}
SortedQueue.pde
class SortedQueue {
LinkedList list = new LinkedList();
void push(int v) {
if(isEmpty())
list.add(new Integer(v));
else
for(int i = size() - 1; i > -1; i--)
if(v > getInt(i)) {
list.add(i + 1, new Integer(v));
break;
}
}
int size() {
return list.size();
}
int pop() {
return ((Integer) list.remove()).intValue();
}
int getInt(int i) {
return ((Integer) list.get(i)).intValue();
}
int peekInt() {
return getInt(0);
}
boolean isEmpty() {
return list.isEmpty();
}
}