Audiovisuals with SC -Example14 - particle system

//Example14 - particle system
//click and drag in the window
(
s.latency= 0.05;
s.waitForBoot{

//--window setup
var width= 640, height= 480;
var w= Window("Example14 - particle system", Rect(99, 99, width, height), false);
var u= UserView(w, Rect(0, 0, width, height));

//--variables
var synths= (); //keep track of synths objects
var prev= Point(0, 0); //previous mouse position
var mouse= prev;
var parts= [];
var makePart= {|pnt| //pseudo class
(
\vel: Point(2.0.rand2, 10.rand.neg), //velocity vector
\pos: pnt,
\age: (~dead*0.5).linrand,
\mas: ~mass.rand,
\syn: Synth(\av)
)
};
SynthDef(\av, {|freq= 400, fm= 1, beat= 1, amp= 0, pan= 0, gate= 1|
var e= EnvGen.ar(Env.asr(0.01, 1, 0.02), gate, doneAction:2);
var z= SinOsc.ar(freq*SinOsc.ar(0, SinOsc.ar(fm, 0, 2pi), beat), 0, amp);
Out.ar(0, Pan2.ar(z, pan, e));
}, #[0.05, 0.05, 0.05, 0.05, 0.05]).send(s);
u.mouseMoveAction_{|v, x, y| mouse= Point(x, y)}; //update mouse position

//--interface
~dead= 500; //max age
~mass= 1.5; //max mass
~damp= 0.98; //general damping
~grav= Point(0, 0.98); //gravity vector

//--main loop
u.drawFunc= {
if(mouse!=prev, {
prev= mouse;
parts= parts.add(makePart.value(prev)); //new particle

});
parts= parts.select{|p| //remove old ones
if(p.age<~dead, {
true;
}, {
p.syn.release;
false;
});
};
parts.do{|p| //update each ball
var r;
p.vel= p.vel+(~grav*p.mas); //apply gravity force
p.vel= p.vel*~damp; //general damping
if(p.pos.x>width or:{p.pos.x<0}, { //bounce leftright bounds
p.vel= p.vel*Point(-1, 1);
});
if(p.pos.y>height or:{p.pos.y<0}, { //bounce topbottom bounds
p.vel= p.vel*Point(1, -1);
});
p.pos= p.pos+p.vel; //move the ball
p.age= p.age+1;
p.syn.set( //system maps to sound
\freq, p.pos.y.linexp(0, height, 2000, 200),
\amp, p.pos.y.linlin(0, height, p.mas, 0)*(1-(p.age/~dead))*0.1,
\fm, p.mas*p.pos.x,
\beat, p.vel.asComplex.magnitude, //ball velocity mapped to beat
\pan, p.pos.x.linlin(0, width, -1, 1)
);
r= 1-(p.age/~dead)*p.mas*10; //radius
Pen.fillColor= Color.grey(1-(p.age/~dead), 1);
Pen.fillOval(Rect.aboutPoint(p.pos, r, r));
};
};

//--window management
u.clearOnRefresh= true;
u.background= Color.black;
w.onClose= {parts.do{|p| p.syn.free}};
w.front;
u.animate= true;
CmdPeriod.doOnce({if(w.isClosed.not, {w.close})});
};
)

//change these while the program is running
~mass= 4;
~grav= Point(0.1, 0.1);
~grav= Point(-0.1, -0.1);
~damp= 0.9;

//close the window to stop or press cmd+.