-
Notifications
You must be signed in to change notification settings - Fork 0
/
sparkfun_vis.c
819 lines (630 loc) · 35.4 KB
/
sparkfun_vis.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
/* SparkFun Addressable RGB LED Sound and Music Visualizer Tutorial Arduino Code
* by: Michael Bartlett
* SparkFun Electronics
* date: 2/7/16
* license: Creative Commons Attribution-ShareAlike 4.0 (CC BY-SA 4.0)
* Do whatever you'd like with this code, use it for any purpose.
* Please attribute and keep this license.
*/
//Libraries
#include <Adafruit_NeoPixel.h> //Library to simplify interacting with the LED strand
#ifdef __AVR__
#include <avr/power.h> //Includes the library for power reduction registers if your chip supports them.
#endif //More info: http://www.nongnu.org/avr-libc/user-manual/group__avr__power.htlm
//Constants (change these as necessary)
#define LED_PIN A5 //Pin for the pixel strand. Can be analog or digital.
#define LED_TOTAL 36 //Change this to the number of LEDs in your strand.
#define LED_HALF LED_TOTAL/2
#define VISUALS 6 //Change this accordingly if you add/remove a visual in the switch-case in Visualize()
#define AUDIO_PIN A0 //Pin for the envelope of the sound detector
#define KNOB_PIN A1 //Pin for the trimpot 10K
#define BUTTON_1 6 //Button 1 cycles color palettes
#define BUTTON_2 5 //Button 2 cycles visualization modes
#define BUTTON_3 4 //Button 3 toggles shuffle mode (automated changing of color and visual)
//////////<Globals>
// These values either need to be remembered from the last pass of loop() or
// need to be accessed by several functions in one pass, so they need to be global.
Adafruit_NeoPixel strand = Adafruit_NeoPixel(LED_TOTAL, LED_PIN, NEO_GRB + NEO_KHZ800); //LED strand objetcs
uint16_t gradient = 0; //Used to iterate and loop through each color palette gradually
//IMPORTANT:
// This array holds the "threshold" of each color function (i.e. the largest number they take before repeating).
// The values are in the same order as in ColorPalette()'s switch case (Rainbow() is first, etc). This is simply to
// keep "gradient" from overflowing, the color functions themselves can take any positive value. For example, the
// largest value Rainbow() takes before looping is 1529, so "gradient" should reset after 1529, as listed.
// Make sure you add/remove values accordingly if you add/remove a color function in the switch-case in ColorPalette().
uint16_t thresholds[] = {1529, 1019, 764, 764, 764, 1274};
uint8_t palette = 0; //Holds the current color palette.
uint8_t visual = 0; //Holds the current visual being displayed.
uint8_t volume = 0; //Holds the volume level read from the sound detector.
uint8_t last = 0; //Holds the value of volume from the previous loop() pass.
float maxVol = 15; //Holds the largest volume recorded thus far to proportionally adjust the visual's responsiveness.
float knob = 1023.0; //Holds the percentage of how twisted the trimpot is. Used for adjusting the max brightness.
float avgBump = 0; //Holds the "average" volume-change to trigger a "bump."
float avgVol = 0; //Holds the "average" volume-level to proportionally adjust the visual experience.
float shuffleTime = 0; //Holds how many seconds of runtime ago the last shuffle was (if shuffle mode is on).
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//NOTE: The reason "average" is quoted is because it is not a true mathematical average. This is because I have
// found what I call a "sequenced average" is more successful in execution than a real average. The difference
// is that the sequenced average doesn't use the pool of all values recorded thus far, but rather averages the
// last average and the current value received (in sequence). Concretely:
//
// True average: (1 + 2 + 3) / 3 = 2
// Sequenced: (1 + 2) / 2 = 1.5 --> (1.5 + 3) / 2 = 2.25 (if 1, 2, 3 was the order the values were received)
//
// All "averages" in the program operate this way. The difference is subtle, but the reason is that sequenced
// averages are more adaptive to changes in the overall volume. In other words, if you went from loud to quiet,
// the sequenced average is more likely to show an accurate and proportional adjustment more fluently.
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
bool shuffle = false; //Toggles shuffle mode.
bool bump = false; //Used to pass if there was a "bump" in volume
//For Traffic() visual
int8_t pos[LED_TOTAL] = { -2}; //Stores a population of color "dots" to iterate across the LED strand.
uint8_t rgb[LED_TOTAL][3] = {0}; //Stores each dot's specific RGB values.
//For Snake() visual
bool left = false; //Determines the direction of iteration. Recycled in PaletteDance()
int8_t dotPos = 0; //Holds which LED in the strand the dot is positioned at. Recycled in most other visuals.
float timeBump = 0; //Holds the time (in runtime seconds) the last "bump" occurred.
float avgTime = 0; //Holds the "average" amount of time between each "bump" (used for pacing the dot's movement).
//////////</Globals>
//////////<Standard Functions>
void setup() { //Like it's named, this gets ran before any other function.
Serial.begin(9600); //Sets data rate for serial data transmission.
//Defines the buttons pins to be input.
pinMode(BUTTON_1, INPUT); pinMode(BUTTON_2, INPUT); pinMode(BUTTON_3, INPUT);
//Write a "HIGH" value to the button pins.
digitalWrite(BUTTON_1, HIGH); digitalWrite(BUTTON_2, HIGH); digitalWrite(BUTTON_3, HIGH);
strand.begin(); //Initialize the LED strand object.
strand.show(); //Show a blank strand, just to get the LED's ready for use.
}
void loop() { //This is where the magic happens. This loop produces each frame of the visual.
volume = analogRead(AUDIO_PIN); //Record the volume level from the sound detector
knob = analogRead(KNOB_PIN) / 1023.0; //Record how far the trimpot is twisted
//Sets a threshold for volume.
// In practice I've found noise can get up to 15, so if it's lower, the visual thinks it's silent.
// Also if the volume is less than average volume / 2 (essentially an average with 0), it's considered silent.
if (volume < avgVol / 2.0 || volume < 15) volume = 0;
else avgVol = (avgVol + volume) / 2.0; //If non-zeo, take an "average" of volumes.
//If the current volume is larger than the loudest value recorded, overwrite
if (volume > maxVol) maxVol = volume;
//Check the Cycle* functions for specific instructions if you didn't include buttons in your design.
////////////////////////////////////////////////////////////////////////////////////////////////////
CyclePalette(); //Changes palette for shuffle mode or button press.
CycleVisual(); //Changes visualization for shuffle mode or button press.
ToggleShuffle(); //Toggles shuffle mode. Delete this if you didn't use buttons.
////////////////////////////////////////////////////////////////////////////////////////////////////
//This is where "gradient" is modulated to prevent overflow.
if (gradient > thresholds[palette]) {
gradient %= thresholds[palette] + 1;
//Everytime a palette gets completed is a good time to readjust "maxVol," just in case
// the song gets quieter; we also don't want to lose brightness intensity permanently
// because of one stray loud sound.
maxVol = (maxVol + volume) / 2.0;
}
//If there is a decent change in volume since the last pass, average it into "avgBump"
if (volume - last > 10) avgBump = (avgBump + (volume - last)) / 2.0;
//If there is a notable change in volume, trigger a "bump"
// avgbump is lowered just a little for comparing to make the visual slightly more sensitive to a beat.
bump = (volume - last > avgBump * .9);
//If a "bump" is triggered, average the time between bumps
if (bump) {
avgTime = (((millis() / 1000.0) - timeBump) + avgTime) / 2.0;
timeBump = millis() / 1000.0;
}
Visualize(); //Calls the appropriate visualization to be displayed with the globals as they are.
gradient++; //Increments gradient
last = volume; //Records current volume for next pass
delay(30); //Paces visuals so they aren't too fast to be enjoyable
}
//////////</Standard Functions>
//////////<Visual Functions>
//This function calls the appropriate visualization based on the value of "visual"
void Visualize() {
switch (visual) {
case 0: return Pulse();
case 1: return PalettePulse();
case 2: return Traffic();
case 3: return Snake();
case 4: return PaletteDance();
case 5: return Glitter();
case 6: return Paintball();
default: return Pulse();
}
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////
//NOTE: The strand displays RGB values as a 32 bit unsigned integer (uint32_t), which is why ColorPalette()
// and all associated color functions' return types are uint32_t. This value is a composite of 3
// unsigned 8bit integer (uint8_t) values (0-255 for each of red, blue, and green). You'll notice the
// function split() (listed below) is used to dissect these 8bit values from the 32-bit color value.
//////////////////////////////////////////////////////////////////////////////////////////////////////////
//This function calls the appropriate color palette based on "palette"
// If a negative value is passed, returns the appropriate palette withe "gradient" passed.
// Otherwise returns the color palette with the passed value (useful for fitting a whole palette on the strand).
uint32_t ColorPalette(float num) {
switch (palette) {
case 0: return (num < 0) ? Rainbow(gradient) : Rainbow(num);
case 1: return (num < 0) ? Sunset(gradient) : Sunset(num);
case 2: return (num < 0) ? Ocean(gradient) : Ocean(num);
case 3: return (num < 0) ? PinaColada(gradient) : PinaColada(num);
case 4: return (num < 0) ? Sulfur(gradient) : Sulfur(num);
case 5: return (num < 0) ? NoGreen(gradient) : NoGreen(num);
default: return Rainbow(gradient);
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
//NOTE: All of these visualizations feature some aspect that affects brightness based on the volume relative to
// maxVol, so that louder = brighter. Initially, I did simple proportions (volume/maxvol), but I found this
// to be visually indistinct. I then tried an exponential method (raising the value to the power of
// volume/maxvol). While this was more visually satisfying, I've opted for a balance between the two. You'll
// notice something like pow(volume/maxVol, 2.0) in the functions below. This simply squares the ratio of
// volume to maxVol to get a more exponential curve, but not as exaggerated as an actual exponential curve.
// In essence, this makes louder volumes brighter, and lower volumes dimmer, to be more visually distinct.
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
//PULSE
//Pulse from center of the strand
void Pulse() {
fade(0.75); //Listed below, this function simply dims the colors a little bit each pass of loop()
//Advances the palette to the next noticeable color if there is a "bump"
if (bump) gradient += thresholds[palette] / 24;
//If it's silent, we want the fade effect to take over, hence this if-statement
if (volume > 0) {
uint32_t col = ColorPalette(-1); //Our retrieved 32-bit color
//These variables determine where to start and end the pulse since it starts from the middle of the strand.
// The quantities are stored in variables so they only have to be computed once (plus we use them in the loop).
int start = LED_HALF - (LED_HALF * (volume / maxVol));
int finish = LED_HALF + (LED_HALF * (volume / maxVol)) + strand.numPixels() % 2;
//Listed above, LED_HALF is simply half the number of LEDs on your strand. ↑ this part adjusts for an odd quantity.
for (int i = start; i < finish; i++) {
//"damp" creates the fade effect of being dimmer the farther the pixel is from the center of the strand.
// It returns a value between 0 and 1 that peaks at 1 at the center of the strand and 0 at the ends.
float damp = sin((i - start) * PI / float(finish - start));
//Squaring damp creates more distinctive brightness.
damp = pow(damp, 2.0);
//Fetch the color at the current pixel so we can see if it's dim enough to overwrite.
uint32_t col2 = strand.getPixelColor(i);
//Takes advantage of one for loop to do the following:
// Appropriatley adjust the brightness of this pixel using location, volume, and "knob"
// Take the average RGB value of the intended color and the existing color, for comparison
uint8_t colors[3];
float avgCol = 0, avgCol2 = 0;
for (int k = 0; k < 3; k++) {
colors[k] = split(col, k) * damp * knob * pow(volume / maxVol, 2);
avgCol += colors[k];
avgCol2 += split(col2, k);
}
avgCol /= 3.0, avgCol2 /= 3.0;
//Compare the average colors as "brightness". Only overwrite dim colors so the fade effect is more apparent.
if (avgCol > avgCol2) strand.setPixelColor(i, strand.Color(colors[0], colors[1], colors[2]));
}
}
//This command actually shows the lights. If you make a new visualization, don't forget this!
strand.show();
}
//PALETTEPULSE
//Same as Pulse(), but colored the entire pallet instead of one solid color
void PalettePulse() {
fade(0.75);
if (bump) gradient += thresholds[palette] / 24;
if (volume > 0) {
int start = LED_HALF - (LED_HALF * (volume / maxVol));
int finish = LED_HALF + (LED_HALF * (volume / maxVol)) + strand.numPixels() % 2;
for (int i = start; i < finish; i++) {
float damp = sin((i - start) * PI / float(finish - start));
damp = pow(damp, 2.0);
//This is the only difference from Pulse(). The color for each pixel isn't the same, but rather the
// entire gradient fitted to the spread of the pulse, with some shifting from "gradient".
int val = thresholds[palette] * (i - start) / (finish - start);
val += gradient;
uint32_t col = ColorPalette(val);
uint32_t col2 = strand.getPixelColor(i);
uint8_t colors[3];
float avgCol = 0, avgCol2 = 0;
for (int k = 0; k < 3; k++) {
colors[k] = split(col, k) * damp * knob * pow(volume / maxVol, 2);
avgCol += colors[k];
avgCol2 += split(col2, k);
}
avgCol /= 3.0, avgCol2 /= 3.0;
if (avgCol > avgCol2) strand.setPixelColor(i, strand.Color(colors[0], colors[1], colors[2]));
}
}
strand.show();
}
//TRAFFIC
//Dots racing into each other
void Traffic() {
//fade() actually creates the trail behind each dot here, so it's important to include.
fade(0.8);
//Create a dot to be displayed if a bump is detected.
if (bump) {
//This mess simply checks if there is an open position (-2) in the pos[] array.
int8_t slot = 0;
for (slot; slot < sizeof(pos); slot++) {
if (pos[slot] < -1) break;
else if (slot + 1 >= sizeof(pos)) {
slot = -3;
break;
}
}
//If there is an open slot, set it to an initial position on the strand.
if (slot != -3) {
//Evens go right, odds go left, so evens start at 0, odds at the largest position.
pos[slot] = (slot % 2 == 0) ? -1 : strand.numPixels();
//Give it a color based on the value of "gradient" during its birth.
uint32_t col = ColorPalette(-1);
gradient += thresholds[palette] / 24;
for (int j = 0; j < 3; j++) {
rgb[slot][j] = split(col, j);
}
}
}
//Again, if it's silent we want the colors to fade out.
if (volume > 0) {
//If there's sound, iterate each dot appropriately along the strand.
for (int i = 0; i < sizeof(pos); i++) {
//If a dot is -2, that means it's an open slot for another dot to take over eventually.
if (pos[i] < -1) continue;
//As above, evens go right (+1) and odds go left (-1)
pos[i] += (i % 2) ? -1 : 1;
//Odds will reach -2 by subtraction, but if an even dot goes beyond the LED strip, it'll be purged.
if (pos[i] >= strand.numPixels()) pos[i] = -2;
//Set the dot to its new position and respective color.
// I's old position's color will gradually fade out due to fade(), leaving a trail behind it.
strand.setPixelColor( pos[i], strand.Color(
float(rgb[i][0]) * pow(volume / maxVol, 2.0) * knob,
float(rgb[i][1]) * pow(volume / maxVol, 2.0) * knob,
float(rgb[i][2]) * pow(volume / maxVol, 2.0) * knob)
);
}
}
strand.show(); //Again, don't forget to actually show the lights!
}
//SNAKE
//Dot sweeping back and forth to the beat
void Snake() {
if (bump) {
//Change color a little on a bump
gradient += thresholds[palette] / 30;
//Change the direction the dot is going to create the illusion of "dancing."
left = !left;
}
fade(0.975); //Leave a trail behind the dot.
uint32_t col = ColorPalette(-1); //Get the color at current "gradient."
//The dot should only be moved if there's sound happening.
// Otherwise if noise starts and it's been moving, it'll appear to teleport.
if (volume > 0) {
//Sets the dot to appropriate color and intensity
strand.setPixelColor(dotPos, strand.Color(
float(split(col, 0)) * pow(volume / maxVol, 1.5) * knob,
float(split(col, 1)) * pow(volume / maxVol, 1.5) * knob,
float(split(col, 2)) * pow(volume / maxVol, 1.5) * knob)
);
//This is where "avgTime" comes into play.
// That variable is the "average" amount of time between each "bump" detected.
// So we can use that to determine how quickly the dot should move so it matches the tempo of the music.
// The dot moving at normal loop speed is pretty quick, so it's the max speed if avgTime < 0.15 seconds.
// Slowing it down causes the color to update, but only change position every other amount of loops.
if (avgTime < 0.15) dotPos += (left) ? -1 : 1;
else if (avgTime >= 0.15 && avgTime < 0.5 && gradient % 2 == 0) dotPos += (left) ? -1 : 1;
else if (avgTime >= 0.5 && avgTime < 1.0 && gradient % 3 == 0) dotPos += (left) ? -1 : 1;
else if (gradient % 4 == 0) dotPos += (left) ? -1 : 1;
}
strand.show(); // Display the lights
//Check if dot position is out of bounds.
if (dotPos < 0) dotPos = strand.numPixels() - 1;
else if (dotPos >= strand.numPixels()) dotPos = 0;
}
//PALETTEDANCE
//Projects a whole palette which oscillates to the beat, similar to the snake but a whole gradient instead of a dot
void PaletteDance() {
//This is the most calculation-intensive visual, which is why it doesn't need delayed.
if (bump) left = !left; //Change direction of iteration on bump
//Only show if there's sound.
if (volume > avgVol) {
//This next part is convoluted, here's a summary of what's happening:
// First, a sin wave function is introduced to change the brightness of all the pixels (stored in "sinVal")
// This is to make the dancing effect more obvious. The trick is to shift the sin wave with the color so it all appears
// to be the same object, one "hump" of color. "dotPos" is added here to achieve this effect.
// Second, the entire current palette is proportionally fitted to the length of the LED strand (stored in "val" each pixel).
// This is done by multiplying the ratio of position and the total amount of LEDs to the palette's threshold.
// Third, the palette is then "shifted" (what color is displayed where) by adding "dotPos."
// "dotPos" is added to the position before dividing, so it's a mathematical shift. However, "dotPos"'s range is not
// the same as the range of position values, so the function map() is used. It's basically a built in proportion adjuster.
// Lastly, it's all multiplied together to get the right color, and intensity, in the correct spot.
// "gradient" is also added to slowly shift the colors over time.
for (int i = 0; i < strand.numPixels(); i++) {
float sinVal = abs(sin(
(i + dotPos) *
(PI / float(strand.numPixels() / 1.25) )
));
sinVal *= sinVal;
sinVal *= volume / maxVol;
sinVal *= knob;
unsigned int val = float(thresholds[palette] + 1)
//map takes a value between -LED_TOTAL and +LED_TOTAL and returns one between 0 and LED_TOTAL
* (float(i + map(dotPos, -1 * (strand.numPixels() - 1), strand.numPixels() - 1, 0, strand.numPixels() - 1))
/ float(strand.numPixels()))
+ (gradient);
val %= thresholds[palette]; //make sure "val" is within range of the palette
uint32_t col = ColorPalette(val); //get the color at "val"
strand.setPixelColor(i, strand.Color(
float(split(col, 0))*sinVal,
float(split(col, 1))*sinVal,
float(split(col, 2))*sinVal)
);
}
//After all that, appropriately reposition "dotPos."
dotPos += (left) ? -1 : 1;
}
//If there's no sound, fade.
else fade(0.8);
strand.show(); //Show lights.
//Loop "dotPos" if it goes out of bounds.
if (dotPos < 0) dotPos = strand.numPixels() - strand.numPixels() / 6;
else if (dotPos >= strand.numPixels() - strand.numPixels() / 6) dotPos = 0;
}
//GLITTER
//Creates white sparkles on a color palette to the beat
void Glitter() {
//This visual also fits a whole palette on the entire strip
// This just makes the palette cycle more quickly so it's more visually pleasing
gradient += thresholds[palette] / 204;
//"val" is used again as the proportional value to pass to ColorPalette() to fit the whole palette.
for (int i = 0; i < strand.numPixels(); i++) {
unsigned int val = float(thresholds[palette] + 1) *
(float(i) / float(strand.numPixels()))
+ (gradient);
val %= thresholds[palette];
uint32_t col = ColorPalette(val);
//We want the sparkles to be obvious, so we dim the background color.
strand.setPixelColor(i, strand.Color(
split(col, 0) / 6.0 * knob,
split(col, 1) / 6.0 * knob,
split(col, 2) / 6.0 * knob)
);
}
//Create sparkles every bump
if (bump) {
//Random generator needs a seed, and micros() gives a large range of values.
// micros() is the amount of microseconds since the program started running.
randomSeed(micros());
//Pick a random spot on the strand.
dotPos = random(strand.numPixels() - 1);
//Draw sparkle at the random position, with appropriate brightness.
strand.setPixelColor(dotPos, strand.Color(
255.0 * pow(volume / maxVol, 2.0) * knob,
255.0 * pow(volume / maxVol, 2.0) * knob,
255.0 * pow(volume / maxVol, 2.0) * knob
));
}
bleed(dotPos);
strand.show(); //Show the lights.
}
//PAINTBALL
//Recycles Glitter()'s random positioning; simulates "paintballs" of
// color splattering randomly on the strand and bleeding together.
void Paintball() {
//If it's been twice the average time for a "bump" since the last "bump," start fading.
if ((millis() / 1000.0) - timeBump > avgTime * 2.0) fade(0.99);
//Bleeds colors together. Operates similarly to fade. For more info, see its definition below
bleed(dotPos);
//Create a new paintball if there's a bump (like the sparkles in Glitter())
if (bump) {
//Random generator needs a seed, and micros() gives a large range of values.
// micros() is the amount of microseconds since the program started running.
randomSeed(micros());
//Pick a random spot on the strip. Random was already reseeded above, so no real need to do it again.
dotPos = random(strand.numPixels() - 1);
//Grab a random color from our palette.
uint32_t col = ColorPalette(random(thresholds[palette]));
//Array to hold final RGB values
uint8_t colors[3];
//Relates brightness of the color to the relative volume and potentiometer value.
for (int i = 0; i < 3; i++) colors[i] = split(col, i) * pow(volume / maxVol, 2.0) * knob;
//Splatters the "paintball" on the random position.
strand.setPixelColor(dotPos, strand.Color(colors[0], colors[1], colors[2]));
//This next part places a less bright version of the same color next to the left and right of the
// original position, so that the bleed effect is stronger and the colors are more vibrant.
for (int i = 0; i < 3; i++) colors[i] *= .8;
strand.setPixelColor(dotPos - 1, strand.Color(colors[0], colors[1], colors[2]));
strand.setPixelColor(dotPos + 1, strand.Color(colors[0], colors[1], colors[2]));
}
strand.show(); //Show lights.
}
/////////////////////////////////////////////////////////////////////////////////////////////////////
//DEBUG CYCLE
//No reaction to sound, merely to see gradient progression of color palettes
//NOT implemented in code as is, but is easily includable in the switch-case.
void Cycle() {
for (int i = 0; i < strand.numPixels(); i++) {
float val = float(thresholds[palette]) * (float(i) / float(strand.numPixels())) + (gradient);
val = int(val) % thresholds[palette];
strand.setPixelColor(i, ColorPalette(val));
}
strand.show();
gradient += 32;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////
//////////</Visual Functions>
//////////<Helper Functions>
void CyclePalette() {
//IMPORTANT: Delete this whole if-block if you didn't use buttons//////////////////////////////////
//If a button is pushed, it sends a "false" reading
if (!digitalRead(BUTTON_1)) {
palette++; //This is this button's purpose, to change the color palette.
//If palette is larger than the population of thresholds[], start back at 0
// This is why it's important you add a threshold to the array if you add a
// palette, or the program will cylce back to Rainbow() before reaching it.
if (palette >= sizeof(thresholds) / 2) palette = 0;
gradient %= thresholds[palette]; //Modulate gradient to prevent any overflow that may occur.
//The button is close to the microphone on my setup, so the sound of pushing it is
// relatively loud to the sound detector. This causes the visual to think a loud noise
// happened, so the delay simply allows the sound of the button to pass unabated.
delay(350);
maxVol = avgVol; //Set max volume to average for a fresh experience.
}
///////////////////////////////////////////////////////////////////////////////////////////////////
//If shuffle mode is on, and it's been 30 seconds since the last shuffle, and then a modulo
// of gradient to get a random decision between palette or visualization shuffle
if (shuffle && millis() / 1000.0 - shuffleTime > 30 && gradient % 2) {
shuffleTime = millis() / 1000.0; //Record the time this shuffle happened.
palette++;
if (palette >= sizeof(thresholds) / 2) palette = 0;
gradient %= thresholds[palette];
maxVol = avgVol; //Set the max volume to average for a fresh experience.
}
}
void CycleVisual() {
//IMPORTANT: Delete this whole if-block if you didn't use buttons//////////////////////////////////
if (!digitalRead(BUTTON_2)) {
visual++; //The purpose of this button: change the visual mode
gradient = 0; //Prevent overflow
//Resets "visual" if there are no more visuals to cycle through.
if (visual > VISUALS) visual = 0;
//This is why you should change "VISUALS" if you add a visual, or the program loop over it.
//Resets the positions of all dots to nonexistent (-2) if you cycle to the Traffic() visual.
if (visual == 1) memset(pos, -2, sizeof(pos));
//Gives Snake() and PaletteDance() visuals a random starting point if cycled to.
if (visual == 2 || visual == 3) {
randomSeed(analogRead(0));
dotPos = random(strand.numPixels());
}
//Like before, this delay is to prevent a button press from affecting "maxVol."
delay(350);
maxVol = avgVol; //Set max volume to average for a fresh experience
}
///////////////////////////////////////////////////////////////////////////////////////////////////
//If shuffle mode is on, and it's been 30 seconds since the last shuffle, and then a modulo
// of gradient WITH INVERTED LOGIC to get a random decision between what to shuffle.
// This guarantees one and only one of these shuffles will occur.
if (shuffle && millis() / 1000.0 - shuffleTime > 30 && !(gradient % 2)) {
shuffleTime = millis() / 1000.0; //Record the time this shuffle happened.
visual++;
gradient = 0;
if (visual > VISUALS) visual = 0;
if (visual == 1) memset(pos, -2, sizeof(pos));
if (visual == 2 || visual == 3) {
randomSeed(analogRead(0));
dotPos = random(strand.numPixels());
}
maxVol = avgVol;
}
}
//IMPORTANT: Delete this function if you didn't use buttons./////////////////////////////////////////
void ToggleShuffle() {
if (!digitalRead(BUTTON_3)) {
shuffle = !shuffle; //This button's purpose: toggle shuffle mode.
//This delay is to prevent the button from taking another reading while you're pressing it
delay(500);
//Reset these things for a fresh experience.
maxVol = avgVol;
avgBump = 0;
}
}
//////////////////////////////////////////////////////////////////////////////////////////////////////
//Fades lights by multiplying them by a value between 0 and 1 each pass of loop().
void fade(float damper) {
//"damper" must be between 0 and 1, or else you'll end up brightening the lights or doing nothing.
for (int i = 0; i < strand.numPixels(); i++) {
//Retrieve the color at the current position.
uint32_t col = strand.getPixelColor(i);
//If it's black, you can't fade that any further.
if (col == 0) continue;
float colors[3]; //Array of the three RGB values
//Multiply each value by "damper"
for (int j = 0; j < 3; j++) colors[j] = split(col, j) * damper;
//Set the dampened colors back to their spot.
strand.setPixelColor(i, strand.Color(colors[0] , colors[1], colors[2]));
}
}
//"Bleeds" colors currently in the strand by averaging from a designated "Point"
void bleed(uint8_t Point) {
for (int i = 1; i < strand.numPixels(); i++) {
//Starts by look at the pixels left and right of "Point"
// then slowly works its way out
int sides[] = {Point - i, Point + i};
for (int i = 0; i < 2; i++) {
//For each of Point+i and Point-i, the pixels to the left and right, plus themselves, are averaged together.
// Basically, it's setting one pixel to the average of it and its neighbors, starting on the left and right
// of the starting "Point," and moves to the ends of the strand
int point = sides[i];
uint32_t colors[] = {strand.getPixelColor(point - 1), strand.getPixelColor(point), strand.getPixelColor(point + 1) };
//Sets the new average values to just the central point, not the left and right points.
strand.setPixelColor(point, strand.Color(
float( split(colors[0], 0) + split(colors[1], 0) + split(colors[2], 0) ) / 3.0,
float( split(colors[0], 1) + split(colors[1], 1) + split(colors[2], 1) ) / 3.0,
float( split(colors[0], 2) + split(colors[1], 2) + split(colors[2], 2) ) / 3.0)
);
}
}
}
//As mentioned above, split() gives you one 8-bit color value
//from the composite 32-bit value that the NeoPixel deals with.
//This is accomplished with the right bit shift operator, ">>"
uint8_t split(uint32_t color, uint8_t i ) {
//0 = Red, 1 = Green, 2 = Blue
if (i == 0) return color >> 16;
if (i == 1) return color >> 8;
if (i == 2) return color >> 0;
return -1;
}
//////////</Helper Functions>
//////////<Palette Functions>
//These functions simply take a value and return a gradient color
// in the form of an unsigned 32-bit integer
//The gradients return a different, changing color for each multiple of 255
// This is because the max value of any of the 3 RGB values is 255, so it's
// an intuitive cutoff for the next color to start appearing.
// Gradients should also loop back to their starting color so there's no jumps in color.
uint32_t Rainbow(unsigned int i) {
if (i > 1529) return Rainbow(i % 1530);
if (i > 1274) return strand.Color(255, 0, 255 - (i % 255)); //violet -> red
if (i > 1019) return strand.Color((i % 255), 0, 255); //blue -> violet
if (i > 764) return strand.Color(0, 255 - (i % 255), 255); //aqua -> blue
if (i > 509) return strand.Color(0, 255, (i % 255)); //green -> aqua
if (i > 255) return strand.Color(255 - (i % 255), 255, 0); //yellow -> green
return strand.Color(255, i, 0); //red -> yellow
}
uint32_t Sunset(unsigned int i) {
if (i > 1019) return Sunset(i % 1020);
if (i > 764) return strand.Color((i % 255), 0, 255 - (i % 255)); //blue -> red
if (i > 509) return strand.Color(255 - (i % 255), 0, 255); //purple -> blue
if (i > 255) return strand.Color(255, 128 - (i % 255) / 2, (i % 255)); //orange -> purple
return strand.Color(255, i / 2, 0); //red -> orange
}
uint32_t Ocean(unsigned int i) {
if (i > 764) return Ocean(i % 765);
if (i > 509) return strand.Color(0, i % 255, 255 - (i % 255)); //blue -> green
if (i > 255) return strand.Color(0, 255 - (i % 255), 255); //aqua -> blue
return strand.Color(0, 255, i); //green -> aqua
}
uint32_t PinaColada(unsigned int i) {
if (i > 764) return PinaColada(i % 765);
if (i > 509) return strand.Color(255 - (i % 255) / 2, (i % 255) / 2, (i % 255) / 2); //red -> half white
if (i > 255) return strand.Color(255, 255 - (i % 255), 0); //yellow -> red
return strand.Color(128 + (i / 2), 128 + (i / 2), 128 - i / 2); //half white -> yellow
}
uint32_t Sulfur(unsigned int i) {
if (i > 764) return Sulfur(i % 765);
if (i > 509) return strand.Color(i % 255, 255, 255 - (i % 255)); //aqua -> yellow
if (i > 255) return strand.Color(0, 255, i % 255); //green -> aqua
return strand.Color(255 - i, 255, 0); //yellow -> green
}
uint32_t NoGreen(unsigned int i) {
if (i > 1274) return NoGreen(i % 1275);
if (i > 1019) return strand.Color(255, 0, 255 - (i % 255)); //violet -> red
if (i > 764) return strand.Color((i % 255), 0, 255); //blue -> violet
if (i > 509) return strand.Color(0, 255 - (i % 255), 255); //aqua -> blue
if (i > 255) return strand.Color(255 - (i % 255), 255, i % 255); //yellow -> aqua
return strand.Color(255, i, 0); //red -> yellow
}
//NOTE: This is an example of a non-gradient palette: you will get straight red, white, or blue
// This works fine, but there is no gradient effect, this was merely included as an example.
// If you wish to include it, put it in the switch-case in ColorPalette() and add its
// threshold (764) to thresholds[] at the top.
uint32_t USA(unsigned int i) {
if (i > 764) return USA(i % 765);
if (i > 509) return strand.Color(0, 0, 255); //blue
if (i > 255) return strand.Color(128, 128, 128); //white
return strand.Color(255, 0, 0); //red
}
//////////</Palette Functions>