2 * Not very flushed out, but kind of fun nonetheless.
4 class TimSpheres extends SCPattern {
5 private BasicParameter hueParameter = new BasicParameter("RAD", 1.0);
6 private BasicParameter periodParameter = new BasicParameter("PERIOD", 4000.0, 200.0, 10000.0);
7 private final SawLFO lfo = new SawLFO(0, 1, 10000);
8 private final SinLFO sinLfo = new SinLFO(0, 1, periodParameter);
9 private final float centerX, centerY, centerZ;
17 private final Sphere[] spheres;
19 public TimSpheres(GLucose glucose) {
21 addParameter(hueParameter);
22 addParameter(periodParameter);
23 addModulator(lfo).trigger();
24 addModulator(sinLfo).trigger();
25 centerX = (model.xMax + model.xMin) / 2;
26 centerY = (model.yMax + model.yMin) / 2;
27 centerZ = (model.zMax + model.zMin) / 2;
29 spheres = new Sphere[2];
31 spheres[0] = new Sphere();
32 spheres[0].x = model.xMin;
33 spheres[0].y = centerY;
34 spheres[0].z = centerZ;
36 spheres[0].radius = 50;
38 spheres[1] = new Sphere();
39 spheres[1].x = model.xMax;
40 spheres[1].y = centerY;
41 spheres[1].z = centerZ;
42 spheres[1].hue = 0.33;
43 spheres[1].radius = 50;
46 public void run(double deltaMs) {
47 // Access the core master hue via this method call
48 float hv = hueParameter.getValuef();
49 float lfoValue = lfo.getValuef();
50 float sinLfoValue = sinLfo.getValuef();
52 spheres[0].x = model.xMin + sinLfoValue * model.xMax;
53 spheres[1].x = model.xMax - sinLfoValue * model.xMax;
55 spheres[0].radius = 100 * hueParameter.getValuef();
56 spheres[1].radius = 100 * hueParameter.getValuef();
58 for (LXPoint p : model.points) {
61 color c = lx.hsb(0, 0, 0);
62 for (Sphere s : spheres) {
63 float d = sqrt(pow(p.x - s.x, 2) + pow(p.y - s.y, 2) + pow(p.z - s.z, 2));
64 float r = (s.radius); // * (sinLfoValue + 0.5));
65 value = max(0, 1 - max(0, d - r) / 10);
67 c = blendColor(c, lx.hsb(((s.hue + lfoValue) % 1) * 360, 100, min(1, value) * 100), ADD);
82 Vector2(float x, float y) {
87 float distanceTo(float x, float y) {
88 return sqrt(pow(x - this.x, 2) + pow(y - this.y, 2));
91 float distanceTo(Vector2 v) {
92 return distanceTo(v.x, v.y);
95 Vector2 plus(float x, float y) {
96 return new Vector2(this.x + x, this.y + y);
99 Vector2 plus(Vector2 v) {
100 return plus(v.x, v.y);
103 Vector2 minus(Vector2 v) {
104 return plus(-1 * v.x, -1 * v.y);
115 Vector3(float x, float y, float z) {
121 float distanceTo(float x, float y, float z) {
122 return sqrt(pow(x - this.x, 2) + pow(y - this.y, 2) + pow(z - this.z, 2));
125 float distanceTo(Vector3 v) {
126 return distanceTo(v.x, v.y, v.z);
129 float distanceTo(LXPoint p) {
130 return distanceTo(p.x, p.y, p.z);
133 void add(Vector3 other, float multiplier) {
134 this.add(other.x * multiplier, other.y * multiplier, other.z * multiplier);
137 void add(float x, float y, float z) {
143 void divide(float factor) {
151 private float a, b, c, d, e, f, g, h, i;
153 Rotation(float yaw, float pitch, float roll) {
154 float cosYaw = cos(yaw);
155 float sinYaw = sin(yaw);
156 float cosPitch = cos(pitch);
157 float sinPitch = sin(pitch);
158 float cosRoll = cos(roll);
159 float sinRoll = sin(roll);
161 a = cosYaw * cosPitch;
162 b = cosYaw * sinPitch * sinRoll - sinYaw * cosRoll;
163 c = cosYaw * sinPitch * cosRoll + sinYaw * sinRoll;
164 d = sinYaw * cosPitch;
165 e = sinYaw * sinPitch * sinRoll + cosYaw * cosRoll;
166 f = sinYaw * sinPitch * cosRoll - cosYaw * sinRoll;
168 h = cosPitch * sinRoll;
169 i = cosPitch * cosRoll;
172 Vector3 rotated(Vector3 v) {
180 float rotatedX(Vector3 v) {
181 return a * v.x + b * v.y + c * v.z;
184 float rotatedY(Vector3 v) {
185 return d * v.x + e * v.y + f * v.z;
188 float rotatedZ(Vector3 v) {
189 return g * v.x + h * v.y + i * v.z;
194 * Very literal rain effect. Not that great as-is but some tweaking could make it nice.
196 * - changing hue and direction of "rain" could make a nice fire effect
197 * - knobs to change frequency and size of rain drops
198 * - sync somehow to tempo but maybe less frequently than every beat?
200 class TimRaindrops extends SCPattern {
201 Vector3 randomVector3() {
203 random(model.xMax - model.xMin) + model.xMin,
204 random(model.yMax - model.yMin) + model.yMin,
205 random(model.zMax - model.zMin) + model.zMin);
216 this.p = new Vector3(
217 random(model.xMax - model.xMin) + model.xMin,
218 model.yMax + this.radius,
219 random(model.zMax - model.zMin) + model.zMin);
220 float velMagnitude = 120;
221 this.v = new Vector3(
225 this.hue = random(40) + 200;
228 // returns TRUE when this should die
229 boolean age(double ms) {
230 p.add(v, (float) (ms / 1000.0));
231 return this.p.y < (0 - this.radius);
235 private float leftoverMs = 0;
236 private float msPerRaindrop = 40;
237 private List<Raindrop> raindrops;
239 public TimRaindrops(GLucose glucose) {
241 raindrops = new LinkedList<Raindrop>();
244 public void run(double deltaMs) {
245 leftoverMs += deltaMs;
246 while (leftoverMs > msPerRaindrop) {
247 leftoverMs -= msPerRaindrop;
248 raindrops.add(new Raindrop());
251 for (LXPoint p : model.points) {
254 lx.hsb(210, 20, (float)Math.max(0, 1 - Math.pow((model.yMax - p.y) / 10, 2)) * 50),
255 lx.hsb(220, 60, (float)Math.max(0, 1 - Math.pow((p.y - model.yMin) / 10, 2)) * 100),
257 for (Raindrop raindrop : raindrops) {
258 if (p.x >= (raindrop.p.x - raindrop.radius) && p.x <= (raindrop.p.x + raindrop.radius) &&
259 p.y >= (raindrop.p.y - raindrop.radius) && p.y <= (raindrop.p.y + raindrop.radius)) {
260 float d = raindrop.p.distanceTo(p) / raindrop.radius;
261 // float value = (float)Math.max(0, 1 - Math.pow(Math.min(0, d - raindrop.radius) / 5, 2));
263 c = blendColor(c, lx.hsb(raindrop.hue, 80, (float)Math.pow(1 - d, 0.01) * 100), ADD);
270 Iterator<Raindrop> i = raindrops.iterator();
271 while (i.hasNext()) {
272 Raindrop raindrop = i.next();
273 boolean dead = raindrop.age(deltaMs);
282 class TimCubes extends SCPattern {
283 private BasicParameter rateParameter = new BasicParameter("RATE", 0.125);
284 private BasicParameter attackParameter = new BasicParameter("ATTK", 0.5);
285 private BasicParameter decayParameter = new BasicParameter("DECAY", 0.5);
286 private BasicParameter hueParameter = new BasicParameter("HUE", 0.5);
287 private BasicParameter hueVarianceParameter = new BasicParameter("H.V.", 0.25);
288 private BasicParameter saturationParameter = new BasicParameter("SAT", 0.5);
297 c = model.cubes.get(floor(random(model.cubes.size())));
299 boolean infiniteAttack = (attackParameter.getValuef() > 0.999);
300 hasPeaked = infiniteAttack;
301 value = (infiniteAttack ? 1 : 0);
304 // returns TRUE if this should die
305 boolean age(double ms) {
307 value = value + (float) (ms / 1000.0f * ((attackParameter.getValuef() + 0.01) * 5));
314 value = value - (float) (ms / 1000.0f * ((decayParameter.getValuef() + 0.01) * 10));
320 private float leftoverMs = 0;
321 private List<CubeFlash> flashes;
323 public TimCubes(GLucose glucose) {
325 addParameter(rateParameter);
326 addParameter(attackParameter);
327 addParameter(decayParameter);
328 addParameter(hueParameter);
329 addParameter(hueVarianceParameter);
330 addParameter(saturationParameter);
331 flashes = new LinkedList<CubeFlash>();
334 public void run(double deltaMs) {
335 leftoverMs += deltaMs;
336 float msPerFlash = 1000 / ((rateParameter.getValuef() + .01) * 100);
337 while (leftoverMs > msPerFlash) {
338 leftoverMs -= msPerFlash;
339 flashes.add(new CubeFlash());
342 for (LXPoint p : model.points) {
346 for (CubeFlash flash : flashes) {
347 float hue = (hueParameter.getValuef() + (hueVarianceParameter.getValuef() * flash.hue)) % 1.0;
348 color c = lx.hsb(hue * 360, saturationParameter.getValuef() * 100, (flash.value) * 100);
349 for (LXPoint p : flash.c.points) {
354 Iterator<CubeFlash> i = flashes.iterator();
355 while (i.hasNext()) {
356 CubeFlash flash = i.next();
357 boolean dead = flash.age(deltaMs);
366 * This one is the best but you need to play with all the knobs. It's synced to
367 * the tempo, with the WSpd knob letting you pick 4 discrete multipliers for
370 * Basically it's just 3 planes all rotating to the beat, but also rotated relative
371 * to one another. The intersection of the planes and the cubes over time makes
372 * for a nice abstract effect.
374 class TimPlanes extends SCPattern {
375 private BasicParameter wobbleParameter = new BasicParameter("Wob", 0.166);
376 private BasicParameter wobbleSpreadParameter = new BasicParameter("WSpr", 0.25);
377 private BasicParameter wobbleSpeedParameter = new BasicParameter("WSpd", 0.375);
378 private BasicParameter wobbleOffsetParameter = new BasicParameter("WOff", 0);
379 private BasicParameter derezParameter = new BasicParameter("Drez", 0.5);
380 private BasicParameter thicknessParameter = new BasicParameter("Thick", 0.4);
381 private BasicParameter ySpreadParameter = new BasicParameter("ySpr", 0.2);
382 private BasicParameter hueParameter = new BasicParameter("Hue", 0.75);
383 private BasicParameter hueSpreadParameter = new BasicParameter("HSpr", 0.68);
385 final float centerX, centerY, centerZ;
393 Plane(Vector3 center, Rotation rotation, float hue) {
394 this.center = center;
395 this.rotation = rotation;
400 TimPlanes(GLucose glucose) {
402 centerX = (model.xMin + model.xMax) / 2;
403 centerY = (model.yMin + model.yMax) / 2;
404 centerZ = (model.zMin + model.zMax) / 2;
406 addParameter(wobbleParameter);
407 addParameter(wobbleSpreadParameter);
408 addParameter(wobbleSpeedParameter);
409 // addParameter(wobbleOffsetParameter);
410 addParameter(derezParameter);
411 addParameter(thicknessParameter);
412 addParameter(ySpreadParameter);
413 addParameter(hueParameter);
414 addParameter(hueSpreadParameter);
419 float[] wobbleSpeeds = { 1.0/8, 1.0/4, 1.0/2, 1.0 };
421 public void run(double deltaMs) {
422 float ramp = (float)lx.tempo.ramp();
423 if (ramp < prevRamp) {
424 beat = (beat + 1) % 32;
428 float wobbleSpeed = wobbleSpeeds[floor(wobbleSpeedParameter.getValuef() * wobbleSpeeds.length * 0.9999)];
430 phase = (((beat + ramp) * wobbleSpeed + wobbleOffsetParameter.getValuef()) % 1) * 2 * PI;
432 float ySpread = ySpreadParameter.getValuef() * 50;
433 float wobble = wobbleParameter.getValuef() * PI;
434 float wobbleSpread = wobbleSpreadParameter.getValuef() * PI;
435 float hue = hueParameter.getValuef() * 360;
436 float hueSpread = (hueSpreadParameter.getValuef() - 0.5) * 360;
438 float saturation = 10 + 60.0 * pow(ramp, 0.25);
440 float derez = derezParameter.getValuef();
444 new Vector3(centerX, centerY + ySpread, centerZ),
445 new Rotation(wobble - wobbleSpread, phase, 0),
446 (hue + 360 - hueSpread) % 360),
448 new Vector3(centerX, centerY, centerZ),
449 new Rotation(wobble, phase, 0),
452 new Vector3(centerX, centerY - ySpread, centerZ),
453 new Rotation(wobble + wobbleSpread, phase, 0),
454 (hue + 360 + hueSpread) % 360)
457 float thickness = (thicknessParameter.getValuef() * 25 + 1);
459 Vector3 normalizedPoint = new Vector3();
461 for (LXPoint p : model.points) {
462 if (random(1.0) < derez) {
468 for (Plane plane : planes) {
469 normalizedPoint.x = p.x - plane.center.x;
470 normalizedPoint.y = p.y - plane.center.y;
471 normalizedPoint.z = p.z - plane.center.z;
473 float v = plane.rotation.rotatedY(normalizedPoint);
476 final color planeColor;
477 if (d <= thickness) {
478 planeColor = lx.hsb(plane.hue, saturation, 100);
479 } else if (d <= thickness * 2) {
480 float value = 1 - ((d - thickness) / thickness);
481 planeColor = lx.hsb(plane.hue, saturation, value * 100);
486 if (planeColor != 0) {
490 c = blendColor(c, planeColor, ADD);
501 * Two spinning wheels, basically XORed together, with a color palette that should
502 * be pretty easy to switch around. Timed to the beat; also introduces "clickiness"
503 * which makes the movement non-linear throughout a given beat, giving it a nice
504 * dance feel. I'm not 100% sure that it's actually going to look like it's _on_
505 * the beat, but that should be easy enough to adjust.
507 * It's particularly nice to turn down the clickiness and turn up derez during
508 * slow/beatless parts of the music and then revert them at the drop :) But maybe
509 * I shouldn't be listening to so much shitty dubstep while making these...
511 class TimPinwheels extends SCPattern {
512 private BasicParameter horizSpreadParameter = new BasicParameter("HSpr", 0.75);
513 private BasicParameter vertSpreadParameter = new BasicParameter("VSpr", 0.5);
514 private BasicParameter vertOffsetParameter = new BasicParameter("VOff", 1.0);
515 private BasicParameter zSlopeParameter = new BasicParameter("ZSlp", 0.6);
516 private BasicParameter sharpnessParameter = new BasicParameter("Shrp", 0.25);
517 private BasicParameter derezParameter = new BasicParameter("Drez", 0.25);
518 private BasicParameter clickinessParameter = new BasicParameter("Clic", 0.5);
519 private BasicParameter hueParameter = new BasicParameter("Hue", 0.667);
520 private BasicParameter hueSpreadParameter = new BasicParameter("HSpd", 0.667);
523 private final int NUM_BLADES = 12;
532 Pinwheel(float xCenter, float yCenter, int numBlades, float speed) {
533 this.center = new Vector2(xCenter, yCenter);
534 this.numBlades = numBlades;
538 void age(float numBeats) {
539 int numSteps = numBlades;
541 realPhase = (realPhase + numBeats / numSteps) % 2.0;
543 float phaseStep = floor(realPhase * numSteps);
544 float phaseRamp = (realPhase * numSteps) % 1.0;
545 phase = (phaseStep + pow(phaseRamp, (clickinessParameter.getValuef() * 10) + 1)) / (numSteps * 2);
546 // phase = (phase + deltaMs / 1000.0 * speed) % 1.0;
549 boolean isOnBlade(float x, float y) {
553 float normalizedAngle = (atan2(x, y) / (2 * PI) + 1 + phase) % 1;
554 float v = (normalizedAngle * 4 * numBlades);
555 int blade_num = floor((v + 2) / 4);
556 return (blade_num % 2) == 0;
560 private final List<Pinwheel> pinwheels;
561 private final float[] values;
563 TimPinwheels(GLucose glucose) {
566 addParameter(horizSpreadParameter);
567 // addParameter(vertSpreadParameter);
568 addParameter(vertOffsetParameter);
569 addParameter(zSlopeParameter);
570 addParameter(sharpnessParameter);
571 addParameter(derezParameter);
572 addParameter(clickinessParameter);
573 addParameter(hueParameter);
574 addParameter(hueSpreadParameter);
576 pinwheels = new ArrayList();
577 pinwheels.add(new Pinwheel(0, 0, NUM_BLADES, 0.1));
578 pinwheels.add(new Pinwheel(0, 0, NUM_BLADES, -0.1));
580 this.updateHorizSpread();
581 this.updateVertPositions();
583 values = new float[model.points.size()];
586 public void onParameterChanged(LXParameter parameter) {
587 if (parameter == horizSpreadParameter) {
589 } else if (parameter == vertSpreadParameter || parameter == vertOffsetParameter) {
590 updateVertPositions();
594 private void updateHorizSpread() {
595 float xDist = model.xMax - model.xMin;
596 float xCenter = (model.xMin + model.xMax) / 2;
598 float spread = horizSpreadParameter.getValuef() - 0.5;
599 pinwheels.get(0).center.x = xCenter - xDist * spread;
600 pinwheels.get(1).center.x = xCenter + xDist * spread;
603 private void updateVertPositions() {
604 float yDist = model.yMax - model.yMin;
605 float yCenter = model.yMin + yDist * vertOffsetParameter.getValuef();
607 float spread = vertSpreadParameter.getValuef() - 0.5;
608 pinwheels.get(0).center.y = yCenter - yDist * spread;
609 pinwheels.get(1).center.y = yCenter + yDist * spread;
612 private float prevRamp = 0;
614 public void run(double deltaMs) {
615 float ramp = lx.tempo.rampf();
616 float numBeats = (1 + ramp - prevRamp) % 1;
619 float hue = hueParameter.getValuef() * 360;
623 float hueSpread = (hueSpreadParameter.getValuef() - 0.5) * 360;
625 float fadeAmount = (float) (deltaMs / 1000.0) * pow(sharpnessParameter.getValuef() * 10, 1);
627 for (Pinwheel pw : pinwheels) {
631 float derez = derezParameter.getValuef();
633 float zSlope = (zSlopeParameter.getValuef() - 0.5) * 2;
636 for (LXPoint p : model.points) {
640 for (Pinwheel pw : pinwheels) {
641 value += (pw.isOnBlade(p.x, p.y - p.z * zSlope) ? 1 : 0);
645 // colors[p.index] = lx.hsb(120, 0, 100);
647 values[i] = max(0, values[i] - fadeAmount);
648 //color c = colors[p.index];
649 //colors[p.index] = lx.hsb(max(0, lx.h(c) - 10), min(100, lx.s(c) + 10), lx.b(c) - 5 );
652 if (random(1.0) >= derez) {
654 colors[p.index] = lx.hsb((360 + hue + pow(v, 2) * hueSpread) % 360, 30 + pow(1 - v, 0.25) * 60, v * 100);
661 * This tries to figure out neighboring pixels from one cube to another to
662 * let you have a bunch of moving points tracing all over the structure.
663 * Adds a couple seconds of startup time to do the calculation, and in the
664 * end just comes out looking a lot like a screensaver. Probably not worth
665 * it but there may be useful code here.
667 class TimTrace extends SCPattern {
668 private Map<LXPoint, List<LXPoint>> pointToNeighbors;
669 private Map<LXPoint, Strip> pointToStrip;
670 // private final Map<Strip, List<Strip>> stripToNearbyStrips;
675 LXPoint currentPoint;
677 private Strip currentStrip;
678 private int currentStripIndex;
679 private int direction; // +1 or -1
681 MovingPoint(LXPoint p) {
682 this.setPointOnNewStrip(p);
686 private void setPointOnNewStrip(LXPoint p) {
687 this.currentPoint = p;
688 this.currentStrip = pointToStrip.get(p);
689 for (int i = 0; i < this.currentStrip.points.size(); ++i) {
690 if (this.currentStrip.points.get(i) == p) {
691 this.currentStripIndex = i;
695 if (this.currentStripIndex == 0) {
696 // we are at the beginning of the strip; go forwards
698 } else if (this.currentStripIndex == this.currentStrip.points.size()) {
699 // we are at the end of the strip; go backwards
702 // we are in the middle of a strip; randomly go one way or another
703 this.direction = ((random(1.0) < 0.5) ? -1 : 1);
708 List<LXPoint> neighborsOnOtherStrips = pointToNeighbors.get(this.currentPoint);
710 LXPoint nextPointOnCurrentStrip = null;
711 this.currentStripIndex += this.direction;
712 if (this.currentStripIndex >= 0 && this.currentStripIndex < this.currentStrip.points.size()) {
713 nextPointOnCurrentStrip = this.currentStrip.points.get(this.currentStripIndex);
716 // pick which option to take; if we can keep going on the current strip then
717 // add that as another option
718 int option = floor(random(neighborsOnOtherStrips.size() + (nextPointOnCurrentStrip == null ? 0 : 100)));
720 if (option < neighborsOnOtherStrips.size()) {
721 this.setPointOnNewStrip(neighborsOnOtherStrips.get(option));
723 this.currentPoint = nextPointOnCurrentStrip;
728 List<MovingPoint> movingPoints;
730 TimTrace(GLucose glucose) {
735 pointToNeighbors = this.buildPointToNeighborsMap();
736 pointToStrip = this.buildPointToStripMap();
738 int numMovingPoints = 1000;
739 movingPoints = new ArrayList();
740 for (int i = 0; i < numMovingPoints; ++i) {
741 movingPoints.add(new MovingPoint(model.points.get(floor(random(model.points.size())))));
746 private Map<Strip, List<Strip>> buildStripToNearbyStripsMap() {
747 Map<Strip, Vector3> stripToCenter = new HashMap();
748 for (Strip s : model.strips) {
749 Vector3 v = new Vector3();
750 for (LXPoint p : s.points) {
751 v.add(p.x, p.y, p.z);
753 v.divide(s.points.size());
754 stripToCenter.put(s, v);
757 Map<Strip, List<Strip>> stripToNeighbors = new HashMap();
758 for (Strip s : model.strips) {
759 List<Strip> neighbors = new ArrayList();
760 Vector3 sCenter = stripToCenter.get(s);
761 for (Strip potentialNeighbor : model.strips) {
762 if (s != potentialNeighbor) {
763 float distance = sCenter.distanceTo(stripToCenter.get(potentialNeighbor));
765 neighbors.add(potentialNeighbor);
769 stripToNeighbors.put(s, neighbors);
772 return stripToNeighbors;
775 private Map<LXPoint, List<LXPoint>> buildPointToNeighborsMap() {
776 Map<LXPoint, List<LXPoint>> m = new HashMap();
777 Map<Strip, List<Strip>> stripToNearbyStrips = this.buildStripToNearbyStripsMap();
779 for (Strip s : model.strips) {
780 List<Strip> nearbyStrips = stripToNearbyStrips.get(s);
782 for (LXPoint p : s.points) {
783 Vector3 v = new Vector3(p.x, p.y, p.z);
785 List<LXPoint> neighbors = new ArrayList();
787 for (Strip nearbyStrip : nearbyStrips) {
788 LXPoint closestPoint = null;
789 float closestPointDistance = 100000;
791 for (LXPoint nsp : nearbyStrip.points) {
792 float distance = v.distanceTo(nsp.x, nsp.y, nsp.z);
793 if (closestPoint == null || distance < closestPointDistance) {
795 closestPointDistance = distance;
799 if (closestPointDistance < 15) {
800 neighbors.add(closestPoint);
811 private Map<LXPoint, Strip> buildPointToStripMap() {
812 Map<LXPoint, Strip> m = new HashMap();
813 for (Strip s : model.strips) {
814 for (LXPoint p : s.points) {
821 public void run(double deltaMs) {
822 for (LXPoint p : model.points) {
823 color c = colors[p.index];
824 colors[p.index] = lx.hsb(lx.h(c), lx.s(c), lx.b(c) - 3);
827 for (MovingPoint mp : movingPoints) {
829 colors[mp.currentPoint.index] = blendColor(colors[mp.currentPoint.index], lx.hsb(mp.hue, 10, 100), ADD);
834 class TimMetronome extends SCPattern {
835 private BasicParameter clickyParameter = new BasicParameter("CLICK", 0, 0, 10.0);
836 private BasicParameter derezParameter = new BasicParameter("DREZ", 0.5, 0, 1.0);
837 private BasicParameter driftParameter = new BasicParameter("DRIFT", 0, 0, 1.0);
838 private BasicParameter fadeParameter = new BasicParameter("FADE", 0.05, 0, 0.2);
839 private float modelWidth;
841 private float prevTempoRamp;
842 private LXProjection projection;
843 private float[] values;
844 private float[] hues;
846 TimMetronome(GLucose glucose) {
848 addParameter(clickyParameter);
849 addParameter(derezParameter);
850 addParameter(driftParameter);
851 addParameter(fadeParameter);
852 modelWidth = model.xMax - model.xMin;
853 projection = new LXProjection(model);
856 values = new float[model.points.size()];
857 hues = new float[model.points.size()];
860 public void run(double deltaMs) {
861 float tempoRamp = lx.tempo.rampf();
862 if (tempoRamp < prevTempoRamp) {
863 beatNum = (beatNum + 1) % 1000;
865 prevTempoRamp = tempoRamp;
867 float phase = beatNum + pow(tempoRamp, 1.0 + clickyParameter.getValuef());
870 projection.translateCenter(model.xMin, model.yMin, model.cz);
871 projection.rotate(phase * 0.5 * PI, 0, 0, 1);
873 projection.translate(driftParameter.getValuef() * tempoRamp * modelWidth * 0.5, 0, 0);
875 float derezCutoff = derezParameter.getValuef();
877 float fadeMultiplier = (1.0 - fadeParameter.getValuef());
879 float armRadius = modelWidth * 0.1;
880 for (LXVector p : projection) {
881 boolean onArm = false;
882 if (abs(p.x) < armRadius) {
883 onArm = (p.y > 0) || (sqrt(pow(p.x, 2) + pow(p.y, 2)) < armRadius);
886 values[p.index] = 1.0;
887 hues[p.index] = (floor(phase / 4) * 90) % 360;
889 values[p.index] *= fadeMultiplier;
892 float saturation = pow(1 - values[p.index], 0.5) * 0.7 + 0.3;
893 float brightness = values[p.index];
895 if (random(1.0) > derezCutoff) {
896 colors[p.index] = lx.hsb(hues[p.index], saturation * 100, brightness * 100);