Audiovisuals with SC -Example15 - whitney balls

//Example15 - whitney balls
(
s.latency= 0.05;
s.waitForBoot{
       
        //--window setup
        var width= 500, height= 500;
        var w= Window("Example15 - whitney balls", Rect(99, 99, width, height), false);
        var u= UserView(w, Rect(0, 0, width, height));
       
        //--variables
        var theta= 0;
        var prev= ();                                           //keep previous theta
        SynthDef(\av, {|freq= 400, amp= 0, pan= 0|
                var e= EnvGen.kr(Env.perc, 1, amp, doneAction:2);
                var z= SinOsc.ar(freq, 0, e);
                Out.ar(0, Pan2.ar(z, pan));
        }).add;
        s.sync;
       
        //--interface
        ~speed= 0.005;
        ~radius= 3;
        ~num= 20;
        ~spread= 7;
        ~factor= 100;
        ~root= 100;
       
        //--main loop
        u.drawFunc= {
                Pen.rotate(2pi*0.75, width*0.5, height*0.5);
                Pen.translate(width*0.5, height*0.5);
                Pen.strokeColor= Color.red;
                Pen.line(Point(0, 0), Point(0, width*0.5));
                Pen.stroke;
                Pen.strokeColor= Color.grey(1, 0.7);
                ~num.do{|i|
                        var t= ~num-i*theta%2pi;                //theta for this ball
                        var x= sin(t)*(i+1*~spread);
                        var y= cos(t)*(i+1*~spread);
                        if(prev[i].notNil and:{t-prev[i]<0}, {  //detect border
                                Synth(\av, [\freq, i*~factor+~root, \amp, 0.1, \pan, 0.3.rand2]);
                                //Pen.strokeOval(Rect.aboutPoint(Point(x, y), ~radius*0.75, ~radius*0.75));
                        });
                        prev.put(i, t);
                        Pen.strokeOval(Rect.aboutPoint(Point(x, y), ~radius, ~radius));
                };
                theta= theta+~speed%2pi;
        };
       
        //--window management
        u.clearOnRefresh= true;
        u.background= Color.black;
        w.front;
        u.animate= true;
        CmdPeriod.doOnce({if(w.isClosed.not, {w.close})});
};
)

//change these while the program is running
~speed= 0.001;
~num= 100;
~num= 200;
~spread= 1;
~radius= 40;
~radius= 5;
~factor= 80;
~factor= 50;
~speed= 0.005;
~spread= 2;
~num= 30;
~root= 40;
~spread= 6;
~factor= 25;

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