FastLED 3.9.7
Loading...
Searching...
No Matches
Chromancer.ino
1/*
2 Original Source: https://github.com/ZackFreedman/Chromance
3 GaryWoo's Video: https://www.youtube.com/watch?v=-nSCtxa2Kp0
4 GaryWoo's LedMap: https://gist.github.com/Garywoo/b6cd1ea90cb5e17cc60b01ae68a2b770
5 GaryWoo's presets: https://gist.github.com/Garywoo/82fa67c6e1f9529dc16a01dd97d05d58
6 Chromance wall hexagon source (emotion controlled w/ EmotiBit)
7 Partially cribbed from the DotStar example
8 I smooshed in the ESP32 BasicOTA sketch, too
9
10 (C) Voidstar Lab 2021
11*/
12
13#if defined(__AVR__) || defined(ARDUINO_ARCH_AVR) || defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_RP2040) || defined(ARDUINO_ARCH_RP2350) || defined(ARDUINO_ARCH_RP2040) || defined(ARDUINO_TEENSYLC)
14// Avr is not powerful enough.
15// Other platforms have weird issues. Will revisit this later.
16void setup() {}
17void loop() {}
18#else
19
20#include "mapping.h"
21#include "net.h"
22#include "ripple.h"
23#include <FastLED.h>
24#include "detail.h"
25#include "fl/screenmap.h"
26#include "fl/math_macros.h"
27#include "fl/json.h"
28#include "fl/ui.h"
29#include "fl/map.h"
30
31#include "screenmap.json.h"
32#include "fl/str.h"
33
34using namespace fl;
35
36enum {
37 BlackStrip = 0,
38 GreenStrip = 1,
39 RedStrip = 2,
40 BlueStrip = 3,
41};
42
43
44// Strips are different lengths because I am a dumb
45
46constexpr int lengths[] = {
47 154, // Black strip
48 168, // Green strip
49 84, // Red strip
50 154 // Blue strip
51};
52
53
54
55
56
57
58
59// non emscripten uses separate arrays for each strip. Eventually emscripten
60// should support this as well but right now we don't
61CRGB leds0[lengths[BlackStrip]] = {};
62CRGB leds1[lengths[GreenStrip]] = {};
63CRGB leds2[lengths[RedStrip]] = {}; // Red
64CRGB leds3[lengths[BlueStrip]] = {};
65CRGB *leds[] = {leds0, leds1, leds2, leds3};
66
67
68byte ledColors[40][14][3]; // LED buffer - each ripple writes to this, then we
69 // write this to the strips
70//float decay = 0.97; // Multiply all LED's by this amount each tick to create
71 // fancy fading tails
72
73Slider sliderDecay("decay", .97f, .8, 1.0, .01);
74
75// These ripples are endlessly reused so we don't need to do any memory
76// management
77#define numberOfRipples 30
78Ripple ripples[numberOfRipples] = {
79 Ripple(0), Ripple(1), Ripple(2), Ripple(3), Ripple(4), Ripple(5),
80 Ripple(6), Ripple(7), Ripple(8), Ripple(9), Ripple(10), Ripple(11),
81 Ripple(12), Ripple(13), Ripple(14), Ripple(15), Ripple(16), Ripple(17),
82 Ripple(18), Ripple(19), Ripple(20), Ripple(21), Ripple(22), Ripple(23),
83 Ripple(24), Ripple(25), Ripple(26), Ripple(27), Ripple(28), Ripple(29),
84};
85
86// Biometric detection and interpretation
87// IR (heartbeat) is used to fire outward ripples
88float lastIrReading; // When our heart pumps, reflected IR drops sharply
89float highestIrReading; // These vars let us detect this drop
90unsigned long
91 lastHeartbeat; // Track last heartbeat so we can detect noise/disconnections
92#define heartbeatLockout \
93 500 // Heartbeats that happen within this many milliseconds are ignored
94#define heartbeatDelta 300 // Drop in reflected IR that constitutes a heartbeat
95
96// Heartbeat color ripples are proportional to skin temperature
97#define lowTemperature 33.0 // Resting temperature
98#define highTemperature 37.0 // Really fired up
99float lastKnownTemperature =
100 (lowTemperature + highTemperature) /
101 2.0; // Carries skin temperature from temperature callback to IR callback
102
103// EDA code was too unreliable and was cut.
104// TODO: Rebuild EDA code
105
106// Gyroscope is used to reject data if you're moving too much
107#define gyroAlpha 0.9 // Exponential smoothing constant
108#define gyroThreshold \
109 300 // Minimum angular velocity total (X+Y+Z) that disqualifies readings
110float gyroX, gyroY, gyroZ;
111
112// If you don't have an EmotiBit or don't feel like wearing it, that's OK
113// We'll fire automatic pulses
114#define randomPulsesEnabled true // Fire random rainbow pulses from random nodes
115#define cubePulsesEnabled true // Draw cubes at random nodes
116Checkbox starburstPulsesEnabled("Starburst Pulses", true);
117Checkbox simulatedBiometricsEnabled("Simulated Biometrics", true);
118
119#define autoPulseTimeout \
120 5000 // If no heartbeat is received in this many ms, begin firing
121 // random/simulated pulses
122#define randomPulseTime 2000 // Fire a random pulse every (this many) ms
123unsigned long lastRandomPulse;
124byte lastAutoPulseNode = 255;
125
126byte numberOfAutoPulseTypes =
127 randomPulsesEnabled + cubePulsesEnabled + int(starburstPulsesEnabled);
128byte currentAutoPulseType = 255;
129#define autoPulseChangeTime 30000
130unsigned long lastAutoPulseChange;
131
132#define simulatedHeartbeatBaseTime \
133 600 // Fire a simulated heartbeat pulse after at least this many ms
134#define simulatedHeartbeatVariance \
135 200 // Add random jitter to simulated heartbeat
136#define simulatedEdaBaseTime 1000 // Same, but for inward EDA pulses
137#define simulatedEdaVariance 10000
138unsigned long nextSimulatedHeartbeat;
139unsigned long nextSimulatedEda;
140
141// Helper function to check if a node is on the border
142bool isNodeOnBorder(byte node) {
143 for (int i = 0; i < numberOfBorderNodes; i++) {
144 if (node == borderNodes[i]) {
145 return true;
146 }
147 }
148 return false;
149}
150
151Checkbox allWhite("All White", false);
152
153Button simulatedHeartbeat("Simulated Heartbeat");
154Button triggerStarburst("Trigger Starburst");
155Button triggerRainbowCube("Rainbow Cube");
156Button triggerBorderWave("Border Wave");
157Button triggerSpiral("Spiral Wave");
158bool wasHeartbeatClicked = false;
159bool wasStarburstClicked = false;
160bool wasRainbowCubeClicked = false;
161bool wasBorderWaveClicked = false;
162bool wasSpiralClicked = false;
163
164void setup() {
165 Serial.begin(115200);
166
167 Serial.println("*** LET'S GOOOOO ***");
168
169 Serial.println("JSON SCREENMAP");
170 Serial.println(JSON_SCREEN_MAP);
171
173 ScreenMap::ParseJson(JSON_SCREEN_MAP, &segmentMaps);
174
175 printf("Parsed %d segment maps\n", int(segmentMaps.size()));
176 for (auto kv : segmentMaps) {
177 Serial.print(kv.first.c_str());
178 Serial.print(" ");
179 Serial.println(kv.second.getLength());
180 }
181
182
183 // ScreenMap screenmaps[4];
184 ScreenMap red, black, green, blue;
185 bool ok = true;
186 ok = segmentMaps.get("red_segment", &red) && ok;
187 ok = segmentMaps.get("back_segment", &black) && ok;
188 ok = segmentMaps.get("green_segment", &green) && ok;
189 ok = segmentMaps.get("blue_segment", &blue) && ok;
190 if (!ok) {
191 Serial.println("Failed to get all segment maps");
192 return;
193 }
194
195
196 CRGB* red_leds = leds[RedStrip];
197 CRGB* black_leds = leds[BlackStrip];
198 CRGB* green_leds = leds[GreenStrip];
199 CRGB* blue_leds = leds[BlueStrip];
200
201 FastLED.addLeds<WS2812, 2>(black_leds, lengths[BlackStrip]).setScreenMap(black);
202 FastLED.addLeds<WS2812, 3>(green_leds, lengths[GreenStrip]).setScreenMap(green);
203 FastLED.addLeds<WS2812, 1>(red_leds, lengths[RedStrip]).setScreenMap(red);
204 FastLED.addLeds<WS2812, 4>(blue_leds, lengths[BlueStrip]).setScreenMap(blue);
205
206 FastLED.show();
207 net_init();
208}
209
210
211void loop() {
212 unsigned long benchmark = millis();
213 net_loop();
214
215
216
217 // Fade all dots to create trails
218 for (int strip = 0; strip < 40; strip++) {
219 for (int led = 0; led < 14; led++) {
220 for (int i = 0; i < 3; i++) {
221 ledColors[strip][led][i] *= sliderDecay.value();
222 }
223 }
224 }
225
226 for (int i = 0; i < numberOfRipples; i++) {
227 ripples[i].advance(ledColors);
228 }
229
230 for (int segment = 0; segment < 40; segment++) {
231 for (int fromBottom = 0; fromBottom < 14; fromBottom++) {
232 int strip = ledAssignments[segment][0];
233 int led = round(fmap(fromBottom, 0, 13, ledAssignments[segment][2],
234 ledAssignments[segment][1]));
235 leds[strip][led] = CRGB(ledColors[segment][fromBottom][0],
236 ledColors[segment][fromBottom][1],
237 ledColors[segment][fromBottom][2]);
238 }
239 }
240
241 if (allWhite) {
242 // for all strips
243 for (int i = 0; i < 4; i++) {
244 for (int j = 0; j < lengths[i]; j++) {
245 leds[i][j] = CRGB::White;
246 }
247 }
248 }
249
250 FastLED.show();
251
252
253 // Check if buttons were clicked
254 wasHeartbeatClicked = bool(simulatedHeartbeat);
255 wasStarburstClicked = bool(triggerStarburst);
256 wasRainbowCubeClicked = bool(triggerRainbowCube);
257 wasBorderWaveClicked = bool(triggerBorderWave);
258 wasSpiralClicked = bool(triggerSpiral);
259
260 if (wasSpiralClicked) {
261 // Trigger spiral wave effect from center
262 unsigned int baseColor = random(0xFFFF);
263 byte centerNode = 15; // Center node
264
265 // Create 6 ripples in a spiral pattern
266 for (int i = 0; i < 6; i++) {
267 if (nodeConnections[centerNode][i] >= 0) {
268 for (int j = 0; j < numberOfRipples; j++) {
269 if (ripples[j].state == dead) {
270 ripples[j].start(
271 centerNode, i,
272 Adafruit_DotStar_ColorHSV(
273 baseColor + (0xFFFF / 6) * i, 255, 255),
274 0.3 + (i * 0.1), // Varying speeds creates spiral effect
275 2000,
276 i % 2 ? alwaysTurnsLeft : alwaysTurnsRight); // Alternating turn directions
277 break;
278 }
279 }
280 }
281 }
282 lastHeartbeat = millis();
283 }
284
285 if (wasBorderWaveClicked) {
286 // Trigger immediate border wave effect
287 unsigned int baseColor = random(0xFFFF);
288
289 // Start ripples from each border node in sequence
290 for (int i = 0; i < numberOfBorderNodes; i++) {
291 byte node = borderNodes[i];
292 // Find an inward direction
293 for (int dir = 0; dir < 6; dir++) {
294 if (nodeConnections[node][dir] >= 0 &&
295 !isNodeOnBorder(nodeConnections[node][dir])) {
296 for (int j = 0; j < numberOfRipples; j++) {
297 if (ripples[j].state == dead) {
298 ripples[j].start(
299 node, dir,
300 Adafruit_DotStar_ColorHSV(
301 baseColor + (0xFFFF / numberOfBorderNodes) * i,
302 255, 255),
303 .4, 2000, 0);
304 break;
305 }
306 }
307 break;
308 }
309 }
310 }
311 lastHeartbeat = millis();
312 }
313
314 if (wasRainbowCubeClicked) {
315 // Trigger immediate rainbow cube effect
316 int node = cubeNodes[random(numberOfCubeNodes)];
317 unsigned int baseColor = random(0xFFFF);
318 byte behavior = random(2) ? alwaysTurnsLeft : alwaysTurnsRight;
319
320 for (int i = 0; i < 6; i++) {
321 if (nodeConnections[node][i] >= 0) {
322 for (int j = 0; j < numberOfRipples; j++) {
323 if (ripples[j].state == dead) {
324 ripples[j].start(
325 node, i,
326 Adafruit_DotStar_ColorHSV(
327 baseColor + (0xFFFF / 6) * i, 255, 255),
328 .5, 2000, behavior);
329 break;
330 }
331 }
332 }
333 }
334 lastHeartbeat = millis();
335 }
336
337 if (wasStarburstClicked) {
338 // Trigger immediate starburst effect
339 unsigned int baseColor = random(0xFFFF);
340 byte behavior = random(2) ? alwaysTurnsLeft : alwaysTurnsRight;
341
342 for (int i = 0; i < 6; i++) {
343 for (int j = 0; j < numberOfRipples; j++) {
344 if (ripples[j].state == dead) {
345 ripples[j].start(
346 starburstNode, i,
347 Adafruit_DotStar_ColorHSV(
348 baseColor + (0xFFFF / 6) * i, 255, 255),
349 .65, 1500, behavior);
350 break;
351 }
352 }
353 }
354 lastHeartbeat = millis();
355 }
356
357 if (wasHeartbeatClicked) {
358 // Trigger immediate heartbeat effect
359 for (int i = 0; i < 6; i++) {
360 for (int j = 0; j < numberOfRipples; j++) {
361 if (ripples[j].state == dead) {
362 ripples[j].start(15, i, 0xEE1111,
363 float(random(100)) / 100.0 * .1 + .4, 1000, 0);
364 break;
365 }
366 }
367 }
368 lastHeartbeat = millis();
369 }
370
371 if (millis() - lastHeartbeat >= autoPulseTimeout) {
372 // When biometric data is unavailable, visualize at random
373 if (numberOfAutoPulseTypes &&
374 millis() - lastRandomPulse >= randomPulseTime) {
375 unsigned int baseColor = random(0xFFFF);
376
377 if (currentAutoPulseType == 255 ||
378 (numberOfAutoPulseTypes > 1 &&
379 millis() - lastAutoPulseChange >= autoPulseChangeTime)) {
380 byte possiblePulse = 255;
381 while (true) {
382 possiblePulse = random(3);
383
384 if (possiblePulse == currentAutoPulseType)
385 continue;
386
387 switch (possiblePulse) {
388 case 0:
389 if (!randomPulsesEnabled)
390 continue;
391 break;
392
393 case 1:
394 if (!cubePulsesEnabled)
395 continue;
396 break;
397
398 case 2:
399 if (!starburstPulsesEnabled)
400 continue;
401 break;
402
403 default:
404 continue;
405 }
406
407 currentAutoPulseType = possiblePulse;
408 lastAutoPulseChange = millis();
409 break;
410 }
411 }
412
413 switch (currentAutoPulseType) {
414 case 0: {
415 int node = 0;
416 bool foundStartingNode = false;
417 while (!foundStartingNode) {
418 node = random(25);
419 foundStartingNode = true;
420 for (int i = 0; i < numberOfBorderNodes; i++) {
421 // Don't fire a pulse on one of the outer nodes - it
422 // looks boring
423 if (node == borderNodes[i])
424 foundStartingNode = false;
425 }
426
427 if (node == lastAutoPulseNode)
428 foundStartingNode = false;
429 }
430
431 lastAutoPulseNode = node;
432
433 for (int i = 0; i < 6; i++) {
434 if (nodeConnections[node][i] >= 0) {
435 for (int j = 0; j < numberOfRipples; j++) {
436 if (ripples[j].state == dead) {
437 ripples[j].start(
438 node, i,
439 // strip0.ColorHSV(baseColor
440 // + (0xFFFF / 6) * i,
441 // 255, 255),
442 Adafruit_DotStar_ColorHSV(baseColor, 255,
443 255),
444 float(random(100)) / 100.0 * .2 + .5, 3000,
445 1);
446
447 break;
448 }
449 }
450 }
451 }
452 break;
453 }
454
455 case 1: {
456 int node = cubeNodes[random(numberOfCubeNodes)];
457
458 while (node == lastAutoPulseNode)
459 node = cubeNodes[random(numberOfCubeNodes)];
460
461 lastAutoPulseNode = node;
462
463 byte behavior = random(2) ? alwaysTurnsLeft : alwaysTurnsRight;
464
465 for (int i = 0; i < 6; i++) {
466 if (nodeConnections[node][i] >= 0) {
467 for (int j = 0; j < numberOfRipples; j++) {
468 if (ripples[j].state == dead) {
469 ripples[j].start(
470 node, i,
471 // strip0.ColorHSV(baseColor
472 // + (0xFFFF / 6) * i,
473 // 255, 255),
474 Adafruit_DotStar_ColorHSV(baseColor, 255,
475 255),
476 .5, 2000, behavior);
477
478 break;
479 }
480 }
481 }
482 }
483 break;
484 }
485
486 case 2: {
487 byte behavior = random(2) ? alwaysTurnsLeft : alwaysTurnsRight;
488
489 lastAutoPulseNode = starburstNode;
490
491 for (int i = 0; i < 6; i++) {
492 for (int j = 0; j < numberOfRipples; j++) {
493 if (ripples[j].state == dead) {
494 ripples[j].start(
495 starburstNode, i,
496 Adafruit_DotStar_ColorHSV(
497 baseColor + (0xFFFF / 6) * i, 255, 255),
498 .65, 1500, behavior);
499
500 break;
501 }
502 }
503 }
504 break;
505 }
506
507 default:
508 break;
509 }
510 lastRandomPulse = millis();
511 }
512
513 if (simulatedBiometricsEnabled) {
514 // Simulated heartbeat
515 if (millis() >= nextSimulatedHeartbeat) {
516 for (int i = 0; i < 6; i++) {
517 for (int j = 0; j < numberOfRipples; j++) {
518 if (ripples[j].state == dead) {
519 ripples[j].start(
520 15, i, 0xEE1111,
521 float(random(100)) / 100.0 * .1 + .4, 1000, 0);
522
523 break;
524 }
525 }
526 }
527
528 nextSimulatedHeartbeat = millis() + simulatedHeartbeatBaseTime +
529 random(simulatedHeartbeatVariance);
530 }
531
532 // Simulated EDA ripples
533 if (millis() >= nextSimulatedEda) {
534 for (int i = 0; i < 10; i++) {
535 for (int j = 0; j < numberOfRipples; j++) {
536 if (ripples[j].state == dead) {
537 byte targetNode =
538 borderNodes[random(numberOfBorderNodes)];
539 byte direction = 255;
540
541 while (direction == 255) {
542 direction = random(6);
543 if (nodeConnections[targetNode][direction] < 0)
544 direction = 255;
545 }
546
547 ripples[j].start(
548 targetNode, direction, 0x1111EE,
549 float(random(100)) / 100.0 * .5 + 2, 300, 2);
550
551 break;
552 }
553 }
554 }
555
556 nextSimulatedEda = millis() + simulatedEdaBaseTime +
557 random(simulatedEdaVariance);
558 }
559 }
560 }
561
562 // Serial.print("Benchmark: ");
563 // Serial.println(millis() - benchmark);
564}
565
566#endif // __AVR__
CFastLED FastLED
Global LED strip management instance.
Definition FastLED.cpp:45
central include file for FastLED, defines the CFastLED class/object
void show(uint8_t scale)
Update all our controllers with the current led colors, using the passed in brightness.
Definition FastLED.cpp:94
static CLEDController & addLeds(CLEDController *pLed, struct CRGB *data, int nLedsOrOffset, int nLedsIfOffset=0)
Add a CLEDController instance to the world.
Definition FastLED.cpp:79
WS2812 controller class.
Definition FastLED.h:190
Implements a simple red square effect for 2D LED grids.
Definition crgb.h:16
Representation of an RGB pixel (Red, Green, Blue)
Definition crgb.h:54
@ White
Definition crgb.h:632