Audiovisuals with SC -Example14 - particle system

//Example14 - particle system
//click and drag in the window
(
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
	~fps= 30;
	~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;
	Routine({while({w.isClosed.not}, {u.refresh; (1/~fps).wait})}).play(AppClock);
};
)
 
//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