Audiovisuals with SC -Example19 - japan balls

//Example19 - japan balls
(
s.waitForBoot{
 
	//--window setup
	var width= 500, height= 500;
	var w= Window("Example19 - japan balls", Rect(99, 99, width, height), false);
	var u= UserView(w, Rect(0, 0, width, height));
 
	//--variables
	var theta= 0;		//a counter
	var num= 15;		//try also with more balls
	var syns= {
		SynthDef(\av, {|freq= 400, amp= 0, pan= 0|
			var z= SinOsc.ar(0, SinOsc.ar(freq, 0, 2pi), amp);
			Out.ar(0, Pan2.ar(z, pan));
		}, #[0.02, 0.05, 0.05]).play(s);
	}.dup(num);
	s.sync;
 
	//--interface
	~speed= 0.5;
	~radius= 10;
	~spread= 10;
	~distancex= 1;
	~distancey= 0.5;
	~offsetx= -0.1;
	~offsety= -0.2;
	~root= 100;
 
	//--main loop
	u.drawFunc= {
		Pen.fillColor= Color.red;		//set the fill color to red
		Pen.fillOval(u.bounds);			//draw a filled oval with the size of the whole window
		Pen.translate(width*0.5, height*0.5);	//offset all drawing to middle of the window
		Pen.fillColor= Color.grey(1, 0.5);	//set the fill color with 50% alpha
		num.do{|i|
			var a, x, y;
			a= i%num/num*2pi+theta;		//angle in radians
			x= sin(a+(i*~offsetx))*(~spread+(i*~distancex));
			y= cos(a+(i*~offsety))*(~spread+(i*~distancey));
			Pen.fillOval(Rect.aboutPoint(Point(x, y), ~radius, ~radius));
			if(((x.hypot(y)/1.42/~spread).min(0.5)/num).isNil, {"nil".postln});
			syns[i].set(				//update corresponding synth
				\freq, y.linexp(height.neg*0.5, height*0.5, ~root, 1000),
				\amp, (x.hypot(y)/1.42/~spread).min(0.5)/num,
				\pan, x.linlin(width.neg*0.5, width*0.5, -1, 1)
			);
		};
		theta= theta-~speed%2pi;		//our counter counts in radians
	};
 
	//--window management
	u.clearOnRefresh= true;
	u.background= Color.white;
	w.onClose= {syns.do{|x| x.free}};
	w.front;
	Routine({while({w.isClosed.not}, {u.refresh; (1/30).wait})}).play(AppClock);
};
)
 
(	//use midi input to control the program
MIDIIn.connect(device:1);							//edit to match your own setup
MIDIIn.control= {|src, chan, num, val|
	switch(num.postln,
		1, {~speed= val.linlin(0, 127, 0, 2pi)},		//ctrl number 1 sets the ~speed
		2, {~radius= val.linlin(0, 127, 1, 100)},
		3, {~spread= val.linlin(0, 127, 0, 300)},
		4, {~distancex= val.linlin(0, 127, -10, 10)},
		5, {~distancey= val.linlin(0, 127, -10, 10)},
		6, {~offsetx= val.linlin(0, 127, -pi*0.5, pi*0.5)},
		7, {~offsety= val.linlin(0, 127, -pi*0.5, pi*0.5)}
	);
};
MIDIIn.noteOn= {|src, chan, num, vel|
	~root= num.postln.midicps;						//note set root frequency
};
)
 
//change these while the program is running
~spread= 25;
~distancey= -2.2;
~distancey= -5.7;
~distancex= -6.7;
~spread= 150;
~radius= 50;
~offsetx= 0;
~offsety= -0.6;
~radius= 8;
~spread= 60;
~speed= pi*0.99;
~distancex= -10;
~distancey= 3;
~speed= 0.5;
~speed= -0.15;
~offsety= 0.1;
~offsetx= -0.5;
~offsety= 1;
~speed= 0.02;
~speed= 0.3;
~distancex= 0.12;
~distancey= -9.2;
~speed= 0.2;
~distancex= 5.5;
~offsetx= -0.1;
~offsety= -0.21;
~spread= 100;
~spread= 800;
~spread= 2;
~offsety= 0;
~distancey= 0;
~speed= 0.1
~speed= 22;
~root= 60;
~root= 120;
 
//close the window to stop