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