‹ Audiovisuals with SC - Example13 - ballsAudiovisuals with SC - Example15 - whitney balls ›

Audiovisuals with SC - Example14 - particle system

See /f0blog/audiovisuals-with-sc/

//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]).add;
  s.sync;
  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;
  CmdPeriod.doOnce({w.close});
  Routine({
    var nextTime;
    while({w.isClosed.not}, {
      nextTime= Main.elapsedTime+(1/60);
      u.refresh;
      (nextTime-Main.elapsedTime).max(0.001).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 or press cmd+.